unluac 1.0.0

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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# LuaJIT Dialect 接入设计与实现状态

## 背景

当前仓库已经支持两条 bytecode family:

- PUC-Lua 5.1-5.5
- Luau

这两条线已经说明一个事实:

- “源码语义接近” 不代表 “bytecode 协议可以复用”
- parser / raw model / transformer 的 family 边界,必须按真实 bytecode 格式划分

LuaJIT 就属于第三条 family。

虽然 LuaJIT 的源码语义整体更接近 Lua 5.1,但它的 bytecode dump:

- header 不是 `\x1bLua`
- proto 序列化协议不是 PUC-Lua 的 `lundump`
- 常量池里包含 `I64/U64/COMPLEX/TAB/CHILD` 这类 LuaJIT 专属项
- dump header 还带有 `FFI/FR2/STRIP` 等兼容 flag

因此,LuaJIT 接入不能走“把它伪装成 Lua 5.1,再在 parser/transformer 里补特判”的路线。
这会把公共模型重新拉回错误抽象,后面很容易重复 Luau 接入前的那些结构性问题。

## 目标

第一阶段 LuaJIT 接入的目标是:

- 能解析 LuaJIT bytecode dump
- 能走完现有 decompile pipeline
- 能输出 LuaJIT 可以重新执行的源码
- 生成结果优先面向 `LuaJIT 2.1` 源码语法

换句话说,第一阶段目标仍然是:

- 可执行
- 可复编译
- 结构正确

而不是:

- 高保真恢复原始源码写法
- 一次性兼容 LuaJIT 2.0 和 2.1 的所有 dump 变体

## 非目标

第一阶段明确不做以下事情:

- 不承诺同时完整支持 LuaJIT 2.0 和 2.1
- 不恢复原始 `ffi.cdef` 文本排版
- 不保证恢复原始 cdata 字面量写法
- 不为了兼容 LuaJIT 而污染 PUC-Lua 5.1 parser family
- 不把 `luajit20` / `luajit21` 暴露为新的公开 dialect 名字

## 核心约束

### 1. LuaJIT 必须被视为独立 dialect family

在 parser / raw model 层,LuaJIT 与 PUC-Lua 5.1 不共享协议。

这意味着:

- `Dialect` 需要新增 `LuaJit`
- `ChunkLayout` 需要新增 `LuaJit` payload
- constant pool / opcode / debug payload 都需要 LuaJIT 专属 extra

源码层和 Lua 5.1 接近,不等于 bytecode 层可以复用 `lua51` parser。

### 2. 对外只保留单一 `luajit` 入口

当前决策是:

- 对外公开接口只保留一个 `luajit` dialect
- 第一阶段只明确支持 `LuaJIT 2.1`
- 内部按 dump version 与 header flags 分支

这样做的原因是:

- 用户侧接口保持简洁
- 2.0/2.1 的差异可以在 family 内部建模
- 避免 CLI、tests、文档和实现矩阵过早膨胀

### 3. 第一阶段优先兼容 LuaJIT 2.1

这是当前的推荐路线。

原因:

- LuaJIT 2.1 dump header revision 已经不同于 2.0
- 2.1 多了 `BCDUMP_F_FR2`
- 仓库现有 `tests/lua_cases/luajit` 样例也更偏向 2.1 源码特性

因此第一阶段应明确写成:

- 支持 `BCDUMP_VERSION = 2`
- 正确解析 `BCDUMP_F_STRIP` / `BCDUMP_F_FFI` / `BCDUMP_F_FR2`
- 生成目标以 `LuaJIT 2.1` 源码能力为准

### 4. 避免 downstream fallback

和 Luau 一样,LuaJIT 接入优先做结构性建模:

- raw model 对齐真实 dump 结构
- transformer 对齐真实 VM 语义
- AST / generate 对齐真实目标语法能力

不要在 AST / generate 后层为 parser / transformer 的缺口做静默兜底。

## 版本策略

### 公开策略

公开接口继续只有一个:

- `luajit`

不新增:

- `luajit20`
- `luajit21`

### 内部策略

