analyssa 0.4.1

Target-agnostic SSA IR, analyses, and optimization pipeline
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
//! SSA instruction wrapper with explicit def/use metadata.
//!
//! Unlike stack-based CIL where operands are implicit on the evaluation stack,
//! SSA instructions have explicit operands (uses) and results (defs). Each
//! [`SsaInstruction`] wraps a decomposed [`SsaOp`] along with the original
//! host instruction and optional resolved result type.
//!
//! # Dual Representation
//!
//! Each SSA instruction stores two representations:
//!
//! 1. **Original instruction** (`T::OriginalInstruction`): The raw host instruction
//!    (e.g., a CIL opcode with its operands). Retained for debugging, source mapping,
//!    and code generation to produce faithful output.
//!
//! 2. **Decomposed SSA operation** ([`SsaOp`]): The clean `result = op(operands)` form.
//!    This is the primary representation for analysis and optimization passes.
//!
//! This dual representation enables:
//! - Direct def-use chain construction from `SsaOp::defs()` / `SsaOp::uses()`
//! - Dead code elimination (variable with no uses)
//! - Pattern matching on decomposed operations for optimization
//! - Faithful code generation from the original instruction where needed
//!
//! # Result Type Resolution
//!
//! The `result_type` field captures the precise type available during initial SSA
//! construction (when full assembly metadata is available). This type survives
//! through deobfuscation transforms and is used by rebuild and codegen to recover
//! types that cannot be inferred structurally from the op alone (e.g., call return
//! types, field types, argument/local types).

use std::fmt;

use crate::{
    ir::{ops::SsaOp, variable::SsaVarId},
    target::Target,
};

/// An SSA instruction wrapping a decomposed operation and original host instruction.
///
/// Combines the decomposed [`SsaOp`] (the canonical representation for analysis)
/// with the original host instruction (for debugging and faithful code generation)
/// and an optional resolved result type.
///
/// # Examples
///
/// ```rust
/// use analyssa::{ir::{SsaInstruction, SsaOp, SsaVarId}, MockTarget};
///
/// let left = SsaVarId::from_index(0);
/// let right = SsaVarId::from_index(1);
/// let result = SsaVarId::from_index(2);
/// let instr: SsaInstruction<MockTarget> = SsaInstruction::new(
///     <MockTarget as analyssa::Target>::synthetic_instruction(),
///     SsaOp::Add { dest: result, left, right, flags: None },
/// );
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(bound(
        serialize = "T::Type: serde::Serialize, T::TypeRef: serde::Serialize, \
                     T::MethodRef: serde::Serialize, T::FieldRef: serde::Serialize, \
                     T::SigRef: serde::Serialize, T::OriginalInstruction: serde::Serialize",
        deserialize = "T::Type: serde::Deserialize<'de>, T::TypeRef: serde::Deserialize<'de>, \
                       T::MethodRef: serde::Deserialize<'de>, T::FieldRef: serde::Deserialize<'de>, \
                       T::SigRef: serde::Deserialize<'de>, T::OriginalInstruction: serde::Deserialize<'de>"
    ))
)]
pub struct SsaInstruction<T: Target> {
    /// The original host instruction retained for debugging, source mapping,
    /// and code generation. Hosts can store a decoded instruction, source
    /// location, RVA, or `()` for synthetic instructions.
    original: T::OriginalInstruction,

    /// The decomposed SSA operation in `result = op(operands)` form.
    ///
    /// This is the authoritative representation used by all analysis and
    /// optimization passes. All data dependencies are explicit in the
    /// operation's `defs()` and `uses()`.
    op: SsaOp<T>,

    /// Optional resolved result type captured during SSA construction.
    ///
    /// Set when the SSA converter has access to full assembly metadata
    /// (TypeContext). Survives deobfuscation transforms. Used by rebuild
    /// and codegen to recover types that cannot be inferred structurally
    /// from the SSA op alone: call return types, field types, argument
    /// and local types, etc.
    result_type: Option<T::Type>,
}

impl<T: Target> SsaInstruction<T> {
    /// Creates a new SSA instruction with a decomposed operation.
    ///
    /// # Arguments
    ///
    /// * `original` - The original instruction
    /// * `op` - The decomposed SSA operation
    #[must_use]
    pub fn new(original: T::OriginalInstruction, op: SsaOp<T>) -> Self {
        Self {
            original,
            op,
            result_type: None,
        }
    }

    /// Creates an SSA instruction with only a decomposed operation (no original
    /// instruction breadcrumb).
    ///
    /// This is useful for synthetic instructions like phi nodes that don't
    /// correspond to any source-level instruction. The placeholder original
    /// is supplied by `Target::synthetic_instruction()`.
    #[must_use]
    pub fn synthetic(op: SsaOp<T>) -> Self {
        Self {
            original: T::synthetic_instruction(),
            op,
            result_type: None,
        }
    }

