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_else(|| match builtin {
1216                Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan => Some(1),
1217                // The width-preserving float builtins answer in their operand's
1218                // width, so `trunc(round(XmmReg2[0,32]))` needs no local.
1219                Builtin::Abs | Builtin::Sqrt | Builtin::Floor | Builtin::Ceil | Builtin::Round => {
1220                    args.first().and_then(|arg| self.expr_size(arg))
1221                }
1222                _ => None,
1223            })
1224            .ok_or(PcodeLowerError::UnknownSize)?;
1225        let output = self.output(requested_output, size)?;
1226        // The carry-family builtins return a boolean but consume equally-sized
1227        // integer operands. Their result width therefore cannot provide the
1228        // context required by a nested `zext`; carry the first operand's width
1229        // into the remaining operands instead.
1230        let inputs = if matches!(builtin, Builtin::Carry | Builtin::Scarry | Builtin::Sborrow)
1231            && !args.is_empty()
1232        {
1233            // The first operand can be an unsized literal (`sborrow(0, RAX)`
1234            // in x86 `NEG`). Carry-family operands must all have the same
1235            // width, so derive it from any concrete operand before lowering.
1236            let operand_size = args
1237                .iter()
1238                .find_map(|arg| self.expr_size(arg))
1239                .ok_or(PcodeLowerError::UnknownSize)?;
1240            args.iter()
1241                .map(|arg| self.lower_expr_with_size(arg, operand_size))
1242                .collect::<Result<Vec<_>, _>>()?
1243        } else {
1244            args.iter()
1245                .map(|arg| self.lower_expr(arg, None))
1246                .collect::<Result<Vec<_>, _>>()?
1247        };
1248        self.emit(opcode, Some(output), &inputs);
1249        Ok(output)
1250    }
1251
1252    fn lower_unop(
1253        &mut self,
1254        expr: &Expression,
1255        op: UnaryOperator,
1256        operand: &Expression,
1257        requested_output: Option<Varnode>,
1258    ) -> Result<Varnode, PcodeLowerError> {
1259        if let UnaryOperator::AddressOf(size) = op {
1260            // An address symbol such as `inst_next` already *is* its address;
1261            // taking its address only fixes the width.
1262            if let ExpressionTy::SizedInt {
1263                value,
1264                size: literal_size,
1265            } = &operand.ty
1266            {
1267                let size = size
1268                    .or(*literal_size)
1269                    .or(operand.size)
1270                    .ok_or(PcodeLowerError::UnknownSize)?;
1271                return self.copy_if_requested(Varnode::constant(*value, size), requested_output);
1272            }
1273            let storage = self.storage_from_expr(operand)?;
1274            let size = size
1275                .or_else(|| self.context.address_size(storage.space))
1276                .ok_or(PcodeLowerError::UnknownSize)?;
1277            return self
1278                .copy_if_requested(Varnode::constant(storage.offset, size), requested_output);
1279        }
1280        let opcode = match op {
1281            UnaryOperator::LogicalNot => Opcode::BoolNegate,
1282            UnaryOperator::BitwiseNot => Opcode::IntNegate,
1283            UnaryOperator::Minus => Opcode::Int2Comp,
1284            UnaryOperator::FloatMinus => Opcode::FloatNeg,
1285            UnaryOperator::AddressOf(_) => unreachable!(),
1286        };
1287        // Unsized integer literals are polymorphic. Resolve the unary result
1288        // width before lowering its operand so `~8` can inherit the width of
1289        // its assignment (for example x86 `CLTS`), rather than failing while
1290        // lowering the literal without a consumer.
1291        let size = requested_output
1292            .map(|output| output.size)
1293            .or(expr.size)
1294            .or_else(|| (op == UnaryOperator::LogicalNot).then_some(1))
1295            .or_else(|| self.expr_size(operand))
1296            .ok_or(PcodeLowerError::UnknownSize)?;
1297        // Preserve a concrete operand's native width (notably BOOL_NEGATE,
1298        // whose input need not be one byte); only force the result width into
1299        // a width-less operand such as an integer literal.
1300        let input = if self.expr_size(operand).is_some() {
1301            self.lower_expr(operand, None)?
1302        } else {
1303            self.lower_expr_with_size(operand, size)?
1304        };
1305        let output = self.output(requested_output, size)?;
1306        self.emit(opcode, Some(output), &[input]);
1307        Ok(output)
1308    }
1309
1310    fn lower_binop(
1311        &mut self,
1312        expr: &Expression,
1313        op: BinaryOperator,
1314        lhs: &Expression,
1315        rhs: &Expression,
1316        requested_output: Option<Varnode>,
1317    ) -> Result<Varnode, PcodeLowerError> {
1318        let (opcode, reverse) = binary_opcode(op);
1319        // An arithmetic result has the same width as its operands. Comparisons
1320        // and boolean operations instead produce one byte, so obtain their
1321        // operand width from either side. This supplies the context needed by
1322        // unsized SLEIGH literals and compound expressions (for example the
1323        // `2 * zext(DF)` in x86 MOVS pointer updates).
1324        let is_boolean = op.is_comparison()
1325            || matches!(
1326                op,
1327                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1328            );
1329        let input_size = if is_boolean {
1330            self.expr_size(lhs).or_else(|| self.expr_size(rhs))
1331        } else {
1332            requested_output
1333                .map(|output| output.size)
1334                .or(expr.size)
1335                .or_else(|| self.expr_size(lhs))
1336                .or_else(|| self.expr_size(rhs))
1337        };
1338        let mut inputs = match input_size {
1339            Some(size) => vec![
1340                self.lower_expr_with_size(lhs, size)?,
1341                self.lower_expr_with_size(rhs, size)?,
1342            ],
1343            None => vec![self.lower_expr(lhs, None)?, self.lower_expr(rhs, None)?],
1344        };
1345        if reverse {
1346            inputs.swap(0, 1);
1347        }
1348        let size = requested_output
1349            .map(|output| output.size)
1350            .or(expr.size)
1351            .or_else(|| op.is_comparison().then_some(1))
1352            .or(input_size)
1353            .ok_or(PcodeLowerError::UnknownSize)?;
1354        let output = self.output(requested_output, size)?;
1355        if (op.is_comparison()
1356            || matches!(
1357                op,
1358                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1359            ))
1360            && output.size != 1
1361        {
1362            return Err(PcodeLowerError::InvalidBooleanSize(output.size));
1363        }
1364        if inputs[0].size != inputs[1].size {
1365            return Err(PcodeLowerError::InputSizeMismatch {
1366                operation: "binary operation",
1367                left: inputs[0].size,
1368                right: inputs[1].size,
1369            });
1370        }
1371        if !op.is_comparison()
1372            && !matches!(
1373                op,
1374                BinaryOperator::LogicalXor | BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr
1375            )
1376            && output.size != inputs[0].size
1377        {
1378            return Err(PcodeLowerError::CopySizeMismatch {
1379                input: inputs[0].size,
1380                output: output.size,
1381            });
1382        }
1383        self.emit(opcode, Some(output), &inputs);
1384        Ok(output)
1385    }
1386
1387    /// Lowers an operand in a context that requires `size` bytes.
1388    ///
1389    /// SLEIGH permits a narrow register or temporary as a shift count or bit
1390    /// index for a wider value. Raw p-code does not: both inputs of these
1391    /// operations must have the same width. Requesting an output of `size`
1392    /// propagates that context into compound expressions, while
1393    /// [`copy_if_requested`](Self::copy_if_requested) inserts an explicit
1394    /// zero-extension or low-byte `SUBPIECE` for a directly stored value.
1395    fn lower_expr_with_size(
1396        &mut self,
1397        expr: &Expression,
1398        size: usize,
1399    ) -> Result<Varnode, PcodeLowerError> {
1400        // SLEIGH integer literals are polymorphic in raw p-code: the
1401        // surrounding operation determines their varnode width (for example
1402        // `RAX + 1`). This applies even when parsing retained a literal's
1403        // minimal source width.
1404        if let ExpressionTy::SizedInt { value, .. } = &expr.ty {
1405            return Ok(Varnode::constant(*value, size));
1406        }
1407        if self.expr_size(expr) == Some(size) {
1408            return self.lower_expr(expr, None);
1409        }
1410        let output = self.allocate_unique(size)?;
1411        self.lower_expr(expr, Some(output))
1412    }
1413
1414    fn lower_range(
1415        &mut self,
1416        range: &Range,
1417        requested_output: Option<Varnode>,
1418    ) -> Result<Varnode, PcodeLowerError> {
1419        let input = self.lower_expr(&range.value, None)?;
1420        let (start, bits) = range_params(range)?;
1421        self.extract_range(input, start, bits, requested_output)
1422    }
1423
1424    fn lower_named_bitrange(
1425        &mut self,
1426        id: BitRangeFieldId,
1427        requested_output: Option<Varnode>,
1428    ) -> Result<Varnode, PcodeLowerError> {
1429        let info = self
1430            .context
1431            .bitrange_info(id)
1432            .ok_or(PcodeLowerError::Unsupported("an unknown named bit range"))?;
1433        self.extract_range(info.storage, info.start, info.size, requested_output)
1434    }
1435
1436    fn extract_range(
1437        &mut self,
1438        input: Varnode,
1439        start: usize,
1440        bits: usize,
1441        requested_output: Option<Varnode>,
1442    ) -> Result<Varnode, PcodeLowerError> {
1443        let result_size = Self::validate_range(input, start, bits)?;
1444        if let Some(output) = requested_output {
1445            if output.size != result_size {
1446                return Err(PcodeLowerError::CopySizeMismatch {
1447                    input: result_size,
1448                    output: output.size,
1449                });
1450            }
1451        }
1452        let shifted = self.allocate_unique(input.size)?;
1453        self.emit(
1454            Opcode::IntRight,
1455            Some(shifted),
1456            &[input, Varnode::constant(start as u64, input.size)],
1457        );
1458        let masked = self.allocate_unique(input.size)?;
1459        self.emit(
1460            Opcode::IntAnd,
1461            Some(masked),
1462            &[shifted, Varnode::constant(Self::mask(bits)?, input.size)],
1463        );
1464        let output = self.output(requested_output, result_size)?;
1465        self.emit(
1466            Opcode::SubPiece,
1467            Some(output),
1468            &[masked, Varnode::constant(0, 8)],
1469        );
1470        Ok(output)
1471    }
1472
1473    fn lower_range_assignment(
1474        &mut self,
1475        range: &Range,
1476        rhs: &Expression,
1477    ) -> Result<(), PcodeLowerError> {
1478        if let ExpressionTy::Load(load) = &range.value.ty {
1479            return self.lower_load_range_assignment(load, range, rhs);
1480        }
1481        let storage = self.storage_from_expr(&range.value)?;
1482        let (start, bits) = range_params(range)?;
1483        self.insert_range(storage, start, bits, rhs)
1484    }
1485
1486    /// Lowers a bit-range write into a memory load as load/modify/store. SLEIGH
1487    /// uses this form for packed MMX lanes backed by private RAM, where an
1488    /// address-of expression cannot name a raw-p-code varnode directly.
1489    fn lower_load_range_assignment(
1490        &mut self,
1491        load: &Load,
1492        range: &Range,
1493        rhs: &Expression,
1494    ) -> Result<(), PcodeLowerError> {
1495        let storage = self.lower_load(&range.value, load, None)?;
1496        let (start, bits) = range_params(range)?;
1497        Self::validate_range(storage, start, bits)?;
1498        if storage.size > 8 {
1499            // A byte-aligned lane of a wide memory operand is stored on its
1500            // own at `ptr + start / 8`; the surrounding bytes are untouched.
1501            let Some(lane) = Self::aligned_lane(storage, start, bits) else {
1502                return Err(PcodeLowerError::InvalidRange {
1503                    start,
1504                    size: bits,
1505                    storage_bits: storage.size.saturating_mul(8),
1506                });
1507            };
1508            let value = self.lower_expr_with_size(rhs, lane.size)?;
1509            if value.size != lane.size {
1510                return Err(PcodeLowerError::CopySizeMismatch {
1511                    input: value.size,
1512                    output: lane.size,
1513                });
1514            }
1515            let space = self.load_space(load)?;
1516            if space == SPACE_CONST {
1517                return Err(PcodeLowerError::Unsupported("a store to constant space"));
1518            }
1519            let ptr = self.lower_expr(&load.ptr, None)?;
1520            self.validate_pointer(space, ptr)?;
1521            let lane_ptr = if start == 0 {
1522                ptr
1523            } else {
1524                let output = self.allocate_unique(ptr.size)?;
1525                self.emit(
1526                    Opcode::IntAdd,
1527                    Some(output),
1528                    &[ptr, Varnode::constant((start / 8) as u64, ptr.size)],
1529                );
1530                output
1531            };
1532            self.emit(
1533                Opcode::Store,
1534                None,
1535                &[Self::space_id(space), lane_ptr, value],
1536            );
1537            return Ok(());
1538        }
1539        let value = self.lower_expr_with_size(rhs, bits.div_ceil(8))?;
1540        if value.size > storage.size {
1541            return Err(PcodeLowerError::InputSizeMismatch {
1542                operation: "bit-range assignment",
1543                left: storage.size,
1544                right: value.size,
1545            });
1546        }
1547        let extended = if value.size == storage.size {
1548            value
1549        } else {
1550            let output = self.allocate_unique(storage.size)?;
1551            self.emit(Opcode::IntZext, Some(output), &[value]);
1552            output
1553        };
1554        let inserted = self.allocate_unique(storage.size)?;
1555        self.emit(
1556            Opcode::IntAnd,
1557            Some(inserted),
1558            &[extended, Varnode::constant(Self::mask(bits)?, storage.size)],
1559        );
1560        let shifted = self.allocate_unique(storage.size)?;
1561        self.emit(
1562            Opcode::IntLeft,
1563            Some(shifted),
1564            &[inserted, Varnode::constant(start as u64, storage.size)],
1565        );
1566        let clear_mask = !(Self::mask(bits)? << start);
1567        let kept = self.allocate_unique(storage.size)?;
1568        self.emit(
1569            Opcode::IntAnd,
1570            Some(kept),
1571            &[storage, Varnode::constant(clear_mask, storage.size)],
1572        );
1573        let result = self.allocate_unique(storage.size)?;
1574        self.emit(Opcode::IntOr, Some(result), &[kept, shifted]);
1575
1576        let space = self.load_space(load)?;
1577        if space == SPACE_CONST {
1578            return Err(PcodeLowerError::Unsupported("a store to constant space"));
1579        }
1580        let ptr = self.lower_expr(&load.ptr, None)?;
1581        self.validate_pointer(space, ptr)?;
1582        self.emit(Opcode::Store, None, &[Self::space_id(space), ptr, result]);
1583        Ok(())
1584    }
1585
1586    fn insert_range(
1587        &mut self,
1588        storage: Varnode,
1589        start: usize,
1590        bits: usize,
1591        rhs: &Expression,
1592    ) -> Result<(), PcodeLowerError> {
1593        Self::validate_range(storage, start, bits)?;
1594        // Inserting needs a full-width clear mask. Constants in this AST are
1595        // u64, so zero-extending one into larger storage would incorrectly
1596        // clear every high bit. A byte-aligned lane of wide storage (an XMM
1597        // register, or a 16-byte local) is instead written as the sub-varnode
1598        // it names, which is how SLEIGH itself models overlapping registers.
1599        if storage.size > 8 {
1600            let Some(lane) = Self::aligned_lane(storage, start, bits) else {
1601                return Err(PcodeLowerError::InvalidRange {
1602                    start,
1603                    size: bits,
1604                    storage_bits: storage.size.saturating_mul(8),
1605                });
1606            };
1607            let value = self.lower_expr_with_size(rhs, lane.size)?;
1608            if value.size != lane.size {
1609                return Err(PcodeLowerError::CopySizeMismatch {
1610                    input: value.size,
1611                    output: lane.size,
1612                });
1613            }
1614            self.emit(Opcode::Copy, Some(lane), &[value]);
1615            return Ok(());
1616        }
1617        // A range assignment fixes the RHS width even when the RHS is a
1618        // user-op result whose source expression does not carry one.
1619        let value = self.lower_expr_with_size(rhs, bits.div_ceil(8))?;
1620        if value.size > storage.size {
1621            return Err(PcodeLowerError::InputSizeMismatch {
1622                operation: "bit-range assignment",
1623                left: storage.size,
1624                right: value.size,
1625            });
1626        }
1627        let extended = if value.size == storage.size {
1628            value
1629        } else {
1630            let output = self.allocate_unique(storage.size)?;
1631            self.emit(Opcode::IntZext, Some(output), &[value]);
1632            output
1633        };
1634        let inserted = self.allocate_unique(storage.size)?;
1635        self.emit(
1636            Opcode::IntAnd,
1637            Some(inserted),
1638            &[extended, Varnode::constant(Self::mask(bits)?, storage.size)],
1639        );
1640        let shifted = self.allocate_unique(storage.size)?;
1641        self.emit(
1642            Opcode::IntLeft,
1643            Some(shifted),
1644            &[inserted, Varnode::constant(start as u64, storage.size)],
1645        );
1646        let clear_mask = !(Self::mask(bits)? << start);
1647        let kept = self.allocate_unique(storage.size)?;
1648        self.emit(
1649            Opcode::IntAnd,
1650            Some(kept),
1651            &[storage, Varnode::constant(clear_mask, storage.size)],
1652        );
1653        self.emit(Opcode::IntOr, Some(storage), &[kept, shifted]);
1654        Ok(())
1655    }
1656
1657    /// The sub-varnode a byte-aligned bit range of `storage` names, or `None`
1658    /// when the range does not start and end on a byte boundary.
1659    fn aligned_lane(storage: Varnode, start: usize, bits: usize) -> Option<Varnode> {
1660        if start % 8 != 0 || bits % 8 != 0 {
1661            return None;
1662        }
1663        Some(Varnode::new(
1664            storage.space,
1665            storage.offset + (start / 8) as u64,
1666            bits / 8,
1667        ))
1668    }
1669
1670    fn validate_range(
1671        storage: Varnode,
1672        start: usize,
1673        size: usize,
1674    ) -> Result<usize, PcodeLowerError> {
1675        let storage_bits = storage
1676            .size
1677            .checked_mul(8)
1678            .ok_or(PcodeLowerError::InvalidRange {
1679                start,
1680                size,
1681                storage_bits: usize::MAX,
1682            })?;
1683        if size == 0 || size > 64 || start.checked_add(size).is_none_or(|end| end > storage_bits) {
1684            return Err(PcodeLowerError::InvalidRange {
1685                start,
1686                size,
1687                storage_bits,
1688            });
1689        }
1690        Ok(size.div_ceil(8))
1691    }
1692
1693    fn mask(bits: usize) -> Result<u64, PcodeLowerError> {
1694        match bits {
1695            1..=63 => Ok((1u64 << bits) - 1),
1696            64 => Ok(u64::MAX),
1697            _ => Err(PcodeLowerError::InvalidRange {
1698                start: 0,
1699                size: bits,
1700                storage_bits: 64,
1701            }),
1702        }
1703    }
1704
1705    fn validate_pointer(&self, space: SpaceId, ptr: Varnode) -> Result<(), PcodeLowerError> {
1706        let expected = self
1707            .context
1708            .address_size(space)
1709            .ok_or(PcodeLowerError::UnresolvedSpace)?;
1710        Self::checked_size(expected)?;
1711        if ptr.size != expected {
1712            return Err(PcodeLowerError::AddressSizeMismatch {
1713                expected,
1714                actual: ptr.size,
1715            });
1716        }
1717        Ok(())
1718    }
1719
1720    fn storage_from_expr(&mut self, expr: &Expression) -> Result<Varnode, PcodeLowerError> {
1721        match &expr.ty {
1722            ExpressionTy::Ident(ident) => self.storage_for_ident(ident.clone(), expr.size),
1723            _ => Err(PcodeLowerError::Unsupported(
1724                "address-of a non-varnode expression",
1725            )),
1726        }
1727    }
1728
1729    fn storage_for_ident(
1730        &mut self,
1731        ident: Ident,
1732        size: Option<usize>,
1733    ) -> Result<Varnode, PcodeLowerError> {
1734        match ident {
1735            Ident::Register(id) => self
1736                .context
1737                .register_varnode(id)
1738                .ok_or(PcodeLowerError::UnknownRegister(id)),
1739            Ident::Named(id) => {
1740                let size = self.plan.local_sizes.get(&id).copied().or(size);
1741                if let Some(varnode) = self.locals.get(&id) {
1742                    if let Some(size) = size
1743                        && size != varnode.size
1744                    {
1745                        return Err(PcodeLowerError::CopySizeMismatch {
1746                            input: varnode.size,
1747                            output: size,
1748                        });
1749                    }
1750                    return Ok(*varnode);
1751                }
1752                let varnode = self.allocate_unique(size.ok_or(PcodeLowerError::UnknownSize)?)?;
1753                self.locals.insert(id, varnode);
1754                Ok(varnode)
1755            }
1756            Ident::BitRange(_) => Err(PcodeLowerError::Unsupported("a named bit range")),
1757            Ident::Field(_) => Err(PcodeLowerError::UnresolvedIdentifier("field")),
1758            Ident::Table(_) => Err(PcodeLowerError::UnresolvedIdentifier("table")),
1759            Ident::Global(_) => Err(PcodeLowerError::UnresolvedIdentifier("global")),
1760        }
1761    }
1762
1763    fn lower_userop_inputs(
1764        &mut self,
1765        id: PCodeOpId,
1766        args: &[Expression],
1767    ) -> Result<Vec<Varnode>, PcodeLowerError> {
1768        let mut inputs = Vec::with_capacity(args.len() + 1);
1769        inputs.push(Varnode::constant(usize::from(id) as u64, 4));
1770        inputs.extend(
1771            args.iter()
1772                .map(|arg| self.lower_expr(arg, None))
1773                .collect::<Result<Vec<_>, _>>()?,
1774        );
1775        Ok(inputs)
1776    }
1777
1778    fn copy_if_requested(
1779        &mut self,
1780        input: Varnode,
1781        requested_output: Option<Varnode>,
1782    ) -> Result<Varnode, PcodeLowerError> {
1783        match requested_output {
1784            Some(output) if output != input && input.size == output.size => {
1785                self.emit(Opcode::Copy, Some(output), &[input]);
1786                Ok(output)
1787            }
1788            Some(output) if input.size < output.size => {
1789                self.emit(Opcode::IntZext, Some(output), &[input]);
1790                Ok(output)
1791            }
1792            Some(output) if input.size > output.size => {
1793                self.emit(
1794                    Opcode::SubPiece,
1795                    Some(output),
1796                    &[input, Varnode::constant(0, 8)],
1797                );
1798                Ok(output)
1799            }
1800            Some(output) => Ok(output),
1801            None => Ok(input),
1802        }
1803    }
1804
1805    fn output(
1806        &mut self,
1807        requested_output: Option<Varnode>,
1808        size: usize,
1809    ) -> Result<Varnode, PcodeLowerError> {
1810        match requested_output {
1811            Some(output) => {
1812                Self::checked_size(output.size)?;
1813                Ok(output)
1814            }
1815            None => self.allocate_unique(size),
1816        }
1817    }
1818
1819    fn allocate_unique(&mut self, size: usize) -> Result<Varnode, PcodeLowerError> {
1820        Self::checked_size(size)?;
1821        let offset = self.next_unique;
1822        self.next_unique = self
1823            .next_unique
1824            .checked_add(size as u64)
1825            .ok_or(PcodeLowerError::UniqueSpaceOverflow)?;
1826        Ok(Varnode::new(self.context.unique_space(), offset, size))
1827    }
1828
1829    fn label_id(&self, label: &str) -> Result<LabelId, PcodeLowerError> {
1830        self.plan
1831            .label_id(label)
1832            .ok_or_else(|| PcodeLowerError::UnknownLabel(Box::from(label)))
1833    }
1834
1835    fn checked_size(size: usize) -> Result<(), PcodeLowerError> {
1836        if size == 0 {
1837            Err(PcodeLowerError::ZeroSize)
1838        } else {
1839            Ok(())
1840        }
1841    }
1842
1843    fn space_id(space: SpaceId) -> Varnode {
1844        Varnode::constant(usize::from(space) as u64, 4)
1845    }
1846}
1847
1848/// The read-only pass which produces a [`PcodePlan`].
1849///
1850/// It is generic over the statement span so a producer can run the same width
1851/// inference over its own *source* bodies, before any instruction is decoded,
1852/// rather than keeping a second implementation that can drift from this one.
1853struct Planner<'a, C: PcodeLoweringContext + ?Sized> {
1854    context: &'a C,
1855    plan: PcodePlan,
1856}
1857
1858impl<'a, C: PcodeLoweringContext + ?Sized> Planner<'a, C> {
1859    fn plan(&mut self, ast: &PcodeAst) {
1860        self.plan.local_sizes = SizeInference::run(self.context, &ast.statements);
1861        self.plan_statements(ast);
1862    }
1863
1864    /// Collects the facts that do not depend on local widths: the labels and
1865    /// the addresses this instruction reaches directly.
1866    fn plan_statements(&mut self, ast: &PcodeAst) {
1867        for statement in &ast.statements {
1868            match &statement.ty {
1869                AstNode::Label(label) => {
1870                    self.plan.declare_label(label);
1871                }
1872                AstNode::Branch { target } | AstNode::ConditionalBranch { target, .. } => {
1873                    // A target this pass cannot resolve is left out; emission
1874                    // reports it with the error it would have reported before.
1875                    if let LabelOrNode::Expr(expr) = target
1876                        && let Some(address) = self.direct_address(expr)
1877                    {
1878                        self.plan.declare_direct_branch(address);
1879                    }
1880                }
1881                AstNode::Call { target } => {
1882                    if let LabelOrNode::Expr(expr) = target
1883                        && let Some(address) = self.direct_address(expr)
1884                    {
1885                        self.plan.declare_direct_call(address);
1886                    }
1887                }
1888                _ => {}
1889            }
1890        }
1891
1892        // Only labels may follow the last operation-producing statement, so
1893        // the trailing run of labels is exactly the terminal one.
1894        for statement in ast.statements.iter().rev() {
1895            let AstNode::Label(label) = &statement.ty else {
1896                break;
1897            };
1898            if let Some(id) = self.plan.label_id(label) {
1899                self.plan.terminal[id.index()] = true;
1900            }
1901        }
1902    }
1903
1904    fn direct_address<S>(&self, target: &Expression<S>) -> Option<u64> {
1905        match target.ty {
1906            ExpressionTy::SizedInt { value, .. } => Some(value),
1907            _ => None,
1908        }
1909    }
1910}
1911
1912/// The width-inference pass, shared by specification-compile time and by
1913/// per-instruction planning.
1914///
1915/// It is generic over the statement span so a producer can run it over its own
1916/// *source* bodies, and over the width domain so those bodies can be resolved
1917/// before the values a decode substitutes into them are known.
1918struct SizeInference<'a, C: PcodeLoweringContext + ?Sized, W: Width> {
1919    context: &'a C,
1920    sizes: HashMap<LocalVarId, W>,
1921}
1922
1923impl<'a, C: PcodeLoweringContext + ?Sized, W: Width> SizeInference<'a, C, W> {
1924    fn run<S>(context: &'a C, statements: &[Ast<S>]) -> HashMap<LocalVarId, W> {
1925        let mut inference = Self {
1926            context,
1927            sizes: HashMap::new(),
1928        };
1929        inference.infer(statements);
1930        inference.sizes
1931    }
1932
1933    /// Resolve local widths from their uses. A forward-only allocator cannot
1934    /// size, for example, `v = 255 & 31` until a later `word << v` reveals
1935    /// that `v` is a word-wide shift count.
1936    fn infer<S>(&mut self, statements: &[Ast<S>]) {
1937        // Each pass can discover at least one previously unknown local. The
1938        // extra pass propagates that discovery through a chain of locals.
1939        for _ in 0..=statements.len() {
1940            let before = self.sizes.len();
1941            for statement in statements {
1942                self.constrain_statement(&statement.ty);
1943            }
1944            if self.sizes.len() == before {
1945                break;
1946            }
1947        }
1948    }
1949
1950    fn constrain_statement<S>(&mut self, statement: &AstNode<S>) {
1951        match statement {
1952            AstNode::Assignment { lhs, size, rhs } => {
1953                // Comparisons normally infer a one-byte result. A different
1954                // explicit expression size must still reach lowering so it is
1955                // rejected as an invalid raw boolean output.
1956                let comparison_size =
1957                    matches!(&rhs.ty, ExpressionTy::Binop(binop) if binop.op.is_comparison())
1958                        .then_some(rhs.size)
1959                        .flatten()
1960                        .filter(|&size| size != 1)
1961                        .map(W::fixed);
1962                let expected = (*size)
1963                    .map(W::fixed)
1964                    .or_else(|| self.storage_size(lhs))
1965                    .or(comparison_size);
1966                let inferred = self.constrain_expr(rhs, expected);
1967                if let Ident::Named(id) = lhs
1968                    && let Some(size) = expected.or(inferred)
1969                {
1970                    self.sizes.entry(*id).or_insert(size);
1971                }
1972            }
1973            AstNode::LoadAssignment { lhs, rhs, .. } => {
1974                let space = self.load_space(lhs).ok();
1975                if let Some(space) = space {
1976                    self.constrain_expr(&lhs.ptr, self.context.address_size(space).map(W::fixed));
1977                }
1978                self.constrain_expr(rhs, lhs.size.map(W::fixed));
1979            }
1980            AstNode::RangeAssignment { lhs, rhs, .. } => {
1981                if let Ok((_, bits)) = range_params(lhs) {
1982                    self.constrain_expr(rhs, Some(W::fixed(bits.div_ceil(8))));
1983                }
1984            }
1985            AstNode::ConditionalBranch { condition, .. } => {
1986                self.constrain_expr(condition, Some(W::fixed(1)));
1987            }
1988            AstNode::BranchIndirect { target }
1989            | AstNode::CallIndirect { target }
1990            | AstNode::Return { target } => {
1991                self.constrain_expr(
1992                    target,
1993                    self.context
1994                        .address_size(self.context.default_space())
1995                        .map(W::fixed),
1996                );
1997            }
1998            AstNode::Expression(expr) => {
1999                self.constrain_expr(expr, None);
2000            }
2001            AstNode::Build(_)
2002            | AstNode::DelaySlot(_)
2003            | AstNode::DeferredBuild(_)
2004            | AstNode::Label(_)
2005            | AstNode::Branch { .. }
2006            | AstNode::Call { .. }
2007            | AstNode::Export(_) => {}
2008        }
2009    }
2010
2011    /// Applies an optional consumer width to `expr` and returns any concrete
2012    /// output width known after that constraint. Integer literals intentionally
2013    /// do not establish a width on their own.
2014    fn constrain_expr<S>(&mut self, expr: &Expression<S>, expected: Option<W>) -> Option<W> {
2015        match &expr.ty {
2016            ExpressionTy::SizedInt { .. } => expected,
2017            ExpressionTy::Ident(Ident::Named(id)) => {
2018                if let Some(&size) = self.sizes.get(id) {
2019                    Some(size)
2020                } else if let Some(size) = expected {
2021                    self.sizes.insert(*id, size);
2022                    Some(size)
2023                } else {
2024                    None
2025                }
2026            }
2027            ExpressionTy::Ident(ident) => self.storage_size(ident),
2028            ExpressionTy::Load(load) => {
2029                if let Ok(space) = self.load_space(load) {
2030                    self.constrain_expr(&load.ptr, self.context.address_size(space).map(W::fixed));
2031                }
2032                load.size.map(W::fixed).or(expected)
2033            }
2034            ExpressionTy::SubPieceLsb { src, count } => {
2035                self.constrain_expr(src, None);
2036                Some(W::fixed(*count))
2037            }
2038            ExpressionTy::SubPieceMsb { src, count } => {
2039                // Truncation is arithmetic on a width, so a still-symbolic
2040                // operand width yields no constraint rather than a wrong one.
2041                let size = expected
2042                    .or_else(|| Some(W::fixed(self.expr_size(src)?.size()?.checked_sub(*count)?)));
2043                let source = size
2044                    .and_then(|size| size.size())
2045                    .map(|size| W::fixed(size + count));
2046                self.constrain_expr(src, source);
2047                size
2048            }
2049            ExpressionTy::Range(range) => {
2050                let size = match range.size {
2051                    RangeParam::Literal(bits) => Some(W::fixed(bits.div_ceil(8))),
2052                    RangeParam::MacroArg(_) => expected,
2053                };
2054                self.constrain_expr(&range.value, None);
2055                size
2056            }
2057            ExpressionTy::FunctionCall { builtin, args } => {
2058                let boolean = matches!(
2059                    builtin,
2060                    Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan
2061                );
2062                let size = boolean.then(|| W::fixed(1)).or(expected);
2063                let input_size = args.iter().find_map(|arg| self.constrain_expr(arg, None));
2064                if let Some(input_size) = input_size {
2065                    for arg in args {
2066                        self.constrain_expr(arg, Some(input_size));
2067                    }
2068                }
2069                size
2070            }
2071            ExpressionTy::PcodeOp { args, .. } => {
2072                for arg in args {
2073                    self.constrain_expr(arg, None);
2074                }
2075                expected
2076            }
2077            ExpressionTy::Unop(unop) => match unop.op {
2078                UnaryOperator::LogicalNot => {
2079                    let size = self.constrain_expr(&unop.e, None);
2080                    self.constrain_expr(&unop.e, size);
2081                    Some(W::fixed(1))
2082                }
2083                UnaryOperator::AddressOf(size) => size.map(W::fixed).or_else(|| {
2084                    self.storage_from_expr_size(&unop.e)
2085                        .and_then(|storage| self.context.address_size(storage.space))
2086                        .map(W::fixed)
2087                }),
2088                _ => {
2089                    let size = expected.or_else(|| self.constrain_expr(&unop.e, None));
2090                    self.constrain_expr(&unop.e, size);
2091                    size
2092                }
2093            },
2094            ExpressionTy::Binop(binop) => {
2095                let boolean = binop.op.is_comparison()
2096                    || matches!(
2097                        binop.op,
2098                        BinaryOperator::LogicalXor
2099                            | BinaryOperator::LogicalAnd
2100                            | BinaryOperator::LogicalOr
2101                    );
2102                let input_size = self
2103                    .constrain_expr(&binop.lhs, None)
2104                    .or_else(|| self.constrain_expr(&binop.rhs, None));
2105                let input_size = if boolean {
2106                    input_size
2107                } else {
2108                    expected.or(input_size)
2109                };
2110                self.constrain_expr(&binop.lhs, input_size);
2111                self.constrain_expr(&binop.rhs, input_size);
2112                if boolean {
2113                    Some(W::fixed(1))
2114                } else {
2115                    input_size
2116                }
2117            }
2118            ExpressionTy::MacroCall { .. } | ExpressionTy::DeferredCall { .. } => expected,
2119        }
2120    }
2121}
2122
2123impl<'a, C: PcodeLoweringContext + ?Sized, W: Width> Sizing<W> for SizeInference<'a, C, W> {
2124    type Ctx = C;
2125
2126    fn context(&self) -> &C {
2127        self.context
2128    }
2129
2130    fn local_size(&self, id: &LocalVarId) -> Option<W> {
2131        self.sizes.get(id).copied()
2132    }
2133}
2134
2135impl<C: PcodeLoweringContext + ?Sized, S: PcodeSink + ?Sized> Sizing<usize>
2136    for Lowerer<'_, '_, '_, C, S>
2137{
2138    type Ctx = C;
2139
2140    fn context(&self) -> &C {
2141        self.context
2142    }
2143
2144    fn local_size(&self, id: &LocalVarId) -> Option<usize> {
2145        self.plan
2146            .local_sizes
2147            .get(id)
2148            .copied()
2149            .or_else(|| self.locals.get(id).map(|varnode| varnode.size))
2150    }
2151}
2152
2153/// Width and space queries shared by planning and emission. Both phases must
2154/// answer them identically, so they have one implementation parameterized by
2155/// how each phase knows a local's width.
2156trait Sizing<W: Width> {
2157    type Ctx: PcodeLoweringContext + ?Sized;
2158
2159    fn context(&self) -> &Self::Ctx;
2160
2161    /// The width of a local variable, if it is known in this phase.
2162    fn local_size(&self, id: &LocalVarId) -> Option<W>;
2163
2164    fn expr_size<S>(&self, expr: &Expression<S>) -> Option<W> {
2165        expr.size.map(W::fixed).or(match &expr.ty {
2166            ExpressionTy::SizedInt { size, .. } => size.map(W::fixed),
2167            ExpressionTy::Ident(ident) => self.storage_size(ident),
2168            ExpressionTy::Load(load) => load.size.map(W::fixed),
2169            ExpressionTy::SubPieceLsb { count, .. } => Some(W::fixed(*count)),
2170            ExpressionTy::SubPieceMsb { src, count } => {
2171                Some(W::fixed(self.expr_size(src)?.size()?.checked_sub(*count)?))
2172            }
2173            ExpressionTy::Range(Range {
2174                size: RangeParam::Literal(bits),
2175                ..
2176            }) => Some(W::fixed(bits.div_ceil(8))),
2177            ExpressionTy::Range(Range {
2178                size: RangeParam::MacroArg(_),
2179                ..
2180            }) => None,
2181            ExpressionTy::FunctionCall {
2182                builtin: Builtin::Carry | Builtin::Scarry | Builtin::Sborrow | Builtin::Nan,
2183                ..
2184            } => Some(W::fixed(1)),
2185            ExpressionTy::FunctionCall { .. } => None,
2186            ExpressionTy::Unop(unop) if unop.op == UnaryOperator::LogicalNot => Some(W::fixed(1)),
2187            ExpressionTy::Unop(unop) => self.expr_size(&unop.e),
2188            ExpressionTy::Binop(binop) if binop.op.is_comparison() => Some(W::fixed(1)),
2189            ExpressionTy::Binop(binop) => self
2190                .expr_size(&binop.lhs)
2191                .or_else(|| self.expr_size(&binop.rhs)),
2192            ExpressionTy::PcodeOp { .. }
2193            | ExpressionTy::MacroCall { .. }
2194            | ExpressionTy::DeferredCall { .. } => None,
2195        })
2196    }
2197
2198    fn storage_size(&self, ident: &Ident) -> Option<W> {
2199        match ident {
2200            Ident::Register(id) => self
2201                .context()
2202                .register_varnode(*id)
2203                .map(|varnode| W::fixed(varnode.size)),
2204            Ident::BitRange(id) => self
2205                .context()
2206                .bitrange_info(*id)
2207                .map(|info| W::fixed(info.size.div_ceil(8))),
2208            Ident::Named(id) => self.local_size(id),
2209            // A table operand's width is only known once a decode substitutes
2210            // its export. A symbolic domain names it instead of losing it.
2211            Ident::Table(id) => W::operand(*id),
2212            Ident::Field(_) | Ident::Global(_) => None,
2213        }
2214    }
2215
2216    fn storage_from_expr_size<S>(&self, expr: &Expression<S>) -> Option<Varnode> {
2217        match &expr.ty {
2218            ExpressionTy::Ident(Ident::Register(id)) => self.context().register_varnode(*id),
2219            ExpressionTy::Ident(Ident::BitRange(id)) => {
2220                self.context().bitrange_info(*id).map(|info| info.storage)
2221            }
2222            _ => None,
2223        }
2224    }
2225
2226    fn load_space<S>(&self, load: &Load<S>) -> Result<SpaceId, PcodeLowerError> {
2227        match &load.space {
2228            None => Ok(self.context().default_space()),
2229            Some(crate::PcodeSpaceRef::Resolved(space)) => Ok(*space),
2230            Some(crate::PcodeSpaceRef::Deferred(_)) => Err(PcodeLowerError::UnresolvedSpace),
2231        }
2232    }
2233}
2234
2235/// Reads a bit range's literal start and width.
2236///
2237/// A macro-argument range must have been substituted during expansion.
2238fn range_params<S>(range: &Range<S>) -> Result<(usize, usize), PcodeLowerError> {
2239    let RangeParam::Literal(start) = range.start else {
2240        return Err(PcodeLowerError::UnresolvedRangeParameter);
2241    };
2242    let RangeParam::Literal(size) = range.size else {
2243        return Err(PcodeLowerError::UnresolvedRangeParameter);
2244    };
2245    Ok((start, size))
2246}
2247
2248fn binary_opcode(op: BinaryOperator) -> (Opcode, bool) {
2249    use BinaryOperator::*;
2250    match op {
2251        Mul => (Opcode::IntMult, false),
2252        Div => (Opcode::IntDiv, false),
2253        SignedDiv => (Opcode::IntSDiv, false),
2254        Mod => (Opcode::IntRem, false),
2255        SignedMod => (Opcode::IntSRem, false),
2256        FloatDiv => (Opcode::FloatDiv, false),
2257        FloatMul => (Opcode::FloatMult, false),
2258        Add => (Opcode::IntAdd, false),
2259        Sub => (Opcode::IntSub, false),
2260        FloatAdd => (Opcode::FloatAdd, false),
2261        FloatSub => (Opcode::FloatSub, false),
2262        LeftShift => (Opcode::IntLeft, false),
2263        RightShift => (Opcode::IntRight, false),
2264        SignedRightShift => (Opcode::IntSRight, false),
2265        SignedLessThan => (Opcode::IntSLess, false),
2266        SignedGreaterThan => (Opcode::IntSLess, true),
2267        SignedLessEqual => (Opcode::IntSLessEqual, false),
2268        SignedGreaterEqual => (Opcode::IntSLessEqual, true),
2269        LessEqual => (Opcode::IntLessEqual, false),
2270        GreaterEqual => (Opcode::IntLessEqual, true),
2271        LessThan => (Opcode::IntLess, false),
2272        GreaterThan => (Opcode::IntLess, true),
2273        FloatLessEqual => (Opcode::FloatLessEqual, false),
2274        FloatGreaterEqual => (Opcode::FloatLessEqual, true),
2275        FloatLessThan => (Opcode::FloatLess, false),
2276        FloatGreaterThan => (Opcode::FloatLess, true),
2277        Equal => (Opcode::IntEqual, false),
2278        NotEqual => (Opcode::IntNotEqual, false),
2279        FloatEqual => (Opcode::FloatEqual, false),
2280        FloatNotEqual => (Opcode::FloatNotEqual, false),
2281        LogicalXor => (Opcode::BoolXor, false),
2282        LogicalAnd => (Opcode::BoolAnd, false),
2283        LogicalOr => (Opcode::BoolOr, false),
2284        BitwiseXor => (Opcode::IntXor, false),
2285        BitwiseOr => (Opcode::IntOr, false),
2286        BitwiseAnd => (Opcode::IntAnd, false),
2287    }
2288}
2289
2290#[cfg(test)]
2291mod tests {
2292    use super::{
2293        BitRangeInfo, InstructionPcode, LabelId, LocalSizes, Opcode, PcodeLowerError,
2294        PcodeLoweringContext, PcodeOp, PcodeSink, Varnode, emit_instruction, lower_instruction,
2295        plan_instruction,
2296    };
2297    use crate::{
2298        Ast, AstNode, BinaryOperator, Binop, Expression, ExpressionTy, Ident, LabelOrNode, Load,
2299        LocalVarId, PCodeOpId, PcodeAst, PcodeSpaceRef, Range, RangeParam, RegisterId, SPACE_CONST,
2300        SpaceId,
2301    };
2302    use std::collections::HashMap;
2303
2304    struct Context;
2305
2306    impl PcodeLoweringContext for Context {
2307        fn default_space(&self) -> SpaceId {
2308            SpaceId::new(1)
2309        }
2310
2311        fn unique_space(&self) -> SpaceId {
2312            SpaceId::new(2)
2313        }
2314
2315        fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2316            Some(Varnode::new(SpaceId::new(3), usize::from(id) as u64 * 4, 4))
2317        }
2318
2319        fn bitrange_info(&self, _id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2320            None
2321        }
2322
2323        fn address_size(&self, space: SpaceId) -> Option<usize> {
2324            Some(if space == SpaceId::new(4) { 4 } else { 8 })
2325        }
2326    }
2327
2328    fn int(value: u64, size: usize) -> Expression {
2329        Expression {
2330            ty: ExpressionTy::SizedInt {
2331                value,
2332                size: Some(size),
2333            },
2334            size: Some(size),
2335            span: (),
2336        }
2337    }
2338
2339    fn ident(id: RegisterId) -> Expression {
2340        Expression {
2341            ty: ExpressionTy::Ident(Ident::Register(id)),
2342            size: Some(4),
2343            span: (),
2344        }
2345    }
2346
2347    fn ast(nodes: Vec<AstNode>) -> PcodeAst {
2348        PcodeAst {
2349            statements: nodes.into_iter().map(Ast::from).collect(),
2350        }
2351    }
2352
2353    #[test]
2354    fn varnodes_distinguish_constants_from_storage() {
2355        let constant = Varnode::constant(0x1234, 4);
2356        let storage = Varnode::new(SpaceId::new(2), 0x1234, 4);
2357        assert_eq!(constant.space, SPACE_CONST);
2358        assert!(constant.is_constant());
2359        assert!(!storage.is_constant());
2360    }
2361
2362    #[test]
2363    fn opcode_inventory_identifies_analysis_only_operations() {
2364        assert_eq!(Opcode::ALL.len(), 72);
2365        assert_eq!(Opcode::Copy.ghidra_id(), 1);
2366        assert_eq!(Opcode::FloatLessEqual.ghidra_id(), 44);
2367        assert_eq!(Opcode::FloatNan.ghidra_id(), 46);
2368        assert_eq!(Opcode::LzCount.ghidra_id(), 73);
2369        assert!(Opcode::ALL.contains(&Opcode::Load));
2370        assert!(Opcode::ALL.contains(&Opcode::LzCount));
2371        assert!(Opcode::Load.is_raw_instruction_op());
2372        for opcode in [
2373            Opcode::MultiEqual,
2374            Opcode::Indirect,
2375            Opcode::Cast,
2376            Opcode::PtrAdd,
2377            Opcode::PtrSub,
2378            Opcode::SegmentOp,
2379            Opcode::Insert,
2380            Opcode::Extract,
2381        ] {
2382            assert!(!opcode.is_raw_instruction_op());
2383        }
2384    }
2385
2386    #[test]
2387    fn flat_operations_preserve_input_order_and_round_trip() {
2388        let output = Varnode::new(SpaceId::new(1), 0, 4);
2389        let instruction = InstructionPcode {
2390            ops: vec![PcodeOp::new(
2391                Opcode::IntAdd,
2392                Some(output),
2393                vec![output, Varnode::constant(1, 4)],
2394            )],
2395        };
2396        assert!(!instruction.is_empty());
2397        let bytes =
2398            bincode::serde::encode_to_vec(&instruction, bincode::config::standard()).unwrap();
2399        let (decoded, _): (InstructionPcode, _) =
2400            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
2401        assert_eq!(decoded, instruction);
2402        assert!(InstructionPcode::new().is_empty());
2403    }
2404
2405    #[test]
2406    fn lower_assignment_emits_direct_arithmetic_output() {
2407        let rhs = Expression {
2408            ty: ExpressionTy::Binop(Binop {
2409                op: BinaryOperator::Add,
2410                lhs: Box::new(ident(RegisterId::new(1))),
2411                rhs: Box::new(int(1, 4)),
2412            }),
2413            size: Some(4),
2414            span: (),
2415        };
2416        let output = Varnode::new(SpaceId::new(3), 0, 4);
2417        let input = Varnode::new(SpaceId::new(3), 4, 4);
2418        let pcode = lower_instruction(
2419            &ast(vec![AstNode::Assignment {
2420                lhs: Ident::Register(RegisterId::new(0)),
2421                size: None,
2422                rhs,
2423            }]),
2424            &Context,
2425        )
2426        .unwrap();
2427        assert_eq!(
2428            pcode.ops,
2429            vec![PcodeOp::new(
2430                Opcode::IntAdd,
2431                Some(output),
2432                vec![input, Varnode::constant(1, 4)],
2433            )]
2434        );
2435    }
2436
2437    #[test]
2438    fn lower_load_store_and_userop_use_ghidra_operand_order() {
2439        let ram = SpaceId::new(4);
2440        let pointer = ident(RegisterId::new(1));
2441        let load = Load {
2442            space: Some(PcodeSpaceRef::Resolved(ram)),
2443            size: Some(4),
2444            ptr: Box::new(pointer.clone()),
2445        };
2446        let pcode = lower_instruction(
2447            &ast(vec![
2448                AstNode::Assignment {
2449                    lhs: Ident::Register(RegisterId::new(0)),
2450                    size: None,
2451                    rhs: Expression {
2452                        ty: ExpressionTy::Load(load.clone()),
2453                        size: Some(4),
2454                        span: (),
2455                    },
2456                },
2457                AstNode::LoadAssignment {
2458                    lhs: load,
2459                    size: None,
2460                    rhs: int(9, 4),
2461                },
2462                AstNode::Expression(Expression {
2463                    ty: ExpressionTy::PcodeOp {
2464                        id: PCodeOpId::new(7),
2465                        args: vec![int(2, 4)],
2466                    },
2467                    size: None,
2468                    span: (),
2469                }),
2470            ]),
2471            &Context,
2472        )
2473        .unwrap();
2474        let r0 = Varnode::new(SpaceId::new(3), 0, 4);
2475        let r1 = Varnode::new(SpaceId::new(3), 4, 4);
2476        assert_eq!(
2477            pcode.ops,
2478            vec![
2479                PcodeOp::new(Opcode::Load, Some(r0), vec![Varnode::constant(4, 4), r1],),
2480                PcodeOp::new(
2481                    Opcode::Store,
2482                    None,
2483                    vec![Varnode::constant(4, 4), r1, Varnode::constant(9, 4)],
2484                ),
2485                PcodeOp::new(
2486                    Opcode::CallOther,
2487                    None,
2488                    vec![Varnode::constant(7, 4), Varnode::constant(2, 4)],
2489                ),
2490            ]
2491        );
2492    }
2493
2494    /// Records the events of a streaming lift, keeping local branches symbolic.
2495    #[derive(Default)]
2496    struct Trace {
2497        events: Vec<String>,
2498    }
2499
2500    impl PcodeSink for Trace {
2501        fn op(&mut self, opcode: Opcode, output: Option<Varnode>, inputs: &[Varnode]) {
2502            self.events
2503                .push(format!("{opcode:?} {output:?} {inputs:?}"));
2504        }
2505
2506        fn label(&mut self, label: LabelId) {
2507            self.events.push(format!("label {}", label.index()));
2508        }
2509
2510        fn branch_label(&mut self, opcode: Opcode, label: LabelId, condition: Option<Varnode>) {
2511            self.events.push(format!(
2512                "{opcode:?} -> label {} {condition:?}",
2513                label.index()
2514            ));
2515        }
2516    }
2517
2518    fn branch_statements() -> Vec<AstNode> {
2519        vec![
2520            AstNode::ConditionalBranch {
2521                condition: ident(RegisterId::new(1)),
2522                target: LabelOrNode::Label("skip".into()),
2523            },
2524            AstNode::Call {
2525                target: LabelOrNode::Expr(int(0x2000, 8)),
2526            },
2527            AstNode::Branch {
2528                target: LabelOrNode::Expr(int(0x1000, 8)),
2529            },
2530            AstNode::Label("skip".into()),
2531            AstNode::Assignment {
2532                lhs: Ident::Register(RegisterId::new(1)),
2533                size: None,
2534                rhs: ident(RegisterId::new(2)),
2535            },
2536        ]
2537    }
2538
2539    #[test]
2540    fn local_widths_can_be_inferred_from_a_body_before_planning() {
2541        let statements = vec![
2542            AstNode::Assignment {
2543                lhs: Ident::Named(LocalVarId(0)),
2544                size: None,
2545                rhs: ident(RegisterId::new(1)),
2546            },
2547            AstNode::Assignment {
2548                lhs: Ident::Register(RegisterId::new(2)),
2549                size: None,
2550                rhs: ExpressionTy::Ident(Ident::Named(LocalVarId(0))).with_size(4),
2551            },
2552        ];
2553        let ast = ast(statements.clone());
2554
2555        // The same widths whether resolved from the body up front or by the
2556        // per-instruction planner.
2557        let sizes = super::infer_local_sizes(&ast.statements, &Context);
2558        assert_eq!(sizes.get(&LocalVarId(0)), Some(&4));
2559
2560        let planned = super::plan_instruction_with(&ast, &Context, sizes).unwrap();
2561        let inferred = plan_instruction(&ast, &Context).unwrap();
2562        assert_eq!(planned.labels(), inferred.labels());
2563
2564        // And supplied widths reach emission: the local becomes a 4-byte
2565        // unique, not an unsized-local error.
2566        let pcode = lower_instruction(&ast, &Context).unwrap();
2567        assert_eq!(
2568            pcode.ops[0].output,
2569            Some(Varnode::new(SpaceId::new(2), 0, 4))
2570        );
2571    }
2572
2573    /// A width taken from a table operand must be *named*, not dropped: a
2574    /// pass that dropped it would size the local from the later statement and
2575    /// disagree with the per-instruction planner, which sees the substituted
2576    /// value first.
2577    #[test]
2578    fn symbolic_inference_names_an_operand_width_instead_of_losing_it() {
2579        let table = crate::TableId::new(7);
2580        let statements = ast(vec![
2581            AstNode::Assignment {
2582                lhs: Ident::Named(LocalVarId(0)),
2583                size: None,
2584                rhs: Expression {
2585                    ty: ExpressionTy::Ident(Ident::Table(table)),
2586                    size: None,
2587                    span: (),
2588                },
2589            },
2590            AstNode::Assignment {
2591                lhs: Ident::Register(RegisterId::new(1)),
2592                size: None,
2593                rhs: Expression {
2594                    ty: ExpressionTy::Binop(Binop {
2595                        op: BinaryOperator::Add,
2596                        lhs: Box::new(
2597                            ExpressionTy::Ident(Ident::Named(LocalVarId(0))).with_size(4),
2598                        ),
2599                        rhs: Box::new(ident(RegisterId::new(2))),
2600                    }),
2601                    size: None,
2602                    span: (),
2603                },
2604            },
2605        ])
2606        .statements;
2607
2608        let symbolic: HashMap<LocalVarId, super::SymbolicWidth> =
2609            super::infer_local_sizes(&statements, &Context);
2610        assert_eq!(
2611            symbolic.get(&LocalVarId(0)),
2612            Some(&super::SymbolicWidth::SameAs(table))
2613        );
2614
2615        // The concrete domain cannot name it, so it falls through to the
2616        // later use — which is exactly the disagreement the symbolic domain
2617        // exists to prevent.
2618        let concrete: LocalSizes = super::infer_local_sizes(&statements, &Context);
2619        assert_eq!(concrete.get(&LocalVarId(0)), Some(&4));
2620    }
2621
2622    #[test]
2623    fn plan_reports_labels_and_out_of_instruction_targets() {
2624        let plan = plan_instruction(&ast(branch_statements()), &Context).unwrap();
2625        assert_eq!(plan.labels(), &[Box::<str>::from("skip")]);
2626        assert_eq!(plan.direct_branches(), &[0x1000]);
2627        assert_eq!(plan.direct_calls(), &[0x2000]);
2628    }
2629
2630    #[test]
2631    fn streaming_emission_keeps_local_branch_targets_symbolic() {
2632        let ast = ast(branch_statements());
2633        let plan = plan_instruction(&ast, &Context).unwrap();
2634        let mut trace = Trace::default();
2635        emit_instruction(&ast, &Context, &plan, &mut trace).unwrap();
2636
2637        assert_eq!(
2638            trace.events[0],
2639            "CBranch -> label 0 Some(Varnode { space: SpaceId(3), offset: 4, size: 4 })"
2640        );
2641        assert_eq!(trace.events[3], "label 0");
2642        assert_eq!(trace.events.len(), 5);
2643
2644        // The collecting API resolves the same branch into a relative target.
2645        let pcode = lower_instruction(&ast, &Context).unwrap();
2646        assert_eq!(pcode.ops[0].opcode, Opcode::CBranch);
2647        assert_eq!(pcode.ops[0].inputs[0], Varnode::constant(3, 8));
2648    }
2649
2650    #[test]
2651    fn plan_omits_unresolvable_direct_targets() {
2652        let plan = plan_instruction(
2653            &ast(vec![AstNode::Branch {
2654                target: LabelOrNode::Expr(ident(RegisterId::new(1))),
2655            }]),
2656            &Context,
2657        )
2658        .unwrap();
2659        assert!(plan.direct_branches().is_empty());
2660        assert_eq!(
2661            lower_instruction(
2662                &ast(vec![AstNode::Branch {
2663                    target: LabelOrNode::Expr(ident(RegisterId::new(1))),
2664                }]),
2665                &Context,
2666            )
2667            .unwrap_err(),
2668            PcodeLowerError::InvalidDirectTarget
2669        );
2670    }
2671
2672    #[test]
2673    fn branching_to_an_undefined_label_is_rejected() {
2674        let error = lower_instruction(
2675            &ast(vec![AstNode::Branch {
2676                target: LabelOrNode::Label("missing".into()),
2677            }]),
2678            &Context,
2679        )
2680        .unwrap_err();
2681        assert_eq!(error, PcodeLowerError::UnknownLabel("missing".into()));
2682    }
2683
2684    #[test]
2685    fn duplicate_labels_are_rejected() {
2686        let error = lower_instruction(
2687            &ast(vec![
2688                AstNode::Label("here".into()),
2689                AstNode::Label("here".into()),
2690            ]),
2691            &Context,
2692        )
2693        .unwrap_err();
2694        assert_eq!(error, PcodeLowerError::DuplicateLabel("here".into()));
2695    }
2696
2697    #[test]
2698    fn lower_labels_are_pcode_relative_and_comparisons_reverse_greater_than() {
2699        let comparison = Expression {
2700            ty: ExpressionTy::Binop(Binop {
2701                op: BinaryOperator::GreaterThan,
2702                lhs: Box::new(ident(RegisterId::new(1))),
2703                rhs: Box::new(ident(RegisterId::new(2))),
2704            }),
2705            size: Some(1),
2706            span: (),
2707        };
2708        let pcode = lower_instruction(
2709            &ast(vec![
2710                AstNode::Label("loop".into()),
2711                AstNode::Assignment {
2712                    lhs: Ident::Named(LocalVarId(0)),
2713                    size: None,
2714                    rhs: comparison,
2715                },
2716                AstNode::Branch {
2717                    target: LabelOrNode::Label("loop".into()),
2718                },
2719            ]),
2720            &Context,
2721        )
2722        .unwrap();
2723        assert_eq!(pcode.ops.len(), 2);
2724        assert_eq!(pcode.ops[0].opcode, Opcode::IntLess);
2725        assert_eq!(
2726            pcode.ops[0].inputs,
2727            vec![
2728                Varnode::new(SpaceId::new(3), 8, 4),
2729                Varnode::new(SpaceId::new(3), 4, 4),
2730            ]
2731        );
2732        assert_eq!(
2733            pcode.ops[0].output,
2734            Some(Varnode::new(SpaceId::new(2), 0, 1))
2735        );
2736        assert_eq!(pcode.ops[1].opcode, Opcode::Branch);
2737        assert_eq!(pcode.ops[1].inputs, vec![Varnode::constant(u64::MAX, 8)]);
2738    }
2739
2740    #[test]
2741    fn lower_named_bitranges_as_raw_read_modify_write() {
2742        struct BitRangeContext;
2743        impl PcodeLoweringContext for BitRangeContext {
2744            fn default_space(&self) -> SpaceId {
2745                SpaceId::new(1)
2746            }
2747            fn unique_space(&self) -> SpaceId {
2748                SpaceId::new(2)
2749            }
2750            fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2751                Some(Varnode::new(SpaceId::new(3), usize::from(id) as u64 * 4, 4))
2752            }
2753            fn bitrange_info(&self, id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2754                (id == crate::BitRangeFieldId::new(0)).then_some(BitRangeInfo {
2755                    storage: Varnode::new(SpaceId::new(3), 0, 4),
2756                    start: 3,
2757                    size: 5,
2758                })
2759            }
2760            fn address_size(&self, _space: SpaceId) -> Option<usize> {
2761                Some(4)
2762            }
2763        }
2764
2765        let read = lower_instruction(
2766            &ast(vec![AstNode::Assignment {
2767                lhs: Ident::Named(LocalVarId(0)),
2768                size: None,
2769                rhs: ExpressionTy::Ident(Ident::BitRange(crate::BitRangeFieldId::new(0)))
2770                    .with_size(1),
2771            }]),
2772            &BitRangeContext,
2773        )
2774        .unwrap();
2775        assert_eq!(
2776            read.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
2777            vec![Opcode::IntRight, Opcode::IntAnd, Opcode::SubPiece]
2778        );
2779        assert_eq!(read.ops[0].inputs[1], Varnode::constant(3, 4));
2780        assert_eq!(read.ops[1].inputs[1], Varnode::constant(0x1f, 4));
2781
2782        let write = lower_instruction(
2783            &ast(vec![AstNode::Assignment {
2784                lhs: Ident::BitRange(crate::BitRangeFieldId::new(0)),
2785                size: None,
2786                rhs: int(0xff, 1),
2787            }]),
2788            &BitRangeContext,
2789        )
2790        .unwrap();
2791        assert_eq!(
2792            write.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
2793            vec![
2794                Opcode::IntZext,
2795                Opcode::IntAnd,
2796                Opcode::IntLeft,
2797                Opcode::IntAnd,
2798                Opcode::IntOr,
2799            ]
2800        );
2801        assert_eq!(
2802            write.ops.last().unwrap().output.unwrap().space,
2803            SpaceId::new(3)
2804        );
2805        assert_eq!(write.ops.last().unwrap().output.unwrap().offset, 0);
2806    }
2807
2808    #[test]
2809    fn lower_binary_operations_coerce_narrow_operands() {
2810        struct MixedWidthContext;
2811        impl PcodeLoweringContext for MixedWidthContext {
2812            fn default_space(&self) -> SpaceId {
2813                SpaceId::new(1)
2814            }
2815            fn unique_space(&self) -> SpaceId {
2816                SpaceId::new(2)
2817            }
2818            fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2819                Some(Varnode::new(
2820                    SpaceId::new(3),
2821                    usize::from(id) as u64 * 2,
2822                    if id == RegisterId::new(0) { 2 } else { 1 },
2823                ))
2824            }
2825            fn bitrange_info(&self, _id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2826                None
2827            }
2828            fn address_size(&self, _space: SpaceId) -> Option<usize> {
2829                Some(8)
2830            }
2831        }
2832
2833        let pcode = lower_instruction(
2834            &ast(vec![AstNode::Assignment {
2835                lhs: Ident::Register(RegisterId::new(0)),
2836                size: None,
2837                rhs: ExpressionTy::Binop(Binop {
2838                    op: BinaryOperator::LeftShift,
2839                    lhs: Box::new(
2840                        ExpressionTy::Ident(Ident::Register(RegisterId::new(0))).with_size(2),
2841                    ),
2842                    rhs: Box::new(
2843                        ExpressionTy::Ident(Ident::Register(RegisterId::new(1))).with_size(1),
2844                    ),
2845                })
2846                .with_size(2),
2847            }]),
2848            &MixedWidthContext,
2849        )
2850        .unwrap();
2851        assert_eq!(pcode.ops[0].opcode, Opcode::IntZext);
2852        assert_eq!(pcode.ops[0].output.unwrap().size, 2);
2853        assert_eq!(pcode.ops[1].opcode, Opcode::IntLeft);
2854        assert_eq!(pcode.ops[1].inputs[1].size, 2);
2855    }
2856
2857    #[test]
2858    fn lower_store_coerces_its_value_to_the_declared_width() {
2859        let pcode = lower_instruction(
2860            &ast(vec![AstNode::LoadAssignment {
2861                lhs: Load {
2862                    space: None,
2863                    size: Some(1),
2864                    ptr: Box::new(int(0, 8)),
2865                },
2866                size: None,
2867                rhs: ident(RegisterId::new(0)),
2868            }]),
2869            &Context,
2870        )
2871        .unwrap();
2872        assert_eq!(pcode.ops[0].opcode, Opcode::SubPiece);
2873        assert_eq!(pcode.ops[0].output.unwrap().size, 1);
2874        assert_eq!(pcode.ops[1].opcode, Opcode::Store);
2875        assert_eq!(pcode.ops[1].inputs[2].size, 1);
2876    }
2877
2878    #[test]
2879    fn lower_range_assignment_supplies_its_rhs_width_to_a_userop() {
2880        let pcode = lower_instruction(
2881            &ast(vec![AstNode::RangeAssignment {
2882                lhs: Range {
2883                    value: Box::new(ident(RegisterId::new(0))),
2884                    start: RangeParam::Literal(0),
2885                    size: RangeParam::Literal(8),
2886                },
2887                size: None,
2888                rhs: Expression {
2889                    ty: ExpressionTy::PcodeOp {
2890                        id: PCodeOpId::new(7),
2891                        args: vec![],
2892                    },
2893                    size: None,
2894                    span: (),
2895                },
2896            }]),
2897            &Context,
2898        )
2899        .unwrap();
2900        let userop = pcode
2901            .ops
2902            .iter()
2903            .find(|op| op.opcode == Opcode::CallOther)
2904            .expect("range-assignment user-op was emitted");
2905        assert_eq!(userop.output.unwrap().size, 1);
2906    }
2907
2908    /// A context with one 16-byte register, register 9, next to the 4-byte ones.
2909    struct WideContext;
2910
2911    impl PcodeLoweringContext for WideContext {
2912        fn default_space(&self) -> SpaceId {
2913            SpaceId::new(1)
2914        }
2915
2916        fn unique_space(&self) -> SpaceId {
2917            SpaceId::new(2)
2918        }
2919
2920        fn register_varnode(&self, id: RegisterId) -> Option<Varnode> {
2921            if usize::from(id) == 9 {
2922                Some(Varnode::new(SpaceId::new(3), 0x100, 16))
2923            } else {
2924                Some(Varnode::new(SpaceId::new(3), usize::from(id) as u64 * 4, 4))
2925            }
2926        }
2927
2928        fn bitrange_info(&self, _id: crate::BitRangeFieldId) -> Option<BitRangeInfo> {
2929            None
2930        }
2931
2932        fn address_size(&self, _space: SpaceId) -> Option<usize> {
2933            Some(8)
2934        }
2935    }
2936
2937    fn wide_lane_assignment(start: usize, size: usize, rhs: Expression) -> AstNode {
2938        AstNode::RangeAssignment {
2939            lhs: Range {
2940                value: Box::new(Expression {
2941                    ty: ExpressionTy::Ident(Ident::Register(RegisterId::new(9))),
2942                    size: Some(16),
2943                    span: (),
2944                }),
2945                start: RangeParam::Literal(start),
2946                size: RangeParam::Literal(size),
2947            },
2948            size: None,
2949            rhs,
2950        }
2951    }
2952
2953    #[test]
2954    fn lower_writes_an_aligned_lane_of_wide_storage_as_its_sub_varnode() {
2955        // XmmReg[32,32] = r0 on a 16-byte register: one copy into bytes 4..8.
2956        let pcode = lower_instruction(
2957            &ast(vec![wide_lane_assignment(
2958                32,
2959                32,
2960                ident(RegisterId::new(0)),
2961            )]),
2962            &WideContext,
2963        )
2964        .unwrap();
2965        assert_eq!(pcode.ops.len(), 1);
2966        assert_eq!(pcode.ops[0].opcode, Opcode::Copy);
2967        assert_eq!(
2968            pcode.ops[0].output,
2969            Some(Varnode::new(SpaceId::new(3), 0x104, 4))
2970        );
2971        assert_eq!(
2972            pcode.ops[0].inputs,
2973            vec![Varnode::new(SpaceId::new(3), 0, 4)]
2974        );
2975    }
2976
2977    #[test]
2978    fn lower_writes_an_aligned_lane_of_a_wide_memory_operand_as_a_narrow_store() {
2979        // *:16 (0x2000)[64,32] = r0 stores four bytes at 0x2008.
2980        let pcode = lower_instruction(
2981            &ast(vec![AstNode::RangeAssignment {
2982                lhs: Range {
2983                    value: Box::new(Expression {
2984                        ty: ExpressionTy::Load(Load {
2985                            space: None,
2986                            size: Some(16),
2987                            ptr: Box::new(int(0x2000, 8)),
2988                        }),
2989                        size: Some(16),
2990                        span: (),
2991                    }),
2992                    start: RangeParam::Literal(64),
2993                    size: RangeParam::Literal(32),
2994                },
2995                size: None,
2996                rhs: ident(RegisterId::new(0)),
2997            }]),
2998            &WideContext,
2999        )
3000        .unwrap();
3001        let store = pcode.ops.last().unwrap();
3002        assert_eq!(store.opcode, Opcode::Store);
3003        assert_eq!(store.inputs[2], Varnode::new(SpaceId::new(3), 0, 4));
3004        let add = pcode
3005            .ops
3006            .iter()
3007            .find(|op| op.opcode == Opcode::IntAdd)
3008            .expect("the lane pointer is offset from the operand pointer");
3009        assert_eq!(add.inputs[1], Varnode::constant(8, 8));
3010        assert_eq!(store.inputs[1], add.output.unwrap());
3011    }
3012
3013    #[test]
3014    fn lower_still_rejects_an_unaligned_lane_of_wide_storage() {
3015        for (start, size) in [(4, 32), (0, 12), (127, 1)] {
3016            let error = lower_instruction(
3017                &ast(vec![wide_lane_assignment(start, size, int(1, 1))]),
3018                &WideContext,
3019            )
3020            .unwrap_err();
3021            assert!(
3022                matches!(error, PcodeLowerError::InvalidRange { .. }),
3023                "{start},{size}"
3024            );
3025        }
3026    }
3027
3028    #[test]
3029    fn lower_gives_width_preserving_float_builtins_their_operand_width() {
3030        // r0 = trunc(round(r1[0,32])) with no width on the round() node.
3031        let pcode = lower_instruction(
3032            &ast(vec![AstNode::Assignment {
3033                lhs: Ident::Register(RegisterId::new(0)),
3034                size: None,
3035                rhs: Expression {
3036                    ty: ExpressionTy::FunctionCall {
3037                        builtin: crate::Builtin::Trunc,
3038                        args: vec![Expression {
3039                            ty: ExpressionTy::FunctionCall {
3040                                builtin: crate::Builtin::Round,
3041                                args: vec![Expression {
3042                                    ty: ExpressionTy::Range(crate::Range {
3043                                        value: Box::new(ident(RegisterId::new(1))),
3044                                        start: crate::RangeParam::Literal(0),
3045                                        size: crate::RangeParam::Literal(32),
3046                                    }),
3047                                    size: Some(4),
3048                                    span: (),
3049                                }],
3050                            },
3051                            size: None,
3052                            span: (),
3053                        }],
3054                    },
3055                    size: None,
3056                    span: (),
3057                },
3058            }]),
3059            &Context,
3060        )
3061        .unwrap();
3062        let round = pcode
3063            .ops
3064            .iter()
3065            .find(|op| op.opcode == Opcode::FloatRound)
3066            .expect("round was emitted");
3067        assert_eq!(round.output.unwrap().size, 4);
3068        assert_eq!(pcode.ops.last().unwrap().opcode, Opcode::FloatTrunc);
3069        assert_eq!(pcode.ops.last().unwrap().output.unwrap().size, 4);
3070    }
3071
3072    #[test]
3073    fn lower_rejects_nodes_that_are_not_final_raw_pcode() {
3074        let error = lower_instruction(&ast(vec![AstNode::Build(crate::TableId::new(0))]), &Context)
3075            .unwrap_err();
3076        assert_eq!(error, PcodeLowerError::InternalNode("build statement"));
3077        let error = lower_instruction(
3078            &ast(vec![AstNode::Branch {
3079                target: LabelOrNode::Node("unresolved".into()),
3080            }]),
3081            &Context,
3082        )
3083        .unwrap_err();
3084        assert_eq!(
3085            error,
3086            PcodeLowerError::InternalNode("unresolved branch target")
3087        );
3088    }
3089
3090    #[test]
3091    fn lower_every_binary_operator_uses_its_raw_opcode() {
3092        use BinaryOperator::*;
3093        let cases = [
3094            (Mul, Opcode::IntMult, false),
3095            (Div, Opcode::IntDiv, false),
3096            (SignedDiv, Opcode::IntSDiv, false),
3097            (Mod, Opcode::IntRem, false),
3098            (SignedMod, Opcode::IntSRem, false),
3099            (FloatDiv, Opcode::FloatDiv, false),
3100            (FloatMul, Opcode::FloatMult, false),
3101            (Add, Opcode::IntAdd, false),
3102            (Sub, Opcode::IntSub, false),
3103            (FloatAdd, Opcode::FloatAdd, false),
3104            (FloatSub, Opcode::FloatSub, false),
3105            (LeftShift, Opcode::IntLeft, false),
3106            (RightShift, Opcode::IntRight, false),
3107            (SignedRightShift, Opcode::IntSRight, false),
3108            (SignedLessThan, Opcode::IntSLess, false),
3109            (SignedGreaterThan, Opcode::IntSLess, true),
3110            (SignedLessEqual, Opcode::IntSLessEqual, false),
3111            (SignedGreaterEqual, Opcode::IntSLessEqual, true),
3112            (LessEqual, Opcode::IntLessEqual, false),
3113            (GreaterEqual, Opcode::IntLessEqual, true),
3114            (LessThan, Opcode::IntLess, false),
3115            (GreaterThan, Opcode::IntLess, true),
3116            (FloatLessEqual, Opcode::FloatLessEqual, false),
3117            (FloatGreaterEqual, Opcode::FloatLessEqual, true),
3118            (FloatLessThan, Opcode::FloatLess, false),
3119            (FloatGreaterThan, Opcode::FloatLess, true),
3120            (Equal, Opcode::IntEqual, false),
3121            (NotEqual, Opcode::IntNotEqual, false),
3122            (FloatEqual, Opcode::FloatEqual, false),
3123            (FloatNotEqual, Opcode::FloatNotEqual, false),
3124            (LogicalXor, Opcode::BoolXor, false),
3125            (LogicalAnd, Opcode::BoolAnd, false),
3126            (LogicalOr, Opcode::BoolOr, false),
3127            (BitwiseXor, Opcode::IntXor, false),
3128            (BitwiseOr, Opcode::IntOr, false),
3129            (BitwiseAnd, Opcode::IntAnd, false),
3130        ];
3131        for (operator, opcode, reverse) in cases {
3132            assert_eq!(super::binary_opcode(operator), (opcode, reverse));
3133        }
3134    }
3135
3136    #[test]
3137    fn lower_every_builtin_and_unary_operator() {
3138        let builtins = [
3139            (crate::Builtin::Carry, Opcode::IntCarry),
3140            (crate::Builtin::Scarry, Opcode::IntSCarry),
3141            (crate::Builtin::Sborrow, Opcode::IntSBorrow),
3142            (crate::Builtin::Nan, Opcode::FloatNan),
3143            (crate::Builtin::Abs, Opcode::FloatAbs),
3144            (crate::Builtin::Sqrt, Opcode::FloatSqrt),
3145            (crate::Builtin::Floor, Opcode::FloatFloor),
3146            (crate::Builtin::Ceil, Opcode::FloatCeil),
3147            (crate::Builtin::Round, Opcode::FloatRound),
3148            (crate::Builtin::Int2Float, Opcode::FloatInt2Float),
3149            (crate::Builtin::Float2Float, Opcode::FloatFloat2Float),
3150            (crate::Builtin::Trunc, Opcode::FloatTrunc),
3151            (crate::Builtin::Zext, Opcode::IntZext),
3152            (crate::Builtin::Sext, Opcode::IntSext),
3153            (crate::Builtin::Popcount, Opcode::PopCount),
3154            (crate::Builtin::Lzcount, Opcode::LzCount),
3155            (crate::Builtin::Cpool, Opcode::CpoolRef),
3156            (crate::Builtin::NewObject, Opcode::New),
3157        ];
3158        for (builtin, opcode) in builtins {
3159            let expression = Expression {
3160                ty: ExpressionTy::FunctionCall {
3161                    builtin,
3162                    args: vec![int(1, 4)],
3163                },
3164                size: Some(4),
3165                span: (),
3166            };
3167            let pcode = lower_instruction(
3168                &ast(vec![AstNode::Assignment {
3169                    lhs: Ident::Named(LocalVarId(0)),
3170                    size: None,
3171                    rhs: expression,
3172                }]),
3173                &Context,
3174            )
3175            .unwrap();
3176            assert_eq!(pcode.ops[0].opcode, opcode);
3177        }
3178        for (operator, opcode) in [
3179            (crate::UnaryOperator::LogicalNot, Opcode::BoolNegate),
3180            (crate::UnaryOperator::BitwiseNot, Opcode::IntNegate),
3181            (crate::UnaryOperator::Minus, Opcode::Int2Comp),
3182            (crate::UnaryOperator::FloatMinus, Opcode::FloatNeg),
3183        ] {
3184            let expression = Expression {
3185                ty: ExpressionTy::Unop(crate::Unop {
3186                    op: operator,
3187                    e: Box::new(ident(RegisterId::new(0))),
3188                }),
3189                size: Some(4),
3190                span: (),
3191            };
3192            let pcode = lower_instruction(
3193                &ast(vec![AstNode::Assignment {
3194                    lhs: Ident::Named(LocalVarId(0)),
3195                    size: None,
3196                    rhs: expression,
3197                }]),
3198                &Context,
3199            )
3200            .unwrap();
3201            assert_eq!(pcode.ops[0].opcode, opcode);
3202        }
3203    }
3204
3205    #[test]
3206    fn lower_direct_and_indirect_control_flow() {
3207        let direct = lower_instruction(
3208            &ast(vec![AstNode::Call {
3209                target: LabelOrNode::Expr(int(0x1000, 8)),
3210            }]),
3211            &Context,
3212        )
3213        .unwrap();
3214        assert_eq!(
3215            direct.ops[0],
3216            PcodeOp::new(
3217                Opcode::Call,
3218                None,
3219                vec![Varnode::new(SpaceId::new(1), 0x1000, 8)]
3220            )
3221        );
3222        let indirect = lower_instruction(
3223            &ast(vec![
3224                AstNode::ConditionalBranch {
3225                    condition: int(1, 1),
3226                    target: LabelOrNode::Expr(int(0x2000, 8)),
3227                },
3228                AstNode::BranchIndirect {
3229                    target: ident(RegisterId::new(0)),
3230                },
3231                AstNode::CallIndirect {
3232                    target: ident(RegisterId::new(1)),
3233                },
3234                AstNode::Return {
3235                    target: ident(RegisterId::new(2)),
3236                },
3237            ]),
3238            &Context,
3239        )
3240        .unwrap();
3241        assert_eq!(
3242            indirect.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3243            vec![
3244                Opcode::CBranch,
3245                Opcode::BranchInd,
3246                Opcode::CallInd,
3247                Opcode::Return
3248            ]
3249        );
3250        assert_eq!(
3251            indirect.ops[0].inputs,
3252            vec![
3253                Varnode::new(SpaceId::new(1), 0x2000, 8),
3254                Varnode::constant(1, 1)
3255            ]
3256        );
3257    }
3258
3259    #[test]
3260    fn lowering_errors_are_displayable_and_typed() {
3261        let errors = [
3262            PcodeLowerError::UnknownSize,
3263            PcodeLowerError::ZeroSize,
3264            PcodeLowerError::CopySizeMismatch {
3265                input: 1,
3266                output: 2,
3267            },
3268            PcodeLowerError::InputSizeMismatch {
3269                operation: "operation",
3270                left: 1,
3271                right: 2,
3272            },
3273            PcodeLowerError::InvalidBooleanSize(2),
3274            PcodeLowerError::AddressSizeMismatch {
3275                expected: 4,
3276                actual: 8,
3277            },
3278            PcodeLowerError::StoreSizeMismatch {
3279                declared: 4,
3280                value: 8,
3281            },
3282            PcodeLowerError::InvalidRange {
3283                start: 0,
3284                size: 0,
3285                storage_bits: 32,
3286            },
3287            PcodeLowerError::UniqueSpaceOverflow,
3288            PcodeLowerError::UnknownRegister(RegisterId::new(9)),
3289            PcodeLowerError::UnresolvedIdentifier("field"),
3290            PcodeLowerError::UnresolvedSpace,
3291            PcodeLowerError::UnresolvedRangeParameter,
3292            PcodeLowerError::InternalNode("macro"),
3293            PcodeLowerError::Unsupported("range"),
3294            PcodeLowerError::DuplicateLabel("loop".into()),
3295            PcodeLowerError::UnknownLabel("loop".into()),
3296            PcodeLowerError::InvalidDirectTarget,
3297        ];
3298        for error in errors {
3299            assert!(!error.to_string().is_empty());
3300        }
3301        assert_eq!(
3302            InstructionPcode::lower(&ast(vec![]), &Context).unwrap(),
3303            InstructionPcode::new()
3304        );
3305    }
3306
3307    #[test]
3308    fn lower_rejects_invalid_raw_widths_and_ranges() {
3309        let range = |start, size| Expression {
3310            ty: ExpressionTy::Range(crate::Range {
3311                value: Box::new(ident(RegisterId::new(0))),
3312                start: crate::RangeParam::Literal(start),
3313                size: crate::RangeParam::Literal(size),
3314            }),
3315            size: None,
3316            span: (),
3317        };
3318        for (start, size) in [(0, 0), (0, 65), (31, 2)] {
3319            let error = lower_instruction(
3320                &ast(vec![AstNode::Assignment {
3321                    lhs: Ident::Register(RegisterId::new(0)),
3322                    size: None,
3323                    rhs: range(start, size),
3324                }]),
3325                &Context,
3326            )
3327            .unwrap_err();
3328            assert!(matches!(error, PcodeLowerError::InvalidRange { .. }));
3329        }
3330
3331        let error = lower_instruction(
3332            &ast(vec![AstNode::Assignment {
3333                lhs: Ident::Named(LocalVarId(0)),
3334                size: None,
3335                rhs: Expression {
3336                    ty: ExpressionTy::Range(crate::Range {
3337                        value: Box::new(ident(RegisterId::new(0))),
3338                        start: crate::RangeParam::MacroArg(LocalVarId(1)),
3339                        size: crate::RangeParam::Literal(1),
3340                    }),
3341                    size: None,
3342                    span: (),
3343                },
3344            }]),
3345            &Context,
3346        )
3347        .unwrap_err();
3348        assert_eq!(error, PcodeLowerError::UnresolvedRangeParameter);
3349
3350        let mismatch = ExpressionTy::Binop(Binop {
3351            op: BinaryOperator::Add,
3352            lhs: Box::new(ident(RegisterId::new(0))),
3353            rhs: Box::new(int(1, 1)),
3354        })
3355        .with_size(4);
3356        // Integer literals are sized by their consuming p-code operation,
3357        // rather than forcing a mixed-width raw operation.
3358        assert!(
3359            lower_instruction(
3360                &ast(vec![AstNode::Assignment {
3361                    lhs: Ident::Named(LocalVarId(0)),
3362                    size: None,
3363                    rhs: mismatch,
3364                }]),
3365                &Context,
3366            )
3367            .is_ok()
3368        );
3369
3370        let comparison = ExpressionTy::Binop(Binop {
3371            op: BinaryOperator::Equal,
3372            lhs: Box::new(ident(RegisterId::new(0))),
3373            rhs: Box::new(ident(RegisterId::new(1))),
3374        })
3375        .with_size(4);
3376        assert_eq!(
3377            lower_instruction(
3378                &ast(vec![AstNode::Assignment {
3379                    lhs: Ident::Named(LocalVarId(0)),
3380                    size: None,
3381                    rhs: comparison,
3382                }]),
3383                &Context,
3384            )
3385            .unwrap_err(),
3386            PcodeLowerError::InvalidBooleanSize(4)
3387        );
3388
3389        let bad_load = Load {
3390            space: Some(PcodeSpaceRef::Resolved(SpaceId::new(1))),
3391            size: Some(4),
3392            ptr: Box::new(ident(RegisterId::new(0))),
3393        };
3394        assert!(matches!(
3395            lower_instruction(
3396                &ast(vec![AstNode::Assignment {
3397                    lhs: Ident::Named(LocalVarId(0)),
3398                    size: Some(4),
3399                    rhs: ExpressionTy::Load(bad_load.clone()).with_size(4),
3400                }]),
3401                &Context,
3402            ),
3403            Err(PcodeLowerError::AddressSizeMismatch { .. })
3404        ));
3405        assert!(matches!(
3406            lower_instruction(
3407                &ast(vec![AstNode::LoadAssignment {
3408                    lhs: bad_load,
3409                    size: None,
3410                    rhs: int(1, 1),
3411                }]),
3412                &Context,
3413            ),
3414            Err(PcodeLowerError::AddressSizeMismatch { .. })
3415        ));
3416    }
3417
3418    #[test]
3419    fn lower_handles_subpieces_and_rejects_invalid_final_forms() {
3420        let lsb = Expression {
3421            ty: ExpressionTy::SubPieceLsb {
3422                src: Box::new(ident(RegisterId::new(0))),
3423                count: 2,
3424            },
3425            size: Some(2),
3426            span: (),
3427        };
3428        let msb = Expression {
3429            ty: ExpressionTy::SubPieceMsb {
3430                src: Box::new(ident(RegisterId::new(0))),
3431                count: 2,
3432            },
3433            size: Some(2),
3434            span: (),
3435        };
3436        let pcode = lower_instruction(
3437            &ast(vec![
3438                AstNode::Assignment {
3439                    lhs: Ident::Named(LocalVarId(0)),
3440                    size: None,
3441                    rhs: lsb,
3442                },
3443                AstNode::Assignment {
3444                    lhs: Ident::Named(LocalVarId(1)),
3445                    size: None,
3446                    rhs: msb,
3447                },
3448            ]),
3449            &Context,
3450        )
3451        .unwrap();
3452        assert_eq!(
3453            pcode.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3454            vec![Opcode::SubPiece, Opcode::SubPiece]
3455        );
3456        assert_eq!(pcode.ops[0].inputs[1], Varnode::constant(0, 8));
3457        assert_eq!(pcode.ops[1].inputs[1], Varnode::constant(2, 8));
3458
3459        let range = Expression {
3460            ty: ExpressionTy::Range(crate::Range {
3461                value: Box::new(ident(RegisterId::new(0))),
3462                start: crate::RangeParam::Literal(0),
3463                size: crate::RangeParam::Literal(1),
3464            }),
3465            size: Some(1),
3466            span: (),
3467        };
3468        let pcode = lower_instruction(
3469            &ast(vec![AstNode::Assignment {
3470                lhs: Ident::Named(LocalVarId(0)),
3471                size: None,
3472                rhs: range,
3473            }]),
3474            &Context,
3475        )
3476        .unwrap();
3477        assert_eq!(
3478            pcode.ops.iter().map(|op| op.opcode).collect::<Vec<_>>(),
3479            vec![Opcode::IntRight, Opcode::IntAnd, Opcode::SubPiece]
3480        );
3481        let error = lower_instruction(
3482            &ast(vec![
3483                AstNode::Label("same".into()),
3484                AstNode::Label("same".into()),
3485            ]),
3486            &Context,
3487        )
3488        .unwrap_err();
3489        assert_eq!(error, PcodeLowerError::DuplicateLabel("same".into()));
3490        let error = lower_instruction(
3491            &ast(vec![AstNode::Branch {
3492                target: LabelOrNode::Label("missing".into()),
3493            }]),
3494            &Context,
3495        )
3496        .unwrap_err();
3497        assert_eq!(error, PcodeLowerError::UnknownLabel("missing".into()));
3498        let error = lower_instruction(
3499            &ast(vec![AstNode::Branch {
3500                target: LabelOrNode::Expr(ident(RegisterId::new(0))),
3501            }]),
3502            &Context,
3503        )
3504        .unwrap_err();
3505        assert_eq!(error, PcodeLowerError::InvalidDirectTarget);
3506    }
3507}