内部需要显式记录:

- dump version
- dump flags

推荐的内部布局类似:

- `LuaJitChunkLayout { dump_version, flags }`

并从 flags 再派生 capability:

- `strip_debug`
- `uses_ffi`
- `fr2`

### 为什么不公开拆版本

LuaJIT 2.0 和 2.1 确实不是同一套 dump header revision:

- 2.0: `BCDUMP_VERSION = 1`
- 2.1: `BCDUMP_VERSION = 2`

同时 2.1 还引入了:

- `BCDUMP_F_FR2`

但这些差异更适合在同一个 `LuaJIT` family 内部处理,而不是变成两个公开 dialect。

原因:

- 用户不会因为 CLI 参数多一个 `luajit21` 而得到更多价值
- 真正需要维护的是 parser/transformer 内部 capability,而不是公开名字
- 先公开拆版本,后面很难收回

### 第一阶段承诺边界

第一阶段建议明确承诺:

- 支持 LuaJIT 2.1 dump format
-`BCDUMP_VERSION = 2`

第一阶段不承诺:

- LuaJIT 2.0 dump format
- 任何未验证的旧 dump 变体

LuaJIT 2.0 可以作为后续 Phase 单独补齐,但不应拖住 2.1 主线。

## 现状评估

### parser

当前 parser family 只有:

- `PucLua`
- `Luau`

还没有:

- `LuaJit`

因此 LuaJIT parser 的工作不是“接到现有 lua51 parser 上”,而是:

- 扩展公共 raw model
- 新增 `src/parser/dialect/luajit`
- 按 LuaJIT dump 协议单独解析

#### LuaJIT parser 最低需要覆盖的内容

- `ESC 'L' 'J'` header
- dump version
- dump flags
- optional chunk name
- proto table
- instruction stream
- upvalue metadata
- KGC constants
- number constants
- debug info

#### LuaJIT 常量池与 PUC-Lua 的关键差异

LuaJIT dump 需要能建模至少这些 KGC 类型:

- child proto
- constant table
- int64
- uint64
- complex
- string

这一步如果建模不对,后续 FFI/cdata/imaginary 相关样例都不可能稳定恢复。

### transformer

LuaJIT 的 VM 语义比 Luau 更接近 Lua 5.1,但仍然不是同一套 bytecode opcode。

因此 transformer 层建议:

- 新增 `src/transformer/dialect/luajit`
- 不复用 `src/transformer/dialect/lua51/parser`
- 但可以复用 Lua 5.1 语义上已经存在的很多 low-IR 抽象

LuaJIT transformer 很可能比 Luau 更适合复用这些现有抽象:

- 闭包
- upvalue
- 数值 for / 泛型 for
- method call
- table access
- 普通 branch / loop

但 parser 到 low-IR 的 lowering 仍然必须独立实现。

### AST / generate

LuaJIT 的源码目标与当前 Lua 5.x / Luau 都不完全一样。

第一阶段生成目标建议直接瞄准:

- `LuaJIT 2.1`

原因:

- 2.1 支持范围更接近仓库已有 LuaJIT case
- 后续如果再补 2.0,可以在内部 target capability 上细分

第一阶段至少需要考虑这些语法能力:

- `goto` / label
- Lua 5.1 风格局部函数 / upvalue / 环境语义
- LuaJIT 特有 numeric / cdata 常量表示
- `ffi` / `jit` / `bit` 等库调用按普通全局/require 调用处理

这里要特别注意:

- LuaJIT 目标不是 Luau,也不是 Lua 5.4/5.5
- 不应生成 `_ENV``<close>``local<const>` 这类不属于 LuaJIT 目标的语法

### tests / toolchain

当前仓库已经完成:

- `DecompileDialect::Luajit`
- `LuaCaseDialect::Luajit`
- LuaJIT runtime/compiler 抽象
- `tests/lua_cases/luajit/*` 接入 `case-health`
- `tests/lua_cases/luajit/*``tests/lua_cases/common/*` 接入 `decompile-pipeline-health`

当前测试策略仍然保持为:

- 源码 -> 本地 LuaJIT 编译 -> 反编译 -> 本地 LuaJIT 重新执行
- 不把预生成 dump fixture 当成稳定契约

## 设计原则

### 原则一: 先把 LuaJIT 当成 family,再讨论 2.0/2.1

优先顺序应该是:

1. 先建立 `LuaJit` family
2. 再确定 2.1 parser/transformer 主线
3. 最后补 2.0 兼容

而不是反过来。

### 原则二: 先支持 2.1,再补 2.0

第一阶段就同时兼容 2.0 与 2.1,会让 parser / tests / toolchain / generate 的
复杂度明显上涨,而且收益不高。

更合理的路线是:

- Phase 1: 只做 2.1
- Phase 2: 在既有 `luajit` family 上补 2.0

### 原则三: 测试工具链先对齐 runtime,而不是先追求 cross-target dump

LuaJIT 2.1 的 dump flags 里包含 `FR2`。这意味着:

- bytecode dump 与 runtime 运行模式存在兼容性约束

因此第一阶段 tests 的核心要求应是:

- “编译 bytecode 的 toolchain”和“执行 bytecode 的 runtime”必须来自同一套 LuaJIT

而不是一开始就追求:

- 多 host mode
- 多 target mode
- 预生成二进制 fixture

第一阶段测试更适合继续走:

- 源码 -> 本地 LuaJIT 编译 -> 反编译 -> 本地 LuaJIT 重新执行

## 分层设计

### 一、decompile 入口

需要新增:

- `DecompileDialect::Luajit`

CLI 第一阶段只支持显式选择:

- `--dialect=luajit`

虽然 LuaJIT header 有明确魔数,后续可以评估自动识别;
但第一阶段继续保持显式 dialect,更符合当前仓库入口设计。

### 二、parser 层

#### 2.1 公共 raw model

`src/parser/raw.rs` 需要从当前:

- `PucLua`
- `Luau`

扩展到:

- `PucLua`
- `Luau`
- `LuaJit`

需要新增的 family-aware payload 至少包括:

- `LuaJitChunkLayout`
- `LuaJitHeaderExtra`
- `LuaJitConstPoolExtra`
- `LuaJitInstrExtra`
- `LuaJitProtoExtra`
- `LuaJitDebugExtra`
- `LuaJitUpvalueExtra`

#### 2.2 新增 LuaJIT parser 目录

新增:

- `src/parser/dialect/luajit/mod.rs`
- `src/parser/dialect/luajit/parser.rs`
- `src/parser/dialect/luajit/raw.rs`
- `src/parser/dialect/luajit/debug.rs`

#### 2.3 第一阶段 parser 支持范围

第一阶段建议明确写成:

- 只接受 `BCDUMP_VERSION = 2`

遇到别的 dump version:

- 直接报 unsupported

不要先写成“也许能解析”。

### 三、transformer 层

新增:

- `src/transformer/dialect/luajit/*`

第一阶段 transformer 重点:

- 基本算术 / 比较 / branch
- 数值 for / 泛型 for
- closure / upvalue
- method call
- table access
- goto / label 对应的控制流形状
- LuaJIT 常量类型向 low-IR 的映射

这里有一个重要决策:

- LuaJIT 虽然接近 Lua 5.1,但 transformer 不应依赖“假装它是 lua51”
- 更合理的是“独立 parser/transformer,尽量复用 low-IR 语义抽象”

### 四、AST / generate 层

第一阶段建议新增:

- `AstDialectVersion::LuaJit`

它的 capability 应以 LuaJIT 2.1 为准。

初步建议:

- `goto_label = true`
- `continue_stmt = false`
- `local_close = false`
- `local_const = false`
- `global_decl = false`
- `global_const = false`

第一阶段目标不是恢复“原始 LuaJIT 写法”,而是:

- 生成 LuaJIT 能执行的源码

因此像这些内容可以允许 canonical 化:

- cdata 常量写法
- hexfloat 常量写法
- FFI 相关调用周围的局部变量命名

但前提是:

- 语义不能丢
- 不要为了弥补前层缺口,强行在 emitter 里注入静默 helper/fallback