    /// Returns a reference to the original instruction.
    #[must_use]
    pub const fn original(&self) -> &T::OriginalInstruction {
        &self.original
    }

    /// Returns the decomposed SSA operation.
    #[must_use]
    pub const fn op(&self) -> &SsaOp<T> {
        &self.op
    }

    /// Returns a mutable reference to the decomposed SSA operation.
    pub fn op_mut(&mut self) -> &mut SsaOp<T> {
        &mut self.op
    }

    /// Sets the decomposed SSA operation.
    ///
    /// Clears `result_type` because the new op may have a different result type.
    /// Callers that know the type should call `set_result_type()` afterwards.
    pub fn set_op(&mut self, op: SsaOp<T>) {
        self.op = op;
        self.result_type = None;
    }

    /// Sets the decomposed SSA operation while preserving the resolved result
    /// type.
    ///
    /// Unlike [`Self::set_op`] (which clears `result_type`), this re-stamps the
    /// type from the new op's structural inference
    /// ([`SsaOp::infer_result_type`]) when one is available, falling back to the
    /// prior type otherwise. Rewriting passes that replace an op without
    /// changing the value its destination produces — variable remapping,
    /// constant folding, in-place op replacement — use this so resolved types,
    /// **especially the non-structural ones the rebuild cannot recover** (call
    /// return types, field types), do not silently vanish.
    ///
    /// An op with no destination ([`SsaOp::Nop`], stores, terminators) produces
    /// no value, so there is nothing to preserve and `result_type` is cleared.
    /// This keeps the removal path (`SsaEditor::nop_instruction`) from leaving a
    /// dead type stamped on an instruction that no longer defines anything.
    pub fn set_op_preserving_type(&mut self, op: SsaOp<T>) {
        let prev = self.result_type.take();
        self.op = op;
        self.result_type = if self.op.dest().is_some() {
            self.op.infer_result_type().or(prev)
        } else {
            None
        };
    }

    /// Returns the resolved result type, if set during SSA construction.
    #[must_use]
    pub fn result_type(&self) -> Option<&T::Type> {
        self.result_type.as_ref()
    }

    /// Sets the resolved result type.
    pub fn set_result_type(&mut self, ty: Option<T::Type>) {
        self.result_type = ty;
    }

    /// Builder pattern: sets the result type and returns self.
    #[must_use]
    pub fn with_result_type(mut self, ty: T::Type) -> Self {
        self.result_type = Some(ty);
        self
    }

    /// Returns `true` if this instruction is a terminator.
    ///
    /// Terminators are instructions that end a basic block (jumps, branches, returns, throws).
    #[must_use]
    pub fn is_terminator(&self) -> bool {
        self.op.is_terminator()
    }

    /// Returns `true` if this instruction may throw an exception.
    #[must_use]
    pub fn may_throw(&self) -> bool {
        self.op.may_throw()
    }

    /// Returns `true` if this instruction is pure (has no side effects).
    ///
    /// Pure instructions can be eliminated if their result is unused.
    #[must_use]
    pub fn is_pure(&self) -> bool {
        self.op.is_pure()
    }

    /// Returns the SSA variables used (read) by this instruction.
    #[must_use]
    pub fn uses(&self) -> Vec<SsaVarId> {
        self.op.uses()
    }

    /// Calls `f` for every SSA variable used by this instruction.
    pub fn for_each_use<F>(&self, f: F)
    where
        F: FnMut(SsaVarId),
    {
        self.op.for_each_use(f);
    }

    /// Returns `true` if this instruction uses (reads) the given variable.
    ///
    /// Allocation-free; prefer over `self.uses().contains(&var)` in hot paths.
    #[must_use]
    pub fn uses_var(&self, var: SsaVarId) -> bool {
        self.op.uses_var(var)
    }

    /// Returns the SSA variable defined by this instruction, if any.
    #[must_use]
    pub fn def(&self) -> Option<SsaVarId> {
        self.op.dest()
    }

    /// Returns all SSA variables defined by this instruction.
    pub fn defs(&self) -> impl Iterator<Item = SsaVarId> + '_ {
        self.op.defs()
    }

    /// Returns `true` if this instruction defines a value.
    #[must_use]
    pub fn has_def(&self) -> bool {
        self.op.defs().next().is_some()
    }

    /// Returns `true` if this instruction has no uses.
    #[must_use]
    pub fn has_no_uses(&self) -> bool {
        let mut has_use = false;
        self.op.for_each_use(|_| has_use = true);
        !has_use
    }

    /// Returns all SSA variables referenced by this instruction.
    ///
    /// This includes both uses and the def (if present).
    #[must_use]
    pub fn all_variables(&self) -> Vec<SsaVarId> {
        let mut vars = Vec::new();
        self.op.for_each_use(|var| vars.push(var));
        vars.extend(self.op.defs());
        vars
    }

    /// Calls `f` for every SSA variable referenced (used or defined) by this
    /// instruction, without allocating.
    pub fn for_each_variable<F>(&self, mut f: F)
    where
        F: FnMut(SsaVarId),
    {
        self.op.for_each_use(&mut f);
        for def in self.op.defs() {
            f(def);
        }
    }
}

