unluac 1.2.8

Multi-dialect Lua decompiler written in Rust.
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
//! 这个子模块负责把寄存器读取解释成 local/temp/entry 值引用。
//!
//! 它依赖 Dataflow 的 `use_values` 和 bindings 层已经分配好的 temp/local 身份,不会在这里
//! 重新做 SSA 合流判定。
//! 例如:某条指令读取 `r0`,若对应唯一 `TempId`,这里会直接降成 `TempRef(t0)`。

use super::*;

pub(crate) fn expr_for_reg_use(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    instr_ref: InstrRef,
    reg: Reg,
) -> HirExpr {
    if let Some(local) = loop_local_for_reg(lowering, block, reg) {
        return HirExpr::LocalRef(local);
    }
    let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
        return expr_for_entry_reg(lowering, reg);
    };

    if values.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    if values.len() == 1 {
        let value = values
            .iter()
            .next()
            .expect("len checked above, exactly one SSA-like value exists");
        return match value {
            SsaValue::Def(def) => HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]),
            SsaValue::Phi(phi) => HirExpr::TempRef(lowering.bindings.phi_temps[phi.index()]),
        };
    }

    unresolved_expr(format!(
        "multi-value use r{} @{}",
        reg.index(),
        instr_ref.index()
    ))
}

pub(crate) fn expr_for_closure_capture(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    instr_ref: InstrRef,
    dst: Reg,
    source: crate::transformer::CaptureSource,
) -> HirExpr {
    match source {
        crate::transformer::CaptureSource::Reg(reg) if reg == dst => {
            let self_temp = lowering.bindings.instr_fixed_defs[instr_ref.index()]
                .first()
                .copied()
                .expect("closure writes exactly one fixed target");
            HirExpr::TempRef(self_temp)
        }
        crate::transformer::CaptureSource::Reg(reg) => {
            // 先尝试正常的 SSA use-def 解析
            let expr = expr_for_reg_use(lowering, block, instr_ref, reg);
            // 互递归前向声明模式:Lua upvalue 是引用变量槽而非快照,closure 实际执行
            // 时看到的是寄存器的最终值。需要 forward_def 的场景有两种:
            //  1. 入口空值:寄存器在捕获点没有到达定义(entry-reg),说明没有显式
            //     初始化;普通读取会把它解释成 nil,但 closure 捕获的是槽位,需要继续
            //     向后找同一 block 里的最终定义(如 `local a, b; a = function() b()... end`)。
            //  2. LOADNIL 前缀:三路互递归 `local a, b, c` 编译时先 LOADNIL r2..r4
            //     再依次 CLOSURE,此时 SSA 能看到 LOADNIL 的定义(TempRef),但该
            //     定义只是占位 nil。真正的值是后续 CLOSURE 写入的同一寄存器。
            let entry_empty = reg_use_is_entry_empty(lowering, instr_ref, reg);
            let should_forward = match &expr {
                _ if entry_empty => true,
                HirExpr::TempRef(_) => is_loadnil_def(lowering, instr_ref, reg),
                _ => false,
            };
            if should_forward
                && let Some(forward_expr) = forward_def_in_block(lowering, block, instr_ref, reg)
            {
                return forward_expr;
            }
            expr
        }
        crate::transformer::CaptureSource::Upvalue(upvalue) => {
            HirExpr::UpvalueRef(UpvalueId(upvalue.index()))
        }
    }
}

fn reg_use_is_entry_empty(lowering: &ProtoLowering<'_>, instr_ref: InstrRef, reg: Reg) -> bool {
    lowering
        .dataflow
        .use_values_at(instr_ref)
        .get(reg)
        .is_none_or(|values| values.is_empty())
        && reg.index() >= lowering.bindings.params.len()
        && !lowering.bindings.entry_local_regs.contains_key(&reg)
}

/// 检查某条指令读取的寄存器的 SSA 到达定义是否来自 LOADNIL 指令。
///
/// 用于在 closure capture 解析中区分"有意义的非 nil 定义"和"仅仅是 LOADNIL 占位"。
/// 后者在 `local a, b, c` + 三路互递归编译结果中出现:
/// LOADNIL r2..r4 之后紧跟 CLOSURE r2/r3/r4,capture 的 SSA 到达定义
/// 虽然存在(不是 Unresolved)但只是 LOADNIL 的占位 nil。
fn is_loadnil_def(lowering: &ProtoLowering<'_>, instr_ref: InstrRef, reg: Reg) -> bool {
    let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
        return false;
    };
    if values.len() != 1 {
        return false;
    }
    let value = values.iter().next().unwrap();
    let SsaValue::Def(def) = value else {
        return false;
    };
    let def_instr = lowering.dataflow.def_instr(def);
    matches!(
        &lowering.proto.instrs[def_instr.index()],
        crate::transformer::LowInstr::LoadNil(_)
    )
}