### 五、tests / toolchain

第一阶段 tests 需要新增:

- `LuaCaseDialect::Luajit`
- `LUAJIT_ONLY`
- LuaJIT runtime/compiler 抽象

运行协议建议:

- 运行源码: `luajit source.lua`
- 编译源码: `luajit -b source.lua output`
- 运行 dump: `luajit output`

但要注意:

- bytecode dump 必须和本地 runtime 模式匹配
- 第一阶段不建议把预生成 dump fixture 提交进仓库当稳定契约

因此第一阶段测试策略建议:

- 先把 `tests/lua_cases/luajit` 纳入 `case-health`
- 等 parser / transformer / generate 基本打通后,再纳入 `decompile-pipeline-health`

## 版本差异与后续扩展

### 2.1 与 2.0 的关键差异

当前已确认的关键点:

- 2.0: `BCDUMP_VERSION = 1`
- 2.1: `BCDUMP_VERSION = 2`
- 2.1 新增 `BCDUMP_F_FR2`

这说明:

- 未来要补 2.0,一定是 parser 内部的正式版本分支
- 但不意味着现在就要公开两个 dialect

### 后续 2.0 支持策略

当第一阶段 2.1 稳定后,第二阶段可以:

- `luajit` parser 内补 `dump_version = 1`
- 为 transformer / generate 增加必要 capability 差异
- 再补 2.0 专属测试样例与工具链验证

## 当前决策

目前明确采用以下路线:

- LuaJIT 作为独立 dialect family 接入
- 对外只保留单一 `luajit` dialect
- 第一阶段只支持 LuaJIT 2.1 dump format
- parser / transformer 内部按 dump version 与 flags 分支
- 不把 `luajit20` / `luajit21` 暴露成公开 dialect
- 生成目标先对齐 LuaJIT 2.1 源码能力

## 建议的推进顺序

1. 先新增 `docs/dialect/luajit.md`
2. 再新增 `DecompileDialect::Luajit`
3. 再打通 tests / toolchain 的 LuaJIT runtime/compiler 抽象
4. 再扩展 parser raw model,新增 `LuaJit` family
5. 再实现 LuaJIT 2.1 parser
6. 再实现 LuaJIT 2.1 transformer
7. 最后补 AST / generate 的 LuaJIT target
8. 等 2.1 稳定后,再单独评估 LuaJIT 2.0

## 当前状态与 TODO

### 当前状态

截至目前,LuaJIT 主线已经打通,现状是:

- 已新增 `DecompileDialect::Luajit`
- 已新增 LuaJIT parser family: `src/parser/dialect/luajit/*`
- 已新增 LuaJIT transformer lowering: `src/transformer/dialect/luajit/*`
- 公共 raw model 已扩展 `LuaJit` family 与 `LuaJitChunkLayout`
- AST / generate 已新增 `AstDialectVersion::LuaJit`
- tests/toolchain 已支持 LuaJIT runtime/compiler 协议
- `tests/lua_cases/common``tests/lua_cases/luajit` 已能在 `luajit` 下走完整个 decompile pipeline
- 当前 `cargo unit-test --jobs 8` 已通过,矩阵为 `982/982`

当前这条线的实际承诺边界仍然是:

- 公开接口只有一个 `luajit`
- 第一阶段只明确支持 `LuaJIT 2.1`
- parser 只接受 `BCDUMP_VERSION = 2`
- 目标是“生成 LuaJIT 可执行、可复编译源码”,不是恢复原始 FFI/cdata 写法

### TODO

后续建议的具体任务:

- 继续补 LuaJIT 2.1 的 regression 测试,优先沉淀这次接入过程中修掉的 tricky/control-flow 场景
- 明确文档化当前已支持的 opcode / constant / debug info 边界,避免“看起来支持但其实未验证”
- 第二阶段评估并设计 `LuaJIT 2.0` 支持方案,在内部按 dump version 分支,而不是公开新 dialect
- 如果后续需要支持更多 dump 变体,再把 `dump flags -> capability` 的映射进一步收紧并写入测试矩阵