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