Skip to main content

pcode_types/
instruction.rs

1//! Ghidra-style, flat p-code operations and their operands.
2//!
3//! These types represent one instruction as a sequence of operations over
4//! varnodes. They deliberately complement, rather than replace, the
5//! source-shaped [`crate::PcodeAst`]. A producer lowers nested expressions to
6//! these operations by allocating temporaries in its unique space.
7
8use crate::{
9    Ast, AstNode, BinaryOperator, BitRangeFieldId, Builtin, Expression, ExpressionTy, Ident,
10    LabelOrNode, Load, LocalVarId, PCodeOpId, PcodeAst, Range, RangeParam, RegisterId, SPACE_CONST,
11    SpaceId, TableId, UnaryOperator,
12};
13use serde::{Deserialize, Serialize};
14use std::{
15    collections::{HashMap, HashSet},
16    fmt,
17};
18
19/// A storage location or constant used as an input or output of a p-code op.
20///
21/// `space` and `offset` identify the location; `size` is its width in bytes.
22/// A constant is represented by [`SPACE_CONST`] and stores its value in
23/// `offset`.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub struct Varnode {
26    /// Address space containing this varnode, or [`SPACE_CONST`] for a constant.
27    pub space: SpaceId,
28    /// Byte offset in `space`, or the value for a constant-space varnode.
29    pub offset: u64,
30    /// Width in bytes.
31    pub size: usize,
32}
33
34impl Varnode {
35    /// Creates a varnode at `offset` in `space` with byte width `size`.
36    pub const fn new(space: SpaceId, offset: u64, size: usize) -> Self {
37        Self {
38            space,
39            offset,
40            size,
41        }
42    }
43
44    /// Creates a constant-space varnode containing `value` with byte width `size`.
45    pub const fn constant(value: u64, size: usize) -> Self {
46        Self::new(SPACE_CONST, value, size)
47    }
48
49    /// Returns whether this is a constant-space varnode.
50    pub fn is_constant(self) -> bool {
51        self.space == SPACE_CONST
52    }
53}
54
55/// An opcode from Ghidra's p-code operation reference.
56///
57/// The variants use Rust-style names; each maps one-for-one to Ghidra's
58/// `CPUI_*` opcode with the corresponding uppercase spelling. Some variants
59/// are analysis-only pseudo-operations and are identified by
60/// [`Self::is_raw_instruction_op`].
61#[repr(u8)]
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub enum Opcode {
64    Copy = 1,
65    Load,
66    Store,
67    Branch,
68    CBranch,
69    BranchInd,
70    Call,
71    CallInd,
72    CallOther,
73    Return,
74    IntEqual,
75    IntNotEqual,
76    IntSLess,
77    IntSLessEqual,
78    IntLess,
79    IntLessEqual,
80    IntZext,
81    IntSext,
82    IntAdd,
83    IntSub,
84    IntCarry,
85    IntSCarry,
86    IntSBorrow,
87    Int2Comp,
88    IntNegate,
89    IntXor,
90    IntAnd,
91    IntOr,
92    IntLeft,
93    IntRight,
94    IntSRight,
95    IntMult,
96    IntDiv,
97    IntSDiv,
98    IntRem,
99    IntSRem,
100    BoolNegate,
101    BoolXor,
102    BoolAnd,
103    BoolOr,
104    FloatEqual,
105    FloatNotEqual,
106    FloatLess,
107    FloatLessEqual,
108    // Ghidra reserves opcode slot 45.
109    FloatNan = 46,
110    FloatAdd,
111    FloatDiv,
112    FloatMult,
113    FloatSub,
114    FloatNeg,
115    FloatAbs,
116    FloatSqrt,
117    FloatInt2Float,
118    FloatFloat2Float,
119    FloatTrunc,
120    FloatCeil,
121    FloatFloor,
122    FloatRound,
123    MultiEqual,
124    Indirect,
125    Piece,
126    SubPiece,
127    Cast,
128    PtrAdd,
129    PtrSub,
130    SegmentOp,
131    CpoolRef,
132    New,
133    Insert,
134    Extract,
135    PopCount,
136    LzCount,
137}
138
139impl Opcode {
140    /// Every opcode in Ghidra's p-code operation reference.
141    pub const ALL: &[Self] = &[
142        Self::Copy,
143        Self::Load,
144        Self::Store,
145        Self::Branch,
146        Self::CBranch,
147        Self::BranchInd,
148        Self::Call,
149        Self::CallInd,
150        Self::CallOther,
151        Self::Return,
152        Self::IntEqual,
153        Self::IntNotEqual,
154        Self::IntSLess,
155        Self::IntSLessEqual,
156        Self::IntLess,
157        Self::IntLessEqual,
158        Self::IntZext,
159        Self::IntSext,
160        Self::IntAdd,
161        Self::IntSub,
162        Self::IntCarry,
163        Self::IntSCarry,
164        Self::IntSBorrow,
165        Self::Int2Comp,
166        Self::IntNegate,
167        Self::IntXor,
168        Self::IntAnd,
169        Self::IntOr,
170        Self::IntLeft,
171        Self::IntRight,
172        Self::IntSRight,
173        Self::IntMult,
174        Self::IntDiv,
175        Self::IntSDiv,
176        Self::IntRem,
177        Self::IntSRem,
178        Self::BoolNegate,
179        Self::BoolXor,
180        Self::BoolAnd,
181        Self::BoolOr,
182        Self::FloatEqual,
183        Self::FloatNotEqual,
184        Self::FloatLess,
185        Self::FloatLessEqual,
186        Self::FloatNan,
187        Self::FloatAdd,
188        Self::FloatDiv,
189        Self::FloatMult,
190        Self::FloatSub,
191        Self::FloatNeg,
192        Self::FloatAbs,
193        Self::FloatSqrt,
194        Self::FloatInt2Float,
195        Self::FloatFloat2Float,
196        Self::FloatTrunc,
197        Self::FloatCeil,
198        Self::FloatFloor,
199        Self::FloatRound,
200        Self::MultiEqual,
201        Self::Indirect,
202        Self::Piece,
203        Self::SubPiece,
204        Self::Cast,
205        Self::PtrAdd,
206        Self::PtrSub,
207        Self::SegmentOp,
208        Self::CpoolRef,
209        Self::New,
210        Self::Insert,
211        Self::Extract,
212        Self::PopCount,
213        Self::LzCount,
214    ];
215
216    /// Returns Ghidra's numeric `CPUI_*` opcode value.
217    pub const fn ghidra_id(self) -> u8 {
218        self as u8
219    }
220
221    /// Returns whether this opcode may occur in raw instruction p-code.
222    ///
223    /// Ghidra reserves SSA and type-recovery pseudo-operations for later
224    /// analysis. `INSERT` and `EXTRACT` are likewise pseudo-operations even
225    /// though they correspond to SLEIGH bit-range syntax.
226    pub const fn is_raw_instruction_op(self) -> bool {
227        !matches!(
228            self,
229            Self::MultiEqual
230                | Self::Indirect
231                | Self::Cast
232                | Self::PtrAdd
233                | Self::PtrSub
234                | Self::SegmentOp
235                | Self::Insert
236                | Self::Extract
237        )
238    }
239}
240
241/// One flat p-code operation.
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243pub struct PcodeOp {
244    /// The operation to perform.
245    pub opcode: Opcode,
246    /// Destination varnode, if this operation produces a value.
247    pub output: Option<Varnode>,
248    /// Source varnodes in Ghidra's documented operand order.
249    pub inputs: Vec<Varnode>,
250}
251
252impl PcodeOp {
253    /// Creates an operation with its output and ordered inputs.
254    pub fn new(opcode: Opcode, output: Option<Varnode>, inputs: Vec<Varnode>) -> Self {
255        Self {
256            opcode,
257            output,
258            inputs,
259        }
260    }
261}
262
263/// Flat p-code emitted for one machine instruction.
264#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
265pub struct InstructionPcode {
266    /// Operations in execution order.
267    pub ops: Vec<PcodeOp>,
268}
269
270impl InstructionPcode {
271    /// Creates an empty instruction p-code sequence.
272    pub const fn new() -> Self {
273        Self { ops: Vec::new() }
274    }
275
276    /// Returns whether this instruction has no p-code operations.
277    pub fn is_empty(&self) -> bool {
278        self.ops.is_empty()
279    }
280}
281
282/// Metadata for a named SLEIGH bit range.
283///
284/// The lowerer uses this to turn reads and writes of a bit-range identifier
285/// into raw p-code operations over its containing varnode.
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub struct BitRangeInfo {
288    /// The containing register or other storage varnode.
289    pub storage: Varnode,
290    /// Index of the least-significant bit in `storage`.
291    pub start: usize,
292    /// Number of bits in the named range.
293    pub size: usize,
294}
295
296/// Producer-specific information required to lower a source-shaped AST.
297///
298/// `wazabin-pcode` owns the lowering algorithm and calls this trait for
299/// specification data. A SLEIGH compiler can implement it using its compiled
300/// specification without making this crate depend on that compiler.
301pub trait PcodeLoweringContext {
302    /// The specification's default address space.
303    fn default_space(&self) -> SpaceId;
304    /// The unique address space used for deterministic temporary varnodes.
305    fn unique_space(&self) -> SpaceId;
306    /// Returns the storage varnode for a register identifier.
307    fn register_varnode(&self, id: RegisterId) -> Option<Varnode>;
308    /// Returns metadata for a named register bit range.
309    fn bitrange_info(&self, id: BitRangeFieldId) -> Option<BitRangeInfo>;
310    /// Returns the byte width of offsets in `space`.
311    fn address_size(&self, space: SpaceId) -> Option<usize>;
312}
313
314/// Failure while lowering source-shaped SLEIGH p-code to flat operations.
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub enum PcodeLowerError {
317    /// An expression needs a byte width but neither the AST nor its consumer supplies one.
318    UnknownSize,
319    /// A zero-byte varnode was requested.
320    ZeroSize,
321    /// A raw `COPY` would use different widths for its input and output.
322    CopySizeMismatch { input: usize, output: usize },
323    /// A raw operation requires equally-sized input varnodes.
324    InputSizeMismatch {
325        operation: &'static str,
326        left: usize,
327        right: usize,
328    },
329    /// A comparison or boolean operation did not produce a one-byte result.
330    InvalidBooleanSize(usize),
331    /// A memory pointer did not have the address width of its referenced space.
332    AddressSizeMismatch { expected: usize, actual: usize },
333    /// A store's declared width differed from the value being stored.
334    StoreSizeMismatch { declared: usize, value: usize },
335    /// A bit range is empty, exceeds its containing varnode, or cannot fit in a u64 mask.
336    InvalidRange {
337        start: usize,
338        size: usize,
339        storage_bits: usize,
340    },
341    /// Allocating a temporary overflowed the unique-space offset.
342    UniqueSpaceOverflow,
343    /// The lowering context did not know a register referenced by the AST.
344    UnknownRegister(RegisterId),
345    /// A source-level field, table, or global survived expansion.
346    UnresolvedIdentifier(&'static str),
347    /// A source-level memory space survived resolution.
348    UnresolvedSpace,
349    /// A source-level macro parameter survived expansion.
350    UnresolvedRangeParameter,
351    /// A source construct that must be expanded before lowering survived.
352    InternalNode(&'static str),
353    /// The operation cannot be represented as raw instruction p-code yet.
354    Unsupported(&'static str),
355    /// A label was declared more than once in one instruction.
356    DuplicateLabel(Box<str>),
357    /// A branch referred to a label that was not declared in this instruction.
358    UnknownLabel(Box<str>),
359    /// A direct branch target was not a literal machine address or local label.
360    InvalidDirectTarget,
361}
362
363impl fmt::Display for PcodeLowerError {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        match self {
366            Self::UnknownSize => write!(f, "p-code expression has no known byte width"),
367            Self::ZeroSize => write!(f, "p-code varnodes cannot have zero byte width"),
368            Self::CopySizeMismatch { input, output } => write!(
369                f,
370                "p-code COPY requires equal widths, got {input} and {output} bytes"
371            ),
372            Self::InputSizeMismatch {
373                operation,
374                left,
375                right,
376            } => write!(
377                f,
378                "p-code {operation} requires equal input widths, got {left} and {right} bytes"
379            ),
380            Self::InvalidBooleanSize(size) => {
381                write!(
382                    f,
383                    "p-code comparison and boolean outputs must be one byte, got {size}"
384                )
385            }
386            Self::AddressSizeMismatch { expected, actual } => write!(
387                f,
388                "p-code memory pointer requires {expected} bytes, got {actual}"
389            ),
390            Self::StoreSizeMismatch { declared, value } => write!(
391                f,
392                "p-code STORE declares {declared} bytes but receives {value}"
393            ),
394            Self::InvalidRange {
395                start,
396                size,
397                storage_bits,
398            } => write!(
399                f,
400                "invalid bit range [{start}, {size}] for {storage_bits}-bit storage"
401            ),
402            Self::UniqueSpaceOverflow => write!(f, "unique-space temporary allocation overflowed"),
403            Self::UnknownRegister(id) => write!(f, "unknown register {}", usize::from(*id)),
404            Self::UnresolvedIdentifier(kind) => {
405                write!(f, "unresolved {kind} reached p-code lowering")
406            }
407            Self::UnresolvedSpace => write!(f, "unresolved address space reached p-code lowering"),
408            Self::UnresolvedRangeParameter => {
409                write!(f, "unresolved range parameter reached p-code lowering")
410            }
411            Self::InternalNode(kind) => write!(f, "unexpanded {kind} reached p-code lowering"),
412            Self::Unsupported(what) => write!(f, "raw p-code lowering does not support {what}"),
413            Self::DuplicateLabel(label) => write!(f, "duplicate p-code label `{label}`"),
414            Self::UnknownLabel(label) => write!(f, "unknown p-code label `{label}`"),
415            Self::InvalidDirectTarget => write!(f, "invalid direct p-code branch target"),
416        }
417    }
418}
419
420impl std::error::Error for PcodeLowerError {}
421
422/// Identifies one instruction-local p-code label.
423///
424/// A streaming emitter cannot know the operation index of a forward label, so
425/// labels reach a [`PcodeSink`] symbolically. Identifiers are assigned in the
426/// order the AST defines the labels.
427#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
428pub struct LabelId(u32);
429
430impl LabelId {
431    /// Returns this label's index, which is stable within one [`PcodePlan`].
432    pub const fn index(self) -> usize {
433        self.0 as usize
434    }
435
436    /// Rebuilds a label from an index returned by [`index`](Self::index).
437    pub const fn from_index(index: usize) -> Self {
438        Self(index as u32)
439    }
440}
441
442/// Whole-instruction facts a consumer needs before p-code emission starts.
443///
444/// Planning is a read-only pass over the expanded AST. It exists so a consumer
445/// can prepare instruction-wide state — out-of-instruction branch and call
446/// destinations, instruction-local labels — without a flat p-code vector to
447/// re-scan.
448#[derive(Debug, Clone, Default)]
449pub struct PcodePlan {
450    /// Local widths resolved from their uses, needed before a forward-only
451    /// emitter can allocate a local's temporary.
452    local_sizes: HashMap<LocalVarId, usize>,
453    labels: Vec<Box<str>>,
454    /// Whether each label stands at the end of the instruction, where it is
455    /// the machine instruction's fall-through rather than a local block.
456    terminal: Vec<bool>,
457    label_ids: HashMap<Box<str>, LabelId>,
458    direct_branches: Vec<u64>,
459    direct_calls: Vec<u64>,
460}
461
462impl PcodePlan {
463    /// Names of the instruction-local labels, indexed by [`LabelId::index`].
464    pub fn labels(&self) -> &[Box<str>] {
465        &self.labels
466    }
467
468    /// Returns whether `label` stands after this instruction's last
469    /// operation. Such a label is the machine instruction's fall-through: a
470    /// consumer should send a branch to it wherever execution continues after
471    /// the instruction, rather than open a block for it.
472    pub fn is_terminal(&self, label: LabelId) -> bool {
473        self.terminal[label.index()]
474    }
475
476    /// Addresses this instruction can reach with a direct branch.
477    pub fn direct_branches(&self) -> &[u64] {
478        &self.direct_branches
479    }
480
481    /// Addresses this instruction can reach with a direct call.
482    pub fn direct_calls(&self) -> &[u64] {
483        &self.direct_calls
484    }
485
486    /// Declares an instruction-local label and returns its identifier.
487    ///
488    /// A name is only assigned one identifier: a duplicate *definition* is an
489    /// emission-time error, reported with the name. This is public so a
490    /// consumer holding already-flattened p-code can rebuild an equivalent
491    /// plan for the same emitter.
492    pub fn declare_label(&mut self, label: &str) -> LabelId {
493        if let Some(id) = self.label_ids.get(label) {
494            return *id;
495        }
496        let id = LabelId(self.labels.len() as u32);
497        self.labels.push(Box::from(label));
498        self.terminal.push(false);
499        self.label_ids.insert(Box::from(label), id);
500        id
501    }
502
503    /// Declares an address this instruction can reach with a direct branch.
504    pub fn declare_direct_branch(&mut self, address: u64) {
505        if !self.direct_branches.contains(&address) {
506            self.direct_branches.push(address);
507        }
508    }
509
510    /// Declares an address this instruction can reach with a direct call.
511    pub fn declare_direct_call(&mut self, address: u64) {
512        if !self.direct_calls.contains(&address) {
513            self.direct_calls.push(address);
514        }
515    }
516
517    fn label_id(&self, label: &str) -> Option<LabelId> {
518        self.label_ids.get(label).copied()
519    }
520}
521
522/// Receives resolved p-code operations as an instruction is emitted.
523///
524/// Operations arrive in execution order. Sinks are infallible: a consumer that
525/// can fail records its own error and ignores the rest of the instruction,
526/// because a partially emitted instruction is discarded by its caller.
527pub trait PcodeSink {
528    /// Receives one resolved operation. `inputs` is borrowed for the call
529    /// only, so a sink which needs to retain the operation must copy it.
530    fn op(&mut self, opcode: Opcode, output: Option<Varnode>, inputs: &[Varnode]);
531
532    /// Marks the position of an instruction-local label: the next operation
533    /// reported is its target.
534    fn label(&mut self, label: LabelId);
535
536    /// Receives a branch whose target is instruction-local. `opcode` is
537    /// [`Opcode::Branch`] or [`Opcode::CBranch`], and `condition` is present
538    /// exactly for the latter.
539    fn branch_label(&mut self, opcode: Opcode, label: LabelId, condition: Option<Varnode>);
540}
541
542/// The sink which reproduces [`InstructionPcode`]: it retains operations and
543/// resolves local branches into the relative constant targets raw p-code uses.
544#[derive(Debug, Default)]
545struct Collector {
546    ops: Vec<PcodeOp>,
547    label_ops: HashMap<LabelId, usize>,
548    fixups: Vec<(usize, LabelId)>,
549}
550
551impl Collector {
552    fn finish(mut self, plan: &PcodePlan) -> Result<Vec<PcodeOp>, PcodeLowerError> {
553        for (op_index, label) in &self.fixups {
554            let target = *self
555                .label_ops
556                .get(label)
557                .ok_or_else(|| PcodeLowerError::UnknownLabel(plan.labels[label.index()].clone()))?;
558            let relative = i64::try_from(target)
559                .ok()
560                .and_then(|target| {
561                    i64::try_from(*op_index)
562                        .ok()
563                        .and_then(|source| target.checked_sub(source))
564                })
565                .ok_or(PcodeLowerError::UniqueSpaceOverflow)?;
566            self.ops[*op_index].inputs[0] = Varnode::constant(relative as u64, 8);
567        }
568        Ok(self.ops)
569    }
570}
571
572impl PcodeSink for Collector {
573    fn op(&mut self, opcode: Opcode, output: Option<Varnode>, inputs: &[Varnode]) {
574        self.ops.push(PcodeOp::new(opcode, output, inputs.to_vec()));
575    }
576
577    fn label(&mut self, label: LabelId) {
578        self.label_ops.insert(label, self.ops.len());
579    }
580
581    fn branch_label(&mut self, opcode: Opcode, label: LabelId, condition: Option<Varnode>) {
582        let mut inputs = Vec::with_capacity(1 + usize::from(condition.is_some()));
583        inputs.push(Varnode::constant(0, 8));
584        inputs.extend(condition);
585        self.fixups.push((self.ops.len(), label));
586        self.ops.push(PcodeOp::new(opcode, None, inputs));
587    }
588}
589
590/// Collects the whole-instruction facts of `ast` without emitting p-code.
591///
592/// The plan is the shared contract between the p-code emitter and its
593/// consumer: it is produced from the same expanded AST that emission lowers,
594/// so a consumer never has to re-scan flat p-code to discover them.
595pub fn plan_instruction(
596    ast: &PcodeAst,
597    context: &impl PcodeLoweringContext,
598) -> Result<PcodePlan, PcodeLowerError> {
599    let mut planner = Planner {
600        context,
601        plan: PcodePlan::default(),
602    };
603    planner.plan(ast);
604    Ok(planner.plan)
605}
606
607/// A width in the domain a width-inference pass works over.
608///
609/// Per-instruction planning knows every width as a concrete byte count. A
610/// producer resolving its own source bodies does not: a body's local can take
611/// its width from a table operand whose export only exists once an instruction
612/// is decoded. Naming that dependency rather than dropping it is what makes
613/// the two passes agree — a pass that simply skipped the unknown would size
614/// the local from a *later* statement and reach a different answer.
615pub trait Width: Copy + Eq + std::fmt::Debug {
616    /// A concrete width in bytes.
617    fn fixed(size: usize) -> Self;
618
619    /// This width as a byte count, if it is concrete. Arithmetic on a width
620    /// uses this, so a symbolic width yields no constraint rather than a
621    /// wrong one.
622    fn size(self) -> Option<usize>;
623
624    /// The width of the value a sub-table operand exports, if this domain can
625    /// name it. Concrete domains cannot, and report the width as unknown.
626    ///
627    /// Only a table operand is nameable. A decoder field substitutes an
628    /// integer literal, and a literal deliberately establishes no width, so
629    /// naming one would claim a width the per-instruction pass never infers.
630    fn operand(_table: TableId) -> Option<Self> {
631        None
632    }
633}
634
635impl Width for usize {
636    fn fixed(size: usize) -> Self {
637        size
638    }
639
640    fn size(self) -> Option<usize> {
641        Some(self)
642    }
643}
644
645/// A width resolved before decoding, which may still name an operand.
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647pub enum SymbolicWidth {
648    /// A concrete width in bytes.
649    Fixed(usize),
650    /// The width of the value a sub-table operand exports.
651    SameAs(TableId),
652}
653
654impl Width for SymbolicWidth {
655    fn fixed(size: usize) -> Self {
656        Self::Fixed(size)
657    }
658
659    fn size(self) -> Option<usize> {
660        match self {
661            Self::Fixed(size) => Some(size),
662            Self::SameAs(_) => None,
663        }
664    }
665
666    fn operand(table: TableId) -> Option<Self> {
667        Some(Self::SameAs(table))
668    }
669}
670
671/// Widths, in bytes, of the local variables of one p-code body.
672pub type LocalSizes = HashMap<LocalVarId, usize>;
673
674/// The widths of one p-code body, and the locals that have none.
675#[derive(Debug, Clone, Default)]
676pub struct BodyWidths<W> {
677    /// Resolved widths, keyed by the body's own local ids.
678    pub widths: HashMap<LocalVarId, W>,
679    /// Locals the body uses but no width could be resolved for, in id order.
680    ///
681    /// In a symbolic domain these are unsizable by *any* decode — nothing a
682    /// producer substitutes can give them a width — so they are a defect in
683    /// the body rather than a limitation of this pass.
684    pub unsized_locals: Vec<LocalVarId>,
685}
686
687impl<W> BodyWidths<W> {
688    /// Whether every local the body uses has a width.
689    pub fn is_complete(&self) -> bool {
690        self.unsized_locals.is_empty()
691    }
692}
693
694/// Resolves the widths of a body's locals and reports the ones with none.
695///
696/// Unlike [`infer_local_sizes`] this also visits the body to find which locals
697/// it actually uses, so a caller can tell a width it failed to resolve from an
698/// id that no statement mentions. A body's `local_var_count` is a high-water
699/// mark of the id space after macro inlining, not its live set.
700pub fn resolve_body_widths<S, W: Width>(
701    statements: &[Ast<S>],
702    context: &impl PcodeLoweringContext,
703) -> BodyWidths<W> {
704    let widths: HashMap<LocalVarId, W> = SizeInference::run(context, statements);
705    let mut used = Vec::new();
706    live_locals(statements, &mut used);
707    used.sort_unstable_by_key(|id| id.0);
708    used.dedup();
709    let unsized_locals = used
710        .into_iter()
711        .filter(|id| !widths.contains_key(id))
712        .collect();
713    BodyWidths {
714        widths,
715        unsized_locals,
716    }
717}
718
719/// Collects the locals `statements` reads or writes.
720fn live_locals<S>(statements: &[Ast<S>], out: &mut Vec<LocalVarId>) {
721    fn walk_expr<S>(value: &Expression<S>, out: &mut Vec<LocalVarId>) {
722        match &value.ty {
723            ExpressionTy::Ident(Ident::Named(id)) => out.push(*id),
724            ExpressionTy::Ident(_) | ExpressionTy::SizedInt { .. } => {}
725            ExpressionTy::Load(load) => walk_expr(&load.ptr, out),
726            ExpressionTy::Range(range) => walk_expr(&range.value, out),
727            ExpressionTy::SubPieceLsb { src, .. } | ExpressionTy::SubPieceMsb { src, .. } => {
728                walk_expr(src, out);
729            }
730            ExpressionTy::FunctionCall { args, .. }
731            | ExpressionTy::PcodeOp { args, .. }
732            | ExpressionTy::MacroCall { args, .. }
733            | ExpressionTy::DeferredCall { args, .. } => {
734                args.iter().for_each(|arg| walk_expr(arg, out));
735            }
736            ExpressionTy::Unop(unop) => walk_expr(&unop.e, out),
737            ExpressionTy::Binop(binop) => {
738                walk_expr(&binop.lhs, out);
739                walk_expr(&binop.rhs, out);
740            }
741        }
742    }
743    fn walk_target<S>(target: &LabelOrNode<S>, out: &mut Vec<LocalVarId>) {
744        if let LabelOrNode::Expr(value) = target {
745            walk_expr(value, out);
746        }
747    }
748    for statement in statements {
749        match &statement.ty {
750            AstNode::Assignment { lhs, rhs, .. } => {
751                if let Ident::Named(id) = lhs {
752                    out.push(*id);
753                }
754                walk_expr(rhs, out);
755            }
756            AstNode::LoadAssignment { lhs, rhs, .. } => {
757                walk_expr(&lhs.ptr, out);
758                walk_expr(rhs, out);
759            }
760            AstNode::RangeAssignment { lhs, rhs, .. } => {
761                walk_expr(&lhs.value, out);
762                walk_expr(rhs, out);
763            }
764            AstNode::Branch { target } | AstNode::Call { target } => walk_target(target, out),
765            AstNode::ConditionalBranch { target, condition } => {
766                walk_target(target, out);
767                walk_expr(condition, out);
768            }
769            AstNode::BranchIndirect { target: value }
770            | AstNode::CallIndirect { target: value }
771            | AstNode::Return { target: value }
772            | AstNode::Expression(value)
773            | AstNode::Export(value) => walk_expr(value, out),
774            AstNode::Label(_)
775            | AstNode::Build(_)
776            | AstNode::DeferredBuild(_)
777            | AstNode::DelaySlot(_) => {}
778        }
779    }
780}
781
782/// Infers the local-variable widths of a *source* p-code body.
783///
784/// This is the same inference [`plan_instruction`] runs per decoded
785/// instruction, exposed so a producer can resolve widths once per body at
786/// specification-compile time — reporting an unsizable local as a compile
787/// error rather than an `UnknownSize` at lift time, and leaving nothing for
788/// the per-instruction planner to iterate.
789///
790/// A local absent from the result could not be sized from this body alone: its
791/// width comes from a value the producer substitutes into the body, so the
792/// producer must resolve it or fall back to [`plan_instruction`].
793pub fn infer_local_sizes<S, W: Width>(
794    statements: &[Ast<S>],
795    context: &impl PcodeLoweringContext,
796) -> HashMap<LocalVarId, W> {
797    SizeInference::run(context, statements)
798}
799
800/// Plans `ast` with local widths the producer has already resolved.
801///
802/// The caller guarantees `local_sizes` covers every local the body uses; a
803/// missing width is an `UnknownSize` error at emission, exactly as it is when
804/// the per-instruction inference cannot resolve one.
805pub fn plan_instruction_with(
806    ast: &PcodeAst,
807    context: &impl PcodeLoweringContext,
808    local_sizes: LocalSizes,
809) -> Result<PcodePlan, PcodeLowerError> {
810    let mut planner = Planner {
811        context,
812        plan: PcodePlan {
813            local_sizes,
814            ..PcodePlan::default()
815        },
816    };
817    planner.plan_statements(ast);
818    Ok(planner.plan)
819}
820
821/// Lowers `ast` and reports each resolved operation to `sink`.
822///
823/// `plan` must come from [`plan_instruction`] for the same AST and context.
824/// Unlike [`lower_instruction`], no operation vector is built: local branches
825/// are reported against the plan's labels rather than resolved offsets.
826pub fn emit_instruction(
827    ast: &PcodeAst,
828    context: &impl PcodeLoweringContext,
829    plan: &PcodePlan,
830    sink: &mut impl PcodeSink,
831) -> Result<(), PcodeLowerError> {
832    Lowerer::new(context, plan, sink).emit_all(ast)
833}
834
835/// Lowers a fully expanded source-shaped AST to Ghidra-style instruction p-code.
836///
837/// The AST must be in consumer form: `build`, `export`, delay-slot, macro,
838/// deferred-name, field, and table nodes are rejected. The lowerer allocates
839/// deterministic temporaries in [`PcodeLoweringContext::unique_space`].
840///
841/// Bit ranges are expanded into shifts, masks, and `SUBPIECE` rather than
842/// Ghidra's analysis-only `INSERT` and `EXTRACT` pseudo-operations. Range
843/// writes whose parent storage exceeds 64 bits are rejected because this
844/// source AST's literals cannot represent their full clear mask.
845pub fn lower_instruction(
846    ast: &PcodeAst,
847    context: &impl PcodeLoweringContext,
848) -> Result<InstructionPcode, PcodeLowerError> {
849    Ok(InstructionPcode {
850        ops: collect_ops(ast, context)?,
851    })
852}
853
854fn collect_ops(
855    ast: &PcodeAst,
856    context: &impl PcodeLoweringContext,
857) -> Result<Vec<PcodeOp>, PcodeLowerError> {
858    let plan = plan_instruction(ast, context)?;
859    let mut collector = Collector::default();
860    emit_instruction(ast, context, &plan, &mut collector)?;
861    collector.finish(&plan)
862}
863
864/// Lowers `ast` and exposes its resolved flat p-code to `sink` without
865/// materializing an [`InstructionPcode`] for the caller.
866///
867/// The lowerer still keeps operations temporarily while resolving local branch
868/// labels. Consumers which can process a complete instruction synchronously
869/// can therefore avoid making the flat p-code an owned boundary object.
870pub fn lower_instruction_into<R>(
871    ast: &PcodeAst,
872    context: &impl PcodeLoweringContext,
873    sink: impl FnOnce(&[PcodeOp]) -> R,
874) -> Result<R, PcodeLowerError> {
875    let ops = collect_ops(ast, context)?;
876    Ok(sink(&ops))
877}
878
879impl InstructionPcode {
880    /// Lowers `ast` using producer-specific information from `context`.
881    pub fn lower(
882        ast: &PcodeAst,
883        context: &impl PcodeLoweringContext,
884    ) -> Result<Self, PcodeLowerError> {
885        lower_instruction(ast, context)
886    }
887}
888
889struct Lowerer<'a, 'p, 's, C: PcodeLoweringContext + ?Sized, S: PcodeSink + ?Sized> {
890    context: &'a C,
891    plan: &'p PcodePlan,
892    sink: &'s mut S,
893    locals: HashMap<LocalVarId, Varnode>,
894    defined_labels: HashSet<LabelId>,
895    next_unique: u64,
896}
897
898impl<'a, 'p, 's, C: PcodeLoweringContext + ?Sized, S: PcodeSink + ?Sized>
899    Lowerer<'a, 'p, 's, C, S>
900{
901    fn new(context: &'a C, plan: &'p PcodePlan, sink: &'s mut S) -> Self {
902        Self {
903            context,
904            plan,
905            sink,
906            locals: HashMap::new(),
907            defined_labels: HashSet::new(),
908            next_unique: 0,
909        }
910    }
911
912    fn emit_all(mut self, ast: &PcodeAst) -> Result<(), PcodeLowerError> {
913        for statement in &ast.statements {
914            self.lower_statement(&statement.ty)?;
915        }
916        Ok(())
917    }
918
919    fn emit(&mut self, opcode: Opcode, output: Option<Varnode>, inputs: &[Varnode]) {
920        self.sink.op(opcode, output, inputs);
921    }
922
923    fn lower_statement(&mut self, statement: &AstNode) -> Result<(), PcodeLowerError> {
924        match statement {
925            AstNode::Assignment {
926                lhs: Ident::BitRange(id),
927                rhs,
928                ..
929            } => {
930                let info = self
931                    .context
932                    .bitrange_info(*id)
933                    .ok_or(PcodeLowerError::Unsupported("an unknown named bit range"))?;
934                self.insert_range(info.storage, info.start, info.size, rhs)?;
935            }
936            AstNode::Assignment { lhs, size, rhs } => {
937                let output =
938                    self.storage_for_ident(lhs.clone(), size.or_else(|| self.expr_size(rhs)))?;
939                self.lower_expr(rhs, Some(output))?;
940            }
941            AstNode::LoadAssignment { lhs, rhs, .. } => self.lower_store(lhs, rhs)?,
942            AstNode::RangeAssignment { lhs, rhs, .. } => self.lower_range_assignment(lhs, rhs)?,
943            AstNode::Build(_) => return Err(PcodeLowerError::InternalNode("build statement")),
944            AstNode::DelaySlot(_) => {
945                return Err(PcodeLowerError::InternalNode("delay-slot directive"));
946            }
947            AstNode::DeferredBuild(_) => {
948                return Err(PcodeLowerError::InternalNode("deferred build statement"));
949            }
950            AstNode::Label(label) => {
951                let id = self.label_id(label)?;
952                if !self.defined_labels.insert(id) {
953                    return Err(PcodeLowerError::DuplicateLabel(label.clone()));
954                }
955                self.sink.label(id);
956            }
957            AstNode::Branch { target } => self.lower_direct_flow(Opcode::Branch, target, None)?,
958            AstNode::ConditionalBranch { condition, target } => {
959                let condition = self.lower_expr(condition, None)?;
960                self.lower_direct_flow(Opcode::CBranch, target, Some(condition))?;
961            }
962            AstNode::BranchIndirect { target } => {
963                self.lower_indirect_flow(Opcode::BranchInd, target)?
964            }
965            AstNode::Call { target } => self.lower_direct_flow(Opcode::Call, target, None)?,
966            AstNode::CallIndirect { target } => {
967                self.lower_indirect_flow(Opcode::CallInd, target)?
968            }
969            AstNode::Return { target } => self.lower_indirect_flow(Opcode::Return, target)?,
970            AstNode::Export(_) => return Err(PcodeLowerError::InternalNode("export statement")),
971            AstNode::Expression(expr) => self.lower_effect(expr)?,
972        }
973        Ok(())
974    }
975
976    fn lower_store(&mut self, load: &Load, rhs: &Expression) -> Result<(), PcodeLowerError> {
977        let space = self.load_space(load)?;
978        if space == SPACE_CONST {
979            return Err(PcodeLowerError::Unsupported("a store to constant space"));
980        }
981        let ptr = self.lower_expr(&load.ptr, None)?;
982        self.validate_pointer(space, ptr)?;
983        let value = match load.size {
984            Some(size) => self.lower_expr_with_size(rhs, size)?,
985            None => self.lower_expr(rhs, None)?,
986        };
987        if let Some(size) = load.size {
988            Self::checked_size(size)?;
989            if size != value.size {
990                return Err(PcodeLowerError::StoreSizeMismatch {
991                    declared: size,
992                    value: value.size,
993                });
994            }
995        }
996        self.emit(Opcode::Store, None, &[Self::space_id(space), ptr, value]);
997        Ok(())
998    }
999
1000    fn lower_direct_flow(
1001        &mut self,
1002        opcode: Opcode,
1003        target: &LabelOrNode,
1004        condition: Option<Varnode>,
1005    ) -> Result<(), PcodeLowerError> {
1006        let target = match target {
1007            LabelOrNode::Label(label) => {
1008                // A local branch keeps its symbolic target: a streaming sink
1009                // cannot be handed a relative offset to a label it has not
1010                // reached yet.
1011                let id = self.label_id(label)?;
1012                self.sink.branch_label(opcode, id, condition);
1013                return Ok(());
1014            }
1015            LabelOrNode::Node(_) => {
1016                return Err(PcodeLowerError::InternalNode("unresolved branch target"));
1017            }
1018            LabelOrNode::Expr(expr) => self.direct_target(expr)?,
1019        };
1020        match condition {
1021            Some(condition) => self.emit(opcode, None, &[target, condition]),
1022            None => self.emit(opcode, None, &[target]),
1023        }
1024        Ok(())
1025    }
1026
1027    fn lower_indirect_flow(
1028        &mut self,
1029        opcode: Opcode,
1030        target: &Expression,
1031    ) -> Result<(), PcodeLowerError> {
1032        let target = self.lower_expr(target, None)?;
1033        self.emit(opcode, None, &[target]);
1034        Ok(())
1035    }
1036
1037    fn direct_target(&self, target: &Expression) -> Result<Varnode, PcodeLowerError> {
1038        let ExpressionTy::SizedInt { value, size } = target.ty else {
1039            return Err(PcodeLowerError::InvalidDirectTarget);
1040        };
1041        let size = size
1042            .or(target.size)
1043            .or_else(|| self.context.address_size(self.context.default_space()))
1044            .ok_or(PcodeLowerError::UnknownSize)?;
1045        Self::checked_size(size)?;
1046        Ok(Varnode::new(self.context.default_space(), value, size))
1047    }
1048
1049    fn lower_effect(&mut self, expr: &Expression) -> Result<(), PcodeLowerError> {
1050        match &expr.ty {
1051            ExpressionTy::PcodeOp { id, args } => {
1052                let inputs = self.lower_userop_inputs(*id, args)?;
1053                self.emit(Opcode::CallOther, None, &inputs);
1054                Ok(())
1055            }
1056            ExpressionTy::MacroCall { .. } => Err(PcodeLowerError::InternalNode("macro call")),
1057            ExpressionTy::DeferredCall { .. } => {
1058                Err(PcodeLowerError::InternalNode("deferred call"))
1059            }
1060            _ => Err(PcodeLowerError::Unsupported("a discarded value expression")),
1061        }
1062    }
1063
1064    fn lower_expr(
1065        &mut self,
1066        expr: &Expression,
1067        requested_output: Option<Varnode>,
1068    ) -> Result<Varnode, PcodeLowerError> {
1069        match &expr.ty {
1070            ExpressionTy::SizedInt { value, size } => {
1071                // Raw p-code integer literals take the width of the
1072                // operation that consumes them, including an explicitly
1073                // suffixed source literal used for a wider x86-64 register
1074                // write (for example `R10 = imm32`).
1075                let input = Varnode::constant(
1076                    *value,
1077                    requested_output
1078                        .map(|output| output.size)
1079                        .or(*size)
1080                        .or(expr.size)
1081                        .ok_or(PcodeLowerError::UnknownSize)?,
1082                );
1083                self.copy_if_requested(input, requested_output)
1084            }
1085            ExpressionTy::Ident(Ident::BitRange(id)) => {
1086                self.lower_named_bitrange(*id, requested_output)
1087            }
1088            ExpressionTy::Ident(ident) => {
1089                let input = self.storage_for_ident(ident.clone(), expr.size)?;
1090                self.copy_if_requested(input, requested_output)
1091            }
1092            ExpressionTy::Load(load) => self.lower_load(expr, load, requested_output),
1093            ExpressionTy::SubPieceMsb { src, count } => {
1094                let input = self.lower_expr(src, None)?;
1095                let size = requested_output
1096                    .map(|output| output.size)
1097                    .or(expr.size)
1098                    .unwrap_or_else(|| input.size.saturating_sub(*count));
1099                let output = self.output(requested_output, size)?;
1100                if *count >= input.size || size > input.size - count {
1101                    return Err(PcodeLowerError::InvalidRange {
1102                        start: count.saturating_mul(8),
1103                        size: size.saturating_mul(8),
1104                        storage_bits: input.size.saturating_mul(8),
1105                    });
1106                }
1107                self.emit(
1108                    Opcode::SubPiece,
1109                    Some(output),
1110                    &[input, Varnode::constant(*count as u64, 8)],
1111                );
1112                Ok(output)
1113            }
1114            ExpressionTy::SubPieceLsb { src, count } => {
1115                let input = self.lower_expr(src, None)?;
1116                if *count == 0 || *count > input.size {
1117                    return Err(PcodeLowerError::InvalidRange {
1118                        start: 0,
1119                        size: count.saturating_mul(8),
1120                        storage_bits: input.size.saturating_mul(8),
1121                    });
1122                }
1123                let output = self.output(requested_output, *count)?;
1124                self.emit(
1125                    Opcode::SubPiece,
1126                    Some(output),
1127                    &[input, Varnode::constant(0, 8)],
1128                );
1129                Ok(output)
1130            }
1131            ExpressionTy::Range(range) => self.lower_range(range, requested_output),
1132            ExpressionTy::FunctionCall { builtin, args } => {
1133                self.lower_builtin(expr, *builtin, args, requested_output)
1134            }
1135            ExpressionTy::PcodeOp { id, args } => {
1136                let size = requested_output
1137                    .map(|output| output.size)
1138                    .or(expr.size)
1139                    .ok_or(PcodeLowerError::UnknownSize)?;
1140                let output = self.output(requested_output, size)?;
1141                let inputs = self.lower_userop_inputs(*id, args)?;
1142                self.emit(Opcode::CallOther, Some(output), &inputs);
1143                Ok(output)
1144            }
1145            ExpressionTy::MacroCall { .. } => Err(PcodeLowerError::InternalNode("macro call")),
1146            ExpressionTy::DeferredCall { .. } => {
1147                Err(PcodeLowerError::InternalNode("deferred call"))
1148            }
1149            ExpressionTy::Unop(unop) => self.lower_unop(expr, unop.op, &unop.e, requested_output),
1150            ExpressionTy::Binop(binop) => {
1151                self.lower_binop(expr, binop.op, &binop.lhs, &binop.rhs, requested_output)
1152            }
1153        }
1154    }
1155
1156    fn lower_load(
1157        &mut self,
1158        expr: &Expression,
1159        load: &Load,
1160        requested_output: Option<Varnode>,
1161    ) -> Result<Varnode, PcodeLowerError> {
1162        let space = self.load_space(load)?;
1163        let ptr = self.lower_expr(&load.ptr, None)?;
1164        if space != SPACE_CONST {
1165            self.validate_pointer(space, ptr)?;
1166        }
1167        let size = requested_output
1168            .map(|output| output.size)
1169            .or(load.size)
1170            .or(expr.size)
1171            .ok_or(PcodeLowerError::UnknownSize)?;
1172        if space == SPACE_CONST {
1173            if ptr.size != size {
1174                return Err(PcodeLowerError::Unsupported(
1175                    "a constant-space load that changes width",
1176                ));
1177            }
1178            return self.copy_if_requested(ptr, requested_output);
1179        }
1180        let output = self.output(requested_output, size)?;
1181        self.emit(Opcode::Load, Some(output), &[Self::space_id(space), ptr]);
1182        Ok(output)
1183    }
1184
1185    fn lower_builtin(
1186        &mut self,
1187        expr: &Expression,
1188        builtin: Builtin,
1189        args: &[Expression],
1190        requested_output: Option<Varnode>,
1191    ) -> Result<Varnode, PcodeLowerError> {
1192        let opcode = match builtin {
1193            Builtin::Carry => Opcode::IntCarry,
1194            Builtin::Scarry => Opcode::IntSCarry,
1195            Builtin::Sborrow => Opcode::IntSBorrow,
1196            Builtin::Nan => Opcode::FloatNan,
1197            Builtin::Abs => Opcode::FloatAbs,
1198            Builtin::Sqrt => Opcode::FloatSqrt,
1199            Builtin::Floor => Opcode::FloatFloor,
1200            Builtin::Ceil => Opcode::FloatCeil,
1201            Builtin::Round => Opcode::FloatRound,
1202            Builtin::Int2Float => Opcode::FloatInt2Float,
1203            Builtin::Float2Float => Opcode::FloatFloat2Float,
1204            Builtin::Trunc => Opcode::FloatTrunc,
1205            Builtin::Zext => Opcode::IntZext,
1206            Builtin::Sext => Opcode::IntSext,
1207            Builtin::Popcount => Opcode::PopCount,
1208            Builtin::Lzcount => Opcode::LzCount,
1209            Builtin::Cpool => Opcode::CpoolRef,
1210            Builtin::NewObject => Opcode::New,
1211        };
1212        let size = requested_output
1213            .map(|output| output.size)
1214            .or(expr.size)
1215            .or(match builtin {
1216                Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan => Some(1),
1217                _ => None,
1218            })
1219            .ok_or(PcodeLowerError::UnknownSize)?;
1220        let output = self.output(requested_output, size)?;
1221        // The carry-family builtins return a boolean but consume equally-sized
1222        // integer operands. Their result width therefore cannot provide the
1223        // context required by a nested `zext`; carry the first operand's width
1224        // into the remaining operands instead.
1225        let inputs = if matches!(builtin, Builtin::Carry | Builtin::Scarry | Builtin::Sborrow)
1226            && !args.is_empty()
1227        {
1228            // The first operand can be an unsized literal (`sborrow(0, RAX)`
1229            // in x86 `NEG`). Carry-family operands must all have the same
1230            // width, so derive it from any concrete operand before lowering.
1231            let operand_size = args
1232                .iter()
1233                .find_map(|arg| self.expr_size(arg))
1234                .ok_or(PcodeLowerError::UnknownSize)?;
1235            args.iter()
1236                .map(|arg| self.lower_expr_with_size(arg, operand_size))
1237                .collect::<Result<Vec<_>, _>>()?
1238        } else {
1239            args.iter()
1240                .map(|arg| self.lower_expr(arg, None))
1241                .collect::<Result<Vec<_>, _>>()?
1242        };
1243        self.emit(opcode, Some(output), &inputs);
1244        Ok(output)
1245    }
1246
1247    fn lower_unop(
1248        &mut self,
1249        expr: &Expression,
1250        op: UnaryOperator,
1251        operand: &Expression,
1252        requested_output: Option<Varnode>,
1253    ) -> Result<Varnode, PcodeLowerError> {
1254        if let UnaryOperator::AddressOf(size) = op {
1255            // An address symbol such as `inst_next` already *is* its address;
1256            // taking its address only fixes the width.
1257            if let ExpressionTy::SizedInt {
1258                value,
1259                size: literal_size,
1260            } = &operand.ty
1261            {
1262                let size = size
1263                    .or(*literal_size)
1264                    .or(operand.size)
1265                    .ok_or(PcodeLowerError::UnknownSize)?;
1266                return self.copy_if_requested(Varnode::constant(*value, size), requested_output);
1267            }
1268            let storage = self.storage_from_expr(operand)?;
1269            let size = size
1270                .or_else(|| self.context.address_size(storage.space))
1271                .ok_or(PcodeLowerError::UnknownSize)?;
1272            return self
1273                .copy_if_requested(Varnode::constant(storage.offset, size), requested_output);
1274        }
1275        let opcode = match op {
1276            UnaryOperator::LogicalNot => Opcode::BoolNegate,
1277            UnaryOperator::BitwiseNot => Opcode::IntNegate,
1278            UnaryOperator::Minus => Opcode::Int2Comp,
1279            UnaryOperator::FloatMinus => Opcode::FloatNeg,
1280            UnaryOperator::AddressOf(_) => unreachable!(),
1281        };
1282        // Unsized integer literals are polymorphic. Resolve the unary result
1283        // width before lowering its operand so `~8` can inherit the width of
1284        // its assignment (for example x86 `CLTS`), rather than failing while
1285        // lowering the literal without a consumer.
1286        let size = requested_output
1287            .map(|output| output.size)
1288            .or(expr.size)
1289            .or_else(|| (op == UnaryOperator::LogicalNot).then_some(1))
1290            .or_else(|| self.expr_size(operand))
1291            .ok_or(PcodeLowerError::UnknownSize)?;
1292        // Preserve a concrete operand's native width (notably BOOL_NEGATE,
1293        // whose input need not be one byte); only force the result width into
1294        // a width-less operand such as an integer literal.
1295        let input = if self.expr_size(operand).is_some() {
1296            self.lower_expr(operand, None)?
1297        } else {
1298            self.lower_expr_with_size(operand, size)?
1299        };
1300        let output = self.output(requested_output, size)?;
1301        self.emit(opcode, Some(output), &[input]);
1302        Ok(output)
1303    }
1304
1305    fn lower_binop(
1306        &mut self,
1307        expr: &Expression,
1308        op: BinaryOperator,
1309        lhs: &Expression,
1310        rhs: &Expression,
1311        requested_output: Option<Varnode>,
1312    ) -> Result<Varnode, PcodeLowerError> {
1313        let (opcode, reverse) = binary_opcode(op);
1314        // An arithmetic result has the same width as its operands. Comparisons
1315        // and boolean operations instead produce one byte, so obtain their
1316        // operand width from either side. This supplies the context needed by
1317        // unsized SLEIGH literals and compound expressions (for example the
1318        // `2 * zext(DF)` in x86 MOVS pointer updates).
1319        let is_boolean = op.is_comparison()
1320            || matches!(
1321                op,
1322                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1323            );
1324        let input_size = if is_boolean {
1325            self.expr_size(lhs).or_else(|| self.expr_size(rhs))
1326        } else {
1327            requested_output
1328                .map(|output| output.size)
1329                .or(expr.size)
1330                .or_else(|| self.expr_size(lhs))
1331                .or_else(|| self.expr_size(rhs))
1332        };
1333        let mut inputs = match input_size {
1334            Some(size) => vec![
1335                self.lower_expr_with_size(lhs, size)?,
1336                self.lower_expr_with_size(rhs, size)?,
1337            ],
1338            None => vec![self.lower_expr(lhs, None)?, self.lower_expr(rhs, None)?],
1339        };
1340        if reverse {
1341            inputs.swap(0, 1);
1342        }
1343        let size = requested_output
1344            .map(|output| output.size)
1345            .or(expr.size)
1346            .or_else(|| op.is_comparison().then_some(1))
1347            .or(input_size)
1348            .ok_or(PcodeLowerError::UnknownSize)?;
1349        let output = self.output(requested_output, size)?;
1350        if (op.is_comparison()
1351            || matches!(
1352                op,
1353                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1354            ))
1355            && output.size != 1
1356        {
1357            return Err(PcodeLowerError::InvalidBooleanSize(output.size));
1358        }
1359        if inputs[0].size != inputs[1].size {
1360            return Err(PcodeLowerError::InputSizeMismatch {
1361                operation: "binary operation",
1362                left: inputs[0].size,
1363                right: inputs[1].size,
1364            });
1365        }
1366        if !op.is_comparison()
1367            && !matches!(
1368                op,
1369                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1370            )
1371            && output.size != inputs[0].size
1372        {
1373            return Err(PcodeLowerError::CopySizeMismatch {
1374                input: inputs[0].size,
1375                output: output.size,
1376            });
1377        }
1378        self.emit(opcode, Some(output), &inputs);
1379        Ok(output)
1380    }
1381
1382    /// Lowers an operand in a context that requires `size` bytes.
1383    ///
1384    /// SLEIGH permits a narrow register or temporary as a shift count or bit
1385    /// index for a wider value. Raw p-code does not: both inputs of these
1386    /// operations must have the same width. Requesting an output of `size`
1387    /// propagates that context into compound expressions, while
1388    /// [`copy_if_requested`](Self::copy_if_requested) inserts an explicit
1389    /// zero-extension or low-byte `SUBPIECE` for a directly stored value.
1390    fn lower_expr_with_size(
1391        &mut self,
1392        expr: &Expression,
1393        size: usize,
1394    ) -> Result<Varnode, PcodeLowerError> {
1395        // SLEIGH integer literals are polymorphic in raw p-code: the
1396        // surrounding operation determines their varnode width (for example
1397        // `RAX + 1`). This applies even when parsing retained a literal's
1398        // minimal source width.
1399        if let ExpressionTy::SizedInt { value, .. } = &expr.ty {
1400            return Ok(Varnode::constant(*value, size));
1401        }
1402        if self.expr_size(expr) == Some(size) {
1403            return self.lower_expr(expr, None);
1404        }
1405        let output = self.allocate_unique(size)?;
1406        self.lower_expr(expr, Some(output))
1407    }
1408
1409    fn lower_range(
1410        &mut self,
1411        range: &Range,
1412        requested_output: Option<Varnode>,
1413    ) -> Result<Varnode, PcodeLowerError> {
1414        let input = self.lower_expr(&range.value, None)?;
1415        let (start, bits) = range_params(range)?;
1416        self.extract_range(input, start, bits, requested_output)
1417    }
1418
1419    fn lower_named_bitrange(
1420        &mut self,
1421        id: BitRangeFieldId,
1422        requested_output: Option<Varnode>,
1423    ) -> Result<Varnode, PcodeLowerError> {
1424        let info = self
1425            .context
1426            .bitrange_info(id)
1427            .ok_or(PcodeLowerError::Unsupported("an unknown named bit range"))?;
1428        self.extract_range(info.storage, info.start, info.size, requested_output)
1429    }
1430
1431    fn extract_range(
1432        &mut self,
1433        input: Varnode,
1434        start: usize,
1435        bits: usize,
1436        requested_output: Option<Varnode>,
1437    ) -> Result<Varnode, PcodeLowerError> {
1438        let result_size = Self::validate_range(input, start, bits)?;
1439        if let Some(output) = requested_output {
1440            if output.size != result_size {
1441                return Err(PcodeLowerError::CopySizeMismatch {
1442                    input: result_size,
1443                    output: output.size,
1444                });
1445            }
1446        }
1447        let shifted = self.allocate_unique(input.size)?;
1448        self.emit(
1449            Opcode::IntRight,
1450            Some(shifted),
1451            &[input, Varnode::constant(start as u64, input.size)],
1452        );
1453        let masked = self.allocate_unique(input.size)?;
1454        self.emit(
1455            Opcode::IntAnd,
1456            Some(masked),
1457            &[shifted, Varnode::constant(Self::mask(bits)?, input.size)],
1458        );
1459        let output = self.output(requested_output, result_size)?;
1460        self.emit(
1461            Opcode::SubPiece,
1462            Some(output),
1463            &[masked, Varnode::constant(0, 8)],
1464        );
1465        Ok(output)
1466    }
1467
1468    fn lower_range_assignment(
1469        &mut self,
1470        range: &Range,
1471        rhs: &Expression,
1472    ) -> Result<(), PcodeLowerError> {
1473        if let ExpressionTy::Load(load) = &range.value.ty {
1474            return self.lower_load_range_assignment(load, range, rhs);
1475        }
1476        let storage = self.storage_from_expr(&range.value)?;
1477        let (start, bits) = range_params(range)?;
1478        self.insert_range(storage, start, bits, rhs)
1479    }
1480
1481    /// Lowers a bit-range write into a memory load as load/modify/store. SLEIGH
1482    /// uses this form for packed MMX lanes backed by private RAM, where an
1483    /// address-of expression cannot name a raw-p-code varnode directly.
1484    fn lower_load_range_assignment(
1485        &mut self,
1486        load: &Load,
1487        range: &Range,
1488        rhs: &Expression,
1489    ) -> Result<(), PcodeLowerError> {
1490        let storage = self.lower_load(&range.value, load, None)?;
1491        let (start, bits) = range_params(range)?;
1492        Self::validate_range(storage, start, bits)?;
1493        if storage.size > 8 {
1494            return Err(PcodeLowerError::InvalidRange {
1495                start,
1496                size: bits,
1497                storage_bits: storage.size.saturating_mul(8),
1498            });
1499        }
1500        let value = self.lower_expr_with_size(rhs, bits.div_ceil(8))?;
1501        if value.size > storage.size {
1502            return Err(PcodeLowerError::InputSizeMismatch {
1503                operation: "bit-range assignment",
1504                left: storage.size,
1505                right: value.size,
1506            });
1507        }
1508        let extended = if value.size == storage.size {
1509            value
1510        } else {
1511            let output = self.allocate_unique(storage.size)?;
1512            self.emit(Opcode::IntZext, Some(output), &[value]);
1513            output
1514        };
1515        let inserted = self.allocate_unique(storage.size)?;
1516        self.emit(
1517            Opcode::IntAnd,
1518            Some(inserted),
1519            &[extended, Varnode::constant(Self::mask(bits)?, storage.size)],
1520        );
1521        let shifted = self.allocate_unique(storage.size)?;
1522        self.emit(
1523            Opcode::IntLeft,
1524            Some(shifted),
1525            &[inserted, Varnode::constant(start as u64, storage.size)],
1526        );
1527        let clear_mask = !(Self::mask(bits)? << start);
1528        let kept = self.allocate_unique(storage.size)?;
1529        self.emit(
1530            Opcode::IntAnd,
1531            Some(kept),
1532            &[storage, Varnode::constant(clear_mask, storage.size)],
1533        );
1534        let result = self.allocate_unique(storage.size)?;
1535        self.emit(Opcode::IntOr, Some(result), &[kept, shifted]);
1536
1537        let space = self.load_space(load)?;
1538        if space == SPACE_CONST {
1539            return Err(PcodeLowerError::Unsupported("a store to constant space"));
1540        }
1541        let ptr = self.lower_expr(&load.ptr, None)?;
1542        self.validate_pointer(space, ptr)?;
1543        self.emit(Opcode::Store, None, &[Self::space_id(space), ptr, result]);
1544        Ok(())
1545    }
1546
1547    fn insert_range(
1548        &mut self,
1549        storage: Varnode,
1550        start: usize,
1551        bits: usize,
1552        rhs: &Expression,
1553    ) -> Result<(), PcodeLowerError> {
1554        Self::validate_range(storage, start, bits)?;
1555        // Inserting needs a full-width clear mask. Constants in this AST are
1556        // u64, so zero-extending one into larger storage would incorrectly
1557        // clear every high bit.
1558        if storage.size > 8 {
1559            return Err(PcodeLowerError::InvalidRange {
1560                start,
1561                size: bits,
1562                storage_bits: storage.size.saturating_mul(8),
1563            });
1564        }
1565        // A range assignment fixes the RHS width even when the RHS is a
1566        // user-op result whose source expression does not carry one.
1567        let value = self.lower_expr_with_size(rhs, bits.div_ceil(8))?;
1568        if value.size > storage.size {
1569            return Err(PcodeLowerError::InputSizeMismatch {
1570                operation: "bit-range assignment",
1571                left: storage.size,
1572                right: value.size,
1573            });
1574        }
1575        let extended = if value.size == storage.size {
1576            value
1577        } else {
1578            let output = self.allocate_unique(storage.size)?;
1579            self.emit(Opcode::IntZext, Some(output), &[value]);
1580            output
1581        };
1582        let inserted = self.allocate_unique(storage.size)?;
1583        self.emit(
1584            Opcode::IntAnd,
1585            Some(inserted),
1586            &[extended, Varnode::constant(Self::mask(bits)?, storage.size)],
1587        );
1588        let shifted = self.allocate_unique(storage.size)?;
1589        self.emit(
1590            Opcode::IntLeft,
1591            Some(shifted),
1592            &[inserted, Varnode::constant(start as u64, storage.size)],
1593        );
1594        let clear_mask = !(Self::mask(bits)? << start);
1595        let kept = self.allocate_unique(storage.size)?;
1596        self.emit(
1597            Opcode::IntAnd,
1598            Some(kept),
1599            &[storage, Varnode::constant(clear_mask, storage.size)],
1600        );
1601        self.emit(Opcode::IntOr, Some(storage), &[kept, shifted]);
1602        Ok(())
1603    }
1604
1605    fn validate_range(
1606        storage: Varnode,
1607        start: usize,
1608        size: usize,
1609    ) -> Result<usize, PcodeLowerError> {
1610        let storage_bits = storage
1611            .size
1612            .checked_mul(8)
1613            .ok_or(PcodeLowerError::InvalidRange {
1614                start,
1615                size,
1616                storage_bits: usize::MAX,
1617            })?;
1618        if size == 0 || size > 64 || start.checked_add(size).is_none_or(|end| end > storage_bits) {
1619            return Err(PcodeLowerError::InvalidRange {
1620                start,
1621                size,
1622                storage_bits,
1623            });
1624        }
1625        Ok(size.div_ceil(8))
1626    }
1627
1628    fn mask(bits: usize) -> Result<u64, PcodeLowerError> {
1629        match bits {
1630            1..=63 => Ok((1u64 << bits) - 1),
1631            64 => Ok(u64::MAX),
1632            _ => Err(PcodeLowerError::InvalidRange {
1633                start: 0,
1634                size: bits,
1635                storage_bits: 64,
1636            }),
1637        }
1638    }
1639
1640    fn validate_pointer(&self, space: SpaceId, ptr: Varnode) -> Result<(), PcodeLowerError> {
1641        let expected = self
1642            .context
1643            .address_size(space)
1644            .ok_or(PcodeLowerError::UnresolvedSpace)?;
1645        Self::checked_size(expected)?;
1646        if ptr.size != expected {
1647            return Err(PcodeLowerError::AddressSizeMismatch {
1648                expected,
1649                actual: ptr.size,
1650            });
1651        }
1652        Ok(())
1653    }
1654
1655    fn storage_from_expr(&mut self, expr: &Expression) -> Result<Varnode, PcodeLowerError> {
1656        match &expr.ty {
1657            ExpressionTy::Ident(ident) => self.storage_for_ident(ident.clone(), expr.size),
1658            _ => Err(PcodeLowerError::Unsupported(
1659                "address-of a non-varnode expression",
1660            )),
1661        }
1662    }
1663
1664    fn storage_for_ident(
1665        &mut self,
1666        ident: Ident,
1667        size: Option<usize>,
1668    ) -> Result<Varnode, PcodeLowerError> {
1669        match ident {
1670            Ident::Register(id) => self
1671                .context
1672                .register_varnode(id)
1673                .ok_or(PcodeLowerError::UnknownRegister(id)),
1674            Ident::Named(id) => {
1675                let size = self.plan.local_sizes.get(&id).copied().or(size);
1676                if let Some(varnode) = self.locals.get(&id) {
1677                    if let Some(size) = size
1678                        && size != varnode.size
1679                    {
1680                        return Err(PcodeLowerError::CopySizeMismatch {
1681                            input: varnode.size,
1682                            output: size,
1683                        });
1684                    }
1685                    return Ok(*varnode);
1686                }
1687                let varnode = self.allocate_unique(size.ok_or(PcodeLowerError::UnknownSize)?)?;
1688                self.locals.insert(id, varnode);
1689                Ok(varnode)
1690            }
1691            Ident::BitRange(_) => Err(PcodeLowerError::Unsupported("a named bit range")),
1692            Ident::Field(_) => Err(PcodeLowerError::UnresolvedIdentifier("field")),
1693            Ident::Table(_) => Err(PcodeLowerError::UnresolvedIdentifier("table")),
1694            Ident::Global(_) => Err(PcodeLowerError::UnresolvedIdentifier("global")),
1695        }
1696    }
1697
1698    fn lower_userop_inputs(
1699        &mut self,
1700        id: PCodeOpId,
1701        args: &[Expression],
1702    ) -> Result<Vec<Varnode>, PcodeLowerError> {
1703        let mut inputs = Vec::with_capacity(args.len() + 1);
1704        inputs.push(Varnode::constant(usize::from(id) as u64, 4));
1705        inputs.extend(
1706            args.iter()
1707                .map(|arg| self.lower_expr(arg, None))
1708                .collect::<Result<Vec<_>, _>>()?,
1709        );
1710        Ok(inputs)
1711    }
1712
1713    fn copy_if_requested(
1714        &mut self,
1715        input: Varnode,
1716        requested_output: Option<Varnode>,
1717    ) -> Result<Varnode, PcodeLowerError> {
1718        match requested_output {
1719            Some(output) if output != input && input.size == output.size => {
1720                self.emit(Opcode::Copy, Some(output), &[input]);
1721                Ok(output)
1722            }
1723            Some(output) if input.size < output.size => {
1724                self.emit(Opcode::IntZext, Some(output), &[input]);
1725                Ok(output)
1726            }
1727            Some(output) if input.size > output.size => {
1728                self.emit(
1729                    Opcode::SubPiece,
1730                    Some(output),
1731                    &[input, Varnode::constant(0, 8)],
1732                );
1733                Ok(output)
1734            }
1735            Some(output) => Ok(output),
1736            None => Ok(input),
1737        }
1738    }
1739
1740    fn output(
1741        &mut self,
1742        requested_output: Option<Varnode>,
1743        size: usize,
1744    ) -> Result<Varnode, PcodeLowerError> {
1745        match requested_output {
1746            Some(output) => {
1747                Self::checked_size(output.size)?;
1748                Ok(output)
1749            }
1750            None => self.allocate_unique(size),
1751        }
1752    }
1753
1754    fn allocate_unique(&mut self, size: usize) -> Result<Varnode, PcodeLowerError> {
1755        Self::checked_size(size)?;
1756        let offset = self.next_unique;
1757        self.next_unique = self
1758            .next_unique
1759            .checked_add(size as u64)
1760            .ok_or(PcodeLowerError::UniqueSpaceOverflow)?;
1761        Ok(Varnode::new(self.context.unique_space(), offset, size))
1762    }
1763
1764    fn label_id(&self, label: &str) -> Result<LabelId, PcodeLowerError> {
1765        self.plan
1766            .label_id(label)
1767            .ok_or_else(|| PcodeLowerError::UnknownLabel(Box::from(label)))
1768    }
1769
1770    fn checked_size(size: usize) -> Result<(), PcodeLowerError> {
1771        if size == 0 {
1772            Err(PcodeLowerError::ZeroSize)
1773        } else {
1774            Ok(())
1775        }
1776    }
1777
1778    fn space_id(space: SpaceId) -> Varnode {
1779        Varnode::constant(usize::from(space) as u64, 4)
1780    }
1781}
1782
1783/// The read-only pass which produces a [`PcodePlan`].
1784///
1785/// It is generic over the statement span so a producer can run the same width
1786/// inference over its own *source* bodies, before any instruction is decoded,
1787/// rather than keeping a second implementation that can drift from this one.
1788struct Planner<'a, C: PcodeLoweringContext + ?Sized> {
1789    context: &'a C,
1790    plan: PcodePlan,
1791}
1792
1793impl<'a, C: PcodeLoweringContext + ?Sized> Planner<'a, C> {
1794    fn plan(&mut self, ast: &PcodeAst) {
1795        self.plan.local_sizes = SizeInference::run(self.context, &ast.statements);
1796        self.plan_statements(ast);
1797    }
1798
1799    /// Collects the facts that do not depend on local widths: the labels and
1800    /// the addresses this instruction reaches directly.
1801    fn plan_statements(&mut self, ast: &PcodeAst) {
1802        for statement in &ast.statements {
1803            match &statement.ty {
1804                AstNode::Label(label) => {
1805                    self.plan.declare_label(label);
1806                }
1807                AstNode::Branch { target } | AstNode::ConditionalBranch { target, .. } => {
1808                    // A target this pass cannot resolve is left out; emission
1809                    // reports it with the error it would have reported before.
1810                    if let LabelOrNode::Expr(expr) = target
1811                        && let Some(address) = self.direct_address(expr)
1812                    {
1813                        self.plan.declare_direct_branch(address);
1814                    }
1815                }
1816                AstNode::Call { target } => {
1817                    if let LabelOrNode::Expr(expr) = target
1818                        && let Some(address) = self.direct_address(expr)
1819                    {
1820                        self.plan.declare_direct_call(address);
1821                    }
1822                }
1823                _ => {}
1824            }
1825        }
1826
1827        // Only labels may follow the last operation-producing statement, so
1828        // the trailing run of labels is exactly the terminal one.
1829        for statement in ast.statements.iter().rev() {
1830            let AstNode::Label(label) = &statement.ty else {
1831                break;
1832            };
1833            if let Some(id) = self.plan.label_id(label) {
1834                self.plan.terminal[id.index()] = true;
1835            }
1836        }
1837    }
1838
1839    fn direct_address<S>(&self, target: &Expression<S>) -> Option<u64> {
1840        match target.ty {
1841            ExpressionTy::SizedInt { value, .. } => Some(value),
1842            _ => None,
1843        }
1844    }
1845}
1846
1847/// The width-inference pass, shared by specification-compile time and by
1848/// per-instruction planning.
1849///
1850/// It is generic over the statement span so a producer can run it over its own
1851/// *source* bodies, and over the width domain so those bodies can be resolved
1852/// before the values a decode substitutes into them are known.
1853struct SizeInference<'a, C: PcodeLoweringContext + ?Sized, W: Width> {
1854    context: &'a C,
1855    sizes: HashMap<LocalVarId, W>,
1856}
1857
1858impl<'a, C: PcodeLoweringContext + ?Sized, W: Width> SizeInference<'a, C, W> {
1859    fn run<S>(context: &'a C, statements: &[Ast<S>]) -> HashMap<LocalVarId, W> {
1860        let mut inference = Self {
1861            context,
1862            sizes: HashMap::new(),
1863        };
1864        inference.infer(statements);
1865        inference.sizes
1866    }
1867
1868    /// Resolve local widths from their uses. A forward-only allocator cannot
1869    /// size, for example, `v = 255 & 31` until a later `word << v` reveals
1870    /// that `v` is a word-wide shift count.
1871    fn infer<S>(&mut self, statements: &[Ast<S>]) {
1872        // Each pass can discover at least one previously unknown local. The
1873        // extra pass propagates that discovery through a chain of locals.
1874        for _ in 0..=statements.len() {
1875            let before = self.sizes.len();
1876            for statement in statements {
1877                self.constrain_statement(&statement.ty);
1878            }
1879            if self.sizes.len() == before {
1880                break;
1881            }
1882        }
1883    }
1884
1885    fn constrain_statement<S>(&mut self, statement: &AstNode<S>) {
1886        match statement {
1887            AstNode::Assignment { lhs, size, rhs } => {
1888                // Comparisons normally infer a one-byte result. A different
1889                // explicit expression size must still reach lowering so it is
1890                // rejected as an invalid raw boolean output.
1891                let comparison_size =
1892                    matches!(&rhs.ty, ExpressionTy::Binop(binop) if binop.op.is_comparison())
1893                        .then_some(rhs.size)
1894                        .flatten()
1895                        .filter(|&size| size != 1)
1896                        .map(W::fixed);
1897                let expected = (*size)
1898                    .map(W::fixed)
1899                    .or_else(|| self.storage_size(lhs))
1900                    .or(comparison_size);
1901                let inferred = self.constrain_expr(rhs, expected);
1902                if let Ident::Named(id) = lhs
1903                    && let Some(size) = expected.or(inferred)
1904                {
1905                    self.sizes.entry(*id).or_insert(size);
1906                }
1907            }
1908            AstNode::LoadAssignment { lhs, rhs, .. } => {
1909                let space = self.load_space(lhs).ok();
1910                if let Some(space) = space {
1911                    self.constrain_expr(&lhs.ptr, self.context.address_size(space).map(W::fixed));
1912                }
1913                self.constrain_expr(rhs, lhs.size.map(W::fixed));
1914            }
1915            AstNode::RangeAssignment { lhs, rhs, .. } => {
1916                if let Ok((_, bits)) = range_params(lhs) {
1917                    self.constrain_expr(rhs, Some(W::fixed(bits.div_ceil(8))));
1918                }
1919            }
1920            AstNode::ConditionalBranch { condition, .. } => {
1921                self.constrain_expr(condition, Some(W::fixed(1)));
1922            }
1923            AstNode::BranchIndirect { target }
1924            | AstNode::CallIndirect { target }
1925            | AstNode::Return { target } => {
1926                self.constrain_expr(
1927                    target,
1928                    self.context
1929                        .address_size(self.context.default_space())
1930                        .map(W::fixed),
1931                );
1932            }
1933            AstNode::Expression(expr) => {
1934                self.constrain_expr(expr, None);
1935            }
1936            AstNode::Build(_)
1937            | AstNode::DelaySlot(_)
1938            | AstNode::DeferredBuild(_)
1939            | AstNode::Label(_)
1940            | AstNode::Branch { .. }
1941            | AstNode::Call { .. }
1942            | AstNode::Export(_) => {}
1943        }
1944    }
1945
1946    /// Applies an optional consumer width to `expr` and returns any concrete
1947    /// output width known after that constraint. Integer literals intentionally
1948    /// do not establish a width on their own.
1949    fn constrain_expr<S>(&mut self, expr: &Expression<S>, expected: Option<W>) -> Option<W> {
1950        match &expr.ty {
1951            ExpressionTy::SizedInt { .. } => expected,
1952            ExpressionTy::Ident(Ident::Named(id)) => {
1953                if let Some(&size) = self.sizes.get(id) {
1954                    Some(size)
1955                } else if let Some(size) = expected {
1956                    self.sizes.insert(*id, size);
1957                    Some(size)
1958                } else {
1959                    None
1960                }
1961            }
1962            ExpressionTy::Ident(ident) => self.storage_size(ident),
1963            ExpressionTy::Load(load) => {
1964                if let Ok(space) = self.load_space(load) {
1965                    self.constrain_expr(&load.ptr, self.context.address_size(space).map(W::fixed));
1966                }
1967                load.size.map(W::fixed).or(expected)
1968            }
1969            ExpressionTy::SubPieceLsb { src, count } => {
1970                self.constrain_expr(src, None);
1971                Some(W::fixed(*count))
1972            }
1973            ExpressionTy::SubPieceMsb { src, count } => {
1974                // Truncation is arithmetic on a width, so a still-symbolic
1975                // operand width yields no constraint rather than a wrong one.
1976                let size = expected
1977                    .or_else(|| Some(W::fixed(self.expr_size(src)?.size()?.checked_sub(*count)?)));
1978                let source = size
1979                    .and_then(|size| size.size())
1980                    .map(|size| W::fixed(size + count));
1981                self.constrain_expr(src, source);
1982                size
1983            }
1984            ExpressionTy::Range(range) => {
1985                let size = match range.size {
1986                    RangeParam::Literal(bits) => Some(W::fixed(bits.div_ceil(8))),
1987                    RangeParam::MacroArg(_) => expected,
1988                };
1989                self.constrain_expr(&range.value, None);
1990                size
1991            }
1992            ExpressionTy::FunctionCall { builtin, args } => {
1993                let boolean = matches!(
1994                    builtin,
1995                    Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan
1996                );
1997                let size = boolean.then(|| W::fixed(1)).or(expected);
1998                let input_size = args.iter().find_map(|arg| self.constrain_expr(arg, None));
1999                if let Some(input_size) = input_size {
2000                    for arg in args {
2001                        self.constrain_expr(arg, Some(input_size));
2002                    }
2003                }
2004                size
2005            }
2006            ExpressionTy::PcodeOp { args, .. } => {
2007                for arg in args {
2008                    self.constrain_expr(arg, None);
2009                }
2010                expected
2011            }
2012            ExpressionTy::Unop(unop) => match unop.op {
2013                UnaryOperator::LogicalNot => {
2014                    let size = self.constrain_expr(&unop.e, None);
2015                    self.constrain_expr(&unop.e, size);
2016                    Some(W::fixed(1))
2017                }
2018                UnaryOperator::AddressOf(size) => size.map(W::fixed).or_else(|| {
2019                    self.storage_from_expr_size(&unop.e)
2020                        .and_then(|storage| self.context.address_size(storage.space))
2021                        .map(W::fixed)
2022                }),
2023                _ => {
2024                    let size = expected.or_else(|| self.constrain_expr(&unop.e, None));
2025                    self.constrain_expr(&unop.e, size);
2026                    size
2027                }
2028            },
2029            ExpressionTy::Binop(binop) => {
2030                let boolean = binop.op.is_comparison()
2031                    || matches!(
2032                        binop.op,
2033                        BinaryOperator::LogicalXor
2034                            | BinaryOperator::LogicalAnd
2035                            | BinaryOperator::LogicalOr
2036                    );
2037                let input_size = self
2038                    .constrain_expr(&binop.lhs, None)
2039                    .or_else(|| self.constrain_expr(&binop.rhs, None));
2040                let input_size = if boolean {
2041                    input_size
2042                } else {
2043                    expected.or(input_size)
2044                };
2045                self.constrain_expr(&binop.lhs, input_size);
2046                self.constrain_expr(&binop.rhs, input_size);
2047                if boolean {
2048                    Some(W::fixed(1))
2049                } else {
2050                    input_size
2051                }
2052            }
2053            ExpressionTy::MacroCall { .. } | ExpressionTy::DeferredCall { .. } => expected,
2054        }
2055    }
2056}
2057
2058impl<'a, C: PcodeLoweringContext + ?Sized, W: Width> Sizing<W> for SizeInference<'a, C, W> {
2059    type Ctx = C;
2060
2061    fn context(&self) -> &C {
2062        self.context
2063    }
2064
2065    fn local_size(&self, id: &LocalVarId) -> Option<W> {
2066        self.sizes.get(id).copied()
2067    }
2068}
2069
2070impl<C: PcodeLoweringContext + ?Sized, S: PcodeSink + ?Sized> Sizing<usize>
2071    for Lowerer<'_, '_, '_, C, S>
2072{
2073    type Ctx = C;
2074
2075    fn context(&self) -> &C {
2076        self.context
2077    }
2078
2079    fn local_size(&self, id: &LocalVarId) -> Option<usize> {
2080        self.plan
2081            .local_sizes
2082            .get(id)
2083            .copied()
2084            .or_else(|| self.locals.get(id).map(|varnode| varnode.size))
2085    }
2086}
2087
2088/// Width and space queries shared by planning and emission. Both phases must
2089/// answer them identically, so they have one implementation parameterized by
2090/// how each phase knows a local's width.
2091trait Sizing<W: Width> {
2092    type Ctx: PcodeLoweringContext + ?Sized;
2093
2094    fn context(&self) -> &Self::Ctx;
2095
2096    /// The width of a local variable, if it is known in this phase.
2097    fn local_size(&self, id: &LocalVarId) -> Option<W>;
2098
2099    fn expr_size<S>(&self, expr: &Expression<S>) -> Option<W> {
2100        expr.size.map(W::fixed).or(match &expr.ty {
2101            ExpressionTy::SizedInt { size, .. } => size.map(W::fixed),
2102            ExpressionTy::Ident(ident) => self.storage_size(ident),
2103            ExpressionTy::Load(load) => load.size.map(W::fixed),
2104            ExpressionTy::SubPieceLsb { count, .. } => Some(W::fixed(*count)),
2105            ExpressionTy::SubPieceMsb { src, count } => {
2106                Some(W::fixed(self.expr_size(src)?.size()?.checked_sub(*count)?))
2107            }
2108            ExpressionTy::Range(Range {
2109                size: RangeParam::Literal(bits),
2110                ..
2111            }) => Some(W::fixed(bits.div_ceil(8))),
2112            ExpressionTy::Range(Range {
2113                size: RangeParam::MacroArg(_),
2114                ..
2115            }) => None,
2116            ExpressionTy::FunctionCall {
2117                builtin: Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan,
2118                ..
2119            } => Some(W::fixed(1)),
2120            ExpressionTy::FunctionCall { .. } => None,
2121            ExpressionTy::Unop(unop) if unop.op == UnaryOperator::LogicalNot => Some(W::fixed(1)),
2122            ExpressionTy::Unop(unop) => self.expr_size(&unop.e),
2123            ExpressionTy::Binop(binop) if binop.op.is_comparison() => Some(W::fixed(1)),
2124            ExpressionTy::Binop(binop) => self
2125                .expr_size(&binop.lhs)
2126                .or_else(|| self.expr_size(&binop.rhs)),
2127            ExpressionTy::PcodeOp { .. }
2128            | ExpressionTy::MacroCall { .. }
2129            | ExpressionTy::DeferredCall { .. } => None,
2130        })
2131    }
2132
2133    fn storage_size(&self, ident: &Ident) -> Option<W> {
2134        match ident {
2135            Ident::Register(id) => self
2136                .context()
2137                .register_varnode(*id)
2138                .map(|varnode| W::fixed(varnode.size)),
2139            Ident::BitRange(id) => self
2140                .context()
2141                .bitrange_info(*id)
2142                .map(|info| W::fixed(info.size.div_ceil(8))),
2143            Ident::Named(id) => self.local_size(id),
2144            // A table operand's width is only known once a decode substitutes
2145            // its export. A symbolic domain names it instead of losing it.
2146            Ident::Table(id) => W::operand(*id),
2147            Ident::Field(_) | Ident::Global(_) => None,
2148        }
2149    }
2150
2151    fn storage_from_expr_size<S>(&self, expr: &Expression<S>) -> Option<Varnode> {
2152        match &expr.ty {
2153            ExpressionTy::Ident(Ident::Register(id)) => self.context().register_varnode(*id),
2154            ExpressionTy::Ident(Ident::BitRange(id)) => {
2155                self.context().bitrange_info(*id).map(|info| info.storage)
2156            }
2157            _ => None,
2158        }
2159    }
2160
2161    fn load_space<S>(&self, load: &Load<S>) -> Result<SpaceId, PcodeLowerError> {
2162        match &load.space {
2163            None => Ok(self.context().default_space()),
2164            Some(crate::PcodeSpaceRef::Resolved(space)) => Ok(*space),
2165            Some(crate::PcodeSpaceRef::Deferred(_)) => Err(PcodeLowerError::UnresolvedSpace),
2166        }
2167    }
2168}
2169
2170/// Reads a bit range's literal start and width.
2171///
2172/// A macro-argument range must have been substituted during expansion.
2173fn range_params<S>(range: &Range<S>) -> Result<(usize, usize), PcodeLowerError> {
2174    let RangeParam::Literal(start) = range.start else {
2175        return Err(PcodeLowerError::UnresolvedRangeParameter);
2176    };
2177    let RangeParam::Literal(size) = range.size else {
2178        return Err(PcodeLowerError::UnresolvedRangeParameter);
2179    };
2180    Ok((start, size))
2181}
2182
2183fn binary_opcode(op: BinaryOperator) -> (Opcode, bool) {
2184    use BinaryOperator::*;
2185    match op {
2186        Mul => (Opcode::IntMult, false),
2187        Div => (Opcode::IntDiv, false),
2188        SignedDiv => (Opcode::IntSDiv, false),
2189        Mod => (Opcode::IntRem, false),
2190        SignedMod => (Opcode::IntSRem, false),
2191        FloatDiv => (Opcode::FloatDiv, false),
2192        FloatMul => (Opcode::FloatMult, false),
2193        Add => (Opcode::IntAdd, false),
2194        Sub => (Opcode::IntSub, false),
2195        FloatAdd => (Opcode::FloatAdd, false),
2196        FloatSub => (Opcode::FloatSub, false),
2197        LeftShift => (Opcode::IntLeft, false),
2198        RightShift => (Opcode::IntRight, false),
2199        SignedRightShift => (Opcode::IntSRight, false),
2200        SignedLessThan => (Opcode::IntSLess, false),
2201        SignedGreaterThan => (Opcode::IntSLess, true),
2202        SignedLessEqual => (Opcode::IntSLessEqual, false),
2203        SignedGreaterEqual => (Opcode::IntSLessEqual, true),
2204        LessEqual => (Opcode::IntLessEqual, false),
2205        GreaterEqual => (Opcode::IntLessEqual, true),
2206        LessThan => (Opcode::IntLess, false),
2207        GreaterThan => (Opcode::IntLess, true),
2208        FloatLessEqual => (Opcode::FloatLessEqual, false),
2209        FloatGreaterEqual => (Opcode::FloatLessEqual, true),
2210        FloatLessThan => (Opcode::FloatLess, false),
2211        FloatGreaterThan => (Opcode::FloatLess, true),
2212        Equal => (Opcode::IntEqual, false),
2213        NotEqual => (Opcode::IntNotEqual, false),
2214        FloatEqual => (Opcode::FloatEqual, false),
2215        FloatNotEqual => (Opcode::FloatNotEqual, false),
2216        LogicalXor => (Opcode::BoolXor, false),
2217        LogicalAnd => (Opcode::BoolAnd, false),
2218        LogicalOr => (Opcode::BoolOr, false),
2219        BitwiseXor => (Opcode::IntXor, false),
2220        BitwiseOr => (Opcode::IntOr, false),
2221        BitwiseAnd => (Opcode::IntAnd, false),
2222    }
2223}
2224
2225#[cfg(test)]
2226mod tests {
2227    use super::{
2228        BitRangeInfo, InstructionPcode, LabelId, LocalSizes, Opcode, PcodeLowerError,
2229        PcodeLoweringContext, PcodeOp, PcodeSink, Varnode, emit_instruction, lower_instruction,
2230        plan_instruction,
2231    };
2232    use crate::{
2233        Ast, AstNode, BinaryOperator, Binop, Expression, ExpressionTy, Ident, LabelOrNode, Load,
2234        LocalVarId, PCodeOpId, PcodeAst, PcodeSpaceRef, Range, RangeParam, RegisterId, SPACE_CONST,
2235        SpaceId,
2236    };
2237    use std::collections::HashMap;
2238
2239    struct Context;
2240
2241    impl PcodeLoweringContext for Context {
2242        fn default_space(&self) -> SpaceId {
2243            SpaceId::new(1)
2244        }
2245
2246        fn unique_space(&self) -> SpaceId {
2247            SpaceId::new(2)
2248        }
2249
2250        fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2251            Some(Varnode::new(SpaceId::new(3), usize::from(id) as u64 * 4, 4))
2252        }
2253
2254        fn bitrange_info(&self, _id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2255            None
2256        }
2257
2258        fn address_size(&self, space: SpaceId) -> Option<usize> {
2259            Some(if space == SpaceId::new(4) { 4 } else { 8 })
2260        }
2261    }
2262
2263    fn int(value: u64, size: usize) -> Expression {
2264        Expression {
2265            ty: ExpressionTy::SizedInt {
2266                value,
2267                size: Some(size),
2268            },
2269            size: Some(size),
2270            span: (),
2271        }
2272    }
2273
2274    fn ident(id: RegisterId) -> Expression {
2275        Expression {
2276            ty: ExpressionTy::Ident(Ident::Register(id)),
2277            size: Some(4),
2278            span: (),
2279        }
2280    }
2281
2282    fn ast(nodes: Vec<AstNode>) -> PcodeAst {
2283        PcodeAst {
2284            statements: nodes.into_iter().map(Ast::from).collect(),
2285        }
2286    }
2287
2288    #[test]
2289    fn varnodes_distinguish_constants_from_storage() {
2290        let constant = Varnode::constant(0x1234, 4);
2291        let storage = Varnode::new(SpaceId::new(2), 0x1234, 4);
2292        assert_eq!(constant.space, SPACE_CONST);
2293        assert!(constant.is_constant());
2294        assert!(!storage.is_constant());
2295    }
2296
2297    #[test]
2298    fn opcode_inventory_identifies_analysis_only_operations() {
2299        assert_eq!(Opcode::ALL.len(), 72);
2300        assert_eq!(Opcode::Copy.ghidra_id(), 1);
2301        assert_eq!(Opcode::FloatLessEqual.ghidra_id(), 44);
2302        assert_eq!(Opcode::FloatNan.ghidra_id(), 46);
2303        assert_eq!(Opcode::LzCount.ghidra_id(), 73);
2304        assert!(Opcode::ALL.contains(&Opcode::Load));
2305        assert!(Opcode::ALL.contains(&Opcode::LzCount));
2306        assert!(Opcode::Load.is_raw_instruction_op());
2307        for opcode in [
2308            Opcode::MultiEqual,
2309            Opcode::Indirect,
2310            Opcode::Cast,
2311            Opcode::PtrAdd,
2312            Opcode::PtrSub,
2313            Opcode::SegmentOp,
2314            Opcode::Insert,
2315            Opcode::Extract,
2316        ] {
2317            assert!(!opcode.is_raw_instruction_op());
2318        }
2319    }
2320
2321    #[test]
2322    fn flat_operations_preserve_input_order_and_round_trip() {
2323        let output = Varnode::new(SpaceId::new(1), 0, 4);
2324        let instruction = InstructionPcode {
2325            ops: vec![PcodeOp::new(
2326                Opcode::IntAdd,
2327                Some(output),
2328                vec![output, Varnode::constant(1, 4)],
2329            )],
2330        };
2331        assert!(!instruction.is_empty());
2332        let bytes =
2333            bincode::serde::encode_to_vec(&instruction, bincode::config::standard()).unwrap();
2334        let (decoded, _): (InstructionPcode, _) =
2335            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
2336        assert_eq!(decoded, instruction);
2337        assert!(InstructionPcode::new().is_empty());
2338    }
2339
2340    #[test]
2341    fn lower_assignment_emits_direct_arithmetic_output() {
2342        let rhs = Expression {
2343            ty: ExpressionTy::Binop(Binop {
2344                op: BinaryOperator::Add,
2345                lhs: Box::new(ident(RegisterId::new(1))),
2346                rhs: Box::new(int(1, 4)),
2347            }),
2348            size: Some(4),
2349            span: (),
2350        };
2351        let output = Varnode::new(SpaceId::new(3), 0, 4);
2352        let input = Varnode::new(SpaceId::new(3), 4, 4);
2353        let pcode = lower_instruction(
2354            &ast(vec![AstNode::Assignment {
2355                lhs: Ident::Register(RegisterId::new(0)),
2356                size: None,
2357                rhs,
2358            }]),
2359            &Context,
2360        )
2361        .unwrap();
2362        assert_eq!(
2363            pcode.ops,
2364            vec![PcodeOp::new(
2365                Opcode::IntAdd,
2366                Some(output),
2367                vec![input, Varnode::constant(1, 4)],
2368            )]
2369        );
2370    }
2371
2372    #[test]
2373    fn lower_load_store_and_userop_use_ghidra_operand_order() {
2374        let ram = SpaceId::new(4);
2375        let pointer = ident(RegisterId::new(1));
2376        let load = Load {
2377            space: Some(PcodeSpaceRef::Resolved(ram)),
2378            size: Some(4),
2379            ptr: Box::new(pointer.clone()),
2380        };
2381        let pcode = lower_instruction(
2382            &ast(vec![
2383                AstNode::Assignment {
2384                    lhs: Ident::Register(RegisterId::new(0)),
2385                    size: None,
2386                    rhs: Expression {
2387                        ty: ExpressionTy::Load(load.clone()),
2388                        size: Some(4),
2389                        span: (),
2390                    },
2391                },
2392                AstNode::LoadAssignment {
2393                    lhs: load,
2394                    size: None,
2395                    rhs: int(9, 4),
2396                },
2397                AstNode::Expression(Expression {
2398                    ty: ExpressionTy::PcodeOp {
2399                        id: PCodeOpId::new(7),
2400                        args: vec![int(2, 4)],
2401                    },
2402                    size: None,
2403                    span: (),
2404                }),
2405            ]),
2406            &Context,
2407        )
2408        .unwrap();
2409        let r0 = Varnode::new(SpaceId::new(3), 0, 4);
2410        let r1 = Varnode::new(SpaceId::new(3), 4, 4);
2411        assert_eq!(
2412            pcode.ops,
2413            vec![
2414                PcodeOp::new(Opcode::Load, Some(r0), vec![Varnode::constant(4, 4), r1],),
2415                PcodeOp::new(
2416                    Opcode::Store,
2417                    None,
2418                    vec![Varnode::constant(4, 4), r1, Varnode::constant(9, 4)],
2419                ),
2420                PcodeOp::new(
2421                    Opcode::CallOther,
2422                    None,
2423                    vec![Varnode::constant(7, 4), Varnode::constant(2, 4)],
2424                ),
2425            ]
2426        );
2427    }
2428
2429    /// Records the events of a streaming lift, keeping local branches symbolic.
2430    #[derive(Default)]
2431    struct Trace {
2432        events: Vec<String>,
2433    }
2434
2435    impl PcodeSink for Trace {
2436        fn op(&mut self, opcode: Opcode, output: Option<Varnode>, inputs: &[Varnode]) {
2437            self.events
2438                .push(format!("{opcode:?} {output:?} {inputs:?}"));
2439        }
2440
2441        fn label(&mut self, label: LabelId) {
2442            self.events.push(format!("label {}", label.index()));
2443        }
2444
2445        fn branch_label(&mut self, opcode: Opcode, label: LabelId, condition: Option<Varnode>) {
2446            self.events.push(format!(
2447                "{opcode:?} -> label {} {condition:?}",
2448                label.index()
2449            ));
2450        }
2451    }
2452
2453    fn branch_statements() -> Vec<AstNode> {
2454        vec![
2455            AstNode::ConditionalBranch {
2456                condition: ident(RegisterId::new(1)),
2457                target: LabelOrNode::Label("skip".into()),
2458            },
2459            AstNode::Call {
2460                target: LabelOrNode::Expr(int(0x2000, 8)),
2461            },
2462            AstNode::Branch {
2463                target: LabelOrNode::Expr(int(0x1000, 8)),
2464            },
2465            AstNode::Label("skip".into()),
2466            AstNode::Assignment {
2467                lhs: Ident::Register(RegisterId::new(1)),
2468                size: None,
2469                rhs: ident(RegisterId::new(2)),
2470            },
2471        ]
2472    }
2473
2474    #[test]
2475    fn local_widths_can_be_inferred_from_a_body_before_planning() {
2476        let statements = vec![
2477            AstNode::Assignment {
2478                lhs: Ident::Named(LocalVarId(0)),
2479                size: None,
2480                rhs: ident(RegisterId::new(1)),
2481            },
2482            AstNode::Assignment {
2483                lhs: Ident::Register(RegisterId::new(2)),
2484                size: None,
2485                rhs: ExpressionTy::Ident(Ident::Named(LocalVarId(0))).with_size(4),
2486            },
2487        ];
2488        let ast = ast(statements.clone());
2489
2490        // The same widths whether resolved from the body up front or by the
2491        // per-instruction planner.
2492        let sizes = super::infer_local_sizes(&ast.statements, &Context);
2493        assert_eq!(sizes.get(&LocalVarId(0)), Some(&4));
2494
2495        let planned = super::plan_instruction_with(&ast, &Context, sizes).unwrap();
2496        let inferred = plan_instruction(&ast, &Context).unwrap();
2497        assert_eq!(planned.labels(), inferred.labels());
2498
2499        // And supplied widths reach emission: the local becomes a 4-byte
2500        // unique, not an unsized-local error.
2501        let pcode = lower_instruction(&ast, &Context).unwrap();
2502        assert_eq!(
2503            pcode.ops[0].output,
2504            Some(Varnode::new(SpaceId::new(2), 0, 4))
2505        );
2506    }
2507
2508    /// A width taken from a table operand must be *named*, not dropped: a
2509    /// pass that dropped it would size the local from the later statement and
2510    /// disagree with the per-instruction planner, which sees the substituted
2511    /// value first.
2512    #[test]
2513    fn symbolic_inference_names_an_operand_width_instead_of_losing_it() {
2514        let table = crate::TableId::new(7);
2515        let statements = ast(vec![
2516            AstNode::Assignment {
2517                lhs: Ident::Named(LocalVarId(0)),
2518                size: None,
2519                rhs: Expression {
2520                    ty: ExpressionTy::Ident(Ident::Table(table)),
2521                    size: None,
2522                    span: (),
2523                },
2524            },
2525            AstNode::Assignment {
2526                lhs: Ident::Register(RegisterId::new(1)),
2527                size: None,
2528                rhs: Expression {
2529                    ty: ExpressionTy::Binop(Binop {
2530                        op: BinaryOperator::Add,
2531                        lhs: Box::new(
2532                            ExpressionTy::Ident(Ident::Named(LocalVarId(0))).with_size(4),
2533                        ),
2534                        rhs: Box::new(ident(RegisterId::new(2))),
2535                    }),
2536                    size: None,
2537                    span: (),
2538                },
2539            },
2540        ])
2541        .statements;
2542
2543        let symbolic: HashMap<LocalVarId, super::SymbolicWidth> =
2544            super::infer_local_sizes(&statements, &Context);
2545        assert_eq!(
2546            symbolic.get(&LocalVarId(0)),
2547            Some(&super::SymbolicWidth::SameAs(table))
2548        );
2549
2550        // The concrete domain cannot name it, so it falls through to the
2551        // later use — which is exactly the disagreement the symbolic domain
2552        // exists to prevent.
2553        let concrete: LocalSizes = super::infer_local_sizes(&statements, &Context);
2554        assert_eq!(concrete.get(&LocalVarId(0)), Some(&4));
2555    }
2556
2557    #[test]
2558    fn plan_reports_labels_and_out_of_instruction_targets() {
2559        let plan = plan_instruction(&ast(branch_statements()), &Context).unwrap();
2560        assert_eq!(plan.labels(), &[Box::<str>::from("skip")]);
2561        assert_eq!(plan.direct_branches(), &[0x1000]);
2562        assert_eq!(plan.direct_calls(), &[0x2000]);
2563    }
2564
2565    #[test]
2566    fn streaming_emission_keeps_local_branch_targets_symbolic() {
2567        let ast = ast(branch_statements());
2568        let plan = plan_instruction(&ast, &Context).unwrap();
2569        let mut trace = Trace::default();
2570        emit_instruction(&ast, &Context, &plan, &mut trace).unwrap();
2571
2572        assert_eq!(
2573            trace.events[0],
2574            "CBranch -> label 0 Some(Varnode { space: SpaceId(3), offset: 4, size: 4 })"
2575        );
2576        assert_eq!(trace.events[3], "label 0");
2577        assert_eq!(trace.events.len(), 5);
2578
2579        // The collecting API resolves the same branch into a relative target.
2580        let pcode = lower_instruction(&ast, &Context).unwrap();
2581        assert_eq!(pcode.ops[0].opcode, Opcode::CBranch);
2582        assert_eq!(pcode.ops[0].inputs[0], Varnode::constant(3, 8));
2583    }
2584
2585    #[test]
2586    fn plan_omits_unresolvable_direct_targets() {
2587        let plan = plan_instruction(
2588            &ast(vec![AstNode::Branch {
2589                target: LabelOrNode::Expr(ident(RegisterId::new(1))),
2590            }]),
2591            &Context,
2592        )
2593        .unwrap();
2594        assert!(plan.direct_branches().is_empty());
2595        assert_eq!(
2596            lower_instruction(
2597                &ast(vec![AstNode::Branch {
2598                    target: LabelOrNode::Expr(ident(RegisterId::new(1))),
2599                }]),
2600                &Context,
2601            )
2602            .unwrap_err(),
2603            PcodeLowerError::InvalidDirectTarget
2604        );
2605    }
2606
2607    #[test]
2608    fn branching_to_an_undefined_label_is_rejected() {
2609        let error = lower_instruction(
2610            &ast(vec![AstNode::Branch {
2611                target: LabelOrNode::Label("missing".into()),
2612            }]),
2613            &Context,
2614        )
2615        .unwrap_err();
2616        assert_eq!(error, PcodeLowerError::UnknownLabel("missing".into()));
2617    }
2618
2619    #[test]
2620    fn duplicate_labels_are_rejected() {
2621        let error = lower_instruction(
2622            &ast(vec![
2623                AstNode::Label("here".into()),
2624                AstNode::Label("here".into()),
2625            ]),
2626            &Context,
2627        )
2628        .unwrap_err();
2629        assert_eq!(error, PcodeLowerError::DuplicateLabel("here".into()));
2630    }
2631
2632    #[test]
2633    fn lower_labels_are_pcode_relative_and_comparisons_reverse_greater_than() {
2634        let comparison = Expression {
2635            ty: ExpressionTy::Binop(Binop {
2636                op: BinaryOperator::GreaterThan,
2637                lhs: Box::new(ident(RegisterId::new(1))),
2638                rhs: Box::new(ident(RegisterId::new(2))),
2639            }),
2640            size: Some(1),
2641            span: (),
2642        };
2643        let pcode = lower_instruction(
2644            &ast(vec![
2645                AstNode::Label("loop".into()),
2646                AstNode::Assignment {
2647                    lhs: Ident::Named(LocalVarId(0)),
2648                    size: None,
2649                    rhs: comparison,
2650                },
2651                AstNode::Branch {
2652                    target: LabelOrNode::Label("loop".into()),
2653                },
2654            ]),
2655            &Context,
2656        )
2657        .unwrap();
2658        assert_eq!(pcode.ops.len(), 2);
2659        assert_eq!(pcode.ops[0].opcode, Opcode::IntLess);
2660        assert_eq!(
2661            pcode.ops[0].inputs,
2662            vec![
2663                Varnode::new(SpaceId::new(3), 8, 4),
2664                Varnode::new(SpaceId::new(3), 4, 4),
2665            ]
2666        );
2667        assert_eq!(
2668            pcode.ops[0].output,
2669            Some(Varnode::new(SpaceId::new(2), 0, 1))
2670        );
2671        assert_eq!(pcode.ops[1].opcode, Opcode::Branch);
2672        assert_eq!(pcode.ops[1].inputs, vec![Varnode::constant(u64::MAX, 8)]);
2673    }
2674
2675    #[test]
2676    fn lower_named_bitranges_as_raw_read_modify_write() {
2677        struct BitRangeContext;
2678        impl PcodeLoweringContext for BitRangeContext {
2679            fn default_space(&self) -> SpaceId {
2680                SpaceId::new(1)
2681            }
2682            fn unique_space(&self) -> SpaceId {
2683                SpaceId::new(2)
2684            }
2685            fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2686                Some(Varnode::new(SpaceId::new(3), usize::from(id) as u64 * 4, 4))
2687            }
2688            fn bitrange_info(&self, id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2689                (id == crate::BitRangeFieldId::new(0)).then_some(BitRangeInfo {
2690                    storage: Varnode::new(SpaceId::new(3), 0, 4),
2691                    start: 3,
2692                    size: 5,
2693                })
2694            }
2695            fn address_size(&self, _space: SpaceId) -> Option<usize> {
2696                Some(4)
2697            }
2698        }
2699
2700        let read = lower_instruction(
2701            &ast(vec![AstNode::Assignment {
2702                lhs: Ident::Named(LocalVarId(0)),
2703                size: None,
2704                rhs: ExpressionTy::Ident(Ident::BitRange(crate::BitRangeFieldId::new(0)))
2705                    .with_size(1),
2706            }]),
2707            &BitRangeContext,
2708        )
2709        .unwrap();
2710        assert_eq!(
2711            read.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
2712            vec![Opcode::IntRight, Opcode::IntAnd, Opcode::SubPiece]
2713        );
2714        assert_eq!(read.ops[0].inputs[1], Varnode::constant(3, 4));
2715        assert_eq!(read.ops[1].inputs[1], Varnode::constant(0x1f, 4));
2716
2717        let write = lower_instruction(
2718            &ast(vec![AstNode::Assignment {
2719                lhs: Ident::BitRange(crate::BitRangeFieldId::new(0)),
2720                size: None,
2721                rhs: int(0xff, 1),
2722            }]),
2723            &BitRangeContext,
2724        )
2725        .unwrap();
2726        assert_eq!(
2727            write.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
2728            vec![
2729                Opcode::IntZext,
2730                Opcode::IntAnd,
2731                Opcode::IntLeft,
2732                Opcode::IntAnd,
2733                Opcode::IntOr,
2734            ]
2735        );
2736        assert_eq!(
2737            write.ops.last().unwrap().output.unwrap().space,
2738            SpaceId::new(3)
2739        );
2740        assert_eq!(write.ops.last().unwrap().output.unwrap().offset, 0);
2741    }
2742
2743    #[test]
2744    fn lower_binary_operations_coerce_narrow_operands() {
2745        struct MixedWidthContext;
2746        impl PcodeLoweringContext for MixedWidthContext {
2747            fn default_space(&self) -> SpaceId {
2748                SpaceId::new(1)
2749            }
2750            fn unique_space(&self) -> SpaceId {
2751                SpaceId::new(2)
2752            }
2753            fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2754                Some(Varnode::new(
2755                    SpaceId::new(3),
2756                    usize::from(id) as u64 * 2,
2757                    if id == RegisterId::new(0) { 2 } else { 1 },
2758                ))
2759            }
2760            fn bitrange_info(&self, _id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2761                None
2762            }
2763            fn address_size(&self, _space: SpaceId) -> Option<usize> {
2764                Some(8)
2765            }
2766        }
2767
2768        let pcode = lower_instruction(
2769            &ast(vec![AstNode::Assignment {
2770                lhs: Ident::Register(RegisterId::new(0)),
2771                size: None,
2772                rhs: ExpressionTy::Binop(Binop {
2773                    op: BinaryOperator::LeftShift,
2774                    lhs: Box::new(
2775                        ExpressionTy::Ident(Ident::Register(RegisterId::new(0))).with_size(2),
2776                    ),
2777                    rhs: Box::new(
2778                        ExpressionTy::Ident(Ident::Register(RegisterId::new(1))).with_size(1),
2779                    ),
2780                })
2781                .with_size(2),
2782            }]),
2783            &MixedWidthContext,
2784        )
2785        .unwrap();
2786        assert_eq!(pcode.ops[0].opcode, Opcode::IntZext);
2787        assert_eq!(pcode.ops[0].output.unwrap().size, 2);
2788        assert_eq!(pcode.ops[1].opcode, Opcode::IntLeft);
2789        assert_eq!(pcode.ops[1].inputs[1].size, 2);
2790    }
2791
2792    #[test]
2793    fn lower_store_coerces_its_value_to_the_declared_width() {
2794        let pcode = lower_instruction(
2795            &ast(vec![AstNode::LoadAssignment {
2796                lhs: Load {
2797                    space: None,
2798                    size: Some(1),
2799                    ptr: Box::new(int(0, 8)),
2800                },
2801                size: None,
2802                rhs: ident(RegisterId::new(0)),
2803            }]),
2804            &Context,
2805        )
2806        .unwrap();
2807        assert_eq!(pcode.ops[0].opcode, Opcode::SubPiece);
2808        assert_eq!(pcode.ops[0].output.unwrap().size, 1);
2809        assert_eq!(pcode.ops[1].opcode, Opcode::Store);
2810        assert_eq!(pcode.ops[1].inputs[2].size, 1);
2811    }
2812
2813    #[test]
2814    fn lower_range_assignment_supplies_its_rhs_width_to_a_userop() {
2815        let pcode = lower_instruction(
2816            &ast(vec![AstNode::RangeAssignment {
2817                lhs: Range {
2818                    value: Box::new(ident(RegisterId::new(0))),
2819                    start: RangeParam::Literal(0),
2820                    size: RangeParam::Literal(8),
2821                },
2822                size: None,
2823                rhs: Expression {
2824                    ty: ExpressionTy::PcodeOp {
2825                        id: PCodeOpId::new(7),
2826                        args: vec![],
2827                    },
2828                    size: None,
2829                    span: (),
2830                },
2831            }]),
2832            &Context,
2833        )
2834        .unwrap();
2835        let userop = pcode
2836            .ops
2837            .iter()
2838            .find(|op| op.opcode == Opcode::CallOther)
2839            .expect("range-assignment user-op was emitted");
2840        assert_eq!(userop.output.unwrap().size, 1);
2841    }
2842
2843    #[test]
2844    fn lower_rejects_nodes_that_are_not_final_raw_pcode() {
2845        let error = lower_instruction(&ast(vec![AstNode::Build(crate::TableId::new(0))]), &Context)
2846            .unwrap_err();
2847        assert_eq!(error, PcodeLowerError::InternalNode("build statement"));
2848        let error = lower_instruction(
2849            &ast(vec![AstNode::Branch {
2850                target: LabelOrNode::Node("unresolved".into()),
2851            }]),
2852            &Context,
2853        )
2854        .unwrap_err();
2855        assert_eq!(
2856            error,
2857            PcodeLowerError::InternalNode("unresolved branch target")
2858        );
2859    }
2860
2861    #[test]
2862    fn lower_every_binary_operator_uses_its_raw_opcode() {
2863        use BinaryOperator::*;
2864        let cases = [
2865            (Mul, Opcode::IntMult, false),
2866            (Div, Opcode::IntDiv, false),
2867            (SignedDiv, Opcode::IntSDiv, false),
2868            (Mod, Opcode::IntRem, false),
2869            (SignedMod, Opcode::IntSRem, false),
2870            (FloatDiv, Opcode::FloatDiv, false),
2871            (FloatMul, Opcode::FloatMult, false),
2872            (Add, Opcode::IntAdd, false),
2873            (Sub, Opcode::IntSub, false),
2874            (FloatAdd, Opcode::FloatAdd, false),
2875            (FloatSub, Opcode::FloatSub, false),
2876            (LeftShift, Opcode::IntLeft, false),
2877            (RightShift, Opcode::IntRight, false),
2878            (SignedRightShift, Opcode::IntSRight, false),
2879            (SignedLessThan, Opcode::IntSLess, false),
2880            (SignedGreaterThan, Opcode::IntSLess, true),
2881            (SignedLessEqual, Opcode::IntSLessEqual, false),
2882            (SignedGreaterEqual, Opcode::IntSLessEqual, true),
2883            (LessEqual, Opcode::IntLessEqual, false),
2884            (GreaterEqual, Opcode::IntLessEqual, true),
2885            (LessThan, Opcode::IntLess, false),
2886            (GreaterThan, Opcode::IntLess, true),
2887            (FloatLessEqual, Opcode::FloatLessEqual, false),
2888            (FloatGreaterEqual, Opcode::FloatLessEqual, true),
2889            (FloatLessThan, Opcode::FloatLess, false),
2890            (FloatGreaterThan, Opcode::FloatLess, true),
2891            (Equal, Opcode::IntEqual, false),
2892            (NotEqual, Opcode::IntNotEqual, false),
2893            (FloatEqual, Opcode::FloatEqual, false),
2894            (FloatNotEqual, Opcode::FloatNotEqual, false),
2895            (LogicalXor, Opcode::BoolXor, false),
2896            (LogicalAnd, Opcode::BoolAnd, false),
2897            (LogicalOr, Opcode::BoolOr, false),
2898            (BitwiseXor, Opcode::IntXor, false),
2899            (BitwiseOr, Opcode::IntOr, false),
2900            (BitwiseAnd, Opcode::IntAnd, false),
2901        ];
2902        for (operator, opcode, reverse) in cases {
2903            assert_eq!(super::binary_opcode(operator), (opcode, reverse));
2904        }
2905    }
2906
2907    #[test]
2908    fn lower_every_builtin_and_unary_operator() {
2909        let builtins = [
2910            (crate::Builtin::Carry, Opcode::IntCarry),
2911            (crate::Builtin::Scarry, Opcode::IntSCarry),
2912            (crate::Builtin::Sborrow, Opcode::IntSBorrow),
2913            (crate::Builtin::Nan, Opcode::FloatNan),
2914            (crate::Builtin::Abs, Opcode::FloatAbs),
2915            (crate::Builtin::Sqrt, Opcode::FloatSqrt),
2916            (crate::Builtin::Floor, Opcode::FloatFloor),
2917            (crate::Builtin::Ceil, Opcode::FloatCeil),
2918            (crate::Builtin::Round, Opcode::FloatRound),
2919            (crate::Builtin::Int2Float, Opcode::FloatInt2Float),
2920            (crate::Builtin::Float2Float, Opcode::FloatFloat2Float),
2921            (crate::Builtin::Trunc, Opcode::FloatTrunc),
2922            (crate::Builtin::Zext, Opcode::IntZext),
2923            (crate::Builtin::Sext, Opcode::IntSext),
2924            (crate::Builtin::Popcount, Opcode::PopCount),
2925            (crate::Builtin::Lzcount, Opcode::LzCount),
2926            (crate::Builtin::Cpool, Opcode::CpoolRef),
2927            (crate::Builtin::NewObject, Opcode::New),
2928        ];
2929        for (builtin, opcode) in builtins {
2930            let expression = Expression {
2931                ty: ExpressionTy::FunctionCall {
2932                    builtin,
2933                    args: vec![int(1, 4)],
2934                },
2935                size: Some(4),
2936                span: (),
2937            };
2938            let pcode = lower_instruction(
2939                &ast(vec![AstNode::Assignment {
2940                    lhs: Ident::Named(LocalVarId(0)),
2941                    size: None,
2942                    rhs: expression,
2943                }]),
2944                &Context,
2945            )
2946            .unwrap();
2947            assert_eq!(pcode.ops[0].opcode, opcode);
2948        }
2949        for (operator, opcode) in [
2950            (crate::UnaryOperator::LogicalNot, Opcode::BoolNegate),
2951            (crate::UnaryOperator::BitwiseNot, Opcode::IntNegate),
2952            (crate::UnaryOperator::Minus, Opcode::Int2Comp),
2953            (crate::UnaryOperator::FloatMinus, Opcode::FloatNeg),
2954        ] {
2955            let expression = Expression {
2956                ty: ExpressionTy::Unop(crate::Unop {
2957                    op: operator,
2958                    e: Box::new(ident(RegisterId::new(0))),
2959                }),
2960                size: Some(4),
2961                span: (),
2962            };
2963            let pcode = lower_instruction(
2964                &ast(vec![AstNode::Assignment {
2965                    lhs: Ident::Named(LocalVarId(0)),
2966                    size: None,
2967                    rhs: expression,
2968                }]),
2969                &Context,
2970            )
2971            .unwrap();
2972            assert_eq!(pcode.ops[0].opcode, opcode);
2973        }
2974    }
2975
2976    #[test]
2977    fn lower_direct_and_indirect_control_flow() {
2978        let direct = lower_instruction(
2979            &ast(vec![AstNode::Call {
2980                target: LabelOrNode::Expr(int(0x1000, 8)),
2981            }]),
2982            &Context,
2983        )
2984        .unwrap();
2985        assert_eq!(
2986            direct.ops[0],
2987            PcodeOp::new(
2988                Opcode::Call,
2989                None,
2990                vec![Varnode::new(SpaceId::new(1), 0x1000, 8)]
2991            )
2992        );
2993        let indirect = lower_instruction(
2994            &ast(vec![
2995                AstNode::ConditionalBranch {
2996                    condition: int(1, 1),
2997                    target: LabelOrNode::Expr(int(0x2000, 8)),
2998                },
2999                AstNode::BranchIndirect {
3000                    target: ident(RegisterId::new(0)),
3001                },
3002                AstNode::CallIndirect {
3003                    target: ident(RegisterId::new(1)),
3004                },
3005                AstNode::Return {
3006                    target: ident(RegisterId::new(2)),
3007                },
3008            ]),
3009            &Context,
3010        )
3011        .unwrap();
3012        assert_eq!(
3013            indirect.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3014            vec![
3015                Opcode::CBranch,
3016                Opcode::BranchInd,
3017                Opcode::CallInd,
3018                Opcode::Return
3019            ]
3020        );
3021        assert_eq!(
3022            indirect.ops[0].inputs,
3023            vec![
3024                Varnode::new(SpaceId::new(1), 0x2000, 8),
3025                Varnode::constant(1, 1)
3026            ]
3027        );
3028    }
3029
3030    #[test]
3031    fn lowering_errors_are_displayable_and_typed() {
3032        let errors = [
3033            PcodeLowerError::UnknownSize,
3034            PcodeLowerError::ZeroSize,
3035            PcodeLowerError::CopySizeMismatch {
3036                input: 1,
3037                output: 2,
3038            },
3039            PcodeLowerError::InputSizeMismatch {
3040                operation: "operation",
3041                left: 1,
3042                right: 2,
3043            },
3044            PcodeLowerError::InvalidBooleanSize(2),
3045            PcodeLowerError::AddressSizeMismatch {
3046                expected: 4,
3047                actual: 8,
3048            },
3049            PcodeLowerError::StoreSizeMismatch {
3050                declared: 4,
3051                value: 8,
3052            },
3053            PcodeLowerError::InvalidRange {
3054                start: 0,
3055                size: 0,
3056                storage_bits: 32,
3057            },
3058            PcodeLowerError::UniqueSpaceOverflow,
3059            PcodeLowerError::UnknownRegister(RegisterId::new(9)),
3060            PcodeLowerError::UnresolvedIdentifier("field"),
3061            PcodeLowerError::UnresolvedSpace,
3062            PcodeLowerError::UnresolvedRangeParameter,
3063            PcodeLowerError::InternalNode("macro"),
3064            PcodeLowerError::Unsupported("range"),
3065            PcodeLowerError::DuplicateLabel("loop".into()),
3066            PcodeLowerError::UnknownLabel("loop".into()),
3067            PcodeLowerError::InvalidDirectTarget,
3068        ];
3069        for error in errors {
3070            assert!(!error.to_string().is_empty());
3071        }
3072        assert_eq!(
3073            InstructionPcode::lower(&ast(vec![]), &Context).unwrap(),
3074            InstructionPcode::new()
3075        );
3076    }
3077
3078    #[test]
3079    fn lower_rejects_invalid_raw_widths_and_ranges() {
3080        let range = |start, size| Expression {
3081            ty: ExpressionTy::Range(crate::Range {
3082                value: Box::new(ident(RegisterId::new(0))),
3083                start: crate::RangeParam::Literal(start),
3084                size: crate::RangeParam::Literal(size),
3085            }),
3086            size: None,
3087            span: (),
3088        };
3089        for (start, size) in [(0, 0), (0, 65), (31, 2)] {
3090            let error = lower_instruction(
3091                &ast(vec![AstNode::Assignment {
3092                    lhs: Ident::Register(RegisterId::new(0)),
3093                    size: None,
3094                    rhs: range(start, size),
3095                }]),
3096                &Context,
3097            )
3098            .unwrap_err();
3099            assert!(matches!(error, PcodeLowerError::InvalidRange { .. }));
3100        }
3101
3102        let error = lower_instruction(
3103            &ast(vec![AstNode::Assignment {
3104                lhs: Ident::Named(LocalVarId(0)),
3105                size: None,
3106                rhs: Expression {
3107                    ty: ExpressionTy::Range(crate::Range {
3108                        value: Box::new(ident(RegisterId::new(0))),
3109                        start: crate::RangeParam::MacroArg(LocalVarId(1)),
3110                        size: crate::RangeParam::Literal(1),
3111                    }),
3112                    size: None,
3113                    span: (),
3114                },
3115            }]),
3116            &Context,
3117        )
3118        .unwrap_err();
3119        assert_eq!(error, PcodeLowerError::UnresolvedRangeParameter);
3120
3121        let mismatch = ExpressionTy::Binop(Binop {
3122            op: BinaryOperator::Add,
3123            lhs: Box::new(ident(RegisterId::new(0))),
3124            rhs: Box::new(int(1, 1)),
3125        })
3126        .with_size(4);
3127        // Integer literals are sized by their consuming p-code operation,
3128        // rather than forcing a mixed-width raw operation.
3129        assert!(
3130            lower_instruction(
3131                &ast(vec![AstNode::Assignment {
3132                    lhs: Ident::Named(LocalVarId(0)),
3133                    size: None,
3134                    rhs: mismatch,
3135                }]),
3136                &Context,
3137            )
3138            .is_ok()
3139        );
3140
3141        let comparison = ExpressionTy::Binop(Binop {
3142            op: BinaryOperator::Equal,
3143            lhs: Box::new(ident(RegisterId::new(0))),
3144            rhs: Box::new(ident(RegisterId::new(1))),
3145        })
3146        .with_size(4);
3147        assert_eq!(
3148            lower_instruction(
3149                &ast(vec![AstNode::Assignment {
3150                    lhs: Ident::Named(LocalVarId(0)),
3151                    size: None,
3152                    rhs: comparison,
3153                }]),
3154                &Context,
3155            )
3156            .unwrap_err(),
3157            PcodeLowerError::InvalidBooleanSize(4)
3158        );
3159
3160        let bad_load = Load {
3161            space: Some(PcodeSpaceRef::Resolved(SpaceId::new(1))),
3162            size: Some(4),
3163            ptr: Box::new(ident(RegisterId::new(0))),
3164        };
3165        assert!(matches!(
3166            lower_instruction(
3167                &ast(vec![AstNode::Assignment {
3168                    lhs: Ident::Named(LocalVarId(0)),
3169                    size: Some(4),
3170                    rhs: ExpressionTy::Load(bad_load.clone()).with_size(4),
3171                }]),
3172                &Context,
3173            ),
3174            Err(PcodeLowerError::AddressSizeMismatch { .. })
3175        ));
3176        assert!(matches!(
3177            lower_instruction(
3178                &ast(vec![AstNode::LoadAssignment {
3179                    lhs: bad_load,
3180                    size: None,
3181                    rhs: int(1, 1),
3182                }]),
3183                &Context,
3184            ),
3185            Err(PcodeLowerError::AddressSizeMismatch { .. })
3186        ));
3187    }
3188
3189    #[test]
3190    fn lower_handles_subpieces_and_rejects_invalid_final_forms() {
3191        let lsb = Expression {
3192            ty: ExpressionTy::SubPieceLsb {
3193                src: Box::new(ident(RegisterId::new(0))),
3194                count: 2,
3195            },
3196            size: Some(2),
3197            span: (),
3198        };
3199        let msb = Expression {
3200            ty: ExpressionTy::SubPieceMsb {
3201                src: Box::new(ident(RegisterId::new(0))),
3202                count: 2,
3203            },
3204            size: Some(2),
3205            span: (),
3206        };
3207        let pcode = lower_instruction(
3208            &ast(vec![
3209                AstNode::Assignment {
3210                    lhs: Ident::Named(LocalVarId(0)),
3211                    size: None,
3212                    rhs: lsb,
3213                },
3214                AstNode::Assignment {
3215                    lhs: Ident::Named(LocalVarId(1)),
3216                    size: None,
3217                    rhs: msb,
3218                },
3219            ]),
3220            &Context,
3221        )
3222        .unwrap();
3223        assert_eq!(
3224            pcode.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3225            vec![Opcode::SubPiece, Opcode::SubPiece]
3226        );
3227        assert_eq!(pcode.ops[0].inputs[1], Varnode::constant(0, 8));
3228        assert_eq!(pcode.ops[1].inputs[1], Varnode::constant(2, 8));
3229
3230        let range = Expression {
3231            ty: ExpressionTy::Range(crate::Range {
3232                value: Box::new(ident(RegisterId::new(0))),
3233                start: crate::RangeParam::Literal(0),
3234                size: crate::RangeParam::Literal(1),
3235            }),
3236            size: Some(1),
3237            span: (),
3238        };
3239        let pcode = lower_instruction(
3240            &ast(vec![AstNode::Assignment {
3241                lhs: Ident::Named(LocalVarId(0)),
3242                size: None,
3243                rhs: range,
3244            }]),
3245            &Context,
3246        )
3247        .unwrap();
3248        assert_eq!(
3249            pcode.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3250            vec![Opcode::IntRight, Opcode::IntAnd, Opcode::SubPiece]
3251        );
3252        let error = lower_instruction(
3253            &ast(vec![
3254                AstNode::Label("same".into()),
3255                AstNode::Label("same".into()),
3256            ]),
3257            &Context,
3258        )
3259        .unwrap_err();
3260        assert_eq!(error, PcodeLowerError::DuplicateLabel("same".into()));
3261        let error = lower_instruction(
3262            &ast(vec![AstNode::Branch {
3263                target: LabelOrNode::Label("missing".into()),
3264            }]),
3265            &Context,
3266        )
3267        .unwrap_err();
3268        assert_eq!(error, PcodeLowerError::UnknownLabel("missing".into()));
3269        let error = lower_instruction(
3270            &ast(vec![AstNode::Branch {
3271                target: LabelOrNode::Expr(ident(RegisterId::new(0))),
3272            }]),
3273            &Context,
3274        )
3275        .unwrap_err();
3276        assert_eq!(error, PcodeLowerError::InvalidDirectTarget);
3277    }
3278}