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    /// An intrinsic's evaluator could not produce a result (e.g. a trap or
148    /// unsupported operand width)
149    UnsupportedIntrinsic(Box<str>),
150    /// A user-provided call interceptor failed while modeling a call
151    InterceptError(Box<str>),
152    /// A bounded emulation run (e.g. `run_pure`) exceeded its step budget.
153    StepBudgetExceeded(usize),
154    /// A mnemonic the interpreter does not model (e.g. `map`, whose whole-array
155    /// emulation is deferred). Recoverable: a best-effort consumer such as
156    /// pure-call folding simply declines to harvest, rather than crashing.
157    UnsupportedMnemonic(&'static str),
158    /// Execution reached a block with no instructions (a malformed/degenerate
159    /// block left behind by lifting). Recoverable: bounded consumers decline to
160    /// harvest rather than indexing out of bounds.
161    EmptyBlock(BlockId),
162    /// A [`poison`](qcode::value::poison) value was demanded as a concrete datum.
163    /// Poison has undefined bits, so reading it is a hard error (argpromote v2);
164    /// propagating it as an unread operand is fine. Bounded consumers (e.g.
165    /// pure-call folding) treat this as a bail signal.
166    PoisonRead,
167}
168
169impl std::fmt::Display for EmulatorErrorKind {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        match self {
172            Self::InvalidBlockAddress(addr) => write!(f, "invalid block address {addr:#x}"),
173            Self::EmptyFunctionRoot(func) => write!(f, "function {func:?} has no root block"),
174            Self::UnresolvedMintedCallee(slot) => {
175                write!(f, "minted callee placeholder #{slot} is not executable")
176            }
177            Self::UnknownAddress(addr) => write!(f, "unknown address {addr:#x}"),
178            Self::AddressOverflow(addr, size) => {
179                write!(f, "address overflow at {addr:#x} with size {size}")
180            }
181            Self::MemoryReadError(addr) => write!(f, "memory read error at address {addr:#x}"),
182            Self::MemoryWriteError(addr) => write!(f, "memory write error at address {addr:#x}"),
183            Self::ValueError(value) => write!(f, "value {value} is too large to represent"),
184            Self::UnknownRegister(reg) => write!(f, "register {reg:?} not found in context"),
185            Self::UnknownSpace(space) => write!(f, "memory space {space:?} not initialised"),
186            Self::UnsupportedPCodeOp(op) => write!(f, "unsupported p-code operation `{op}`"),
187            Self::UnsupportedIntrinsic(op) => write!(f, "unsupported intrinsic `{op}`"),
188            Self::InterceptError(message) => write!(f, "call interceptor failed: {message}"),
189            Self::StepBudgetExceeded(budget) => {
190                write!(f, "emulation exceeded step budget of {budget}")
191            }
192            Self::UnsupportedMnemonic(op) => write!(f, "unsupported mnemonic `{op}`"),
193            Self::EmptyBlock(block) => write!(f, "block {block:?} has no instructions"),
194            Self::PoisonRead => write!(f, "read of a poison value (undefined bits)"),
195        }
196    }
197}
198
199impl std::fmt::Display for EmulatorError {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "emulator error: {}", self.kind)?;
202        write!(f, " (context: {})", self.ctx)?;
203        Ok(())
204    }
205}
206
207impl std::error::Error for EmulatorError {}
208
209pub type Result<T> = std::result::Result<T, EmulatorError>;
210
211/// A trait to describe a value
212/// This is used to abstract over different types of interpretations (symbolic, concrete, abstract, etc.)
213pub trait DomainValue: Clone + Copy {
214    /// Returns the size of this value in bytes
215    fn size(&self) -> std::result::Result<usize, EmulatorErrorKind>;
216
217    /// Attempt to read this value as a little-endian unsigned integer.
218    fn value(&self) -> std::result::Result<u64, EmulatorErrorKind>;
219
220    fn from_u64(value: u64) -> Self;
221
222    /// Creates an all-zero value at `size` bytes. Domains that track width
223    /// should override this; the default preserves compatibility for domains
224    /// that only have a machine-word zero representation.
225    fn zero(_size: usize) -> Self {
226        Self::from_u64(0)
227    }
228
229    fn is_float_nan(&self) -> std::result::Result<Self, EmulatorErrorKind>;
230    fn int_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
231    fn float_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
232    fn float_to_int(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
233    fn zext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
234    fn sext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
235    fn range(&self, start: usize, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
236    fn byte_swap(&self) -> std::result::Result<Self, EmulatorErrorKind>;
237
238    /// Evaluate a pure intrinsic on its concrete operands, producing an
239    /// `out_size`-byte result via the intrinsic's shared evaluator.
240    fn intrinsic(
241        id: qcode::value::insn::IntrinsicId,
242        args: &[Self],
243        out_size: usize,
244    ) -> std::result::Result<Self, EmulatorErrorKind>;
245
246    fn pop_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
247    fn lz_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
248    fn carry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
249    fn scarry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
250    fn sborrow(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
251
252    fn int_not(&self) -> std::result::Result<Self, EmulatorErrorKind>;
253    fn int_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
254    fn float_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
255    fn float_abs(&self) -> std::result::Result<Self, EmulatorErrorKind>;
256    fn float_sqrt(&self) -> std::result::Result<Self, EmulatorErrorKind>;
257    fn float_ceil(&self) -> std::result::Result<Self, EmulatorErrorKind>;
258    fn float_floor(&self) -> std::result::Result<Self, EmulatorErrorKind>;
259    fn float_round(&self) -> std::result::Result<Self, EmulatorErrorKind>;
260
261    fn int_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
262    fn int_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
263    fn int_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
264    fn int_sless(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
265    fn int_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
266    fn int_sless_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
267    fn int_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
268    fn int_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
269    fn int_xor(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
270    fn int_and(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
271    fn int_or(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
272    fn int_shift_left(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
273    fn int_shift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
274    fn int_sshift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
275    fn int_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
276    fn int_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
277    fn int_rem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
278    fn int_sdiv(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
279    fn int_srem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
280
281    fn float_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
282    fn float_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
283    fn float_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
284    fn float_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
285    fn float_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
286    fn float_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
287    fn float_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
288    fn float_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
289}
290
291pub trait DomainMemory {
292    type V: DomainValue;
293
294    /// Reads a value from the given address.
295    /// The size of the value must be less than or equal to the size of the region being read from.
296    fn read(
297        &self,
298        space: MemorySpaceId,
299        addr: Self::V,
300        size: usize,
301    ) -> std::result::Result<Self::V, EmulatorErrorKind>;
302
303    /// Writes a value to the given address.
304    /// The size of the value must be less than or equal to the size of the region being written to.
305    fn write(
306        &mut self,
307        space: MemorySpaceId,
308        addr: Self::V,
309        size: usize,
310        data: Self::V,
311    ) -> std::result::Result<(), EmulatorErrorKind>;
312}
313
314pub trait Interpreter {
315    type V: DomainValue;
316    type M: DomainMemory<V = Self::V>;
317
318    fn ctx(&self) -> &Context<'_>;
319
320    fn memory(&mut self) -> &mut Self::M;
321
322    /// Gets the value of the given value ID, if it exists in the context.
323    fn get_value(&mut self, id: ValueId) -> std::result::Result<Self::V, EmulatorErrorKind>;
324
325    /// Reads the value of a varnode from the emulated memory space.
326    fn get_varnode_value(
327        &mut self,
328        id: VarnodeId,
329    ) -> std::result::Result<Self::V, EmulatorErrorKind> {
330        let varnode = Varnode::from_id(self.ctx(), id);
331        let space = varnode.space().id;
332        let addr = Self::V::from_u64(varnode.address() as u64);
333        let size = varnode.size();
334        self.memory().read(space.into(), addr, size)
335    }
336
337    /// Writes a value to a varnode in the emulated memory space.
338    fn set_varnode_value(
339        &mut self,
340        id: VarnodeId,
341        value: Self::V,
342    ) -> std::result::Result<(), EmulatorErrorKind> {
343        let varnode = Varnode::from_id(self.ctx(), id);
344        let space = varnode.space().id;
345        let addr = Self::V::from_u64(varnode.address() as u64);
346        let size = varnode.size();
347        self.memory().write(space.into(), addr, size, value)?;
348        Ok(())
349    }
350
351    /// Returns the value of the given register, if it exists in the context.
352    fn get_register_value(
353        &mut self,
354        reg_id: RegisterId,
355    ) -> std::result::Result<Self::V, EmulatorErrorKind> {
356        let id = *self
357            .ctx()
358            .shared
359            .registers
360            .get(&reg_id)
361            .ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
362        self.get_varnode_value(id)
363    }
364
365    /// Sets the value of the given register, if it exists in the context.
366    fn set_register_value(
367        &mut self,
368        reg_id: RegisterId,
369        value: Self::V,
370    ) -> std::result::Result<(), EmulatorErrorKind> {
371        let id = *self
372            .ctx()
373            .shared
374            .registers
375            .get(&reg_id)
376            .ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
377        self.set_varnode_value(id, value)
378    }
379
380    /// Gets the value of the given instruction, if it has one.
381    /// This is used for instructions that produce a value, such as copy, load, and binary operations.
382    fn interpret_(
383        &mut self,
384        insn: &InstructionRef<'_, '_>,
385        mnemonic: &Mnemonic,
386    ) -> std::result::Result<Option<Self::V>, EmulatorErrorKind> {
387        // Operands are stored bare-local; qualify with the instruction's own
388        // function (strict IR locality: operands live in the same arena).
389        let func = insn.id.func;
390        // Supplied by the caller, which has already resolved it: each
391        // `insn.mnemonic()` walks the instruction out of the module registries.
392        let v = match mnemonic {
393            // ===== Memory operations =====
394            &Mnemonic::Load(Load { space, ptr, size }) => {
395                let addr = self.get_value(ptr.qualify(func))?;
396                Some(self.memory().read(space.qualify(func), addr, size)?)
397            }
398
399            &Mnemonic::Store(Store {
400                space,
401                ptr,
402                size,
403                src,
404            }) => {
405                let addr = self.get_value(ptr.qualify(func))?;
406                let value = self.get_value(src.qualify(func))?;
407                self.memory()
408                    .write(space.qualify(func), addr, size, value)?;
409                None
410            }
411
412            // ===== Control flow operations =====
413            Mnemonic::Branch(_)
414            | Mnemonic::CBranch(_)
415            | Mnemonic::BranchInd(_)
416            | Mnemonic::Call(_)
417            | Mnemonic::CallInd(_)
418            | Mnemonic::Return(_)
419            | Mnemonic::ReturnValue(_)
420            | Mnemonic::Apply(_) => None,
421
422            // ===== Unary operations =====
423            Mnemonic::Unop(Unary { op, src }) => {
424                let value = self.get_value(src.qualify(func))?;
425                let v = match op {
426                    Unop::IntNegate => value.int_negate(),
427                    Unop::IntNot => value.int_not(),
428                    Unop::FloatNegate => value.float_negate(),
429                    Unop::FloatAbs => value.float_abs(),
430                    Unop::FloatSqrt => value.float_sqrt(),
431                    Unop::FloatCeil => value.float_ceil(),
432                    Unop::FloatFloor => value.float_floor(),
433                    Unop::FloatRound => value.float_round(),
434                    _ => todo!("unimplemented unary operation: {:?}", op),
435                }?;
436                Some(v)
437            }
438
439            // ===== Binary operations =====
440            Mnemonic::Binop(Binary { op, lhs, rhs }) => {
441                let value1 = self.get_value(lhs.qualify(func))?;
442                let value2 = self.get_value(rhs.qualify(func))?;
443                let v = match *op {
444                    Binop::Int(IntBinop::Equal) => value1.int_equal(&value2),
445                    Binop::Int(IntBinop::NotEqual) => value1.int_not_equal(&value2),
446                    Binop::Int(IntBinop::Less) => value1.int_less(&value2),
447                    Binop::Int(IntBinop::SLess) => value1.int_sless(&value2),
448                    Binop::Int(IntBinop::LessEqual) => value1.int_less_equal(&value2),
449                    Binop::Int(IntBinop::SLessEqual) => value1.int_sless_equal(&value2),
450                    Binop::Int(IntBinop::Add) => value1.int_add(&value2),
451                    Binop::Int(IntBinop::Sub) => value1.int_sub(&value2),
452                    Binop::Int(IntBinop::Xor) => value1.int_xor(&value2),
453                    Binop::Int(IntBinop::And) => value1.int_and(&value2),
454                    Binop::Int(IntBinop::Or) => value1.int_or(&value2),
455                    Binop::Int(IntBinop::ShiftLeft) => value1.int_shift_left(&value2),
456                    Binop::Int(IntBinop::ShiftRight) => value1.int_shift_right(&value2),
457                    Binop::Int(IntBinop::SShiftRight) => value1.int_sshift_right(&value2),
458                    Binop::Int(IntBinop::Mul) => value1.int_mul(&value2),
459                    Binop::Int(IntBinop::Div) => value1.int_div(&value2),
460                    Binop::Int(IntBinop::Rem) => value1.int_rem(&value2),
461                    Binop::Int(IntBinop::Sdiv) => value1.int_sdiv(&value2),
462                    Binop::Int(IntBinop::Srem) => value1.int_srem(&value2),
463
464                    Binop::Float(FloatBinop::Add) => value1.float_add(&value2),
465                    Binop::Float(FloatBinop::Sub) => value1.float_sub(&value2),
466                    Binop::Float(FloatBinop::Mul) => value1.float_mul(&value2),
467                    Binop::Float(FloatBinop::Div) => value1.float_div(&value2),
468                    Binop::Float(FloatBinop::Equal) => value1.float_equal(&value2),
469                    Binop::Float(FloatBinop::NotEqual) => value1.float_not_equal(&value2),
470                    Binop::Float(FloatBinop::Less) => value1.float_less(&value2),
471                    Binop::Float(FloatBinop::LessEqual) => value1.float_less_equal(&value2),
472                    _ => todo!("unimplemented binary operation: {:?}", op),
473                }?;
474                Some(v)
475            }
476
477            // ===== Bit manipulation operations =====
478            &Mnemonic::PopCount(PopCount { src }) => {
479                let value = self.get_value(src.qualify(func))?;
480                Some(value.pop_count()?)
481            }
482
483            &Mnemonic::LzCount(LzCount { src }) => {
484                let value = self.get_value(src.qualify(func))?;
485                Some(value.lz_count()?)
486            }
487
488            &Mnemonic::Carry(Carry { lhs, rhs }) => {
489                let value1 = self.get_value(lhs.qualify(func))?;
490                let value2 = self.get_value(rhs.qualify(func))?;
491                Some(value1.carry(&value2)?)
492            }
493
494            &Mnemonic::SCarry(SCarry { lhs, rhs }) => {
495                let value1 = self.get_value(lhs.qualify(func))?;
496                let value2 = self.get_value(rhs.qualify(func))?;
497                Some(value1.scarry(&value2)?)
498            }
499
500            &Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
501                let value1 = self.get_value(lhs.qualify(func))?;
502                let value2 = self.get_value(rhs.qualify(func))?;
503                Some(value1.sborrow(&value2)?)
504            }
505
506            // ===== Casting operations =====
507            &Mnemonic::IsFloatNaN(IsFloatNaN { src }) => {
508                let value = self.get_value(src.qualify(func))?;
509                Some(value.is_float_nan()?)
510            }
511            &Mnemonic::IntToFloat(IntToFloat { src, size }) => {
512                let value = self.get_value(src.qualify(func))?;
513                Some(value.int_to_float(size)?)
514            }
515            &Mnemonic::FloatToFloat(FloatToFloat { src, size }) => {
516                let value = self.get_value(src.qualify(func))?;
517                Some(value.float_to_float(size)?)
518            }
519            &Mnemonic::FloatToInt(FloatToInt { src, size }) => {
520                let value = self.get_value(src.qualify(func))?;
521                Some(value.float_to_int(size)?)
522            }
523            &Mnemonic::Zext(Zext { src, size }) => {
524                let value = self.get_value(src.qualify(func))?;
525                Some(value.zext(size)?)
526            }
527            &Mnemonic::Sext(Sext { src, size }) => {
528                let value = self.get_value(src.qualify(func))?;
529                Some(value.sext(size)?)
530            }
531            &Mnemonic::Range(Range { src, start, size }) => {
532                let value = self.get_value(src.qualify(func))?;
533                Some(value.range(start, size)?)
534            }
535
536            // ===== Aggregate operations =====
537            // `Gep` is pure pointer arithmetic: base pointer + constant byte
538            // offset. The width follows the base (int_add uses the lhs width),
539            // so the immediate's default u64 width is harmless.
540            &Mnemonic::Gep(Gep { base, offset }) => {
541                let base = self.get_value(base.qualify(func))?;
542                let offset = Self::V::from_u64(offset as u64);
543                Some(base.int_add(&offset)?)
544            }
545
546            // ===== Other operations =====
547            Mnemonic::PCodeOp(op) => {
548                let name = self.ctx().shared.pcode_ops[op.id].clone();
549                match (name.as_ref(), op.args.as_slice()) {
550                    ("swap_bytes", [src]) => Some(self.get_value(src.qualify(func))?.byte_swap()?),
551                    // SLEIGH uses this zero-argument user-op as an explicit
552                    // write of an architecturally undefined value. Concrete
553                    // emulation deliberately chooses zero, while retaining
554                    // the p-code op and its destination in the IR for
555                    // analysis consumers.
556                    ("undef", []) => Some(Self::V::zero(insn.size())),
557                    // The LOCK prefix's bus semantics are not observable in a
558                    // single-threaded replay: it orders an access against other
559                    // agents, and constrains nothing about the resulting state.
560                    // The paired markers stay in the IR for analysis consumers
561                    // that care which region is atomic; they produce no value.
562                    ("LOCK" | "UNLOCK", []) => None,
563                    _ => return Err(EmulatorErrorKind::UnsupportedPCodeOp(name)),
564                }
565            }
566
567            Mnemonic::Intrinsic(intr) => {
568                let out_size = insn.size();
569                let mut args = Vec::with_capacity(intr.args.len());
570                for &arg in &intr.args {
571                    args.push(self.get_value(arg.qualify(func))?);
572                }
573                Some(Self::V::intrinsic(intr.id, &args, out_size)?)
574            }
575
576            // `map` has no interpreter (whole-array emulation is deferred). Bail
577            // recoverably so a best-effort consumer — pure-call folding emulating a
578            // function whose return depends on a `map` — declines to harvest the
579            // field instead of crashing the whole analysis. (Element projection
580            // does not go through emulation; it inlines the body via `ArrayProject`.)
581            Mnemonic::Map(_) => return Err(EmulatorErrorKind::UnsupportedMnemonic("map")),
582
583            _ => todo!("unimplemented mnemonic: {mnemonic:?}"),
584        };
585
586        Ok(v)
587    }
588
589    fn interpret(
590        &mut self,
591        insn: InstructionRef<'_, '_>,
592        mnemonic: &Mnemonic,
593    ) -> Result<Option<Self::V>> {
594        self.interpret_(&insn, mnemonic)
595            .map_err(|kind| EmulatorError::new(kind, &insn))
596    }
597}