luau-obfuscator 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +171 -74
- package/dist/index.d.cts +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.js +171 -74
- package/generated/grammers.luau +3654 -5314
- package/generated/tests.luau +2196 -3349
- package/package.json +1 -1
- package/scripts/test.js +1 -3
- package/src/passes/StringsToExpressions.ts +6 -6
- package/src/passes/Vmify.ts +24 -11
- package/src/passes/vmify/compiler.ts +90 -13
- package/src/passes/vmify/opcodes.ts +4 -0
- package/src/passes/vmify/runtime.ts +80 -51
- package/src/passes/vmify/serialize.ts +19 -13
|
@@ -2,17 +2,24 @@ import { Opcode, type OpcodeMap } from "./opcodes"
|
|
|
2
2
|
import type { VmNames } from "./names"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* VM 런타임
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 소스를 새로 찍어낸다.
|
|
5
|
+
* VM 런타임 v4 — opcode마다 독립된 핸들러 함수를 만드는 구조는 v3와 동일하다.
|
|
6
|
+
* 모든 전역 식별자(__vm_execute, __vm_handlers, ...)와 frame/Instr/Proto의 테이블
|
|
7
|
+
* 필드 키가 고정 문자열이 아니라, 빌드마다 무작위로 뽑힌 names(names.ts)와 opcode 번호
|
|
8
|
+
* 재배치 opcodeMap(opcodes.ts)을 받아 그때그때 소스를 새로 찍어낸다.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* "
|
|
10
|
+
* v3와의 결정적 차이 — dispatch를 tail call 재귀가 아니라 while 루프로 돌린다:
|
|
11
|
+
* - v3는 "Luau가 proper tail call을 보장하므로 return dispatch(frame, pc+1)가
|
|
12
|
+
* 스택을 안 쌓는다"는 전제로 각 핸들러가 dispatch를 꼬리 호출로 이어붙였다.
|
|
13
|
+
* 그런데 Luau는 (Lua 5.x와 달리) proper tail call을 하지 않는다. 정확한 스택
|
|
14
|
+
* 트레이스/디버깅을 위해 의도적으로 뺐다. 그래서 명령어 하나 실행할 때마다
|
|
15
|
+
* dispatch 프레임이 실제로 하나씩 쌓였고, 명령어 수백 개짜리 함수만 돌려도
|
|
16
|
+
* stack overflow가 났다.
|
|
17
|
+
* - v4에서는 dispatch가 while 루프를 돌며 핸들러를 호출한다. 핸들러는 dispatch를
|
|
18
|
+
* 부르지 않고 "다음 pc(숫자)"를 반환하고, RETURN만 결과 패킷(table.pack 결과)을
|
|
19
|
+
* 반환한다. 루프는 반환값이 숫자면 pc를 갱신하고, 테이블이면 그걸 풀어서 반환한다.
|
|
20
|
+
* 이제 함수 하나를 끝까지 도는 동안 스택 깊이는 일정하다.
|
|
14
21
|
* - "거대한 while+if-chain" 자체가 디컴파일러가 제일 먼저 찾는 VM 시그니처인데,
|
|
15
|
-
* 이
|
|
22
|
+
* 루프 본문이 if-chain이 아니라 handlers[op](...) 단일 디스패치라 그 패턴은 아니다.
|
|
16
23
|
* - opcode 번호 + 식별자 이름을 빌드마다 다르게 뽑기 때문에, 한 번 리버싱해서
|
|
17
24
|
* "MOVE=0, __vm_execute라는 함수가 진입점" 같은 지식을 얻어도 다음 산출물에는
|
|
18
25
|
* 그대로 안 먹힌다 — 매번 새로 분석해야 함.
|
|
@@ -21,16 +28,28 @@ import type { VmNames } from "./names"
|
|
|
21
28
|
* (compiler.ts가 emit() 시점에 같은 opcodeMap으로 인스트럭션에 번호를 박아 넣으므로
|
|
22
29
|
* 반드시 컴파일과 런타임 생성에 동일한 opcodeMap 인스턴스를 넘겨야 함).
|
|
23
30
|
*/
|
|
24
|
-
export function buildVmRuntimeSource(names: VmNames, opcodeMap: OpcodeMap): string {
|
|
31
|
+
export function buildVmRuntimeSource(names: VmNames, opcodeMap: OpcodeMap, debug = false): string {
|
|
25
32
|
const N = names
|
|
26
33
|
const op = (o: Opcode) => opcodeMap[o]
|
|
27
34
|
|
|
28
35
|
return `
|
|
29
36
|
local ${N.handlers} = {}
|
|
30
37
|
|
|
38
|
+
-- 각 핸들러는 "다음 pc(숫자)"를 반환한다. RETURN 핸들러만 table.pack 결과(테이블)를
|
|
39
|
+
-- 반환하며, 그게 함수 실행 종료 신호다. Luau는 proper tail call을 하지 않으므로
|
|
40
|
+
-- 핸들러들이 서로 꼬리 호출로 이어지면 명령어당 스택 프레임이 쌓여 터진다 — 그래서
|
|
41
|
+
-- 여기서 while 루프로 돌려 스택 깊이를 함수 실행 내내 일정하게 유지한다.
|
|
31
42
|
local function ${N.dispatch}(${N.frame}, pc)
|
|
32
|
-
local
|
|
33
|
-
|
|
43
|
+
local ${N.handlers} = ${N.handlers}
|
|
44
|
+
while true do
|
|
45
|
+
local instr = ${N.frame}.${N.code}[pc]
|
|
46
|
+
local next = ${N.handlers}[instr[1]](${N.frame}, pc, instr)
|
|
47
|
+
if type(next) == "number" then
|
|
48
|
+
pc = next
|
|
49
|
+
else
|
|
50
|
+
return table.unpack(next, 1, next.n)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
34
53
|
end
|
|
35
54
|
|
|
36
55
|
local function ${N.rk}(${N.frame}, x)
|
|
@@ -44,67 +63,67 @@ end
|
|
|
44
63
|
-- MOVE
|
|
45
64
|
${N.handlers}[${op(Opcode.MOVE)}] = function(${N.frame}, pc, instr)
|
|
46
65
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
|
|
47
|
-
return
|
|
66
|
+
return pc + 1
|
|
48
67
|
end
|
|
49
68
|
|
|
50
69
|
-- LOADK
|
|
51
70
|
${N.handlers}[${op(Opcode.LOADK)}] = function(${N.frame}, pc, instr)
|
|
52
71
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
|
|
53
|
-
return
|
|
72
|
+
return pc + 1
|
|
54
73
|
end
|
|
55
74
|
|
|
56
75
|
-- LOADBOOL
|
|
57
76
|
${N.handlers}[${op(Opcode.LOADBOOL)}] = function(${N.frame}, pc, instr)
|
|
58
77
|
${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
|
|
59
|
-
return
|
|
78
|
+
return pc + 1
|
|
60
79
|
end
|
|
61
80
|
|
|
62
81
|
-- LOADNIL
|
|
63
82
|
${N.handlers}[${op(Opcode.LOADNIL)}] = function(${N.frame}, pc, instr)
|
|
64
83
|
for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
|
|
65
|
-
return
|
|
84
|
+
return pc + 1
|
|
66
85
|
end
|
|
67
86
|
|
|
68
87
|
-- GETUPVAL
|
|
69
88
|
${N.handlers}[${op(Opcode.GETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
70
89
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
|
|
71
|
-
return
|
|
90
|
+
return pc + 1
|
|
72
91
|
end
|
|
73
92
|
|
|
74
93
|
-- SETUPVAL
|
|
75
94
|
${N.handlers}[${op(Opcode.SETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
76
95
|
${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
|
|
77
|
-
return
|
|
96
|
+
return pc + 1
|
|
78
97
|
end
|
|
79
98
|
|
|
80
99
|
-- GETGLOBAL (b는 RK가 아니라 상수풀 순수 인덱스)
|
|
81
100
|
${N.handlers}[${op(Opcode.GETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
82
101
|
${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
|
|
83
|
-
return
|
|
102
|
+
return pc + 1
|
|
84
103
|
end
|
|
85
104
|
|
|
86
105
|
-- SETGLOBAL
|
|
87
106
|
${N.handlers}[${op(Opcode.SETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
88
107
|
${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
|
|
89
|
-
return
|
|
108
|
+
return pc + 1
|
|
90
109
|
end
|
|
91
110
|
|
|
92
111
|
-- GETTABLE
|
|
93
112
|
${N.handlers}[${op(Opcode.GETTABLE)}] = function(${N.frame}, pc, instr)
|
|
94
113
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
|
|
95
|
-
return
|
|
114
|
+
return pc + 1
|
|
96
115
|
end
|
|
97
116
|
|
|
98
117
|
-- SETTABLE
|
|
99
118
|
${N.handlers}[${op(Opcode.SETTABLE)}] = function(${N.frame}, pc, instr)
|
|
100
119
|
${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
|
|
101
|
-
return
|
|
120
|
+
return pc + 1
|
|
102
121
|
end
|
|
103
122
|
|
|
104
123
|
-- NEWTABLE
|
|
105
124
|
${N.handlers}[${op(Opcode.NEWTABLE)}] = function(${N.frame}, pc, instr)
|
|
106
125
|
${N.frame}.${N.R}[instr[2]] = {}
|
|
107
|
-
return
|
|
126
|
+
return pc + 1
|
|
108
127
|
end
|
|
109
128
|
|
|
110
129
|
-- SELF (R[a] = R[b][key]; R[a+1] = R[b])
|
|
@@ -112,90 +131,90 @@ ${N.handlers}[${op(Opcode.SELF)}] = function(${N.frame}, pc, instr)
|
|
|
112
131
|
local obj = ${N.frame}.${N.R}[instr[3]]
|
|
113
132
|
${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
|
|
114
133
|
${N.frame}.${N.R}[instr[2] + 1] = obj
|
|
115
|
-
return
|
|
134
|
+
return pc + 1
|
|
116
135
|
end
|
|
117
136
|
|
|
118
137
|
-- ADD SUB MUL DIV MOD POW
|
|
119
138
|
${N.handlers}[${op(Opcode.ADD)}] = function(${N.frame}, pc, instr)
|
|
120
139
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
|
|
121
|
-
return
|
|
140
|
+
return pc + 1
|
|
122
141
|
end
|
|
123
142
|
${N.handlers}[${op(Opcode.SUB)}] = function(${N.frame}, pc, instr)
|
|
124
143
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
|
|
125
|
-
return
|
|
144
|
+
return pc + 1
|
|
126
145
|
end
|
|
127
146
|
${N.handlers}[${op(Opcode.MUL)}] = function(${N.frame}, pc, instr)
|
|
128
147
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
|
|
129
|
-
return
|
|
148
|
+
return pc + 1
|
|
130
149
|
end
|
|
131
150
|
${N.handlers}[${op(Opcode.DIV)}] = function(${N.frame}, pc, instr)
|
|
132
151
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
|
|
133
|
-
return
|
|
152
|
+
return pc + 1
|
|
134
153
|
end
|
|
135
154
|
${N.handlers}[${op(Opcode.MOD)}] = function(${N.frame}, pc, instr)
|
|
136
155
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
|
|
137
|
-
return
|
|
156
|
+
return pc + 1
|
|
138
157
|
end
|
|
139
158
|
${N.handlers}[${op(Opcode.POW)}] = function(${N.frame}, pc, instr)
|
|
140
159
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
|
|
141
|
-
return
|
|
160
|
+
return pc + 1
|
|
142
161
|
end
|
|
143
162
|
|
|
144
163
|
-- IDIV (Luau 바닥 나눗셈)
|
|
145
164
|
${N.handlers}[${op(Opcode.IDIV)}] = function(${N.frame}, pc, instr)
|
|
146
165
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
|
|
147
|
-
return
|
|
166
|
+
return pc + 1
|
|
148
167
|
end
|
|
149
168
|
|
|
150
169
|
-- CONCAT
|
|
151
170
|
${N.handlers}[${op(Opcode.CONCAT)}] = function(${N.frame}, pc, instr)
|
|
152
171
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
|
|
153
|
-
return
|
|
172
|
+
return pc + 1
|
|
154
173
|
end
|
|
155
174
|
|
|
156
175
|
-- UNM
|
|
157
176
|
${N.handlers}[${op(Opcode.UNM)}] = function(${N.frame}, pc, instr)
|
|
158
177
|
${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
|
|
159
|
-
return
|
|
178
|
+
return pc + 1
|
|
160
179
|
end
|
|
161
180
|
|
|
162
181
|
-- NOT
|
|
163
182
|
${N.handlers}[${op(Opcode.NOT)}] = function(${N.frame}, pc, instr)
|
|
164
183
|
${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
|
|
165
|
-
return
|
|
184
|
+
return pc + 1
|
|
166
185
|
end
|
|
167
186
|
|
|
168
187
|
-- LEN
|
|
169
188
|
${N.handlers}[${op(Opcode.LEN)}] = function(${N.frame}, pc, instr)
|
|
170
189
|
${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
|
|
171
|
-
return
|
|
190
|
+
return pc + 1
|
|
172
191
|
end
|
|
173
192
|
|
|
174
193
|
-- JMP (pc += a)
|
|
175
194
|
${N.handlers}[${op(Opcode.JMP)}] = function(${N.frame}, pc, instr)
|
|
176
|
-
return
|
|
195
|
+
return pc + instr[2]
|
|
177
196
|
end
|
|
178
197
|
|
|
179
198
|
-- EQ LT LE (값 생성형 비교)
|
|
180
199
|
${N.handlers}[${op(Opcode.EQ)}] = function(${N.frame}, pc, instr)
|
|
181
200
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
|
|
182
|
-
return
|
|
201
|
+
return pc + 1
|
|
183
202
|
end
|
|
184
203
|
${N.handlers}[${op(Opcode.LT)}] = function(${N.frame}, pc, instr)
|
|
185
204
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
|
|
186
|
-
return
|
|
205
|
+
return pc + 1
|
|
187
206
|
end
|
|
188
207
|
${N.handlers}[${op(Opcode.LE)}] = function(${N.frame}, pc, instr)
|
|
189
208
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
|
|
190
|
-
return
|
|
209
|
+
return pc + 1
|
|
191
210
|
end
|
|
192
211
|
|
|
193
212
|
-- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 — 보통 다음 명령은 JMP)
|
|
194
213
|
${N.handlers}[${op(Opcode.TEST)}] = function(${N.frame}, pc, instr)
|
|
195
214
|
if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
|
|
196
|
-
return
|
|
215
|
+
return pc + 2
|
|
197
216
|
end
|
|
198
|
-
return
|
|
217
|
+
return pc + 1
|
|
199
218
|
end
|
|
200
219
|
|
|
201
220
|
-- CALL
|
|
@@ -210,7 +229,13 @@ ${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
|
|
|
210
229
|
end
|
|
211
230
|
local args = table.create(nargs)
|
|
212
231
|
for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
|
|
213
|
-
|
|
232
|
+
${debug ? ` if type(fn) ~= "function" then
|
|
233
|
+
local mt = getmetatable(fn)
|
|
234
|
+
if not (mt and mt.__call) then
|
|
235
|
+
error(("[vmdebug] attempt to call a %s value: '%s' (source line %d)"):format(type(fn), tostring(instr[6]), tonumber(instr[5]) or -1), 0)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
` : ""} local rets = table.pack(fn(table.unpack(args, 1, nargs)))
|
|
214
239
|
if c == 1 then
|
|
215
240
|
-- discard
|
|
216
241
|
elseif c == 0 then
|
|
@@ -220,23 +245,27 @@ ${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
|
|
|
220
245
|
local want = c - 1
|
|
221
246
|
for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
222
247
|
end
|
|
223
|
-
return
|
|
248
|
+
return pc + 1
|
|
224
249
|
end
|
|
225
250
|
|
|
226
251
|
-- RETURN
|
|
252
|
+
-- 다른 핸들러는 "다음 pc(숫자)"를 반환하지만, RETURN만은 결과값을 table.pack으로 싸서
|
|
253
|
+
-- 테이블을 반환한다 — dispatch 루프는 반환값이 숫자가 아니면 이걸 함수 종료 신호로 보고
|
|
254
|
+
-- table.unpack(next, 1, next.n)으로 풀어 최종 반환한다. table.pack이 n을 채워 주므로
|
|
255
|
+
-- 중간 nil이 있어도 반환 개수가 정확히 보존된다.
|
|
227
256
|
${N.handlers}[${op(Opcode.RETURN)}] = function(${N.frame}, pc, instr)
|
|
228
257
|
local a, b = instr[2], instr[3]
|
|
229
258
|
if b == 0 then
|
|
230
|
-
return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
|
|
259
|
+
return table.pack(table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1))
|
|
231
260
|
end
|
|
232
|
-
return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
|
|
261
|
+
return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
|
|
233
262
|
end
|
|
234
263
|
|
|
235
264
|
-- FORPREP
|
|
236
265
|
${N.handlers}[${op(Opcode.FORPREP)}] = function(${N.frame}, pc, instr)
|
|
237
266
|
local a = instr[2]
|
|
238
267
|
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
|
|
239
|
-
return
|
|
268
|
+
return pc + instr[3]
|
|
240
269
|
end
|
|
241
270
|
|
|
242
271
|
-- FORLOOP
|
|
@@ -252,9 +281,9 @@ ${N.handlers}[${op(Opcode.FORLOOP)}] = function(${N.frame}, pc, instr)
|
|
|
252
281
|
end
|
|
253
282
|
if ok then
|
|
254
283
|
${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
|
|
255
|
-
return
|
|
284
|
+
return pc + instr[3]
|
|
256
285
|
end
|
|
257
|
-
return
|
|
286
|
+
return pc + 1
|
|
258
287
|
end
|
|
259
288
|
|
|
260
289
|
-- CLOSURE
|
|
@@ -274,7 +303,7 @@ ${N.handlers}[${op(Opcode.CLOSURE)}] = function(${N.frame}, pc, instr)
|
|
|
274
303
|
${N.frame}.${N.R}[instr[2]] = function(...)
|
|
275
304
|
return ${N.execute}(childProto, capturedUpvals, ...)
|
|
276
305
|
end
|
|
277
|
-
return
|
|
306
|
+
return pc + 1 + n
|
|
278
307
|
end
|
|
279
308
|
|
|
280
309
|
-- VARARG
|
|
@@ -286,7 +315,7 @@ ${N.handlers}[${op(Opcode.VARARG)}] = function(${N.frame}, pc, instr)
|
|
|
286
315
|
if instr[3] == 0 then
|
|
287
316
|
${N.frame}.${N.multiTop} = instr[2] + count
|
|
288
317
|
end
|
|
289
|
-
return
|
|
318
|
+
return pc + 1
|
|
290
319
|
end
|
|
291
320
|
|
|
292
321
|
-- SETLIST
|
|
@@ -299,7 +328,7 @@ ${N.handlers}[${op(Opcode.SETLIST)}] = function(${N.frame}, pc, instr)
|
|
|
299
328
|
for i = 0, n - 1 do
|
|
300
329
|
tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
|
|
301
330
|
end
|
|
302
|
-
return
|
|
331
|
+
return pc + 1
|
|
303
332
|
end
|
|
304
333
|
|
|
305
334
|
function ${N.execute}(proto, upvals, ...)
|
|
@@ -26,16 +26,22 @@ function serializeConstValue(v: ConstValue): Expression {
|
|
|
26
26
|
* 테이블을 바로 찾아낼 수 있기 때문. runtime.ts가 같은 names로 instr.<key>를 읽으므로
|
|
27
27
|
* 반드시 컴파일 시점에 쓴 것과 동일한 VmNames 인스턴스를 넘겨야 한다.
|
|
28
28
|
*/
|
|
29
|
-
function serializeInstrs(proto: Proto): Expression {
|
|
30
|
-
const fields: TableField[] = proto.code.map((instr) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
function serializeInstrs(proto: Proto, debug: boolean): Expression {
|
|
30
|
+
const fields: TableField[] = proto.code.map((instr) => {
|
|
31
|
+
const tuple = [
|
|
32
|
+
positionalField(vmNumberLiteral(instr.op as number)),
|
|
33
|
+
positionalField(vmNumberLiteral(instr.a)),
|
|
34
|
+
positionalField(vmNumberLiteral(instr.b)),
|
|
35
|
+
positionalField(vmNumberLiteral(instr.c)),
|
|
36
|
+
]
|
|
37
|
+
if (debug) {
|
|
38
|
+
// [5] = 소스 줄(없으면 0), [6] = 호출 대상 이름(없으면 ""). 런타임이 non-callable을
|
|
39
|
+
// 잡았을 때 이 두 값으로 원본 위치를 찍는다. instr[1..4]는 그대로라 안 건드림.
|
|
40
|
+
tuple.push(positionalField(numberLiteral(instr.dbgLine ?? 0)))
|
|
41
|
+
tuple.push(positionalField(stringLiteral(instr.dbgName ?? "")))
|
|
42
|
+
}
|
|
43
|
+
return positionalField(table(tuple))
|
|
44
|
+
})
|
|
39
45
|
return table(fields)
|
|
40
46
|
}
|
|
41
47
|
|
|
@@ -52,15 +58,15 @@ function serializeUpvalDescs(proto: Proto, names: VmNames): Expression {
|
|
|
52
58
|
|
|
53
59
|
/** Proto 하나를 { numParams=.., hasVarargs=.., maxRegs=.., code={...}, consts={...}, upvalDescs={...}, protos={...} }로
|
|
54
60
|
* (필드 키는 전부 names에서 온 무작위 이름). */
|
|
55
|
-
export function serializeProto(proto: Proto, names: VmNames): Expression {
|
|
61
|
+
export function serializeProto(proto: Proto, names: VmNames, debug = false): Expression {
|
|
56
62
|
const fields: TableField[] = [
|
|
57
63
|
namedField(names.numParams, vmNumberLiteral(proto.numParams)),
|
|
58
64
|
namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
|
|
59
65
|
namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
|
|
60
|
-
namedField(names.code, serializeInstrs(proto)),
|
|
66
|
+
namedField(names.code, serializeInstrs(proto, debug)),
|
|
61
67
|
namedField(names.consts, serializeConsts(proto.consts)),
|
|
62
68
|
namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
|
|
63
|
-
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names))))),
|
|
69
|
+
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names, debug))))),
|
|
64
70
|
]
|
|
65
71
|
return table(fields)
|
|
66
72
|
}
|