/// 在当前 block 中查找 `instr_ref` 之后最后一个写入 `reg` 的定义。
///
/// Lua upvalue 引用的是变量槽而非快照,所以 closure 捕获时应指向该寄存器
/// 在此 block 内的最终定义。这个函数查找从 `instr_ref` 之后到 block 末尾
/// 的最后一个 must_def,确保互递归和 LOADNIL+CLOSURE 前向声明模式都能
/// 正确解析到最终绑定。
fn forward_def_in_block(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    instr_ref: InstrRef,
    reg: Reg,
) -> Option<HirExpr> {
    let block_range = lowering.cfg.blocks[block.index()].instrs;
    let mut last_def_temp = None;
    for idx in (instr_ref.index() + 1)..block_range.end() {
        for def in &lowering.dataflow.instr_defs[idx] {
            if lowering.dataflow.def_reg(*def) == reg {
                last_def_temp = Some(lowering.bindings.fixed_temps[def.index()]);
            }
        }
    }
    last_def_temp.map(HirExpr::TempRef)
}

/// 某些结构恢复需要读取“进入 block 时这个寄存器代表哪个稳定值”,而不是某条真实 use。
///
/// 例如值短路被恢复成 `if + assign` 后,leaf block 可能根本没有再次显式读取结果寄存器,
/// 但我们仍然需要知道“走到这个 leaf 时 merge 值应该取谁”。
pub(crate) fn expr_for_reg_at_block_entry(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    reg: Reg,
) -> HirExpr {
    if let Some(local) = loop_local_for_reg(lowering, block, reg) {
        return HirExpr::LocalRef(local);
    }
    let range = lowering.cfg.blocks[block.index()].instrs;
    if range.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    let Some(values) = lowering.dataflow.reaching_values_at(range.start).get(reg) else {
        return expr_for_entry_reg(lowering, reg);
    };

    if values.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    if values.len() == 1 {
        let value = values
            .iter()
            .next()
            .expect("len checked above, exactly one SSA-like value exists");
        return match value {
            SsaValue::Def(def) => HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]),
            SsaValue::Phi(phi) => HirExpr::TempRef(lowering.bindings.phi_temps[phi.index()]),
        };
    }

    unresolved_expr(format!(
        "multi-value entry r{} block#{}",
        reg.index(),
        block.index()
    ))
}

/// 某些 `goto + label` 形状需要读取“离开 block 时这个寄存器的稳定值”。
///
/// 这和普通 `expr_for_reg_use` 不同:phi edge copy 不一定对应某条真实 use,
/// 也不能只看 `incoming.defs`,否则像“从 inner loop header 直接跳回 outer header”
/// 这种边会把 block 入口 phi 的稳定值丢掉。
pub(crate) fn expr_for_reg_at_block_exit(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    reg: Reg,
) -> HirExpr {
    if let Some(local) = loop_local_for_reg(lowering, block, reg) {
        return HirExpr::LocalRef(local);
    }

    let range = lowering.cfg.blocks[block.index()].instrs;
    let Some(last_instr_ref) = range.last() else {
        return expr_for_entry_reg(lowering, reg);
    };

    let effect = &lowering.dataflow.instr_effects[last_instr_ref.index()];
    if effect.fixed_must_defs.contains(&reg) {
        let Some(def) = fixed_def_for_reg(lowering, last_instr_ref, reg) else {
            return unresolved_expr(format!(
                "missing block-exit def r{} block#{}",
                reg.index(),
                block.index()
            ));
        };
        return HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]);
    }

    let mut values = lowering
        .dataflow
        .reaching_values_at(last_instr_ref)
        .get(reg)
        .map(|values| values.to_compact_set())
        .unwrap_or_default();
    if effect.fixed_may_defs.contains(&reg) {
        let Some(def) = fixed_def_for_reg(lowering, last_instr_ref, reg) else {
            return unresolved_expr(format!(
                "missing block-exit may-def r{} block#{}",
                reg.index(),
                block.index()
            ));
        };
        values.insert(SsaValue::Def(def));
    }

    if values.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    if values.len() == 1 {
        let value = values
            .iter()
            .next()
            .expect("len checked above, exactly one SSA-like value exists");
        return match value {
            SsaValue::Def(def) => HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]),
            SsaValue::Phi(phi) => HirExpr::TempRef(lowering.bindings.phi_temps[phi.index()]),
        };
    }

    unresolved_expr(format!(
        "multi-value exit r{} block#{}",
        reg.index(),
        block.index()
    ))
}

/// 当值恢复跨过被整体吸收的 branch 区域时,内部 leaf/node block 可能不会单独物化。
///
/// 这里允许沿着单一 `DefId` 继续下钻,但只展开“可以安全重复求值”的定义。
/// 像 `call/newtable/gettable` 这类一旦重复展开就可能改写求值次数或对象身份的值,
/// 仍然退回已有 temp,避免 HIR 先天带入错误语义。
pub(crate) fn expr_for_reg_use_inline(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    instr_ref: InstrRef,
    reg: Reg,
) -> HirExpr {
    if let Some(local) = loop_local_for_reg(lowering, block, reg) {
        return HirExpr::LocalRef(local);
    }
    let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
        return expr_for_entry_reg(lowering, reg);
    };

    if values.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    if values.len() == 1 {
        let value = values
            .iter()
            .next()
            .expect("len checked above, exactly one SSA-like value exists");
        return match value {
            SsaValue::Def(def) => expr_for_dup_safe_fixed_def(lowering, def)
                .unwrap_or_else(|| HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()])),
            SsaValue::Phi(phi) => HirExpr::TempRef(lowering.bindings.phi_temps[phi.index()]),
        };
    }

    unresolved_expr(format!(
        "multi-value use r{} @{}",
        reg.index(),
        instr_ref.index()
    ))
}