// Original-instruction accessors. Implementation routes through
// `Target::instruction_mnemonic` / `instruction_rva`, so this works for any
// host that retains a meaningful `OriginalInstruction`.
impl<T: Target> SsaInstruction<T> {
    /// Returns the instruction's mnemonic via `Target::instruction_mnemonic`.
    #[must_use]
    pub fn mnemonic(&self) -> &'static str {
        T::instruction_mnemonic(&self.original)
    }

    /// Returns the instruction's RVA via `Target::instruction_rva`.
    #[must_use]
    pub fn rva(&self) -> u64 {
        T::instruction_rva(&self.original)
    }
}

impl<T: Target> fmt::Display for SsaInstruction<T>
where
    SsaOp<T>: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.op)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        ir::{ops::SsaOp, value::ConstValue},
        testing::{MockTarget, MockType},
    };

    fn var(index: usize) -> SsaVarId {
        SsaVarId::from_index(index)
    }

    #[test]
    fn instruction_accessors_reflect_operation_shape() {
        let instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::Add {
            dest: var(2),
            left: var(0),
            right: var(1),
            flags: None,
        });

        assert_eq!(instr.original(), &());
        assert!(matches!(instr.op(), SsaOp::Add { .. }));
        assert!(instr.is_pure());
        assert!(!instr.is_terminator());
        assert!(!instr.may_throw());
        assert_eq!(instr.uses(), vec![var(0), var(1)]);
        assert_eq!(instr.def(), Some(var(2)));
        assert!(instr.has_def());
        assert!(!instr.has_no_uses());
        assert_eq!(instr.all_variables(), vec![var(0), var(1), var(2)]);
        assert_eq!(instr.mnemonic(), "<mock>");
        assert_eq!(instr.rva(), 0);
    }

    #[test]
    fn mutating_op_clears_result_type_until_reset() {
        let mut instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::Const {
            dest: var(0),
            value: ConstValue::I32(1),
        })
        .with_result_type(MockType::I32);

        assert_eq!(instr.result_type(), Some(&MockType::I32));

        if let SsaOp::Const { value, .. } = instr.op_mut() {
            *value = ConstValue::I32(2);
        }
        assert_eq!(instr.result_type(), Some(&MockType::I32));

        instr.set_op(SsaOp::Return {
            value: Some(var(0)),
        });
        assert_eq!(instr.result_type(), None);
        assert!(instr.is_terminator());
        assert_eq!(instr.def(), None);
        assert!(!instr.has_def());
        assert_eq!(instr.uses(), vec![var(0)]);

        instr.set_result_type(Some(MockType::Unknown));
        assert_eq!(instr.result_type(), Some(&MockType::Unknown));
        instr.set_result_type(None);
        assert_eq!(instr.result_type(), None);
    }

    #[test]
    fn set_op_preserving_type_keeps_non_inferrable_type() {
        // `LoadArg`'s type is not structurally inferrable (the same class as
        // call returns and field loads). A rewriting pass that replaces the op
        // via `set_op_preserving_type` must carry the resolved type forward;
        // clearing it (as `set_op` does) would make the type silently vanish
        // before the next rebuild.
        let mut instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::LoadArg {
            dest: var(0),
            arg_index: 0,
        })
        .with_result_type(MockType::I32);
        assert_eq!(instr.result_type(), Some(&MockType::I32));

        instr.set_op_preserving_type(SsaOp::LoadArg {
            dest: var(0),
            arg_index: 1,
        });
        assert_eq!(
            instr.result_type(),
            Some(&MockType::I32),
            "a non-inferrable replacement carries the prior type forward",
        );

        // The clearing primitive still clears.
        instr.set_op(SsaOp::LoadArg {
            dest: var(0),
            arg_index: 2,
        });
        assert_eq!(instr.result_type(), None);
    }

    #[test]
    fn set_op_preserving_type_clears_type_for_destless_op() {
        // `SsaOp::Nop` defines nothing, so there is no value whose type could be
        // preserved. This is the removal primitive DCE and GVN drive through
        // `SsaEditor::nop_instruction`; carrying `I32` onto a `Nop` leaves a
        // dead type stamped on an instruction with no destination.
        let mut instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::LoadArg {
            dest: var(0),
            arg_index: 0,
        })
        .with_result_type(MockType::I32);

        instr.set_op_preserving_type(SsaOp::Nop);

        assert_eq!(
            instr.result_type(),
            None,
            "an op with no destination must not retain the replaced value's type",
        );
    }

    #[test]
    fn no_operand_instructions_report_no_uses() {
        let instr = SsaInstruction::<MockTarget>::new((), SsaOp::Return { value: None });

        assert!(instr.has_no_uses());
        assert_eq!(instr.all_variables(), Vec::<SsaVarId>::new());
    }
}