tjs2dec\decompile/
hlir.rs

1use anyhow::Result;
2
3use super::ssa::{SsaBlock, SsaInsn, SsaProgram, Var, VarId};
4
5/// HLIR is a higher-level intermediate representation intended to be "decompile-friendly".
6///
7/// In this initial implementation, HLIR is deliberately conservative:
8/// - It preserves basic blocks, φ-nodes, and explicit side-effects.
9/// - It does not attempt structured control-flow reconstruction.
10/// - It only folds a subset of simple unary/binary register operations.
11///
12/// The goal is to provide a stable foundation for later passes (SSA cleanup, DCE, type info,
13/// control-flow structuring, and finally TJS pretty-printing).
14
15#[derive(Debug, Clone)]
16pub enum HExpr {
17    Var(VarId),
18    Opaque { op: &'static str, args: Vec<VarId> },
19}
20
21#[derive(Debug, Clone)]
22pub enum HStmt {
23    Phi {
24        result: VarId,
25        args: Vec<(usize, VarId)>,
26    },
27    Let {
28        defs: Vec<VarId>,
29        expr: HExpr,
30    },
31    Opaque {
32        defs: Vec<VarId>,
33        op: &'static str,
34        uses: Vec<VarId>,
35    },
36}
37
38#[derive(Debug, Clone)]
39pub struct HlirBlock {
40    pub id: usize,
41    pub start_pc: usize,
42    pub pred: Vec<usize>,
43    pub succ: Vec<usize>,
44    pub stmts: Vec<HStmt>,
45}
46
47#[derive(Debug, Clone)]
48pub struct HlirProgram {
49    pub obj_index: usize,
50    pub blocks: Vec<HlirBlock>,
51    pub entry_block: usize,
52}
53
54impl HlirProgram {
55    pub fn from_ssa(ssa: &SsaProgram) -> Result<Self> {
56        let mut blocks = Vec::new();
57        for b in &ssa.blocks {
58            blocks.push(lower_block(b));
59        }
60        Ok(Self {
61            obj_index: ssa.obj_index,
62            blocks,
63            entry_block: ssa.entry_block,
64        })
65    }
66
67    pub fn dump(&self) -> String {
68        let mut out = String::new();
69        for b in &self.blocks {
70            out.push_str(&format!(
71                "-- bb{} @{} (pred={:?}, succ={:?})\n",
72                b.id, b.start_pc, b.pred, b.succ
73            ));
74            for st in &b.stmts {
75                match st {
76                    HStmt::Phi { result, args } => {
77                        out.push_str(&format!("  {} = phi [", fmt_vid(*result)));
78                        for (i, (pred, v)) in args.iter().enumerate() {
79                            if i != 0 {
80                                out.push_str(", ");
81                            }
82                            out.push_str(&format!("bb{}: {}", pred, fmt_vid(*v)));
83                        }
84                        out.push_str("]\n");
85                    }
86                    HStmt::Let { defs, expr } => {
87                        if !defs.is_empty() {
88                            out.push_str("  ");
89                            for (i, d) in defs.iter().enumerate() {
90                                if i != 0 {
91                                    out.push_str(", ");
92                                }
93                                out.push_str(&fmt_vid(*d));
94                            }
95                            out.push_str(" = ");
96                        } else {
97                            out.push_str("  ");
98                        }
99                        match expr {
100                            HExpr::Var(v) => out.push_str(&fmt_vid(*v)),
101                            HExpr::Opaque { op, args } => {
102                                out.push_str(op);
103                                if !args.is_empty() {
104                                    out.push('(');
105                                    for (i, a) in args.iter().enumerate() {
106                                        if i != 0 {
107                                            out.push_str(", ");
108                                        }
109                                        out.push_str(&fmt_vid(*a));
110                                    }
111                                    out.push(')');
112                                }
113                            }
114                        }
115                        out.push('\n');
116                    }
117                    HStmt::Opaque { defs, op, uses } => {
118                        out.push_str("  ");
119                        if !defs.is_empty() {
120                            for (i, d) in defs.iter().enumerate() {
121                                if i != 0 {
122                                    out.push_str(", ");
123                                }
124                                out.push_str(&fmt_vid(*d));
125                            }
126                            out.push_str(" = ");
127                        }
128                        out.push_str(op);
129                        if !uses.is_empty() {
130                            out.push('(');
131                            for (i, u) in uses.iter().enumerate() {
132                                if i != 0 {
133                                    out.push_str(", ");
134                                }
135                                out.push_str(&fmt_vid(*u));
136                            }
137                            out.push(')');
138                        }
139                        out.push('\n');
140                    }
141                }
142            }
143        }
144        out
145    }
146}
147
148fn lower_block(b: &SsaBlock) -> HlirBlock {
149    let mut stmts = Vec::new();
150    for p in &b.phi {
151        stmts.push(HStmt::Phi {
152            result: p.result,
153            args: p.args.clone(),
154        });
155    }
156    for insn in &b.insns {
157        stmts.push(lower_insn(insn));
158    }
159    HlirBlock {
160        id: b.id,
161        start_pc: b.start_pc,
162        pred: b.pred.clone(),
163        succ: b.succ.clone(),
164        stmts,
165    }
166}
167
168fn lower_insn(insn: &SsaInsn) -> HStmt {
169    // Very conservative lowering for now.
170    // As we learn more semantics, this can fold into structured expressions.
171    if insn.op == -1 {
172        return HStmt::Opaque {
173            defs: insn.defs.clone(),
174            op: insn.mnemonic,
175            uses: insn.uses.clone(),
176        };
177    }
178
179    if insn.mnemonic == "VM_CP" && insn.defs.len() == 1 && insn.uses.len() == 1 {
180        return HStmt::Let {
181            defs: insn.defs.clone(),
182            expr: HExpr::Var(insn.uses[0]),
183        };
184    }
185
186    HStmt::Opaque {
187        defs: insn.defs.clone(),
188        op: insn.mnemonic,
189        uses: insn.uses.clone(),
190    }
191}
192
193fn fmt_vid(v: VarId) -> String {
194    match v.var {
195        Var::Reg(r) => format!("r{}#{}", r, v.ver),
196        Var::Flag => format!("flag#{}", v.ver),
197        Var::Exception => format!("exc#{}", v.ver),
198    }
199}