/// `single-eval` 只承诺“这次求值可以直接表达出来”,并不承诺“可以重复复制很多次”。
///
/// 这条语义专门服务短路节点的单次 test:像 `call(...)` 这种不可复制但可单次出现的值,
/// 在这里应该优先恢复成本体表达式,而不是先掉回 temp。
///
/// 纯表达式壳层(如 `not call()` / `call() + 1`)本身不会额外观察 call 的结果,
/// 被短路 header 整体吸收时也不会单独物化这些中间 temp。因此
/// `allow_call_consumed_by_pure_wrapper` 只应在 unary/binary/concat 这类纯壳层 operand
/// 中打开;普通 call-arg / table-base 仍传 `false`。
pub(crate) fn expr_for_reg_use_single_eval_with_call_policy(
    lowering: &ProtoLowering<'_>,
    block: BlockRef,
    instr_ref: InstrRef,
    reg: Reg,
    allow_call_consumed_by_pure_wrapper: bool,
) -> HirExpr {
    if let Some(local) = loop_local_for_reg(lowering, block, reg) {
        return HirExpr::LocalRef(local);
    }
    let Some(values) = lowering.dataflow.use_values_at(instr_ref).get(reg) else {
        return expr_for_entry_reg(lowering, reg);
    };

    if values.is_empty() {
        return expr_for_entry_reg(lowering, reg);
    }

    if values.len() == 1 {
        let value = values
            .iter()
            .next()
            .expect("len checked above, exactly one SSA-like value exists");
        return match value {
            SsaValue::Def(def) => {
                if lowering.dataflow.def_block(def) != block {
                    return HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]);
                }
                if def_has_intervening_use(lowering, def, instr_ref) {
                    return HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]);
                }
                if def_is_call_consumed_by_non_branch(lowering, def, instr_ref)
                    && (!allow_call_consumed_by_pure_wrapper
                        || def_has_later_use_after_pure_wrapper(lowering, def, instr_ref))
                {
                    return HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]);
                }
                expr_for_fixed_def_single_eval(lowering, def)
                    .unwrap_or_else(|| HirExpr::TempRef(lowering.bindings.fixed_temps[def.index()]))
            }
            SsaValue::Phi(phi) => HirExpr::TempRef(lowering.bindings.phi_temps[phi.index()]),
        };
    }

    unresolved_expr(format!(
        "multi-value use r{} @{}",
        reg.index(),
        instr_ref.index()
    ))
}

fn def_is_call_consumed_by_non_branch(
    lowering: &ProtoLowering<'_>,
    def: DefId,
    consumer_instr: InstrRef,
) -> bool {
    let def_instr = lowering.dataflow.def_instr(def);
    matches!(lowering.proto.instrs[def_instr.index()], LowInstr::Call(_))
        && !matches!(
            lowering.proto.instrs[consumer_instr.index()],
            LowInstr::Branch(_)
        )
}

// 纯壳层只代表同一次条件求值;如果 call 结果在壳层之后还被非 branch 指令读取,
// 展开 call 会把一次求值变成多次求值,因此必须退回 temp。最终 branch/test
// 读取同一个结果是这条条件求值的一部分,不应算作额外消费。
fn def_has_later_use_after_pure_wrapper(
    lowering: &ProtoLowering<'_>,
    def: DefId,
    wrapper_instr: InstrRef,
) -> bool {
    let def_reg = lowering.dataflow.def_reg(def);
    let def_block = lowering.dataflow.def_block(def);
    if lowering.cfg.instr_to_block.get(wrapper_instr.index()) != Some(&def_block) {
        return true;
    }

    let range = lowering.cfg.blocks[def_block.index()].instrs;
    for instr_index in (wrapper_instr.index() + 1)..range.end() {
        let effect = &lowering.dataflow.instr_effects[instr_index];
        if effect.fixed_uses.contains(&def_reg)
            && !matches!(lowering.proto.instrs[instr_index], LowInstr::Branch(_))
        {
            return true;
        }
        if effect.fixed_must_defs.contains(&def_reg) {
            return false;
        }
    }
    false
}

fn def_has_intervening_use(
    lowering: &ProtoLowering<'_>,
    def: DefId,
    consumer_instr: InstrRef,
) -> bool {
    let def_instr = lowering.dataflow.def_instr(def);
    if def_instr.index() >= consumer_instr.index() {
        return false;
    }
    let effect = &lowering.dataflow.instr_effects[def_instr.index()];
    effect.fixed_must_defs.iter().any(|reg| {
        ((def_instr.index() + 1)..consumer_instr.index()).any(|instr_index| {
            lowering.dataflow.instr_effects[instr_index]
                .fixed_uses
                .contains(reg)
        })
    })
}