Lua: Lua5.3の逆アセンブラを書いたこと

***※ はてなブログから移行するために持ってきた遠い昔の記事です***

# はじめに

Lua 5.3のソースコードを追っていたのですが、コンパイラの部分で詰まってしまいました。
処理自体は特に難しいことはやっていないのですが、Luaのコンパイラは1パスで、構文解析、意味解析、コード生成がグチャグチャに入り組んでおり非常に読み辛いです。

複雑に絡み合ったCコードを読んでいるうちに、次第にソースコードから処理を把握するよりは、吐き出されるバイトコードから逆に実装を追った方が理解が進むのではないかと思い始めました。
ということでLua 5.3のバイトコードの逆アセンブラを開発しました。

* **ソースコード** : https://github.com/tacigar/lhades

### インストール ###

[LuaRocks](https://luarocks.org/)を入れて以下のコマンドを叩いてください。

```
git clone https://github.com/tacigar/lhades.git
cd lhades
luarocks make
```

簡単！LuaRocks最高！

### 使い方 ###

**`luac`**が吐く**バイトコードのファイル**を **`lhades`** に食わせてください。

```
lhades <filename>
```

#### 利用例 ####

例として以下の3回`Hello! John`と叫ぶだけのLuaプログラム準備し、`test.lua`として保存します。  

```lua
local name = "John"

function hello_world(count)
	for i = 1, count do
		print("Hello!, " .. name)
	end
end

hello_world(3)
```

次に以下のコマンドでこのプログラムをコンパイルしてください。

```
luac test.lua
```

すると、以下のバイトコードが書かれた`luac.out`というファイルが生成されるはずです。

```
1b4c 7561 5300 1993 0d0a 1a0a 0408 0408
0878 5600 0000 0000 0000 0000 0000 2877
4001 0a40 7465 7374 2e6c 7561 0000 0000
0000 0000 0002 0307 0000 0001 0000 006c
0000 0008 4080 8046 4040 0081 8000 0064
4000 0126 0080 0003 0000 0004 054a 6f68
6e04 0c68 656c 6c6f 5f77 6f72 6c64 1303
0000 0000 0000 0001 0000 0001 0001 0000
0000 0300 0000 0700 0000 0100 080b 0000
0041 0000 0080 0000 00c1 0000 0068 0001
8046 4140 0081 8100 00c5 0180 009d c101
0364 4100 0167 40fe 7f26 0080 0003 0000
0013 0100 0000 0000 0000 0406 7072 696e
7404 0948 656c 6c6f 212c 2002 0000 0000
0001 0000 0000 000b 0000 0004 0000 0004
0000 0004 0000 0004 0000 0005 0000 0005
0000 0005 0000 0005 0000 0005 0000 0004
0000 0007 0000 0005 0000 0006 636f 756e
7400 0000 000b 0000 000c 2866 6f72 2069
6e64 6578 2903 0000 000a 0000 000c 2866
6f72 206c 696d 6974 2903 0000 000a 0000
000b 2866 6f72 2073 7465 7029 0300 0000
0a00 0000 0269 0400 0000 0900 0000 0200
0000 055f 454e 5605 6e61 6d65 0700 0000
0100 0000 0700 0000 0300 0000 0900 0000
0900 0000 0900 0000 0900 0000 0100 0000
056e 616d 6501 0000 0007 0000 0001 0000
0005 5f45 4e56                         
```

このファイルを以下のコマンドで`lhades`に食わせてください。

```
lhades luac.out
```

すると見事アセンブリコードが標準出力されるはずです！やったぜ！

```
source name "@test.lua": 0 - 0: 0 params, 3 stacks
.codes: 7
  [1] LOADK           0               0    ; R(0) := Kst(0)
  [2] CLOSURE         1               0    ; R(1) := closure(KPROTO[0])
  [3] SETTABUP        0     257       1    ; UpValue[0][RK(257)] := RK(1)
  [4] GETTABUP        1       0     257    ; R(1) := UpValue[0][RK(257)]
  [5] LOADK           2               2    ; R(2) := Kst(2)
  [6] CALL            1       2       1    ; R(1), ... ,R(1+1-2) := R(1)(R(1+1), ... ,R(1+2-1))
  [7] RETURN          0               1    ; return R(0), ... ,R(0+1-2)
.consts: 3
  [0] "John"
  [1] "hello_world"
  [2] 3
.upvalues: 1
  [0] inStack: true, index: 0    ; _ENV
.locals: 1
  [0] "name" (1 - 7)
.protos: 1
  [0] source name "@test.lua": 3 - 7: 1 params, 8 stacks
    .codes: 11
      [ 1] LOADK           1               0    ; R(1) := Kst(0)
      [ 2] MOVE            2               0    ; R(2) := R(0)
      [ 3] LOADK           3               0    ; R(3) := Kst(0)
      [ 4] FORPREP         1               5    ; R(1)-=R(1+2); pc+=5
      [ 5] GETTABUP        5       0     257    ; R(5) := UpValue[0][RK(257)]
      [ 6] LOADK           6               2    ; R(6) := Kst(2)
      [ 7] GETUPVAL        7               1    ; R(7) := UpValue[1]
      [ 8] CONCAT          6       6       7    ; R(6) := R(6).. ... ..R(7)
      [ 9] CALL            5       2       1    ; R(5), ... ,R(5+1-2) := R(5)(R(5+1), ... ,R(5+2-1))
      [10] FORLOOP         1              -6    ; R(1)+=R(1+2); if R(1) <?= R(1+1) then { pc+=-6; R(1+3)=R(1) }
      [11] RETURN          0               1    ; return R(0), ... ,R(0+1-2)
    .consts: 3
      [0] 1
      [1] "print"
      [2] "Hello!, "
    .upvalues: 2
      [0] inStack: false, index: 0    ; _ENV
      [1] inStack: true, index: 0    ; name
    .locals: 5
      [0] "count" (0 - 11)
      [1] "(for index)" (3 - 10)
      [2] "(for limit)" (3 - 10)
      [3] "(for step)" (3 - 10)
      [4] "i" (4 - 9)
    .protos: 0
```

なおLuaのアセンブリコードが読めない人は以下を参考にしてください(説明責任放棄)。

* [Lua Bytecode Reference](https://the-ravi-programming-language.readthedocs.io/en/latest/lua_bytecode_reference.html)