Skip to main content

pcode_types/
statement.rs

1use std::fmt::{self, Debug};
2
3use crate::{Expression, FieldId, Ident, Load, PcodeResolver, Range, TableId, pretty_print_ident};
4use serde::{Deserialize, Serialize};
5
6/// A branch/call target that may be a label, an unresolved name, or an expression.
7///
8/// `S` is the span type: `(usize, usize)` at parse time, `()` in the stored/runtime form.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub enum LabelOrNode<S = ()> {
11    /// A label declared elsewhere in the same body: `goto <loopstart>`.
12    ///
13    /// Control flow *within* one instruction's p-code. The matching
14    /// [`AstNode::Label`] carries the same name. Names are scoped per spliced
15    /// body, so two macro expansions in one instruction cannot collide.
16    Label(Box<str>),
17
18    /// A name the compiler could not resolve to a value. One reaching a
19    /// consumer means the destination could not be worked out.
20    Node(Box<str>),
21
22    /// A computed destination: an address, or a value to branch through.
23    Expr(Expression<S>),
24}
25
26impl<S> LabelOrNode<S> {
27    /// Discards source spans.
28    pub fn strip_span(self) -> LabelOrNode<()> {
29        match self {
30            LabelOrNode::Label(name) => LabelOrNode::Label(name),
31            LabelOrNode::Node(name) => LabelOrNode::Node(name),
32            LabelOrNode::Expr(expr) => LabelOrNode::Expr(expr.strip_span()),
33        }
34    }
35}
36
37/// The minimum number of delay-slot bytes a `delayslot(n)` directive asks for.
38///
39/// SLEIGH counts *bytes*, not instructions: whole instructions are parsed after
40/// the current one until at least this many bytes have been consumed.
41/// `delayslot(1)` is the idiom for "exactly one following instruction".
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub enum DelaySlotArg {
44    /// A literal byte count.
45    Bytes(u64),
46
47    /// A field whose decoded value is the byte count — pi32v2's `rep` computes
48    /// one in its disassembly action.
49    Field(FieldId),
50
51    /// A name that was not a known symbol during Phase 2 parsing. Resolved to
52    /// [`DelaySlotArg::Field`] by the Phase 3 resolve pass.
53    Deferred(Box<str>),
54}
55
56/// A single p-code statement node.
57///
58/// `S` is the span type: `(usize, usize)` at parse time, `()` in the stored/runtime form.
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub enum AstNode<S = ()> {
61    /// `x = rhs;` — write to a register, a bit-range field or a temporary.
62    Assignment {
63        /// What is written.
64        lhs: Ident,
65        /// Width in bytes from an explicit `:n` on the destination, or `None`
66        /// to take it from the destination itself.
67        size: Option<usize>,
68        /// The value written.
69        rhs: Expression<S>,
70    },
71
72    /// `*[space]:n ptr = rhs;` — a memory write.
73    LoadAssignment {
74        /// The destination address, space and width.
75        lhs: Load<S>,
76        /// Width from a `:n` on the statement rather than on the store; almost
77        /// always `None`, since the store carries its own.
78        size: Option<usize>,
79        /// The value written.
80        rhs: Expression<S>,
81    },
82
83    /// `x[start, size] = rhs;` — write to a bit range of a varnode, leaving
84    /// the surrounding bits alone.
85    RangeAssignment {
86        /// The destination range.
87        lhs: Range<S>,
88        /// Explicit statement width, usually `None`.
89        size: Option<usize>,
90        /// The value written.
91        rhs: Expression<S>,
92    },
93
94    /// `build X;` — splice in the p-code of the sub-table operand `X`.
95    ///
96    /// Expanded before a consumer sees the AST; one surviving is a bug in
97    /// this crate.
98    Build(TableId),
99
100    /// `delayslot(n);` — splice in the p-code of the instruction(s) that follow.
101    DelaySlot(DelaySlotArg),
102
103    /// A `build X` where X was not in the symbol table during Phase 2 parsing.
104    /// Resolved to `Build(TableId)` by the Phase 3 resolve pass.
105    DeferredBuild(Box<str>),
106
107    /// `<name>` — a branch destination within this instruction's own p-code.
108    Label(Box<str>),
109
110    /// `goto dest;` — an unconditional branch.
111    Branch {
112        /// Where to.
113        target: LabelOrNode<S>,
114    },
115
116    /// `if cond goto dest;` — branch when `cond` is non-zero. Falls through
117    /// to the next statement otherwise.
118    ConditionalBranch {
119        /// The condition, read as false when zero.
120        condition: Expression<S>,
121        /// Where to when it holds.
122        target: LabelOrNode<S>,
123    },
124
125    /// `goto [expr];` — branch to a computed address.
126    BranchIndirect {
127        /// The address to branch to.
128        target: Expression<S>,
129    },
130
131    /// `call dest;` — a call, which a consumer may treat as a branch that is
132    /// expected to return.
133    Call {
134        /// Where to.
135        target: LabelOrNode<S>,
136    },
137
138    /// `call [expr];` — a call to a computed address.
139    CallIndirect {
140        /// The address to call.
141        target: Expression<S>,
142    },
143
144    /// `return [expr];` — return to a computed address.
145    Return {
146        /// The address returned to.
147        target: Expression<S>,
148    },
149
150    /// `export x;` — the value a sub-table constructor hands to its parent.
151    ///
152    /// Consumed while the parent's body is expanded, so a consumer does not
153    /// see this statement.
154    Export(Expression<S>),
155
156    /// An expression evaluated for its effect — in practice a call to a
157    /// `define pcodeop`, whose result is discarded.
158    Expression(Expression<S>),
159}
160
161impl AstNode {
162    /// Renders this statement in a SLEIGH-like syntax, resolving identifiers
163    /// against `spec`. For diagnostics and tests; not a stable format.
164    pub fn pretty_print(&self, spec: &impl PcodeResolver) -> String {
165        match self {
166            AstNode::Assignment { lhs, size, rhs } => format!(
167                "{}{} = {};",
168                pretty_print_ident(spec, lhs),
169                pretty_print_size(*size),
170                rhs.pretty_print(spec)
171            ),
172            AstNode::LoadAssignment { lhs, size, rhs } => format!(
173                "{}{} = {};",
174                lhs.pretty_print(spec),
175                pretty_print_size(*size),
176                rhs.pretty_print(spec)
177            ),
178            AstNode::RangeAssignment { lhs, size, rhs } => format!(
179                "{}{} = {};",
180                lhs.pretty_print(spec),
181                pretty_print_size(*size),
182                rhs.pretty_print(spec)
183            ),
184            AstNode::Build(table_id) => format!("build table{};", usize::from(*table_id)),
185            AstNode::DelaySlot(arg) => match arg {
186                DelaySlotArg::Bytes(n) => format!("delayslot({n});"),
187                DelaySlotArg::Field(id) => {
188                    format!("delayslot({});", spec.field_name(*id))
189                }
190                DelaySlotArg::Deferred(name) => format!("delayslot({name});"),
191            },
192            AstNode::DeferredBuild(name) => format!("build {name};"),
193            AstNode::Label(name) => format!("<{name}>"),
194            AstNode::Branch { target } => format!("goto {};", pretty_print_target(spec, target)),
195            AstNode::ConditionalBranch { condition, target } => format!(
196                "if {} goto {};",
197                condition.pretty_print(spec),
198                pretty_print_target(spec, target)
199            ),
200            AstNode::BranchIndirect { target } => {
201                format!("goto [{}];", target.pretty_print(spec))
202            }
203            AstNode::Call { target } => format!("call {};", pretty_print_target(spec, target)),
204            AstNode::CallIndirect { target } => {
205                format!("call [{}];", target.pretty_print(spec))
206            }
207            AstNode::Return { target } => format!("return [{}];", target.pretty_print(spec)),
208            AstNode::Export(expr) => format!("export {};", expr.pretty_print(spec)),
209            AstNode::Expression(expr) => format!("{};", expr.pretty_print(spec)),
210        }
211    }
212}
213
214impl<S> AstNode<S> {
215    /// Discards source spans.
216    pub fn strip_span(self) -> AstNode<()> {
217        match self {
218            AstNode::Assignment { lhs, size, rhs } => AstNode::Assignment {
219                lhs,
220                size,
221                rhs: rhs.strip_span(),
222            },
223            AstNode::LoadAssignment { lhs, size, rhs } => AstNode::LoadAssignment {
224                lhs: lhs.strip_span(),
225                size,
226                rhs: rhs.strip_span(),
227            },
228            AstNode::RangeAssignment { lhs, size, rhs } => AstNode::RangeAssignment {
229                lhs: lhs.strip_span(),
230                size,
231                rhs: rhs.strip_span(),
232            },
233            AstNode::Build(table_id) => AstNode::Build(table_id),
234            AstNode::DelaySlot(arg) => AstNode::DelaySlot(arg),
235            AstNode::DeferredBuild(name) => AstNode::DeferredBuild(name),
236            AstNode::Label(name) => AstNode::Label(name),
237            AstNode::Branch { target } => AstNode::Branch {
238                target: target.strip_span(),
239            },
240            AstNode::ConditionalBranch { condition, target } => AstNode::ConditionalBranch {
241                condition: condition.strip_span(),
242                target: target.strip_span(),
243            },
244            AstNode::BranchIndirect { target } => AstNode::BranchIndirect {
245                target: target.strip_span(),
246            },
247            AstNode::Call { target } => AstNode::Call {
248                target: target.strip_span(),
249            },
250            AstNode::CallIndirect { target } => AstNode::CallIndirect {
251                target: target.strip_span(),
252            },
253            AstNode::Return { target } => AstNode::Return {
254                target: target.strip_span(),
255            },
256            AstNode::Export(expr) => AstNode::Export(expr.strip_span()),
257            AstNode::Expression(expr) => AstNode::Expression(expr.strip_span()),
258        }
259    }
260}
261
262/// A p-code statement with a byte-range span.
263#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub struct Ast<S = ()> {
265    /// What kind of statement this is, and its operands.
266    pub ty: AstNode<S>,
267    /// Where it came from in the preprocessed source, or `()` once the
268    /// compiler is done with it.
269    pub span: S,
270}
271
272impl<S: Debug> Debug for Ast<S> {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        self.ty.fmt(f)
275    }
276}
277
278impl<S> Ast<S> {
279    /// Discards source spans.
280    pub fn strip_span(self) -> Ast<()> {
281        Ast {
282            ty: self.ty.strip_span(),
283            span: (),
284        }
285    }
286}
287
288impl From<AstNode> for Ast {
289    fn from(ty: AstNode) -> Self {
290        Self { ty, span: () }
291    }
292}
293
294impl Ast {
295    /// Renders this statement in a SLEIGH-like syntax, resolving identifiers
296    /// against `spec`. For diagnostics and tests; not a stable format.
297    pub fn pretty_print(&self, spec: &impl PcodeResolver) -> String {
298        self.ty.pretty_print(spec)
299    }
300}
301
302fn pretty_print_target(spec: &impl PcodeResolver, target: &LabelOrNode) -> String {
303    match target {
304        LabelOrNode::Label(name) => format!("<{name}>"),
305        LabelOrNode::Node(name) => (*name).to_string(),
306        LabelOrNode::Expr(expr) => expr.pretty_print(spec),
307    }
308}
309
310fn pretty_print_size(size: Option<usize>) -> String {
311    size.map(|size| format!(":{size}")).unwrap_or_default()
312}