Skip to main content

jdc_core/ir/
build.rs

1//! The front-end → core hand-off: how a basic block ends and what it produced.
2//!
3//! A front-end walks its own instruction stream, splits it into blocks that
4//! match [`crate::cfg::Cfg`], and emits one [`BlockResult`] per block, indexed
5//! by block id. Everything downstream (structuring, conversion, emission)
6//! works only from these values.
7
8use crate::ir::expr::{BinOp, ConcatPart, ConstVal, Expr};
9use crate::ir::stmt::Stmt;
10use crate::types::JavaType;
11
12/// How a basic block ends.
13#[derive(Debug, Clone)]
14pub enum Term {
15    Fallthrough,
16    Goto,
17    /// Jumps to `succ[1]` when `cond` is true; falls through to `succ[0]`.
18    Cond {
19        cond: Expr,
20    },
21    /// A multi-way branch. `default` is the machine offset of the default
22    /// arm when the front-end can identify it (JVM: the switch payload's
23    /// default; DEX: the `packed-switch`/`sparse-switch` payload's default);
24    /// `None` means "unknown / not recorded".
25    Switch {
26        selector: Expr,
27        targets: SwitchTargets,
28        default: Option<u32>,
29    },
30    Return(Option<Expr>),
31    Throw(Expr),
32    /// Legacy `jsr` (JVM only; kept so legacy class files can be modelled).
33    Jsr,
34    /// Legacy `ret` (JVM only).
35    Ret,
36}
37
38#[derive(Debug, Clone)]
39pub enum SwitchTargets {
40    /// `targets[i]` is the target for key `low + i`.
41    Table { low: i32, targets: Vec<u32> },
42    /// `(match value, target)` pairs.
43    Lookup { pairs: Vec<(i32, u32)> },
44}
45
46#[derive(Debug, Clone)]
47pub struct BlockResult {
48    /// Statements produced by this block, in order, covering everything up to
49    /// (not including) the terminator.
50    pub stmts: Vec<Stmt>,
51    /// Value-machine leftovers at block exit — for a stack machine, the
52    /// operand stack; for a register machine, normally empty. Only the
53    /// front-end's own merge handling consumes this.
54    pub out_stack: Vec<Expr>,
55    pub term: Term,
56}
57
58#[derive(Debug)]
59pub struct BuildError(pub String);
60
61pub type BResult<T> = Result<T, BuildError>;
62
63/// `Expr::Const(ConstVal::Int(i))`.
64pub fn int_const(i: i32) -> Expr {
65    Expr::Const(ConstVal::Int(i))
66}
67
68/// True if dropping this expression from a discarded-value context would
69/// change semantics (a call, a store, a field read that can trap, ...).
70pub fn has_side_effects(e: &Expr) -> bool {
71    match e {
72        Expr::Const(_) | Expr::Local { .. } | Expr::This | Expr::Raw(_) | Expr::RawT(..) => false,
73        Expr::New { .. }
74        | Expr::Method { .. }
75        | Expr::Invokedynamic { .. }
76        | Expr::Lambda(_)
77        | Expr::AnonNew { .. } => true,
78        Expr::Assign { .. } | Expr::PreIncDec { .. } | Expr::PostIncDec { .. } => true,
79        Expr::Field { owner, .. } => match owner.as_deref() {
80            // `this.f` in both spellings (implicit-this and the receiver
81            // register var 0, the ddc lifter's ABI slot) cannot NPE — the
82            // read is semantically dead in a discarded-value context.
83            None => false,
84            Some(Expr::This) => false,
85            Some(Expr::Local { var: 0, .. }) => false,
86            Some(_) => true,
87        },
88        Expr::ArrayIndex { .. } => true,
89        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => {
90            has_side_effects(e)
91        }
92        Expr::Bin { l, r, .. } => has_side_effects(l) || has_side_effects(r),
93        Expr::Cond { c, t, f } => has_side_effects(c) || has_side_effects(t) || has_side_effects(f),
94        Expr::NewArray { dims, init, .. } => {
95            dims.iter().any(has_side_effects)
96                || init
97                    .as_ref()
98                    .map(|v| v.iter().any(has_side_effects))
99                    .unwrap_or(false)
100        }
101        Expr::NewMultiArray { dims, .. } => dims.iter().any(has_side_effects),
102        Expr::StringConcat(parts) => parts.iter().any(|p| match p {
103            ConcatPart::Str(e) => has_side_effects(e),
104            ConcatPart::Const(_) => false,
105        }),
106    }
107}
108
109/// If the value is a cmp sentinel, unfold into `(l, r, op)`; otherwise compare
110/// against zero.
111pub fn unfold_cmp(v: Expr, op_if_int: BinOp) -> (Expr, Expr, BinOp) {
112    if let Expr::Invokedynamic { name, args, .. } = &v {
113        if name.starts_with("\u{0}cmp") && args.len() == 2 {
114            let mut args = match v {
115                Expr::Invokedynamic { args, .. } => args,
116                _ => unreachable!(),
117            };
118            let r = args.pop().unwrap();
119            let l = args.pop().unwrap();
120            return (l, r, op_if_int);
121        }
122    }
123    (v, int_const(0), op_if_int)
124}
125
126/// A machine type reference (`[`-prefixed descriptors are array types).
127pub fn class_name_to_type(name: &str) -> JavaType {
128    if name.starts_with('[') {
129        crate::types::parse_field_descriptor(name).unwrap_or(JavaType::Object(name.into()))
130    } else {
131        JavaType::Object(name.into())
132    }
133}