js-confuser-vm 0.0.5 → 0.0.7
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/CHANGELOG.md +112 -2
- package/README.MD +249 -106
- package/dist/build-runtime.js +22 -3
- package/dist/compiler.js +864 -801
- package/dist/runtime.js +414 -333
- package/dist/transforms/bytecode/aliasedOpcodes.js +134 -0
- package/dist/transforms/bytecode/concealConstants.js +31 -0
- package/dist/transforms/bytecode/macroOpcodes.js +37 -23
- package/dist/transforms/bytecode/microOpcodes.js +236 -0
- package/dist/transforms/bytecode/resolveContants.js +69 -12
- package/dist/transforms/bytecode/resolveLabels.js +5 -3
- package/dist/transforms/bytecode/selfModifying.js +3 -2
- package/dist/transforms/bytecode/specializedOpcodes.js +54 -39
- package/dist/transforms/runtime/aliasedOpcodes.js +134 -0
- package/dist/transforms/runtime/internalVariables.js +202 -0
- package/dist/transforms/runtime/macroOpcodes.js +30 -18
- package/dist/transforms/runtime/microOpcodes.js +76 -0
- package/dist/transforms/runtime/shuffleOpcodes.js +1 -1
- package/dist/transforms/runtime/specializedOpcodes.js +36 -29
- package/dist/utils/op-utils.js +36 -0
- package/dist/utils/random-utils.js +27 -0
- package/index.ts +11 -8
- package/jest.config.js +12 -0
- package/package.json +1 -1
- package/src/build-runtime.ts +25 -4
- package/src/compiler.ts +2482 -2069
- package/src/options.ts +3 -0
- package/src/runtime.ts +842 -771
- package/src/transforms/bytecode/aliasedOpcodes.ts +148 -0
- package/src/transforms/bytecode/concealConstants.ts +52 -0
- package/src/transforms/bytecode/macroOpcodes.ts +49 -33
- package/src/transforms/bytecode/microOpcodes.ts +291 -0
- package/src/transforms/bytecode/resolveContants.ts +82 -18
- package/src/transforms/bytecode/resolveLabels.ts +5 -4
- package/src/transforms/bytecode/selfModifying.ts +3 -3
- package/src/transforms/bytecode/specializedOpcodes.ts +85 -46
- package/src/transforms/runtime/aliasedOpcodes.ts +191 -0
- package/src/transforms/runtime/internalVariables.ts +270 -0
- package/src/transforms/runtime/macroOpcodes.ts +47 -20
- package/src/transforms/runtime/microOpcodes.ts +93 -0
- package/src/transforms/runtime/shuffleOpcodes.ts +1 -1
- package/src/transforms/runtime/specializedOpcodes.ts +56 -46
- package/src/types.ts +1 -1
- package/src/utils/op-utils.ts +46 -0
- package/src/transforms/utils/op-utils.ts +0 -26
- package/src/utilts.ts +0 -3
- /package/src/{transforms/utils → utils}/random-utils.ts +0 -0
package/src/compiler.ts
CHANGED
|
@@ -1,2069 +1,2482 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import { ok } from "assert";
|
|
10
|
-
import { obfuscateRuntime } from "./build-runtime.ts";
|
|
11
|
-
import { DEFAULT_OPTIONS, type Options } from "./options.ts";
|
|
12
|
-
import { resolveLabels } from "./transforms/bytecode/resolveLabels.ts";
|
|
13
|
-
import { resolveConstants } from "./transforms/bytecode/resolveContants.ts";
|
|
14
|
-
import { selfModifying } from "./transforms/bytecode/selfModifying.ts";
|
|
15
|
-
import { macroOpcodes } from "./transforms/bytecode/macroOpcodes.ts";
|
|
16
|
-
import
|
|
17
|
-
import { specializedOpcodes } from "./transforms/bytecode/specializedOpcodes.ts";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
code = readFileSync(join(import.meta.dirname, "./runtime.
|
|
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
|
-
STORE_UPVALUE:
|
|
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
|
-
const
|
|
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
|
-
this.
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
bc
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
[
|
|
792
|
-
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
this.
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
bc
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
]
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
this.emit(
|
|
979
|
-
bc,
|
|
980
|
-
[null, { type: "defineLabel", label:
|
|
981
|
-
node,
|
|
982
|
-
);
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
this.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
break;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
case "
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
this.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
type
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
this._compileStatement(
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
this.
|
|
1292
|
-
|
|
1293
|
-
this.
|
|
1294
|
-
break;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
case "
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
this.
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
);
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
this.emit(
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
this.
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
this.
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
)
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
bc,
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import * as b from "./types.ts";
|
|
3
|
+
import { parse } from "@babel/parser";
|
|
4
|
+
import traverseImport from "@babel/traverse";
|
|
5
|
+
import { generate } from "@babel/generator";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { stripTypeScriptTypes } from "module";
|
|
9
|
+
import { ok } from "assert";
|
|
10
|
+
import { obfuscateRuntime } from "./build-runtime.ts";
|
|
11
|
+
import { DEFAULT_OPTIONS, type Options } from "./options.ts";
|
|
12
|
+
import { resolveLabels } from "./transforms/bytecode/resolveLabels.ts";
|
|
13
|
+
import { resolveConstants } from "./transforms/bytecode/resolveContants.ts";
|
|
14
|
+
import { selfModifying } from "./transforms/bytecode/selfModifying.ts";
|
|
15
|
+
import { macroOpcodes } from "./transforms/bytecode/macroOpcodes.ts";
|
|
16
|
+
import { microOpcodes } from "./transforms/bytecode/microOpcodes.ts";
|
|
17
|
+
import { specializedOpcodes } from "./transforms/bytecode/specializedOpcodes.ts";
|
|
18
|
+
import { aliasedOpcodes } from "./transforms/bytecode/aliasedOpcodes.ts";
|
|
19
|
+
import { getRandomInt } from "./utils/random-utils.ts";
|
|
20
|
+
import { U16_MAX } from "./utils/op-utils.ts";
|
|
21
|
+
import { concealConstants } from "./transforms/bytecode/concealConstants.ts";
|
|
22
|
+
|
|
23
|
+
const traverse = (traverseImport.default ||
|
|
24
|
+
traverseImport) as typeof traverseImport.default;
|
|
25
|
+
|
|
26
|
+
const readVMRuntimeFile = () => {
|
|
27
|
+
let code;
|
|
28
|
+
try {
|
|
29
|
+
code = readFileSync(join(import.meta.dirname, "./runtime.ts"), "utf-8");
|
|
30
|
+
} catch (e) {
|
|
31
|
+
code = readFileSync(join(import.meta.dirname, "./runtime.js"), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return stripTypeScriptTypes?.(code) || code;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const VM_RUNTIME = readVMRuntimeFile().split("@START")[1];
|
|
38
|
+
export const SOURCE_NODE_SYM = Symbol("SOURCE_NODE");
|
|
39
|
+
|
|
40
|
+
// ── Opcodes ──────────────────────────────────────────────────────────────────
|
|
41
|
+
// Register-based encoding. Operand convention (x86 / CPython style):
|
|
42
|
+
// destination register first, then source registers, then immediates.
|
|
43
|
+
//
|
|
44
|
+
// dst – register index that receives the result
|
|
45
|
+
// src – register index holding an input value
|
|
46
|
+
// imm/Idx – immediate integer (constant-pool index, upvalue index, argc …)
|
|
47
|
+
//
|
|
48
|
+
// Every arithmetic/comparison/unary instruction: [op, dst, src1, src2?]
|
|
49
|
+
// Every load: [op, dst, ...]
|
|
50
|
+
// Every store: [op, target, src]
|
|
51
|
+
// Calls: CALL [op, dst, callee, argc, arg0, arg1, …]
|
|
52
|
+
// CALL_METHOD [op, dst, receiver, callee, argc, arg0, …]
|
|
53
|
+
export const OP_ORIGINAL = {
|
|
54
|
+
// ── Loads ─────────────────────────────────────────────────────────────────
|
|
55
|
+
LOAD_CONST: 0, // dst, constIdx regs[dst] = constants[constIdx]
|
|
56
|
+
LOAD_INT: 1, // dst, imm regs[dst] = imm (raw u16 literal)
|
|
57
|
+
LOAD_GLOBAL: 2, // dst, nameIdx regs[dst] = globals[constants[nameIdx]]
|
|
58
|
+
LOAD_UPVALUE: 3, // dst, uvIdx regs[dst] = upvalues[uvIdx].read()
|
|
59
|
+
LOAD_THIS: 4, // dst regs[dst] = frame.thisVal
|
|
60
|
+
MOVE: 5, // dst, src regs[dst] = regs[src]
|
|
61
|
+
|
|
62
|
+
// ── Stores ────────────────────────────────────────────────────────────────
|
|
63
|
+
STORE_GLOBAL: 6, // nameIdx, src globals[constants[nameIdx]] = regs[src]
|
|
64
|
+
STORE_UPVALUE: 7, // uvIdx, src upvalues[uvIdx].write(regs[src])
|
|
65
|
+
|
|
66
|
+
// ── Property access ───────────────────────────────────────────────────────
|
|
67
|
+
GET_PROP: 8, // dst, obj, key regs[dst] = regs[obj][regs[key]]
|
|
68
|
+
SET_PROP: 9, // obj, key, val regs[obj][regs[key]] = regs[val] (result stays in val reg)
|
|
69
|
+
DELETE_PROP: 10, // dst, obj, key regs[dst] = delete regs[obj][regs[key]]
|
|
70
|
+
|
|
71
|
+
// ── Arithmetic / bitwise (dst, src1, src2) ───────────────────────────────
|
|
72
|
+
ADD: 11,
|
|
73
|
+
SUB: 12,
|
|
74
|
+
MUL: 13,
|
|
75
|
+
DIV: 14,
|
|
76
|
+
MOD: 15,
|
|
77
|
+
BAND: 16,
|
|
78
|
+
BOR: 17,
|
|
79
|
+
BXOR: 18,
|
|
80
|
+
SHL: 19,
|
|
81
|
+
SHR: 20,
|
|
82
|
+
USHR: 21,
|
|
83
|
+
|
|
84
|
+
// ── Comparison (dst, src1, src2) ─────────────────────────────────────────
|
|
85
|
+
LT: 22,
|
|
86
|
+
GT: 23,
|
|
87
|
+
LTE: 24,
|
|
88
|
+
GTE: 25,
|
|
89
|
+
EQ: 26,
|
|
90
|
+
NEQ: 27,
|
|
91
|
+
LOOSE_EQ: 28,
|
|
92
|
+
LOOSE_NEQ: 29,
|
|
93
|
+
IN: 30,
|
|
94
|
+
INSTANCEOF: 31,
|
|
95
|
+
|
|
96
|
+
// ── Unary (dst, src) ─────────────────────────────────────────────────────
|
|
97
|
+
UNARY_NEG: 32,
|
|
98
|
+
UNARY_POS: 33,
|
|
99
|
+
UNARY_NOT: 34,
|
|
100
|
+
UNARY_BITNOT: 35,
|
|
101
|
+
TYPEOF: 36, // dst, src
|
|
102
|
+
VOID: 37, // dst, src – regs[dst] = undefined (src evaluated for side-effects)
|
|
103
|
+
TYPEOF_SAFE: 38, // dst, nameConstIdx – safe typeof for potentially-undeclared globals
|
|
104
|
+
|
|
105
|
+
// ── Control flow ──────────────────────────────────────────────────────────
|
|
106
|
+
JUMP: 39, // target
|
|
107
|
+
JUMP_IF_FALSE: 40, // src, target if !regs[src] then pc = target
|
|
108
|
+
JUMP_IF_TRUE: 41, // src, target if regs[src] then pc = target (|| short-circuit)
|
|
109
|
+
|
|
110
|
+
// ── Calls & constructors ──────────────────────────────────────────────────
|
|
111
|
+
CALL: 42, // dst, callee, argc, [argRegs…]
|
|
112
|
+
CALL_METHOD: 43, // dst, receiver, callee, argc, [argRegs…]
|
|
113
|
+
NEW: 44, // dst, callee, argc, [argRegs…]
|
|
114
|
+
RETURN: 45, // src
|
|
115
|
+
THROW: 46, // src
|
|
116
|
+
|
|
117
|
+
// ── Closures ──────────────────────────────────────────────────────────────
|
|
118
|
+
// dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, …]
|
|
119
|
+
MAKE_CLOSURE: 47,
|
|
120
|
+
|
|
121
|
+
// ── Collections ───────────────────────────────────────────────────────────
|
|
122
|
+
BUILD_ARRAY: 48, // dst, count, [elemRegs…]
|
|
123
|
+
BUILD_OBJECT: 49, // dst, pairCount, [keyReg, valReg, …]
|
|
124
|
+
|
|
125
|
+
// ── Property definitions (getters / setters) ──────────────────────────────
|
|
126
|
+
DEFINE_GETTER: 50, // obj, key, fn
|
|
127
|
+
DEFINE_SETTER: 51, // obj, key, fn
|
|
128
|
+
|
|
129
|
+
// ── For-in iteration ──────────────────────────────────────────────────────
|
|
130
|
+
FOR_IN_SETUP: 52, // dst, src dst = { _keys: enumKeys(src), i: 0 }
|
|
131
|
+
FOR_IN_NEXT: 53, // dst, iter, exitTarget
|
|
132
|
+
|
|
133
|
+
// ── Exception handling ────────────────────────────────────────────────────
|
|
134
|
+
TRY_SETUP: 54, // handlerPc, exceptionReg
|
|
135
|
+
TRY_END: 55,
|
|
136
|
+
|
|
137
|
+
// ── Self-modifying bytecode ───────────────────────────────────────────────
|
|
138
|
+
PATCH: 56, // destPc, sliceStart, sliceEnd
|
|
139
|
+
|
|
140
|
+
// ── Debug ─────────────────────────────────────────────────────────────────
|
|
141
|
+
DEBUGGER: 57,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// ── Scope ─────────────────────────────────────────────────────────────────────
|
|
145
|
+
// Maps variable names to register indices (slot numbers).
|
|
146
|
+
// Locals are allocated at compile time; zero name lookups at runtime.
|
|
147
|
+
class Scope {
|
|
148
|
+
parent: Scope | null;
|
|
149
|
+
_locals: Map<string, number>;
|
|
150
|
+
_next: number;
|
|
151
|
+
|
|
152
|
+
constructor(parent = null) {
|
|
153
|
+
this.parent = parent;
|
|
154
|
+
this._locals = new Map();
|
|
155
|
+
this._next = 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
define(name: string): number {
|
|
159
|
+
if (!this._locals.has(name)) {
|
|
160
|
+
this._locals.set(name, this._next++);
|
|
161
|
+
}
|
|
162
|
+
return this._locals.get(name)!;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
resolve(name: string): { kind: "local"; slot: number } | { kind: "global" } {
|
|
166
|
+
if (this._locals.has(name)) {
|
|
167
|
+
return { kind: "local", slot: this._locals.get(name)! };
|
|
168
|
+
}
|
|
169
|
+
if (this.parent) return this.parent.resolve(name);
|
|
170
|
+
return { kind: "global" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get localCount() {
|
|
174
|
+
return this._next;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── FnContext ─────────────────────────────────────────────────────────────────
|
|
179
|
+
// Compiler-side state for the function currently being compiled.
|
|
180
|
+
// Distinct from the runtime Frame — this is compile-time only.
|
|
181
|
+
class FnContext {
|
|
182
|
+
upvalues: { name: string; isLocal: number; index: number }[];
|
|
183
|
+
parentCtx: FnContext | null;
|
|
184
|
+
scope: Scope;
|
|
185
|
+
compiler: Compiler;
|
|
186
|
+
bc: b.Instruction[];
|
|
187
|
+
|
|
188
|
+
// Register allocator. Locals occupy regs[0..scope.localCount-1];
|
|
189
|
+
// temps are allocated above that and freed at statement boundaries.
|
|
190
|
+
regTop: number = 0;
|
|
191
|
+
maxRegTop: number = 0;
|
|
192
|
+
|
|
193
|
+
constructor(compiler: Compiler, parentCtx: FnContext | null = null) {
|
|
194
|
+
this.compiler = compiler;
|
|
195
|
+
this.parentCtx = parentCtx;
|
|
196
|
+
this.scope = new Scope();
|
|
197
|
+
this.bc = [];
|
|
198
|
+
this.upvalues = [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Allocate the next free temp register and return its index. */
|
|
202
|
+
allocReg(): number {
|
|
203
|
+
const r = this.regTop++;
|
|
204
|
+
if (this.regTop > this.maxRegTop) this.maxRegTop = this.regTop;
|
|
205
|
+
return r;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Release all temps above the local region. Called at each statement boundary. */
|
|
209
|
+
resetTemps(): void {
|
|
210
|
+
this.regTop = this.scope.localCount;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
addUpvalue(name: string, isLocal: number, index: number): number {
|
|
214
|
+
const existing = this.upvalues.findIndex((u) => u.name === name);
|
|
215
|
+
if (existing !== -1) return existing;
|
|
216
|
+
const idx = this.upvalues.length;
|
|
217
|
+
this.upvalues.push({ name, isLocal, index });
|
|
218
|
+
return idx;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface FnDescriptor {
|
|
223
|
+
name?: string;
|
|
224
|
+
entryLabel?: string;
|
|
225
|
+
startLabel?: string;
|
|
226
|
+
bytecode?: b.Bytecode;
|
|
227
|
+
paramCount?: number;
|
|
228
|
+
regCount?: number;
|
|
229
|
+
upvalues?: any[];
|
|
230
|
+
_fnIdx?: number;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Only populated AFTER resolveLabels
|
|
234
|
+
*/
|
|
235
|
+
startPc?: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Compiler ──────────────────────────────────────────────────────────────────
|
|
239
|
+
export class Compiler {
|
|
240
|
+
fnDescriptors: FnDescriptor[];
|
|
241
|
+
bytecode: b.Bytecode;
|
|
242
|
+
mainRegCount: number;
|
|
243
|
+
mainFn: ReturnType<typeof this._compileFunctionDecl>;
|
|
244
|
+
mainStartPc: number;
|
|
245
|
+
|
|
246
|
+
_currentCtx: FnContext | null;
|
|
247
|
+
_pendingLabel: string | null;
|
|
248
|
+
_forInCount: number;
|
|
249
|
+
_labelCount: number;
|
|
250
|
+
_loopStack: {
|
|
251
|
+
type: "loop" | "switch" | "block";
|
|
252
|
+
label: string | null;
|
|
253
|
+
breakLabel: string;
|
|
254
|
+
continueLabel: string;
|
|
255
|
+
}[];
|
|
256
|
+
|
|
257
|
+
options: Options;
|
|
258
|
+
serializer: Serializer;
|
|
259
|
+
|
|
260
|
+
OP: Partial<typeof OP_ORIGINAL>;
|
|
261
|
+
MACRO_OPS: Record<number, number[]>;
|
|
262
|
+
SPECIALIZED_OPS: Record<
|
|
263
|
+
number,
|
|
264
|
+
{
|
|
265
|
+
originalOp: number;
|
|
266
|
+
operands: b.InstrOperand[];
|
|
267
|
+
}
|
|
268
|
+
>;
|
|
269
|
+
ALIASED_OPS: Record<number, { originalOp: number; order: number[] }>;
|
|
270
|
+
MICRO_OPS: Record<
|
|
271
|
+
number,
|
|
272
|
+
{ originalOp: number; stmtIndex: number; irOperandCount: number }
|
|
273
|
+
>;
|
|
274
|
+
|
|
275
|
+
/** Internal variable slot registry.
|
|
276
|
+
* globally: shared name→index pool (written on first sight; reused by non-random mode or by 50% chance in random mode).
|
|
277
|
+
* opcodes: per-opcode source-of-truth — all assignment lookups are read/written here. */
|
|
278
|
+
_internals: {
|
|
279
|
+
globally: Map<string, number>;
|
|
280
|
+
opcodes: Map<number, Map<string, number>>;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
OP_NAME: Record<number, string>;
|
|
284
|
+
JUMP_OPS: Set<number>;
|
|
285
|
+
|
|
286
|
+
constants: any[];
|
|
287
|
+
|
|
288
|
+
emit(bc: b.Bytecode, instr: b.Instruction, node: t.Node) {
|
|
289
|
+
bc.push(instr);
|
|
290
|
+
instr[SOURCE_NODE_SYM] = node;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
constructor(options: Options = DEFAULT_OPTIONS) {
|
|
294
|
+
this.options = options;
|
|
295
|
+
this.fnDescriptors = [];
|
|
296
|
+
this.bytecode = [];
|
|
297
|
+
this.mainStartPc = 0;
|
|
298
|
+
this.mainRegCount = 0;
|
|
299
|
+
this._currentCtx = null;
|
|
300
|
+
this._loopStack = [];
|
|
301
|
+
this._pendingLabel = null;
|
|
302
|
+
this._forInCount = 0;
|
|
303
|
+
this._labelCount = 0;
|
|
304
|
+
|
|
305
|
+
this.serializer = new Serializer(this);
|
|
306
|
+
this.MACRO_OPS = {};
|
|
307
|
+
this.MICRO_OPS = {};
|
|
308
|
+
this.SPECIALIZED_OPS = {};
|
|
309
|
+
this.ALIASED_OPS = {};
|
|
310
|
+
this._internals = { globally: new Map(), opcodes: new Map() };
|
|
311
|
+
|
|
312
|
+
this.OP = { ...OP_ORIGINAL };
|
|
313
|
+
|
|
314
|
+
if (this.options.randomizeOpcodes) {
|
|
315
|
+
let usedNumbers = new Set<number>();
|
|
316
|
+
for (const key in this.OP) {
|
|
317
|
+
let val;
|
|
318
|
+
do {
|
|
319
|
+
val = getRandomInt(0, U16_MAX);
|
|
320
|
+
} while (usedNumbers.has(val));
|
|
321
|
+
usedNumbers.add(val);
|
|
322
|
+
this.OP[key] = val;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.OP_NAME = Object.fromEntries(
|
|
327
|
+
Object.entries(this.OP).map(([k, v]) => [v, k]),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
this.JUMP_OPS = new Set([
|
|
331
|
+
this.OP.JUMP,
|
|
332
|
+
this.OP.JUMP_IF_FALSE,
|
|
333
|
+
this.OP.JUMP_IF_TRUE,
|
|
334
|
+
this.OP.FOR_IN_NEXT,
|
|
335
|
+
this.OP.TRY_SETUP,
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_makeLabel(hint = ""): string {
|
|
340
|
+
return `${hint || "L"}_${this._labelCount++}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_resolve(
|
|
344
|
+
name: string,
|
|
345
|
+
ctx: FnContext | null,
|
|
346
|
+
):
|
|
347
|
+
| { kind: "local"; slot: number }
|
|
348
|
+
| { kind: "upvalue"; index: number }
|
|
349
|
+
| { kind: "global" } {
|
|
350
|
+
if (!ctx) return { kind: "global" };
|
|
351
|
+
|
|
352
|
+
if (ctx.scope._locals.has(name)) {
|
|
353
|
+
return { kind: "local", slot: ctx.scope._locals.get(name)! };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!ctx.parentCtx) return { kind: "global" };
|
|
357
|
+
|
|
358
|
+
const parentResult = this._resolve(name, ctx.parentCtx);
|
|
359
|
+
if (parentResult.kind === "global") return { kind: "global" };
|
|
360
|
+
|
|
361
|
+
const isLocal = parentResult.kind === "local";
|
|
362
|
+
const index = isLocal ? parentResult.slot : parentResult.index;
|
|
363
|
+
const uvIdx = ctx.addUpvalue(name, isLocal ? 1 : 0, index);
|
|
364
|
+
return { kind: "upvalue", index: uvIdx };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── Variable hoisting ──────────────────────────────────────────────────────
|
|
368
|
+
// Pre-scan a statement list and reserve scope slots for every var declaration,
|
|
369
|
+
// function declaration, for-in iterator, and try-catch binding.
|
|
370
|
+
// Must be called before compilation begins so that ctx.regTop can be set
|
|
371
|
+
// safely above ALL locals (including those declared late in the body).
|
|
372
|
+
_hoistVars(stmts: t.Statement[], scope: Scope): void {
|
|
373
|
+
for (const stmt of stmts) {
|
|
374
|
+
switch (stmt.type) {
|
|
375
|
+
case "VariableDeclaration":
|
|
376
|
+
for (const decl of stmt.declarations) {
|
|
377
|
+
if (decl.id.type === "Identifier") scope.define(decl.id.name);
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
|
|
381
|
+
case "FunctionDeclaration":
|
|
382
|
+
if (stmt.id) scope.define(stmt.id.name);
|
|
383
|
+
break;
|
|
384
|
+
|
|
385
|
+
case "BlockStatement":
|
|
386
|
+
this._hoistVars(stmt.body, scope);
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case "IfStatement": {
|
|
390
|
+
const cons =
|
|
391
|
+
stmt.consequent.type === "BlockStatement"
|
|
392
|
+
? stmt.consequent.body
|
|
393
|
+
: [stmt.consequent];
|
|
394
|
+
this._hoistVars(cons, scope);
|
|
395
|
+
if (stmt.alternate) {
|
|
396
|
+
const alt =
|
|
397
|
+
stmt.alternate.type === "BlockStatement"
|
|
398
|
+
? stmt.alternate.body
|
|
399
|
+
: [stmt.alternate];
|
|
400
|
+
this._hoistVars(alt, scope);
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
case "WhileStatement":
|
|
406
|
+
case "DoWhileStatement": {
|
|
407
|
+
const body =
|
|
408
|
+
stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
409
|
+
this._hoistVars(body, scope);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
case "ForStatement": {
|
|
414
|
+
if (stmt.init?.type === "VariableDeclaration") {
|
|
415
|
+
for (const decl of stmt.init.declarations) {
|
|
416
|
+
if (decl.id.type === "Identifier") scope.define(decl.id.name);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const body =
|
|
420
|
+
stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
421
|
+
this._hoistVars(body, scope);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
case "ForInStatement": {
|
|
426
|
+
// Reserve a hidden register for the iterator object.
|
|
427
|
+
(stmt as any)._iterSlot = scope._next++;
|
|
428
|
+
if (stmt.left.type === "VariableDeclaration") {
|
|
429
|
+
for (const decl of stmt.left.declarations) {
|
|
430
|
+
if (decl.id.type === "Identifier") scope.define(decl.id.name);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const body =
|
|
434
|
+
stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
435
|
+
this._hoistVars(body, scope);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
case "SwitchStatement":
|
|
440
|
+
for (const c of stmt.cases) this._hoistVars(c.consequent, scope);
|
|
441
|
+
break;
|
|
442
|
+
|
|
443
|
+
case "TryStatement":
|
|
444
|
+
this._hoistVars(stmt.block.body, scope);
|
|
445
|
+
if (stmt.handler) {
|
|
446
|
+
if (stmt.handler.param?.type === "Identifier") {
|
|
447
|
+
// Catch parameter IS the exception register.
|
|
448
|
+
scope.define((stmt.handler.param as t.Identifier).name);
|
|
449
|
+
} else {
|
|
450
|
+
// No catch binding – reserve a dummy slot for the exception value.
|
|
451
|
+
(stmt as any)._exceptionSlot = scope._next++;
|
|
452
|
+
}
|
|
453
|
+
this._hoistVars(stmt.handler.body.body, scope);
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case "LabeledStatement":
|
|
458
|
+
this._hoistVars([stmt.body], scope);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ── Entry point ───────────────────────────────────────────────────────────
|
|
465
|
+
compile(source: string) {
|
|
466
|
+
const ast = parse(source, { sourceType: "script" });
|
|
467
|
+
return this.compileAST(ast);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
compileAST(ast: t.File) {
|
|
471
|
+
this._compileMain(ast.program.body);
|
|
472
|
+
return this.bytecode;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ── Function compilation ───────────────────────────────────────────────────
|
|
476
|
+
_compileFunctionDecl(node: t.FunctionDeclaration | t.FunctionExpression) {
|
|
477
|
+
ok(!node.generator, "Generator functions are not supported");
|
|
478
|
+
ok(!node.async, "Async functions are not supported");
|
|
479
|
+
|
|
480
|
+
var fnIdx = this.fnDescriptors.length;
|
|
481
|
+
const entryLabel = this._makeLabel(`fn_${fnIdx}`);
|
|
482
|
+
var desc: FnDescriptor = {};
|
|
483
|
+
this.fnDescriptors.push(desc);
|
|
484
|
+
|
|
485
|
+
const ctx = new FnContext(this, this._currentCtx);
|
|
486
|
+
const savedCtx = this._currentCtx;
|
|
487
|
+
this._currentCtx = ctx;
|
|
488
|
+
|
|
489
|
+
const savedLoopStack = this._loopStack;
|
|
490
|
+
this._loopStack = [];
|
|
491
|
+
|
|
492
|
+
// 1. Define parameters (occupy regs 0..paramCount-1).
|
|
493
|
+
for (const param of node.params) {
|
|
494
|
+
let identifier = param.type === "AssignmentPattern" ? param.left : param;
|
|
495
|
+
ok(
|
|
496
|
+
identifier.type === "Identifier",
|
|
497
|
+
"Only simple identifiers allowed as parameters",
|
|
498
|
+
);
|
|
499
|
+
ctx.scope.define((identifier as t.Identifier).name);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 2. Reserve the `arguments` slot (reg index = paramCount).
|
|
503
|
+
ctx.scope.define("arguments");
|
|
504
|
+
|
|
505
|
+
// 3. Hoist all var declarations so temps start above every local.
|
|
506
|
+
this._hoistVars(node.body.body, ctx.scope);
|
|
507
|
+
|
|
508
|
+
// 4. Temps now start above all locals.
|
|
509
|
+
ctx.regTop = ctx.scope.localCount;
|
|
510
|
+
ctx.maxRegTop = ctx.regTop;
|
|
511
|
+
|
|
512
|
+
// 5. Emit default-value guards.
|
|
513
|
+
for (const param of node.params) {
|
|
514
|
+
if (param.type !== "AssignmentPattern") continue;
|
|
515
|
+
|
|
516
|
+
const slot = ctx.scope._locals.get((param.left as t.Identifier).name)!;
|
|
517
|
+
const skipLabel = this._makeLabel("param_skip");
|
|
518
|
+
|
|
519
|
+
// if (param === undefined) param = <default>
|
|
520
|
+
const reg_undef = ctx.allocReg();
|
|
521
|
+
this.emit(
|
|
522
|
+
ctx.bc,
|
|
523
|
+
[this.OP.LOAD_CONST, reg_undef, b.constantOperand(undefined)],
|
|
524
|
+
param,
|
|
525
|
+
);
|
|
526
|
+
const reg_cmp = ctx.allocReg();
|
|
527
|
+
this.emit(ctx.bc, [this.OP.EQ, reg_cmp, slot, reg_undef], param);
|
|
528
|
+
this.emit(
|
|
529
|
+
ctx.bc,
|
|
530
|
+
[this.OP.JUMP_IF_FALSE, reg_cmp, { type: "label", label: skipLabel }],
|
|
531
|
+
param,
|
|
532
|
+
);
|
|
533
|
+
ctx.resetTemps();
|
|
534
|
+
|
|
535
|
+
const srcReg = this._compileExpr(param.right, ctx.scope, ctx.bc);
|
|
536
|
+
if (srcReg !== slot) {
|
|
537
|
+
this.emit(ctx.bc, [this.OP.MOVE, slot, srcReg], param);
|
|
538
|
+
}
|
|
539
|
+
ctx.resetTemps();
|
|
540
|
+
|
|
541
|
+
this.emit(
|
|
542
|
+
ctx.bc,
|
|
543
|
+
[null, { type: "defineLabel", label: skipLabel }],
|
|
544
|
+
param,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 6. Compile body.
|
|
549
|
+
for (const stmt of node.body.body) {
|
|
550
|
+
this._compileStatement(stmt, ctx.scope, ctx.bc);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Implicit return undefined at end of function.
|
|
554
|
+
const reg_undef = ctx.allocReg();
|
|
555
|
+
this.emit(
|
|
556
|
+
ctx.bc,
|
|
557
|
+
[this.OP.LOAD_CONST, reg_undef, b.constantOperand(undefined)],
|
|
558
|
+
node,
|
|
559
|
+
);
|
|
560
|
+
this.emit(ctx.bc, [this.OP.RETURN, reg_undef], node);
|
|
561
|
+
|
|
562
|
+
this._currentCtx = savedCtx;
|
|
563
|
+
this._loopStack = savedLoopStack;
|
|
564
|
+
|
|
565
|
+
(node as any)._fnIdx = fnIdx;
|
|
566
|
+
|
|
567
|
+
desc.name = (node as any).id?.name || "<anonymous>";
|
|
568
|
+
desc.entryLabel = entryLabel;
|
|
569
|
+
desc.bytecode = ctx.bc as b.Bytecode;
|
|
570
|
+
desc._fnIdx = fnIdx;
|
|
571
|
+
desc.paramCount = node.params.length;
|
|
572
|
+
desc.regCount = ctx.maxRegTop; // total registers needed at runtime
|
|
573
|
+
desc.upvalues = ctx.upvalues.slice();
|
|
574
|
+
|
|
575
|
+
return desc;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Emit MAKE_CLOSURE with all metadata as inline operands.
|
|
579
|
+
// Layout: dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, …]
|
|
580
|
+
_emitMakeClosure(desc: any, node: t.Node, bc: b.Bytecode) {
|
|
581
|
+
const ctx = this._currentCtx!;
|
|
582
|
+
const dst = ctx.allocReg();
|
|
583
|
+
const uvOperands: (number | b.InstrOperand)[] = [];
|
|
584
|
+
for (const uv of desc.upvalues) {
|
|
585
|
+
uvOperands.push(uv.isLocal ? 1 : 0);
|
|
586
|
+
uvOperands.push(uv.index);
|
|
587
|
+
}
|
|
588
|
+
this.emit(
|
|
589
|
+
bc,
|
|
590
|
+
[
|
|
591
|
+
this.OP.MAKE_CLOSURE,
|
|
592
|
+
dst,
|
|
593
|
+
{ type: "label", label: desc.entryLabel },
|
|
594
|
+
desc.paramCount,
|
|
595
|
+
desc.regCount,
|
|
596
|
+
desc.upvalues.length,
|
|
597
|
+
...uvOperands,
|
|
598
|
+
] as b.Instruction,
|
|
599
|
+
node,
|
|
600
|
+
);
|
|
601
|
+
return dst;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ── Main (top-level) ───────────────────────────────────────────────────────
|
|
605
|
+
_compileMain(body: t.Statement[]) {
|
|
606
|
+
const mainCtx = new FnContext(this, null);
|
|
607
|
+
const savedCtx = this._currentCtx;
|
|
608
|
+
this._currentCtx = mainCtx;
|
|
609
|
+
|
|
610
|
+
var desc = this._compileFunctionDecl({
|
|
611
|
+
type: "FunctionDeclaration",
|
|
612
|
+
async: false,
|
|
613
|
+
generator: false,
|
|
614
|
+
params: [],
|
|
615
|
+
id: null,
|
|
616
|
+
body: t.blockStatement([...body]),
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
for (const descriptor of this.fnDescriptors) {
|
|
620
|
+
this.bytecode.push([
|
|
621
|
+
null,
|
|
622
|
+
{ type: "defineLabel", label: descriptor.entryLabel },
|
|
623
|
+
]);
|
|
624
|
+
for (const instr of descriptor.bytecode) {
|
|
625
|
+
this.bytecode.push(instr);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
this.mainRegCount = mainCtx.maxRegTop;
|
|
630
|
+
this.mainFn = desc;
|
|
631
|
+
this._currentCtx = savedCtx;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ── Statements ────────────────────────────────────────────────────────────
|
|
635
|
+
// Wrapper that resets temps after every statement so that short-lived
|
|
636
|
+
// expression temps don't accumulate across statements.
|
|
637
|
+
_compileStatement(node: t.Statement, scope: Scope | null, bc: b.Bytecode) {
|
|
638
|
+
this._compileStatementImpl(node, scope, bc);
|
|
639
|
+
this._currentCtx?.resetTemps();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
_compileStatementImpl(
|
|
643
|
+
node: t.Statement,
|
|
644
|
+
scope: Scope | null,
|
|
645
|
+
bc: b.Bytecode,
|
|
646
|
+
) {
|
|
647
|
+
const ctx = this._currentCtx!;
|
|
648
|
+
|
|
649
|
+
switch (node.type) {
|
|
650
|
+
case "EmptyStatement":
|
|
651
|
+
break;
|
|
652
|
+
|
|
653
|
+
case "DebuggerStatement":
|
|
654
|
+
this.emit(bc, [this.OP.DEBUGGER], node);
|
|
655
|
+
break;
|
|
656
|
+
|
|
657
|
+
case "BlockStatement":
|
|
658
|
+
for (const stmt of node.body) {
|
|
659
|
+
this._compileStatement(stmt, scope, bc);
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
|
|
663
|
+
case "FunctionDeclaration": {
|
|
664
|
+
const desc = this._compileFunctionDecl(node);
|
|
665
|
+
const closureReg = this._emitMakeClosure(desc, node, bc);
|
|
666
|
+
if (scope) {
|
|
667
|
+
const slot = scope._locals.get(node.id!.name)!;
|
|
668
|
+
if (closureReg !== slot) {
|
|
669
|
+
this.emit(bc, [this.OP.MOVE, slot, closureReg], node);
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
this.emit(
|
|
673
|
+
bc,
|
|
674
|
+
[
|
|
675
|
+
this.OP.STORE_GLOBAL,
|
|
676
|
+
b.constantOperand(node.id!.name),
|
|
677
|
+
closureReg,
|
|
678
|
+
],
|
|
679
|
+
node,
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
case "ThrowStatement": {
|
|
686
|
+
const reg = this._compileExpr(node.argument, scope, bc);
|
|
687
|
+
this.emit(bc, [this.OP.THROW, reg], node);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
case "ReturnStatement": {
|
|
692
|
+
let reg: number;
|
|
693
|
+
if (node.argument) {
|
|
694
|
+
reg = this._compileExpr(node.argument, scope, bc);
|
|
695
|
+
} else {
|
|
696
|
+
reg = ctx.allocReg();
|
|
697
|
+
this.emit(
|
|
698
|
+
bc,
|
|
699
|
+
[this.OP.LOAD_CONST, reg, b.constantOperand(undefined)],
|
|
700
|
+
node,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
for (let _ri = this._loopStack.length - 1; _ri >= 0; _ri--) {
|
|
704
|
+
if ((this._loopStack[_ri].type as any) === "try") {
|
|
705
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this.emit(bc, [this.OP.RETURN, reg], node);
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
case "ExpressionStatement":
|
|
713
|
+
this._compileExpr(node.expression, scope, bc);
|
|
714
|
+
// Result is discarded; resetTemps in the wrapper handles cleanup.
|
|
715
|
+
break;
|
|
716
|
+
|
|
717
|
+
case "VariableDeclaration": {
|
|
718
|
+
for (const decl of node.declarations) {
|
|
719
|
+
ok(
|
|
720
|
+
decl.id.type === "Identifier",
|
|
721
|
+
"Only simple identifiers can be declared",
|
|
722
|
+
);
|
|
723
|
+
const name = (decl.id as t.Identifier).name;
|
|
724
|
+
|
|
725
|
+
if (scope) {
|
|
726
|
+
const slot = scope._locals.get(name)!; // already defined by _hoistVars
|
|
727
|
+
if (decl.init) {
|
|
728
|
+
const srcReg = this._compileExpr(decl.init, scope, bc);
|
|
729
|
+
if (srcReg !== slot) {
|
|
730
|
+
this.emit(bc, [this.OP.MOVE, slot, srcReg], node);
|
|
731
|
+
}
|
|
732
|
+
} else {
|
|
733
|
+
this.emit(bc, [this.OP.MOVE, slot, ctx.allocReg()], node);
|
|
734
|
+
// Load undefined into the just-allocated temp, then move.
|
|
735
|
+
// Actually: just emit LOAD_CONST directly into slot.
|
|
736
|
+
// Undo the allocReg – instead emit directly:
|
|
737
|
+
ctx.regTop--; // undo the allocReg above
|
|
738
|
+
const tmp = ctx.allocReg();
|
|
739
|
+
this.emit(
|
|
740
|
+
bc,
|
|
741
|
+
[this.OP.LOAD_CONST, tmp, b.constantOperand(undefined)],
|
|
742
|
+
node,
|
|
743
|
+
);
|
|
744
|
+
if (tmp !== slot) this.emit(bc, [this.OP.MOVE, slot, tmp], node);
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
if (decl.init) {
|
|
748
|
+
const srcReg = this._compileExpr(decl.init, scope, bc);
|
|
749
|
+
this.emit(
|
|
750
|
+
bc,
|
|
751
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name), srcReg],
|
|
752
|
+
node,
|
|
753
|
+
);
|
|
754
|
+
} else {
|
|
755
|
+
const tmp = ctx.allocReg();
|
|
756
|
+
this.emit(
|
|
757
|
+
bc,
|
|
758
|
+
[this.OP.LOAD_CONST, tmp, b.constantOperand(undefined)],
|
|
759
|
+
node,
|
|
760
|
+
);
|
|
761
|
+
this.emit(
|
|
762
|
+
bc,
|
|
763
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name), tmp],
|
|
764
|
+
node,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
case "IfStatement": {
|
|
773
|
+
const elseOrEndLabel = this._makeLabel("if_else");
|
|
774
|
+
|
|
775
|
+
const savedTop = ctx.regTop;
|
|
776
|
+
const testReg = this._compileExpr(node.test, scope, bc);
|
|
777
|
+
this.emit(
|
|
778
|
+
bc,
|
|
779
|
+
[
|
|
780
|
+
this.OP.JUMP_IF_FALSE,
|
|
781
|
+
testReg,
|
|
782
|
+
{ type: "label", label: elseOrEndLabel },
|
|
783
|
+
],
|
|
784
|
+
node,
|
|
785
|
+
);
|
|
786
|
+
ctx.regTop = savedTop; // free test temps
|
|
787
|
+
|
|
788
|
+
const consequentBody =
|
|
789
|
+
node.consequent.type === "BlockStatement"
|
|
790
|
+
? node.consequent.body
|
|
791
|
+
: [node.consequent];
|
|
792
|
+
for (const stmt of consequentBody) {
|
|
793
|
+
this._compileStatement(stmt, scope, bc);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (node.alternate) {
|
|
797
|
+
const endLabel = this._makeLabel("if_end");
|
|
798
|
+
this.emit(
|
|
799
|
+
bc,
|
|
800
|
+
[this.OP.JUMP, { type: "label", label: endLabel }],
|
|
801
|
+
node,
|
|
802
|
+
);
|
|
803
|
+
this.emit(
|
|
804
|
+
bc,
|
|
805
|
+
[null, { type: "defineLabel", label: elseOrEndLabel }],
|
|
806
|
+
node,
|
|
807
|
+
);
|
|
808
|
+
const altBody =
|
|
809
|
+
node.alternate.type === "BlockStatement"
|
|
810
|
+
? node.alternate.body
|
|
811
|
+
: [node.alternate];
|
|
812
|
+
for (const stmt of altBody) {
|
|
813
|
+
this._compileStatement(stmt, scope, bc);
|
|
814
|
+
}
|
|
815
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
816
|
+
} else {
|
|
817
|
+
this.emit(
|
|
818
|
+
bc,
|
|
819
|
+
[null, { type: "defineLabel", label: elseOrEndLabel }],
|
|
820
|
+
node,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
case "WhileStatement": {
|
|
827
|
+
const _wLabel = this._pendingLabel;
|
|
828
|
+
this._pendingLabel = null;
|
|
829
|
+
|
|
830
|
+
const loopTopLabel = this._makeLabel("while_top");
|
|
831
|
+
const exitLabel = this._makeLabel("while_exit");
|
|
832
|
+
|
|
833
|
+
this._loopStack.push({
|
|
834
|
+
type: "loop",
|
|
835
|
+
label: _wLabel,
|
|
836
|
+
breakLabel: exitLabel,
|
|
837
|
+
continueLabel: loopTopLabel,
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
this.emit(
|
|
841
|
+
bc,
|
|
842
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
843
|
+
node,
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
const savedTop = ctx.regTop;
|
|
847
|
+
const testReg = this._compileExpr(node.test, scope, bc);
|
|
848
|
+
this.emit(
|
|
849
|
+
bc,
|
|
850
|
+
[this.OP.JUMP_IF_FALSE, testReg, { type: "label", label: exitLabel }],
|
|
851
|
+
node,
|
|
852
|
+
);
|
|
853
|
+
ctx.regTop = savedTop;
|
|
854
|
+
|
|
855
|
+
const whileBody =
|
|
856
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
857
|
+
for (const stmt of whileBody) {
|
|
858
|
+
this._compileStatement(stmt, scope, bc);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
this.emit(
|
|
862
|
+
bc,
|
|
863
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
864
|
+
node,
|
|
865
|
+
);
|
|
866
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
867
|
+
|
|
868
|
+
this._loopStack.pop();
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
case "DoWhileStatement": {
|
|
873
|
+
const _dwLabel = this._pendingLabel;
|
|
874
|
+
this._pendingLabel = null;
|
|
875
|
+
|
|
876
|
+
const loopTopLabel = this._makeLabel("dowhile_top");
|
|
877
|
+
const continueLabel = this._makeLabel("dowhile_cont");
|
|
878
|
+
const exitLabel = this._makeLabel("dowhile_exit");
|
|
879
|
+
|
|
880
|
+
this._loopStack.push({
|
|
881
|
+
type: "loop",
|
|
882
|
+
label: _dwLabel,
|
|
883
|
+
breakLabel: exitLabel,
|
|
884
|
+
continueLabel: continueLabel,
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
this.emit(
|
|
888
|
+
bc,
|
|
889
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
890
|
+
node,
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
const doWhileBody =
|
|
894
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
895
|
+
for (const stmt of doWhileBody) {
|
|
896
|
+
this._compileStatement(stmt, scope, bc);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
this.emit(
|
|
900
|
+
bc,
|
|
901
|
+
[null, { type: "defineLabel", label: continueLabel }],
|
|
902
|
+
node,
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
const savedTop = ctx.regTop;
|
|
906
|
+
const testReg = this._compileExpr(node.test, scope, bc);
|
|
907
|
+
this.emit(
|
|
908
|
+
bc,
|
|
909
|
+
[this.OP.JUMP_IF_FALSE, testReg, { type: "label", label: exitLabel }],
|
|
910
|
+
node,
|
|
911
|
+
);
|
|
912
|
+
ctx.regTop = savedTop;
|
|
913
|
+
this.emit(
|
|
914
|
+
bc,
|
|
915
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
916
|
+
node,
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
920
|
+
this._loopStack.pop();
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
case "ForStatement": {
|
|
925
|
+
const _fLabel = this._pendingLabel;
|
|
926
|
+
this._pendingLabel = null;
|
|
927
|
+
|
|
928
|
+
const loopTopLabel = this._makeLabel("for_top");
|
|
929
|
+
const exitLabel = this._makeLabel("for_exit");
|
|
930
|
+
const updateLabel = node.update
|
|
931
|
+
? this._makeLabel("for_update")
|
|
932
|
+
: loopTopLabel;
|
|
933
|
+
|
|
934
|
+
this._loopStack.push({
|
|
935
|
+
type: "loop",
|
|
936
|
+
label: _fLabel,
|
|
937
|
+
breakLabel: exitLabel,
|
|
938
|
+
continueLabel: updateLabel,
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
if (node.init) {
|
|
942
|
+
if (node.init.type === "VariableDeclaration") {
|
|
943
|
+
this._compileStatement(node.init, scope, bc);
|
|
944
|
+
} else {
|
|
945
|
+
this._compileExpr(node.init as t.Expression, scope, bc);
|
|
946
|
+
// result discarded; resetTemps in next iteration
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
this.emit(
|
|
951
|
+
bc,
|
|
952
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
953
|
+
node,
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
if (node.test) {
|
|
957
|
+
const savedTop = ctx.regTop;
|
|
958
|
+
const testReg = this._compileExpr(node.test, scope, bc);
|
|
959
|
+
this.emit(
|
|
960
|
+
bc,
|
|
961
|
+
[
|
|
962
|
+
this.OP.JUMP_IF_FALSE,
|
|
963
|
+
testReg,
|
|
964
|
+
{ type: "label", label: exitLabel },
|
|
965
|
+
],
|
|
966
|
+
node,
|
|
967
|
+
);
|
|
968
|
+
ctx.regTop = savedTop;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const forBody =
|
|
972
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
973
|
+
for (const stmt of forBody) {
|
|
974
|
+
this._compileStatement(stmt, scope, bc);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (node.update) {
|
|
978
|
+
this.emit(
|
|
979
|
+
bc,
|
|
980
|
+
[null, { type: "defineLabel", label: updateLabel }],
|
|
981
|
+
node,
|
|
982
|
+
);
|
|
983
|
+
this._compileExpr(node.update, scope, bc);
|
|
984
|
+
ctx.resetTemps(); // discard update expression result
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
this.emit(
|
|
988
|
+
bc,
|
|
989
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
990
|
+
node,
|
|
991
|
+
);
|
|
992
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
993
|
+
|
|
994
|
+
this._loopStack.pop();
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
case "BreakStatement": {
|
|
999
|
+
let _bTargetIdx = -1;
|
|
1000
|
+
if (node.label) {
|
|
1001
|
+
const _bLabelName = node.label.name;
|
|
1002
|
+
for (let _bi = this._loopStack.length - 1; _bi >= 0; _bi--) {
|
|
1003
|
+
if (this._loopStack[_bi].label === _bLabelName) {
|
|
1004
|
+
_bTargetIdx = _bi;
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (_bTargetIdx === -1)
|
|
1009
|
+
throw new Error(`Label '${node.label.name}' not found`);
|
|
1010
|
+
} else {
|
|
1011
|
+
for (let _bi = this._loopStack.length - 1; _bi >= 0; _bi--) {
|
|
1012
|
+
if ((this._loopStack[_bi].type as any) !== "try") {
|
|
1013
|
+
_bTargetIdx = _bi;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (_bTargetIdx === -1) throw new Error("break outside loop");
|
|
1018
|
+
}
|
|
1019
|
+
for (let _bi = this._loopStack.length - 1; _bi > _bTargetIdx; _bi--) {
|
|
1020
|
+
if ((this._loopStack[_bi].type as any) === "try") {
|
|
1021
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
this.emit(
|
|
1025
|
+
bc,
|
|
1026
|
+
[
|
|
1027
|
+
this.OP.JUMP,
|
|
1028
|
+
{ type: "label", label: this._loopStack[_bTargetIdx].breakLabel },
|
|
1029
|
+
],
|
|
1030
|
+
node,
|
|
1031
|
+
);
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
case "ContinueStatement": {
|
|
1036
|
+
let _cTargetIdx = -1;
|
|
1037
|
+
if (node.label) {
|
|
1038
|
+
const _cLabelName = node.label.name;
|
|
1039
|
+
for (let _ci = this._loopStack.length - 1; _ci >= 0; _ci--) {
|
|
1040
|
+
if (
|
|
1041
|
+
this._loopStack[_ci].label === _cLabelName &&
|
|
1042
|
+
this._loopStack[_ci].type === "loop"
|
|
1043
|
+
) {
|
|
1044
|
+
_cTargetIdx = _ci;
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (_cTargetIdx === -1)
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
`Label '${node.label.name}' not found for continue`,
|
|
1051
|
+
);
|
|
1052
|
+
} else {
|
|
1053
|
+
for (let _ci = this._loopStack.length - 1; _ci >= 0; _ci--) {
|
|
1054
|
+
if (this._loopStack[_ci].type === "loop") {
|
|
1055
|
+
_cTargetIdx = _ci;
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (_cTargetIdx === -1) throw new Error("continue outside loop");
|
|
1060
|
+
}
|
|
1061
|
+
for (let _ci = this._loopStack.length - 1; _ci > _cTargetIdx; _ci--) {
|
|
1062
|
+
if ((this._loopStack[_ci].type as any) === "try") {
|
|
1063
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
this.emit(
|
|
1067
|
+
bc,
|
|
1068
|
+
[
|
|
1069
|
+
this.OP.JUMP,
|
|
1070
|
+
{
|
|
1071
|
+
type: "label",
|
|
1072
|
+
label: this._loopStack[_cTargetIdx].continueLabel,
|
|
1073
|
+
},
|
|
1074
|
+
],
|
|
1075
|
+
node,
|
|
1076
|
+
);
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
case "SwitchStatement": {
|
|
1081
|
+
const _swLabel = this._pendingLabel;
|
|
1082
|
+
this._pendingLabel = null;
|
|
1083
|
+
|
|
1084
|
+
const switchBreakLabel = this._makeLabel("sw_break");
|
|
1085
|
+
|
|
1086
|
+
this._loopStack.push({
|
|
1087
|
+
type: "switch",
|
|
1088
|
+
label: _swLabel,
|
|
1089
|
+
breakLabel: switchBreakLabel,
|
|
1090
|
+
continueLabel: switchBreakLabel,
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// Compile discriminant into a register that lives for the whole switch.
|
|
1094
|
+
const discReg = this._compileExpr(node.discriminant, scope, bc);
|
|
1095
|
+
|
|
1096
|
+
const cases = node.cases;
|
|
1097
|
+
const defaultIdx = cases.findIndex((c) => c.test === null);
|
|
1098
|
+
const caseLabels = cases.map((_, i) => this._makeLabel(`sw_case_${i}`));
|
|
1099
|
+
|
|
1100
|
+
// Dispatch: for each non-default case, test and jump.
|
|
1101
|
+
for (let i = 0; i < cases.length; i++) {
|
|
1102
|
+
const cas = cases[i];
|
|
1103
|
+
if (cas.test === null) continue;
|
|
1104
|
+
|
|
1105
|
+
const nextCheckLabel = this._makeLabel("sw_next");
|
|
1106
|
+
const savedTop = ctx.regTop;
|
|
1107
|
+
const caseValReg = this._compileExpr(cas.test, scope, bc);
|
|
1108
|
+
const cmpReg = ctx.allocReg();
|
|
1109
|
+
this.emit(bc, [this.OP.EQ, cmpReg, discReg, caseValReg], node);
|
|
1110
|
+
this.emit(
|
|
1111
|
+
bc,
|
|
1112
|
+
[
|
|
1113
|
+
this.OP.JUMP_IF_FALSE,
|
|
1114
|
+
cmpReg,
|
|
1115
|
+
{ type: "label", label: nextCheckLabel },
|
|
1116
|
+
],
|
|
1117
|
+
node,
|
|
1118
|
+
);
|
|
1119
|
+
ctx.regTop = savedTop;
|
|
1120
|
+
this.emit(
|
|
1121
|
+
bc,
|
|
1122
|
+
[this.OP.JUMP, { type: "label", label: caseLabels[i] }],
|
|
1123
|
+
node,
|
|
1124
|
+
);
|
|
1125
|
+
this.emit(
|
|
1126
|
+
bc,
|
|
1127
|
+
[null, { type: "defineLabel", label: nextCheckLabel }],
|
|
1128
|
+
node,
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
this.emit(
|
|
1133
|
+
bc,
|
|
1134
|
+
[
|
|
1135
|
+
this.OP.JUMP,
|
|
1136
|
+
{
|
|
1137
|
+
type: "label",
|
|
1138
|
+
label:
|
|
1139
|
+
defaultIdx !== -1 ? caseLabels[defaultIdx] : switchBreakLabel,
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
node,
|
|
1143
|
+
);
|
|
1144
|
+
|
|
1145
|
+
for (let i = 0; i < cases.length; i++) {
|
|
1146
|
+
this.emit(
|
|
1147
|
+
bc,
|
|
1148
|
+
[null, { type: "defineLabel", label: caseLabels[i] }],
|
|
1149
|
+
node,
|
|
1150
|
+
);
|
|
1151
|
+
for (const stmt of cases[i].consequent) {
|
|
1152
|
+
this._compileStatement(stmt, scope, bc);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Break lands here – discriminant register is simply abandoned.
|
|
1157
|
+
this.emit(
|
|
1158
|
+
bc,
|
|
1159
|
+
[null, { type: "defineLabel", label: switchBreakLabel }],
|
|
1160
|
+
node,
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
this._loopStack.pop();
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
case "LabeledStatement": {
|
|
1168
|
+
const _lName = node.label.name;
|
|
1169
|
+
const _lBody = node.body;
|
|
1170
|
+
const _lIsLoop =
|
|
1171
|
+
_lBody.type === "ForStatement" ||
|
|
1172
|
+
_lBody.type === "WhileStatement" ||
|
|
1173
|
+
_lBody.type === "DoWhileStatement" ||
|
|
1174
|
+
_lBody.type === "ForInStatement";
|
|
1175
|
+
const _lIsSwitch = _lBody.type === "SwitchStatement";
|
|
1176
|
+
|
|
1177
|
+
if (_lIsLoop || _lIsSwitch) {
|
|
1178
|
+
this._pendingLabel = _lName;
|
|
1179
|
+
this._compileStatement(_lBody, scope, bc);
|
|
1180
|
+
this._pendingLabel = null;
|
|
1181
|
+
} else {
|
|
1182
|
+
const blockBreakLabel = this._makeLabel("block_break");
|
|
1183
|
+
this._loopStack.push({
|
|
1184
|
+
type: "block",
|
|
1185
|
+
label: _lName,
|
|
1186
|
+
breakLabel: blockBreakLabel,
|
|
1187
|
+
continueLabel: blockBreakLabel,
|
|
1188
|
+
});
|
|
1189
|
+
this._compileStatement(_lBody, scope, bc);
|
|
1190
|
+
this._loopStack.pop();
|
|
1191
|
+
this.emit(
|
|
1192
|
+
bc,
|
|
1193
|
+
[null, { type: "defineLabel", label: blockBreakLabel }],
|
|
1194
|
+
node,
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
case "ForInStatement": {
|
|
1201
|
+
const _fiLabel = this._pendingLabel;
|
|
1202
|
+
this._pendingLabel = null;
|
|
1203
|
+
|
|
1204
|
+
// Iterator register was reserved by _hoistVars.
|
|
1205
|
+
const iterSlot: number = (node as any)._iterSlot;
|
|
1206
|
+
|
|
1207
|
+
// FOR_IN_SETUP dst, src
|
|
1208
|
+
const objReg = this._compileExpr(node.right, scope, bc);
|
|
1209
|
+
this.emit(bc, [this.OP.FOR_IN_SETUP, iterSlot, objReg], node);
|
|
1210
|
+
|
|
1211
|
+
const loopTopLabel = this._makeLabel("forin_top");
|
|
1212
|
+
const exitLabel = this._makeLabel("forin_exit");
|
|
1213
|
+
|
|
1214
|
+
this._loopStack.push({
|
|
1215
|
+
type: "loop",
|
|
1216
|
+
label: _fiLabel,
|
|
1217
|
+
breakLabel: exitLabel,
|
|
1218
|
+
continueLabel: loopTopLabel,
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
this.emit(
|
|
1222
|
+
bc,
|
|
1223
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
1224
|
+
node,
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
// FOR_IN_NEXT keyDst, iter, exitTarget
|
|
1228
|
+
const keyReg = ctx.allocReg();
|
|
1229
|
+
this.emit(
|
|
1230
|
+
bc,
|
|
1231
|
+
[
|
|
1232
|
+
this.OP.FOR_IN_NEXT,
|
|
1233
|
+
keyReg,
|
|
1234
|
+
iterSlot,
|
|
1235
|
+
{ type: "label", label: exitLabel },
|
|
1236
|
+
],
|
|
1237
|
+
node,
|
|
1238
|
+
);
|
|
1239
|
+
|
|
1240
|
+
// Assign the key to the loop variable.
|
|
1241
|
+
if (node.left.type === "VariableDeclaration") {
|
|
1242
|
+
const identifier = node.left.declarations[0].id;
|
|
1243
|
+
ok(
|
|
1244
|
+
identifier.type === "Identifier",
|
|
1245
|
+
"Only simple identifiers can be declared in for-in loops",
|
|
1246
|
+
);
|
|
1247
|
+
const name = (identifier as t.Identifier).name;
|
|
1248
|
+
if (scope) {
|
|
1249
|
+
const slot = scope._locals.get(name)!;
|
|
1250
|
+
if (keyReg !== slot)
|
|
1251
|
+
this.emit(bc, [this.OP.MOVE, slot, keyReg], node);
|
|
1252
|
+
} else {
|
|
1253
|
+
this.emit(
|
|
1254
|
+
bc,
|
|
1255
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name), keyReg],
|
|
1256
|
+
node,
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
} else if (node.left.type === "Identifier") {
|
|
1260
|
+
const res = this._resolve(node.left.name, this._currentCtx);
|
|
1261
|
+
if (res.kind === "local") {
|
|
1262
|
+
if (keyReg !== res.slot)
|
|
1263
|
+
this.emit(bc, [this.OP.MOVE, res.slot, keyReg], node);
|
|
1264
|
+
} else if (res.kind === "upvalue") {
|
|
1265
|
+
this.emit(bc, [this.OP.STORE_UPVALUE, res.index, keyReg], node);
|
|
1266
|
+
} else {
|
|
1267
|
+
this.emit(
|
|
1268
|
+
bc,
|
|
1269
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(node.left.name), keyReg],
|
|
1270
|
+
node,
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
} else {
|
|
1274
|
+
const src = generate(node.left).code;
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`Unsupported for-in left-hand side: ${node.left.type}\n -> ${src}`,
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const fiBody =
|
|
1281
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
1282
|
+
for (const stmt of fiBody) {
|
|
1283
|
+
this._compileStatement(stmt, scope, bc);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
this.emit(
|
|
1287
|
+
bc,
|
|
1288
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
1289
|
+
node,
|
|
1290
|
+
);
|
|
1291
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
1292
|
+
|
|
1293
|
+
this._loopStack.pop();
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
case "TryStatement": {
|
|
1298
|
+
if (node.finalizer) {
|
|
1299
|
+
throw new Error("try..finally is not supported");
|
|
1300
|
+
}
|
|
1301
|
+
if (!node.handler) {
|
|
1302
|
+
throw new Error("try without catch is not supported");
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const catchLabel = this._makeLabel("catch");
|
|
1306
|
+
const afterCatchLabel = this._makeLabel("after_catch");
|
|
1307
|
+
|
|
1308
|
+
// Determine where the caught exception is written.
|
|
1309
|
+
const exceptionReg =
|
|
1310
|
+
node.handler.param?.type === "Identifier"
|
|
1311
|
+
? (scope?._locals.get((node.handler.param as t.Identifier).name) ??
|
|
1312
|
+
ctx.allocReg()) // shouldn't normally reach here
|
|
1313
|
+
: (node as any)._exceptionSlot;
|
|
1314
|
+
|
|
1315
|
+
this.emit(
|
|
1316
|
+
bc,
|
|
1317
|
+
[
|
|
1318
|
+
this.OP.TRY_SETUP,
|
|
1319
|
+
{ type: "label", label: catchLabel },
|
|
1320
|
+
exceptionReg,
|
|
1321
|
+
],
|
|
1322
|
+
node,
|
|
1323
|
+
);
|
|
1324
|
+
|
|
1325
|
+
this._loopStack.push({
|
|
1326
|
+
type: "try" as any,
|
|
1327
|
+
label: null,
|
|
1328
|
+
breakLabel: "",
|
|
1329
|
+
continueLabel: "",
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
for (const stmt of node.block.body) {
|
|
1333
|
+
this._compileStatement(stmt, scope, bc);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
this._loopStack.pop();
|
|
1337
|
+
|
|
1338
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
1339
|
+
this.emit(
|
|
1340
|
+
bc,
|
|
1341
|
+
[this.OP.JUMP, { type: "label", label: afterCatchLabel }],
|
|
1342
|
+
node,
|
|
1343
|
+
);
|
|
1344
|
+
|
|
1345
|
+
// Catch block: exceptionReg already holds the caught value.
|
|
1346
|
+
this.emit(bc, [null, { type: "defineLabel", label: catchLabel }], node);
|
|
1347
|
+
|
|
1348
|
+
// If no param binding, just ignore the exception (it's in the dummy slot).
|
|
1349
|
+
for (const stmt of node.handler!.body.body) {
|
|
1350
|
+
this._compileStatement(stmt, scope, bc);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
this.emit(
|
|
1354
|
+
bc,
|
|
1355
|
+
[null, { type: "defineLabel", label: afterCatchLabel }],
|
|
1356
|
+
node,
|
|
1357
|
+
);
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
default: {
|
|
1362
|
+
const src = generate(node).code;
|
|
1363
|
+
throw new Error(`Unsupported statement: ${node.type}\n -> ${src}`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// ── Expressions ───────────────────────────────────────────────────────────
|
|
1369
|
+
// Returns the register index that holds the result.
|
|
1370
|
+
// For local variables: returns their slot directly (no instruction emitted).
|
|
1371
|
+
// For all others: allocates a fresh temp register, emits the instruction(s),
|
|
1372
|
+
// and returns the allocated register.
|
|
1373
|
+
_compileExpr(
|
|
1374
|
+
node: t.Expression | t.Node,
|
|
1375
|
+
scope: Scope | null,
|
|
1376
|
+
bc: b.Bytecode,
|
|
1377
|
+
): number {
|
|
1378
|
+
const ctx = this._currentCtx!;
|
|
1379
|
+
|
|
1380
|
+
switch ((node as any).type) {
|
|
1381
|
+
case "NumericLiteral":
|
|
1382
|
+
case "StringLiteral":
|
|
1383
|
+
case "BooleanLiteral": {
|
|
1384
|
+
const dst = ctx.allocReg();
|
|
1385
|
+
this.emit(
|
|
1386
|
+
bc,
|
|
1387
|
+
[this.OP.LOAD_CONST, dst, b.constantOperand((node as any).value)],
|
|
1388
|
+
node,
|
|
1389
|
+
);
|
|
1390
|
+
return dst;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
case "NullLiteral": {
|
|
1394
|
+
const dst = ctx.allocReg();
|
|
1395
|
+
this.emit(bc, [this.OP.LOAD_CONST, dst, b.constantOperand(null)], node);
|
|
1396
|
+
return dst;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
case "Identifier": {
|
|
1400
|
+
const res = this._resolve(
|
|
1401
|
+
(node as t.Identifier).name,
|
|
1402
|
+
this._currentCtx,
|
|
1403
|
+
);
|
|
1404
|
+
if (res.kind === "local") return res.slot; // register IS the local
|
|
1405
|
+
if (res.kind === "upvalue") {
|
|
1406
|
+
const dst = ctx.allocReg();
|
|
1407
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, dst, res.index], node);
|
|
1408
|
+
return dst;
|
|
1409
|
+
}
|
|
1410
|
+
// global
|
|
1411
|
+
const dst = ctx.allocReg();
|
|
1412
|
+
this.emit(
|
|
1413
|
+
bc,
|
|
1414
|
+
[
|
|
1415
|
+
this.OP.LOAD_GLOBAL,
|
|
1416
|
+
dst,
|
|
1417
|
+
b.constantOperand((node as t.Identifier).name),
|
|
1418
|
+
],
|
|
1419
|
+
node,
|
|
1420
|
+
);
|
|
1421
|
+
return dst;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
case "ThisExpression": {
|
|
1425
|
+
const dst = ctx.allocReg();
|
|
1426
|
+
this.emit(bc, [this.OP.LOAD_THIS, dst], node);
|
|
1427
|
+
return dst;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
case "NewExpression": {
|
|
1431
|
+
const calleeReg = this._compileExpr(
|
|
1432
|
+
(node as t.NewExpression).callee,
|
|
1433
|
+
scope,
|
|
1434
|
+
bc,
|
|
1435
|
+
);
|
|
1436
|
+
const argRegs = (node as t.NewExpression).arguments.map((a) =>
|
|
1437
|
+
this._compileExpr(a as t.Expression, scope, bc),
|
|
1438
|
+
);
|
|
1439
|
+
const dst = ctx.allocReg();
|
|
1440
|
+
this.emit(
|
|
1441
|
+
bc,
|
|
1442
|
+
[
|
|
1443
|
+
this.OP.NEW,
|
|
1444
|
+
dst,
|
|
1445
|
+
calleeReg,
|
|
1446
|
+
(node as t.NewExpression).arguments.length,
|
|
1447
|
+
...argRegs,
|
|
1448
|
+
],
|
|
1449
|
+
node,
|
|
1450
|
+
);
|
|
1451
|
+
return dst;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
case "SequenceExpression": {
|
|
1455
|
+
const exprs = (node as t.SequenceExpression).expressions;
|
|
1456
|
+
for (let i = 0; i < exprs.length - 1; i++) {
|
|
1457
|
+
const savedTop = ctx.regTop;
|
|
1458
|
+
this._compileExpr(exprs[i], scope, bc);
|
|
1459
|
+
ctx.regTop = savedTop; // discard intermediate result
|
|
1460
|
+
}
|
|
1461
|
+
return this._compileExpr(exprs[exprs.length - 1], scope, bc);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
case "ConditionalExpression": {
|
|
1465
|
+
const n = node as t.ConditionalExpression;
|
|
1466
|
+
const elseLabel = this._makeLabel("ternary_else");
|
|
1467
|
+
const endLabel = this._makeLabel("ternary_end");
|
|
1468
|
+
|
|
1469
|
+
// Compile test; free its temps after the jump is emitted.
|
|
1470
|
+
const baseTop = ctx.regTop;
|
|
1471
|
+
const testReg = this._compileExpr(n.test, scope, bc);
|
|
1472
|
+
this.emit(
|
|
1473
|
+
bc,
|
|
1474
|
+
[this.OP.JUMP_IF_FALSE, testReg, { type: "label", label: elseLabel }],
|
|
1475
|
+
node,
|
|
1476
|
+
);
|
|
1477
|
+
ctx.regTop = baseTop; // free test temps
|
|
1478
|
+
|
|
1479
|
+
// Reserve reg_result at the base of the temp space.
|
|
1480
|
+
const reg_result = ctx.allocReg();
|
|
1481
|
+
|
|
1482
|
+
// Consequent branch.
|
|
1483
|
+
const consReg = this._compileExpr(n.consequent, scope, bc);
|
|
1484
|
+
if (consReg !== reg_result)
|
|
1485
|
+
this.emit(bc, [this.OP.MOVE, reg_result, consReg], node);
|
|
1486
|
+
this.emit(bc, [this.OP.JUMP, { type: "label", label: endLabel }], node);
|
|
1487
|
+
|
|
1488
|
+
// Alternate branch: reset to baseTop then re-reserve reg_result.
|
|
1489
|
+
this.emit(bc, [null, { type: "defineLabel", label: elseLabel }], node);
|
|
1490
|
+
ctx.regTop = baseTop;
|
|
1491
|
+
ctx.allocReg(); // re-occupy reg_result slot
|
|
1492
|
+
const altReg = this._compileExpr(n.alternate, scope, bc);
|
|
1493
|
+
if (altReg !== reg_result)
|
|
1494
|
+
this.emit(bc, [this.OP.MOVE, reg_result, altReg], node);
|
|
1495
|
+
|
|
1496
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
1497
|
+
|
|
1498
|
+
// Leave reg_result allocated above baseTop.
|
|
1499
|
+
ctx.regTop = baseTop + 1;
|
|
1500
|
+
return reg_result;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
case "LogicalExpression": {
|
|
1504
|
+
const n = node as t.LogicalExpression;
|
|
1505
|
+
const endLabel = this._makeLabel("logical_end");
|
|
1506
|
+
const isOr = n.operator === "||";
|
|
1507
|
+
if (!isOr && n.operator !== "&&")
|
|
1508
|
+
throw new Error(`Unsupported logical operator: ${n.operator}`);
|
|
1509
|
+
|
|
1510
|
+
const baseTop = ctx.regTop;
|
|
1511
|
+
const lhsReg = this._compileExpr(n.left, scope, bc);
|
|
1512
|
+
ctx.regTop = baseTop;
|
|
1513
|
+
const reg_result = ctx.allocReg();
|
|
1514
|
+
if (lhsReg !== reg_result)
|
|
1515
|
+
this.emit(bc, [this.OP.MOVE, reg_result, lhsReg], node);
|
|
1516
|
+
|
|
1517
|
+
// For ||: if truthy keep LHS, jump past RHS.
|
|
1518
|
+
// For &&: if falsy keep LHS, jump past RHS.
|
|
1519
|
+
this.emit(
|
|
1520
|
+
bc,
|
|
1521
|
+
[
|
|
1522
|
+
isOr ? this.OP.JUMP_IF_TRUE : this.OP.JUMP_IF_FALSE,
|
|
1523
|
+
reg_result,
|
|
1524
|
+
{ type: "label", label: endLabel },
|
|
1525
|
+
],
|
|
1526
|
+
node,
|
|
1527
|
+
);
|
|
1528
|
+
|
|
1529
|
+
// Compile RHS into reg_result.
|
|
1530
|
+
ctx.regTop = baseTop;
|
|
1531
|
+
ctx.allocReg(); // re-occupy reg_result
|
|
1532
|
+
const rhsReg = this._compileExpr(n.right, scope, bc);
|
|
1533
|
+
if (rhsReg !== reg_result)
|
|
1534
|
+
this.emit(bc, [this.OP.MOVE, reg_result, rhsReg], node);
|
|
1535
|
+
|
|
1536
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
1537
|
+
|
|
1538
|
+
ctx.regTop = baseTop + 1;
|
|
1539
|
+
return reg_result;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
case "TemplateLiteral": {
|
|
1543
|
+
const n = node as t.TemplateLiteral;
|
|
1544
|
+
// Fold: quasi[0] + expr[0] + quasi[1] + ... + quasi[last]
|
|
1545
|
+
let acc = ctx.allocReg();
|
|
1546
|
+
this.emit(
|
|
1547
|
+
bc,
|
|
1548
|
+
[
|
|
1549
|
+
this.OP.LOAD_CONST,
|
|
1550
|
+
acc,
|
|
1551
|
+
b.constantOperand(n.quasis[0].value.cooked ?? ""),
|
|
1552
|
+
],
|
|
1553
|
+
node,
|
|
1554
|
+
);
|
|
1555
|
+
for (let i = 0; i < n.expressions.length; i++) {
|
|
1556
|
+
const exprReg = this._compileExpr(
|
|
1557
|
+
n.expressions[i] as t.Expression,
|
|
1558
|
+
scope,
|
|
1559
|
+
bc,
|
|
1560
|
+
);
|
|
1561
|
+
const t1 = ctx.allocReg();
|
|
1562
|
+
this.emit(bc, [this.OP.ADD, t1, acc, exprReg], node);
|
|
1563
|
+
acc = t1;
|
|
1564
|
+
const quasiReg = ctx.allocReg();
|
|
1565
|
+
this.emit(
|
|
1566
|
+
bc,
|
|
1567
|
+
[
|
|
1568
|
+
this.OP.LOAD_CONST,
|
|
1569
|
+
quasiReg,
|
|
1570
|
+
b.constantOperand(n.quasis[i + 1].value.cooked ?? ""),
|
|
1571
|
+
],
|
|
1572
|
+
node,
|
|
1573
|
+
);
|
|
1574
|
+
const t2 = ctx.allocReg();
|
|
1575
|
+
this.emit(bc, [this.OP.ADD, t2, acc, quasiReg], node);
|
|
1576
|
+
acc = t2;
|
|
1577
|
+
}
|
|
1578
|
+
return acc;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
case "BinaryExpression": {
|
|
1582
|
+
const n = node as t.BinaryExpression;
|
|
1583
|
+
const lhsReg = this._compileExpr(n.left as t.Expression, scope, bc);
|
|
1584
|
+
const rhsReg = this._compileExpr(n.right as t.Expression, scope, bc);
|
|
1585
|
+
const dst = ctx.allocReg();
|
|
1586
|
+
|
|
1587
|
+
const op = (
|
|
1588
|
+
{
|
|
1589
|
+
"+": this.OP.ADD,
|
|
1590
|
+
"-": this.OP.SUB,
|
|
1591
|
+
"*": this.OP.MUL,
|
|
1592
|
+
"/": this.OP.DIV,
|
|
1593
|
+
"%": this.OP.MOD,
|
|
1594
|
+
"&": this.OP.BAND,
|
|
1595
|
+
"|": this.OP.BOR,
|
|
1596
|
+
"^": this.OP.BXOR,
|
|
1597
|
+
"<<": this.OP.SHL,
|
|
1598
|
+
">>": this.OP.SHR,
|
|
1599
|
+
">>>": this.OP.USHR,
|
|
1600
|
+
"<": this.OP.LT,
|
|
1601
|
+
">": this.OP.GT,
|
|
1602
|
+
"===": this.OP.EQ,
|
|
1603
|
+
"==": this.OP.LOOSE_EQ,
|
|
1604
|
+
"<=": this.OP.LTE,
|
|
1605
|
+
">=": this.OP.GTE,
|
|
1606
|
+
"!==": this.OP.NEQ,
|
|
1607
|
+
"!=": this.OP.LOOSE_NEQ,
|
|
1608
|
+
in: this.OP.IN,
|
|
1609
|
+
instanceof: this.OP.INSTANCEOF,
|
|
1610
|
+
} as Record<string, number | undefined>
|
|
1611
|
+
)[n.operator];
|
|
1612
|
+
|
|
1613
|
+
if (op === undefined)
|
|
1614
|
+
throw new Error(`Unsupported operator: ${n.operator}`);
|
|
1615
|
+
|
|
1616
|
+
this.emit(bc, [op, dst, lhsReg, rhsReg], node);
|
|
1617
|
+
return dst;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
case "UpdateExpression": {
|
|
1621
|
+
const n = node as t.UpdateExpression;
|
|
1622
|
+
const bumpOp = n.operator === "++" ? this.OP.ADD : this.OP.SUB;
|
|
1623
|
+
|
|
1624
|
+
// Shared: compute curReg +/- 1 into newReg, return [postfixResult, newReg]
|
|
1625
|
+
const applyBump = (curReg: number): [number, number] => {
|
|
1626
|
+
const postfixReg = n.prefix
|
|
1627
|
+
? -1
|
|
1628
|
+
: (() => {
|
|
1629
|
+
const r = ctx.allocReg();
|
|
1630
|
+
this.emit(bc, [this.OP.MOVE, r, curReg], node as t.Node);
|
|
1631
|
+
return r;
|
|
1632
|
+
})();
|
|
1633
|
+
const oneReg = ctx.allocReg();
|
|
1634
|
+
this.emit(
|
|
1635
|
+
bc,
|
|
1636
|
+
[this.OP.LOAD_CONST, oneReg, b.constantOperand(1)],
|
|
1637
|
+
node as t.Node,
|
|
1638
|
+
);
|
|
1639
|
+
const newReg = ctx.allocReg();
|
|
1640
|
+
this.emit(bc, [bumpOp, newReg, curReg, oneReg], node as t.Node);
|
|
1641
|
+
return [postfixReg, newReg];
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
if (n.argument.type === "MemberExpression") {
|
|
1645
|
+
const mem = n.argument as t.MemberExpression;
|
|
1646
|
+
const objReg = this._compileExpr(mem.object, scope, bc);
|
|
1647
|
+
let keyReg: number;
|
|
1648
|
+
if (mem.computed) {
|
|
1649
|
+
keyReg = this._compileExpr(mem.property as t.Expression, scope, bc);
|
|
1650
|
+
} else {
|
|
1651
|
+
keyReg = ctx.allocReg();
|
|
1652
|
+
this.emit(
|
|
1653
|
+
bc,
|
|
1654
|
+
[
|
|
1655
|
+
this.OP.LOAD_CONST,
|
|
1656
|
+
keyReg,
|
|
1657
|
+
b.constantOperand((mem.property as t.Identifier).name),
|
|
1658
|
+
],
|
|
1659
|
+
node as t.Node,
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
const curReg = ctx.allocReg();
|
|
1663
|
+
this.emit(
|
|
1664
|
+
bc,
|
|
1665
|
+
[this.OP.GET_PROP, curReg, objReg, keyReg],
|
|
1666
|
+
node as t.Node,
|
|
1667
|
+
);
|
|
1668
|
+
const [postfixReg, newReg] = applyBump(curReg);
|
|
1669
|
+
this.emit(
|
|
1670
|
+
bc,
|
|
1671
|
+
[this.OP.SET_PROP, objReg, keyReg, newReg],
|
|
1672
|
+
node as t.Node,
|
|
1673
|
+
);
|
|
1674
|
+
return n.prefix ? newReg : postfixReg;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
ok(
|
|
1678
|
+
n.argument.type === "Identifier",
|
|
1679
|
+
"UpdateExpression requires identifier or member expression",
|
|
1680
|
+
);
|
|
1681
|
+
const name = (n.argument as t.Identifier).name;
|
|
1682
|
+
const res = this._resolve(name, this._currentCtx);
|
|
1683
|
+
|
|
1684
|
+
let curReg: number;
|
|
1685
|
+
if (res.kind === "local") {
|
|
1686
|
+
curReg = res.slot;
|
|
1687
|
+
} else if (res.kind === "upvalue") {
|
|
1688
|
+
curReg = ctx.allocReg();
|
|
1689
|
+
this.emit(
|
|
1690
|
+
bc,
|
|
1691
|
+
[this.OP.LOAD_UPVALUE, curReg, res.index],
|
|
1692
|
+
node as t.Node,
|
|
1693
|
+
);
|
|
1694
|
+
} else {
|
|
1695
|
+
curReg = ctx.allocReg();
|
|
1696
|
+
this.emit(
|
|
1697
|
+
bc,
|
|
1698
|
+
[this.OP.LOAD_GLOBAL, curReg, b.constantOperand(name)],
|
|
1699
|
+
node as t.Node,
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
const [postfixReg, newReg] = applyBump(curReg);
|
|
1704
|
+
|
|
1705
|
+
if (res.kind === "local") {
|
|
1706
|
+
this.emit(bc, [this.OP.MOVE, res.slot, newReg], node as t.Node);
|
|
1707
|
+
} else if (res.kind === "upvalue") {
|
|
1708
|
+
this.emit(
|
|
1709
|
+
bc,
|
|
1710
|
+
[this.OP.STORE_UPVALUE, res.index, newReg],
|
|
1711
|
+
node as t.Node,
|
|
1712
|
+
);
|
|
1713
|
+
} else {
|
|
1714
|
+
this.emit(
|
|
1715
|
+
bc,
|
|
1716
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name), newReg],
|
|
1717
|
+
node as t.Node,
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
return n.prefix ? newReg : postfixReg;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
case "AssignmentExpression": {
|
|
1725
|
+
const n = node as t.AssignmentExpression;
|
|
1726
|
+
const compoundOp = (
|
|
1727
|
+
{
|
|
1728
|
+
"+=": this.OP.ADD,
|
|
1729
|
+
"-=": this.OP.SUB,
|
|
1730
|
+
"*=": this.OP.MUL,
|
|
1731
|
+
"/=": this.OP.DIV,
|
|
1732
|
+
"%=": this.OP.MOD,
|
|
1733
|
+
"&=": this.OP.BAND,
|
|
1734
|
+
"|=": this.OP.BOR,
|
|
1735
|
+
"^=": this.OP.BXOR,
|
|
1736
|
+
"<<=": this.OP.SHL,
|
|
1737
|
+
">>=": this.OP.SHR,
|
|
1738
|
+
">>>=": this.OP.USHR,
|
|
1739
|
+
} as Record<string, number | undefined>
|
|
1740
|
+
)[n.operator];
|
|
1741
|
+
const isCompound = compoundOp !== undefined;
|
|
1742
|
+
|
|
1743
|
+
if (n.operator !== "=" && !isCompound)
|
|
1744
|
+
throw new Error(`Unsupported assignment operator: ${n.operator}`);
|
|
1745
|
+
|
|
1746
|
+
// Member assignment: obj.x = val or arr[i] = val
|
|
1747
|
+
if (n.left.type === "MemberExpression") {
|
|
1748
|
+
const objReg = this._compileExpr(n.left.object, scope, bc);
|
|
1749
|
+
|
|
1750
|
+
let keyReg: number;
|
|
1751
|
+
if (n.left.computed) {
|
|
1752
|
+
keyReg = this._compileExpr(
|
|
1753
|
+
n.left.property as t.Expression,
|
|
1754
|
+
scope,
|
|
1755
|
+
bc,
|
|
1756
|
+
);
|
|
1757
|
+
} else {
|
|
1758
|
+
keyReg = ctx.allocReg();
|
|
1759
|
+
this.emit(
|
|
1760
|
+
bc,
|
|
1761
|
+
[
|
|
1762
|
+
this.OP.LOAD_CONST,
|
|
1763
|
+
keyReg,
|
|
1764
|
+
b.constantOperand((n.left.property as t.Identifier).name),
|
|
1765
|
+
],
|
|
1766
|
+
node,
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
let valReg: number;
|
|
1771
|
+
if (isCompound) {
|
|
1772
|
+
const curReg = ctx.allocReg();
|
|
1773
|
+
this.emit(bc, [this.OP.GET_PROP, curReg, objReg, keyReg], node);
|
|
1774
|
+
const rhsReg = this._compileExpr(n.right, scope, bc);
|
|
1775
|
+
valReg = ctx.allocReg();
|
|
1776
|
+
this.emit(bc, [compoundOp!, valReg, curReg, rhsReg], node);
|
|
1777
|
+
} else {
|
|
1778
|
+
valReg = this._compileExpr(n.right, scope, bc);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
this.emit(bc, [this.OP.SET_PROP, objReg, keyReg, valReg], node);
|
|
1782
|
+
return valReg;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Plain identifier assignment.
|
|
1786
|
+
const res = this._resolve(
|
|
1787
|
+
(n.left as t.Identifier).name,
|
|
1788
|
+
this._currentCtx,
|
|
1789
|
+
);
|
|
1790
|
+
|
|
1791
|
+
let rhsReg: number;
|
|
1792
|
+
if (isCompound) {
|
|
1793
|
+
// Load current value of the variable.
|
|
1794
|
+
let curReg: number;
|
|
1795
|
+
if (res.kind === "local") {
|
|
1796
|
+
curReg = res.slot;
|
|
1797
|
+
} else if (res.kind === "upvalue") {
|
|
1798
|
+
curReg = ctx.allocReg();
|
|
1799
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, curReg, res.index], node);
|
|
1800
|
+
} else {
|
|
1801
|
+
curReg = ctx.allocReg();
|
|
1802
|
+
this.emit(
|
|
1803
|
+
bc,
|
|
1804
|
+
[
|
|
1805
|
+
this.OP.LOAD_GLOBAL,
|
|
1806
|
+
curReg,
|
|
1807
|
+
b.constantOperand((n.left as t.Identifier).name),
|
|
1808
|
+
],
|
|
1809
|
+
node,
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
const rhs2 = this._compileExpr(n.right, scope, bc);
|
|
1813
|
+
rhsReg = ctx.allocReg();
|
|
1814
|
+
this.emit(bc, [compoundOp!, rhsReg, curReg, rhs2], node);
|
|
1815
|
+
} else {
|
|
1816
|
+
rhsReg = this._compileExpr(n.right, scope, bc);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// Store result and return it.
|
|
1820
|
+
if (res.kind === "local") {
|
|
1821
|
+
if (rhsReg !== res.slot)
|
|
1822
|
+
this.emit(bc, [this.OP.MOVE, res.slot, rhsReg], node);
|
|
1823
|
+
return res.slot;
|
|
1824
|
+
} else if (res.kind === "upvalue") {
|
|
1825
|
+
this.emit(bc, [this.OP.STORE_UPVALUE, res.index, rhsReg], node);
|
|
1826
|
+
return rhsReg;
|
|
1827
|
+
} else {
|
|
1828
|
+
const nameIdx = b.constantOperand((n.left as t.Identifier).name);
|
|
1829
|
+
this.emit(bc, [this.OP.STORE_GLOBAL, nameIdx, rhsReg], node);
|
|
1830
|
+
return rhsReg;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
case "CallExpression": {
|
|
1835
|
+
const n = node as t.CallExpression;
|
|
1836
|
+
|
|
1837
|
+
if (n.callee.type === "MemberExpression") {
|
|
1838
|
+
// Method call: receiver.method(args)
|
|
1839
|
+
const receiverReg = this._compileExpr(n.callee.object, scope, bc);
|
|
1840
|
+
|
|
1841
|
+
let methodKeyReg: number;
|
|
1842
|
+
if (n.callee.computed) {
|
|
1843
|
+
methodKeyReg = this._compileExpr(
|
|
1844
|
+
n.callee.property as t.Expression,
|
|
1845
|
+
scope,
|
|
1846
|
+
bc,
|
|
1847
|
+
);
|
|
1848
|
+
} else {
|
|
1849
|
+
methodKeyReg = ctx.allocReg();
|
|
1850
|
+
this.emit(
|
|
1851
|
+
bc,
|
|
1852
|
+
[
|
|
1853
|
+
this.OP.LOAD_CONST,
|
|
1854
|
+
methodKeyReg,
|
|
1855
|
+
b.constantOperand((n.callee.property as t.Identifier).name),
|
|
1856
|
+
],
|
|
1857
|
+
node,
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
const calleeReg = ctx.allocReg();
|
|
1862
|
+
this.emit(
|
|
1863
|
+
bc,
|
|
1864
|
+
[this.OP.GET_PROP, calleeReg, receiverReg, methodKeyReg],
|
|
1865
|
+
node,
|
|
1866
|
+
);
|
|
1867
|
+
|
|
1868
|
+
const argRegs = n.arguments.map((a) =>
|
|
1869
|
+
this._compileExpr(a as t.Expression, scope, bc),
|
|
1870
|
+
);
|
|
1871
|
+
const dst = ctx.allocReg();
|
|
1872
|
+
this.emit(
|
|
1873
|
+
bc,
|
|
1874
|
+
[
|
|
1875
|
+
this.OP.CALL_METHOD,
|
|
1876
|
+
dst,
|
|
1877
|
+
receiverReg,
|
|
1878
|
+
calleeReg,
|
|
1879
|
+
n.arguments.length,
|
|
1880
|
+
...argRegs,
|
|
1881
|
+
],
|
|
1882
|
+
node,
|
|
1883
|
+
);
|
|
1884
|
+
return dst;
|
|
1885
|
+
} else {
|
|
1886
|
+
// Plain call: fn(args)
|
|
1887
|
+
const calleeReg = this._compileExpr(
|
|
1888
|
+
n.callee as t.Expression,
|
|
1889
|
+
scope,
|
|
1890
|
+
bc,
|
|
1891
|
+
);
|
|
1892
|
+
const argRegs = n.arguments.map((a) =>
|
|
1893
|
+
this._compileExpr(a as t.Expression, scope, bc),
|
|
1894
|
+
);
|
|
1895
|
+
const dst = ctx.allocReg();
|
|
1896
|
+
this.emit(
|
|
1897
|
+
bc,
|
|
1898
|
+
[this.OP.CALL, dst, calleeReg, n.arguments.length, ...argRegs],
|
|
1899
|
+
node,
|
|
1900
|
+
);
|
|
1901
|
+
return dst;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
case "UnaryExpression": {
|
|
1906
|
+
const n = node as t.UnaryExpression;
|
|
1907
|
+
|
|
1908
|
+
// typeof on a potentially-undeclared global -- safe guard.
|
|
1909
|
+
if (n.operator === "typeof" && n.argument.type === "Identifier") {
|
|
1910
|
+
const res = this._resolve(n.argument.name, this._currentCtx);
|
|
1911
|
+
if (res.kind === "global") {
|
|
1912
|
+
const dst = ctx.allocReg();
|
|
1913
|
+
this.emit(
|
|
1914
|
+
bc,
|
|
1915
|
+
[this.OP.TYPEOF_SAFE, dst, b.constantOperand(n.argument.name)],
|
|
1916
|
+
node,
|
|
1917
|
+
);
|
|
1918
|
+
return dst;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// delete expression.
|
|
1923
|
+
if (n.operator === "delete") {
|
|
1924
|
+
const arg = n.argument;
|
|
1925
|
+
if (arg.type === "MemberExpression") {
|
|
1926
|
+
const objReg = this._compileExpr(arg.object, scope, bc);
|
|
1927
|
+
let keyReg: number;
|
|
1928
|
+
if (arg.computed) {
|
|
1929
|
+
keyReg = this._compileExpr(
|
|
1930
|
+
arg.property as t.Expression,
|
|
1931
|
+
scope,
|
|
1932
|
+
bc,
|
|
1933
|
+
);
|
|
1934
|
+
} else {
|
|
1935
|
+
keyReg = ctx.allocReg();
|
|
1936
|
+
this.emit(
|
|
1937
|
+
bc,
|
|
1938
|
+
[
|
|
1939
|
+
this.OP.LOAD_CONST,
|
|
1940
|
+
keyReg,
|
|
1941
|
+
b.constantOperand((arg.property as t.Identifier).name),
|
|
1942
|
+
],
|
|
1943
|
+
node,
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
const dst = ctx.allocReg();
|
|
1947
|
+
this.emit(bc, [this.OP.DELETE_PROP, dst, objReg, keyReg], node);
|
|
1948
|
+
return dst;
|
|
1949
|
+
} else {
|
|
1950
|
+
// delete x or delete 0 -- always true in sloppy mode.
|
|
1951
|
+
const dst = ctx.allocReg();
|
|
1952
|
+
this.emit(
|
|
1953
|
+
bc,
|
|
1954
|
+
[this.OP.LOAD_CONST, dst, b.constantOperand(true)],
|
|
1955
|
+
node,
|
|
1956
|
+
);
|
|
1957
|
+
return dst;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// All other unary operators.
|
|
1962
|
+
const srcReg = this._compileExpr(n.argument, scope, bc);
|
|
1963
|
+
const dst = ctx.allocReg();
|
|
1964
|
+
const unaryOp = (
|
|
1965
|
+
{
|
|
1966
|
+
"-": this.OP.UNARY_NEG,
|
|
1967
|
+
"+": this.OP.UNARY_POS,
|
|
1968
|
+
"!": this.OP.UNARY_NOT,
|
|
1969
|
+
"~": this.OP.UNARY_BITNOT,
|
|
1970
|
+
typeof: this.OP.TYPEOF,
|
|
1971
|
+
void: this.OP.VOID,
|
|
1972
|
+
} as Record<string, number | undefined>
|
|
1973
|
+
)[n.operator];
|
|
1974
|
+
|
|
1975
|
+
if (unaryOp === undefined)
|
|
1976
|
+
throw new Error(`Unsupported unary operator: ${n.operator}`);
|
|
1977
|
+
|
|
1978
|
+
this.emit(bc, [unaryOp, dst, srcReg], node);
|
|
1979
|
+
return dst;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
case "RegExpLiteral": {
|
|
1983
|
+
const n = node as t.RegExpLiteral;
|
|
1984
|
+
// new RegExp(pattern, flags)
|
|
1985
|
+
const regExpReg = ctx.allocReg();
|
|
1986
|
+
this.emit(
|
|
1987
|
+
bc,
|
|
1988
|
+
[this.OP.LOAD_GLOBAL, regExpReg, b.constantOperand("RegExp")],
|
|
1989
|
+
node,
|
|
1990
|
+
);
|
|
1991
|
+
const patternReg = ctx.allocReg();
|
|
1992
|
+
this.emit(
|
|
1993
|
+
bc,
|
|
1994
|
+
[this.OP.LOAD_CONST, patternReg, b.constantOperand(n.pattern)],
|
|
1995
|
+
node,
|
|
1996
|
+
);
|
|
1997
|
+
const flagsReg = ctx.allocReg();
|
|
1998
|
+
this.emit(
|
|
1999
|
+
bc,
|
|
2000
|
+
[this.OP.LOAD_CONST, flagsReg, b.constantOperand(n.flags)],
|
|
2001
|
+
node,
|
|
2002
|
+
);
|
|
2003
|
+
const dst = ctx.allocReg();
|
|
2004
|
+
this.emit(
|
|
2005
|
+
bc,
|
|
2006
|
+
[this.OP.NEW, dst, regExpReg, 2, patternReg, flagsReg],
|
|
2007
|
+
node,
|
|
2008
|
+
);
|
|
2009
|
+
return dst;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
case "FunctionExpression": {
|
|
2013
|
+
const desc = this._compileFunctionDecl(node as t.FunctionExpression);
|
|
2014
|
+
return this._emitMakeClosure(desc, node, bc);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
case "MemberExpression": {
|
|
2018
|
+
const n = node as t.MemberExpression;
|
|
2019
|
+
const objReg = this._compileExpr(n.object, scope, bc);
|
|
2020
|
+
let keyReg: number;
|
|
2021
|
+
if (n.computed) {
|
|
2022
|
+
keyReg = this._compileExpr(n.property as t.Expression, scope, bc);
|
|
2023
|
+
} else {
|
|
2024
|
+
keyReg = ctx.allocReg();
|
|
2025
|
+
this.emit(
|
|
2026
|
+
bc,
|
|
2027
|
+
[
|
|
2028
|
+
this.OP.LOAD_CONST,
|
|
2029
|
+
keyReg,
|
|
2030
|
+
b.constantOperand((n.property as t.Identifier).name),
|
|
2031
|
+
],
|
|
2032
|
+
node,
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
const dst = ctx.allocReg();
|
|
2036
|
+
this.emit(bc, [this.OP.GET_PROP, dst, objReg, keyReg], node);
|
|
2037
|
+
return dst;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
case "ArrayExpression": {
|
|
2041
|
+
const n = node as t.ArrayExpression;
|
|
2042
|
+
const elemRegs = n.elements.map((el) => {
|
|
2043
|
+
if (el === null) {
|
|
2044
|
+
const r = ctx.allocReg();
|
|
2045
|
+
this.emit(
|
|
2046
|
+
bc,
|
|
2047
|
+
[this.OP.LOAD_CONST, r, b.constantOperand(undefined)],
|
|
2048
|
+
node,
|
|
2049
|
+
);
|
|
2050
|
+
return r;
|
|
2051
|
+
}
|
|
2052
|
+
return this._compileExpr(el as t.Expression, scope, bc);
|
|
2053
|
+
});
|
|
2054
|
+
const dst = ctx.allocReg();
|
|
2055
|
+
this.emit(
|
|
2056
|
+
bc,
|
|
2057
|
+
[this.OP.BUILD_ARRAY, dst, n.elements.length, ...elemRegs],
|
|
2058
|
+
node,
|
|
2059
|
+
);
|
|
2060
|
+
return dst;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
case "ObjectExpression": {
|
|
2064
|
+
const n = node as t.ObjectExpression;
|
|
2065
|
+
const regularProps: t.ObjectProperty[] = [];
|
|
2066
|
+
const accessorProps: t.ObjectMethod[] = [];
|
|
2067
|
+
|
|
2068
|
+
for (const prop of n.properties) {
|
|
2069
|
+
if (prop.type === "SpreadElement")
|
|
2070
|
+
throw new Error("Object spread not supported");
|
|
2071
|
+
if (prop.type === "ObjectMethod") {
|
|
2072
|
+
if (prop.kind === "get" || prop.kind === "set") {
|
|
2073
|
+
if (prop.computed)
|
|
2074
|
+
throw new Error(
|
|
2075
|
+
"Computed getter/setter keys are not supported",
|
|
2076
|
+
);
|
|
2077
|
+
accessorProps.push(prop);
|
|
2078
|
+
} else {
|
|
2079
|
+
throw new Error("Shorthand method syntax is not supported");
|
|
2080
|
+
}
|
|
2081
|
+
} else {
|
|
2082
|
+
regularProps.push(prop as t.ObjectProperty);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// Build flat [key, val, key, val, …] register list.
|
|
2087
|
+
const pairRegs: number[] = [];
|
|
2088
|
+
for (const prop of regularProps) {
|
|
2089
|
+
let keyStr: string;
|
|
2090
|
+
const key = prop.key;
|
|
2091
|
+
if (key.type === "Identifier") keyStr = key.name;
|
|
2092
|
+
else if (
|
|
2093
|
+
key.type === "StringLiteral" ||
|
|
2094
|
+
key.type === "NumericLiteral"
|
|
2095
|
+
)
|
|
2096
|
+
keyStr = String(key.value);
|
|
2097
|
+
else throw new Error(`Unsupported object key type: ${key.type}`);
|
|
2098
|
+
|
|
2099
|
+
const keyReg = ctx.allocReg();
|
|
2100
|
+
this.emit(
|
|
2101
|
+
bc,
|
|
2102
|
+
[this.OP.LOAD_CONST, keyReg, b.constantOperand(keyStr)],
|
|
2103
|
+
node,
|
|
2104
|
+
);
|
|
2105
|
+
const valReg = this._compileExpr(
|
|
2106
|
+
prop.value as t.Expression,
|
|
2107
|
+
scope,
|
|
2108
|
+
bc,
|
|
2109
|
+
);
|
|
2110
|
+
pairRegs.push(keyReg, valReg);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const dst = ctx.allocReg();
|
|
2114
|
+
this.emit(
|
|
2115
|
+
bc,
|
|
2116
|
+
[this.OP.BUILD_OBJECT, dst, regularProps.length, ...pairRegs],
|
|
2117
|
+
node,
|
|
2118
|
+
);
|
|
2119
|
+
|
|
2120
|
+
// Define accessors on the object now sitting in `dst`.
|
|
2121
|
+
for (const prop of accessorProps) {
|
|
2122
|
+
const key = prop.key;
|
|
2123
|
+
let keyStr: string;
|
|
2124
|
+
if (key.type === "Identifier") keyStr = key.name;
|
|
2125
|
+
else if (
|
|
2126
|
+
key.type === "StringLiteral" ||
|
|
2127
|
+
key.type === "NumericLiteral"
|
|
2128
|
+
)
|
|
2129
|
+
keyStr = String(key.value);
|
|
2130
|
+
else throw new Error(`Unsupported object key type: ${key.type}`);
|
|
2131
|
+
|
|
2132
|
+
const keyReg = ctx.allocReg();
|
|
2133
|
+
this.emit(
|
|
2134
|
+
bc,
|
|
2135
|
+
[this.OP.LOAD_CONST, keyReg, b.constantOperand(keyStr)],
|
|
2136
|
+
node,
|
|
2137
|
+
);
|
|
2138
|
+
const fnReg = this._emitMakeClosure(
|
|
2139
|
+
this._compileFunctionDecl(prop as any),
|
|
2140
|
+
prop as any,
|
|
2141
|
+
bc,
|
|
2142
|
+
);
|
|
2143
|
+
this.emit(
|
|
2144
|
+
bc,
|
|
2145
|
+
[
|
|
2146
|
+
prop.kind === "get"
|
|
2147
|
+
? this.OP.DEFINE_GETTER
|
|
2148
|
+
: this.OP.DEFINE_SETTER,
|
|
2149
|
+
dst,
|
|
2150
|
+
keyReg,
|
|
2151
|
+
fnReg,
|
|
2152
|
+
],
|
|
2153
|
+
node,
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
return dst;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
default: {
|
|
2161
|
+
throw new Error(`Unsupported expression: ${(node as any).type}`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// ── Serializer ────────────────────────────────────────────────────────────────
|
|
2168
|
+
class Serializer {
|
|
2169
|
+
compiler: Compiler;
|
|
2170
|
+
|
|
2171
|
+
constructor(compiler: Compiler) {
|
|
2172
|
+
this.compiler = compiler;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
get options() {
|
|
2176
|
+
return this.compiler.options;
|
|
2177
|
+
}
|
|
2178
|
+
get OP() {
|
|
2179
|
+
return this.compiler.OP;
|
|
2180
|
+
}
|
|
2181
|
+
get OP_NAME() {
|
|
2182
|
+
return this.compiler.OP_NAME;
|
|
2183
|
+
}
|
|
2184
|
+
get JUMP_OPS() {
|
|
2185
|
+
return this.compiler.JUMP_OPS;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
_serializeConst(val: any) {
|
|
2189
|
+
if (val === null) return "null";
|
|
2190
|
+
if (val === undefined) return "undefined";
|
|
2191
|
+
return JSON.stringify(val);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
// Reverse the concealment applied by resolveConstants so disassembly comments
|
|
2195
|
+
// always show the plaintext value regardless of the concealConstants option.
|
|
2196
|
+
_decryptConst(constants: any[], idx: number, key: number): any {
|
|
2197
|
+
const v = constants[idx];
|
|
2198
|
+
if (!key) return v;
|
|
2199
|
+
if (typeof v === "number") return v ^ key;
|
|
2200
|
+
// String: base64 → u16 LE byte pairs → XOR with (key + i) (mirrors _readConstant)
|
|
2201
|
+
const bytes = Buffer.from(v as string, "base64");
|
|
2202
|
+
let out = "";
|
|
2203
|
+
for (let i = 0; i < bytes.length / 2; i++) {
|
|
2204
|
+
const code = bytes[i * 2] | (bytes[i * 2 + 1] << 8);
|
|
2205
|
+
out += String.fromCharCode(code ^ ((key + i) & 0xffff));
|
|
2206
|
+
}
|
|
2207
|
+
return out;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
_serializeInstr(instr: b.Instruction): { text: string; values: number[] } {
|
|
2211
|
+
const op = instr[0] as number;
|
|
2212
|
+
const operands = instr.slice(1) as number[];
|
|
2213
|
+
|
|
2214
|
+
const constants = this.compiler.constants;
|
|
2215
|
+
|
|
2216
|
+
const resolvedOperands = operands
|
|
2217
|
+
.filter((operand) => (operand as any)?.placeholder !== true)
|
|
2218
|
+
.map((o) => (o as any)?.resolvedValue ?? o);
|
|
2219
|
+
|
|
2220
|
+
for (const o of resolvedOperands) {
|
|
2221
|
+
ok(typeof o === "number", "Unresolved operand: " + JSON.stringify(o));
|
|
2222
|
+
ok(o >= 0 && o <= 0xffff, `Operand overflow (max 0xFFFF u16): ${o}`);
|
|
2223
|
+
}
|
|
2224
|
+
ok(op >= 0 && op <= 0xffff, `Opcode overflow (max 0xFFFF u16): ${op}`);
|
|
2225
|
+
|
|
2226
|
+
let name = this.OP_NAME[op];
|
|
2227
|
+
if (!name || name.includes("{")) {
|
|
2228
|
+
name = `OP_${op}`;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
let comment = name;
|
|
2232
|
+
|
|
2233
|
+
function formatLoc(loc: t.Node["loc"]["start"]) {
|
|
2234
|
+
return loc ? `${loc.line}:${loc.column}` : "";
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
const sourceNode = instr[SOURCE_NODE_SYM];
|
|
2238
|
+
const sourceLocation = sourceNode?.loc
|
|
2239
|
+
? [formatLoc(sourceNode.loc.start), formatLoc(sourceNode.loc.end)]
|
|
2240
|
+
.filter(Boolean)
|
|
2241
|
+
.join("-")
|
|
2242
|
+
: "";
|
|
2243
|
+
|
|
2244
|
+
if (resolvedOperands.length > 0) {
|
|
2245
|
+
// Operand[0] is always `dst` for instruction types that produce a value.
|
|
2246
|
+
const dst = resolvedOperands[0];
|
|
2247
|
+
|
|
2248
|
+
switch (op) {
|
|
2249
|
+
case this.OP.LOAD_CONST: {
|
|
2250
|
+
// resolvedOperands: [dst, constIdx, concealKey]
|
|
2251
|
+
const val = this._decryptConst(
|
|
2252
|
+
constants,
|
|
2253
|
+
resolvedOperands[1],
|
|
2254
|
+
resolvedOperands[2],
|
|
2255
|
+
);
|
|
2256
|
+
comment += ` reg[${dst}] = ${this._serializeConst(val)}`;
|
|
2257
|
+
break;
|
|
2258
|
+
}
|
|
2259
|
+
case this.OP.LOAD_GLOBAL:
|
|
2260
|
+
// resolvedOperands: [dst, constIdx, concealKey]
|
|
2261
|
+
comment += ` reg[${dst}] = ${this._decryptConst(constants, resolvedOperands[1], resolvedOperands[2])}`;
|
|
2262
|
+
break;
|
|
2263
|
+
case this.OP.STORE_GLOBAL:
|
|
2264
|
+
// resolvedOperands: [constIdx, concealKey, srcReg]
|
|
2265
|
+
comment += ` ${this._decryptConst(constants, resolvedOperands[0], resolvedOperands[1])} = reg[${resolvedOperands[2]}]`;
|
|
2266
|
+
break;
|
|
2267
|
+
case this.OP.LOAD_UPVALUE:
|
|
2268
|
+
comment += ` reg[${dst}] = upvalue[${resolvedOperands[1]}]`;
|
|
2269
|
+
break;
|
|
2270
|
+
case this.OP.STORE_UPVALUE:
|
|
2271
|
+
comment += ` upvalue[${resolvedOperands[0]}] = reg[${resolvedOperands[1]}]`;
|
|
2272
|
+
break;
|
|
2273
|
+
case this.OP.MOVE:
|
|
2274
|
+
comment += ` reg[${dst}] = reg[${resolvedOperands[1]}]`;
|
|
2275
|
+
break;
|
|
2276
|
+
case this.OP.MAKE_CLOSURE:
|
|
2277
|
+
comment += ` reg[${dst}] PC=${resolvedOperands[1]} (params=${resolvedOperands[2]} regs=${resolvedOperands[3]} upvalues=${resolvedOperands[4]})`;
|
|
2278
|
+
break;
|
|
2279
|
+
case this.OP.CALL:
|
|
2280
|
+
comment += ` reg[${dst}] = reg[${resolvedOperands[1]}](${resolvedOperands[2]} args)`;
|
|
2281
|
+
break;
|
|
2282
|
+
case this.OP.CALL_METHOD:
|
|
2283
|
+
comment += ` reg[${dst}] = reg[${resolvedOperands[2]}](recv=reg[${resolvedOperands[1]}], ${resolvedOperands[3]} args)`;
|
|
2284
|
+
break;
|
|
2285
|
+
case this.OP.NEW:
|
|
2286
|
+
comment += ` reg[${dst}] = new reg[${resolvedOperands[1]}](${resolvedOperands[2]} args)`;
|
|
2287
|
+
break;
|
|
2288
|
+
case this.OP.RETURN:
|
|
2289
|
+
comment += ` reg[${resolvedOperands[0]}]`;
|
|
2290
|
+
break;
|
|
2291
|
+
case this.OP.BUILD_ARRAY:
|
|
2292
|
+
comment += ` reg[${dst}] = [${resolvedOperands[2]} elems]`;
|
|
2293
|
+
break;
|
|
2294
|
+
case this.OP.BUILD_OBJECT:
|
|
2295
|
+
comment += ` reg[${dst}] = {${resolvedOperands[1]} pairs}`;
|
|
2296
|
+
break;
|
|
2297
|
+
case this.OP.GET_PROP:
|
|
2298
|
+
comment += ` reg[${dst}] = reg[${resolvedOperands[1]}][reg[${resolvedOperands[2]}]]`;
|
|
2299
|
+
break;
|
|
2300
|
+
case this.OP.SET_PROP:
|
|
2301
|
+
comment += ` reg[${resolvedOperands[0]}][reg[${resolvedOperands[1]}]] = reg[${resolvedOperands[2]}]`;
|
|
2302
|
+
break;
|
|
2303
|
+
|
|
2304
|
+
default:
|
|
2305
|
+
comment +=
|
|
2306
|
+
resolvedOperands.length === 1
|
|
2307
|
+
? ` ${resolvedOperands[0]}`
|
|
2308
|
+
: ` [${resolvedOperands.join(", ")}]`;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
comment = comment.padEnd(50) + sourceLocation;
|
|
2313
|
+
|
|
2314
|
+
const values = [op, ...resolvedOperands];
|
|
2315
|
+
const instrText = `[${values.join(", ")}]`;
|
|
2316
|
+
const text = `${(instrText + ",").padEnd(20)} ${comment}`;
|
|
2317
|
+
|
|
2318
|
+
return { text, values };
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
_serializeConstants(constants: any[]) {
|
|
2322
|
+
const lines = ["var CONSTANTS = ["];
|
|
2323
|
+
constants.forEach((val, idx) => {
|
|
2324
|
+
lines.push(` /* ${idx} */ ${this._serializeConst(val)},`);
|
|
2325
|
+
});
|
|
2326
|
+
lines.push("];");
|
|
2327
|
+
return lines.join("\n");
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
_serializeBytecode(
|
|
2331
|
+
bytecode: b.Bytecode,
|
|
2332
|
+
compiler: Compiler,
|
|
2333
|
+
): { bytecode: b.Bytecode } {
|
|
2334
|
+
const serialized = [];
|
|
2335
|
+
for (const instr of bytecode) {
|
|
2336
|
+
if (instr[0] === null) continue;
|
|
2337
|
+
|
|
2338
|
+
const specializedOpInfo = compiler.SPECIALIZED_OPS[instr[0]];
|
|
2339
|
+
if (specializedOpInfo) {
|
|
2340
|
+
const operands = instr.slice(1);
|
|
2341
|
+
|
|
2342
|
+
const resolvedValues = operands.map(
|
|
2343
|
+
(o) => (o as any)?.resolvedValue ?? o,
|
|
2344
|
+
);
|
|
2345
|
+
const originalName = compiler.OP_NAME[specializedOpInfo.originalOp];
|
|
2346
|
+
compiler.OP_NAME[instr[0]] =
|
|
2347
|
+
`${originalName}_${resolvedValues.join("_")}`;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
serialized.push(instr);
|
|
2351
|
+
}
|
|
2352
|
+
return { bytecode: serialized };
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
_encodeBytecode(flat: number[]) {
|
|
2356
|
+
const buf = new Uint8Array(flat.length * 2);
|
|
2357
|
+
flat.forEach((w, i) => {
|
|
2358
|
+
buf[i * 2] = w & 0xff;
|
|
2359
|
+
buf[i * 2 + 1] = (w >>> 8) & 0xff;
|
|
2360
|
+
});
|
|
2361
|
+
return Buffer.from(buf).toString("base64");
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
serialize(bytecode: b.Bytecode, constants: any[], compiler: Compiler) {
|
|
2365
|
+
const mainStartPc = compiler.mainStartPc;
|
|
2366
|
+
const mainRegCount = compiler.mainRegCount;
|
|
2367
|
+
let sections = [];
|
|
2368
|
+
|
|
2369
|
+
var initBody = [];
|
|
2370
|
+
var bytecodeResult = this._serializeBytecode(bytecode, compiler);
|
|
2371
|
+
|
|
2372
|
+
const flat = bytecodeResult.bytecode.flatMap((instr) => {
|
|
2373
|
+
let filtered = instr.filter((x) => (x as any)?.placeholder !== true);
|
|
2374
|
+
let resolved = filtered.map((x) => (x as any)?.resolvedValue ?? x);
|
|
2375
|
+
return resolved as number[];
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
if (this.options.encodeBytecode) {
|
|
2379
|
+
sections.push(`var BYTECODE = "${this._encodeBytecode(flat)}";`);
|
|
2380
|
+
} else {
|
|
2381
|
+
sections.push(`var BYTECODE = [${flat.join(",")}]`);
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
sections.push(`var MAIN_START_PC = ${mainStartPc};`);
|
|
2385
|
+
sections.push(`var MAIN_REG_COUNT = ${mainRegCount};`);
|
|
2386
|
+
sections.push(`var ENCODE_BYTECODE = ${!!this.options.encodeBytecode};`);
|
|
2387
|
+
sections.push(`var TIMING_CHECKS = ${!!this.options.timingChecks};`);
|
|
2388
|
+
|
|
2389
|
+
const object = t.objectExpression(
|
|
2390
|
+
Object.entries(this.OP).map(([name, value]) =>
|
|
2391
|
+
t.objectProperty(t.identifier(name), t.numericLiteral(value)),
|
|
2392
|
+
),
|
|
2393
|
+
);
|
|
2394
|
+
sections.push(`var OP = ${generate(object).code};`);
|
|
2395
|
+
|
|
2396
|
+
initBody.push(this._serializeConstants(constants));
|
|
2397
|
+
|
|
2398
|
+
sections = [...initBody, ...sections];
|
|
2399
|
+
sections.push(VM_RUNTIME);
|
|
2400
|
+
|
|
2401
|
+
return sections.join("\n\n");
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
export async function compileAndSerialize(
|
|
2406
|
+
sourceCode: string,
|
|
2407
|
+
options: Options,
|
|
2408
|
+
) {
|
|
2409
|
+
const compiler = new Compiler(options);
|
|
2410
|
+
let bytecode = compiler.compile(sourceCode);
|
|
2411
|
+
|
|
2412
|
+
const passes = [];
|
|
2413
|
+
|
|
2414
|
+
passes.push(concealConstants);
|
|
2415
|
+
|
|
2416
|
+
if (options.specializedOpcodes) {
|
|
2417
|
+
passes.push(specializedOpcodes);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
if (options.microOpcodes) {
|
|
2421
|
+
passes.push(microOpcodes);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
if (options.macroOpcodes) {
|
|
2425
|
+
passes.push(macroOpcodes);
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
if (options.selfModifying) {
|
|
2429
|
+
passes.push(selfModifying);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
if (options.aliasedOpcodes) {
|
|
2433
|
+
passes.push(aliasedOpcodes);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
for (const pass of passes) {
|
|
2437
|
+
const passResult = pass(bytecode, compiler);
|
|
2438
|
+
bytecode = passResult.bytecode;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// Resolve label references to flat bytecode indices.
|
|
2442
|
+
const labelsResult = resolveLabels(bytecode, compiler);
|
|
2443
|
+
bytecode = labelsResult.bytecode;
|
|
2444
|
+
|
|
2445
|
+
// Set mainStartPc from the first function descriptor (or 0 for top-level start).
|
|
2446
|
+
compiler.mainStartPc = compiler.mainFn.startPc;
|
|
2447
|
+
|
|
2448
|
+
// Resolve constant references to pool indices (+ conceal key operand).
|
|
2449
|
+
const constResult = resolveConstants(bytecode, compiler);
|
|
2450
|
+
bytecode = constResult.bytecode;
|
|
2451
|
+
compiler.constants = constResult.constants;
|
|
2452
|
+
|
|
2453
|
+
// Build and obfuscate the runtime.
|
|
2454
|
+
const runtimeSource = compiler.serializer.serialize(
|
|
2455
|
+
bytecode,
|
|
2456
|
+
constResult.constants,
|
|
2457
|
+
compiler,
|
|
2458
|
+
);
|
|
2459
|
+
|
|
2460
|
+
// This part was purposefully pulled out Serializer as OP_NAME's get resolved during obfuscateRuntime
|
|
2461
|
+
// So for the most useful comments, it's ran absolutely last
|
|
2462
|
+
// Tests also rely on correct comments so it's required
|
|
2463
|
+
const generateBytecodeComment = () => {
|
|
2464
|
+
var lines = [];
|
|
2465
|
+
for (const instr of bytecode) {
|
|
2466
|
+
const serialized = compiler.serializer._serializeInstr(instr);
|
|
2467
|
+
lines.push("// " + serialized.text);
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
return lines.join("\n");
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
const code = await obfuscateRuntime(
|
|
2474
|
+
runtimeSource,
|
|
2475
|
+
bytecode,
|
|
2476
|
+
options,
|
|
2477
|
+
compiler,
|
|
2478
|
+
generateBytecodeComment,
|
|
2479
|
+
);
|
|
2480
|
+
|
|
2481
|
+
return { code };
|
|
2482
|
+
}
|