js-confuser-vm 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/LICENSE +21 -21
- package/README.MD +370 -190
- package/babel-plugin-inline-runtime.cjs +34 -0
- package/babel.config.json +23 -24
- package/index.ts +34 -28
- package/jest-strip-types.js +10 -10
- package/jest.config.js +35 -18
- package/package.json +50 -48
- package/src/build-runtime.ts +57 -0
- package/src/compiler.ts +2069 -1677
- package/src/index.ts +14 -13
- package/src/minify.ts +21 -21
- package/src/options.ts +14 -10
- package/src/runtime.ts +771 -645
- package/src/transforms/bytecode/macroOpcodes.ts +177 -0
- package/src/transforms/bytecode/resolveContants.ts +62 -0
- package/src/transforms/bytecode/resolveLabels.ts +107 -0
- package/src/transforms/bytecode/selfModifying.ts +121 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +118 -0
- package/src/transforms/runtime/macroOpcodes.ts +111 -0
- package/src/transforms/runtime/minify.ts +1 -0
- package/src/transforms/runtime/shuffleOpcodes.ts +24 -0
- package/src/transforms/runtime/specializedOpcodes.ts +146 -0
- package/src/transforms/utils/op-utils.ts +26 -0
- package/src/{random.ts → transforms/utils/random-utils.ts} +31 -31
- package/src/types.ts +33 -0
- package/src/utilts.ts +3 -3
- package/tsconfig.json +12 -12
- package/dist/compiler.js +0 -1505
- package/dist/index.js +0 -9
- package/dist/minify.js +0 -18
- package/dist/minify_empty_externs.js +0 -4
- package/dist/options.js +0 -1
- package/dist/random.js +0 -27
- package/dist/runtime.js +0 -620
- package/dist/runtimeObf.js +0 -36
- package/dist/utilts.js +0 -3
- package/src/runtimeObf.ts +0 -48
package/src/compiler.ts
CHANGED
|
@@ -1,1677 +1,2069 @@
|
|
|
1
|
-
import
|
|
2
|
-
import traverseImport from "@babel/traverse";
|
|
3
|
-
import { generate } from "@babel/generator";
|
|
4
|
-
|
|
5
|
-
import { readFileSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
import { stripTypeScriptTypes } from "module";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (this.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
compiler
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
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
|
-
this.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
this.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.OP
|
|
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
|
-
return {
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
break;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
this.
|
|
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
|
-
const
|
|
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
|
-
this.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
this._compileExpr(
|
|
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
|
-
for (
|
|
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
|
-
this.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
//
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
-
for (
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
this.
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
1108
|
-
"
|
|
1109
|
-
|
|
1110
|
-
"
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const
|
|
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
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import traverseImport from "@babel/traverse";
|
|
3
|
+
import { generate } from "@babel/generator";
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { stripTypeScriptTypes } from "module";
|
|
8
|
+
import * as t from "@babel/types";
|
|
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 * as b from "./types.ts";
|
|
17
|
+
import { specializedOpcodes } from "./transforms/bytecode/specializedOpcodes.ts";
|
|
18
|
+
import { getRandomInt } from "./transforms/utils/random-utils.ts";
|
|
19
|
+
import { U16_MAX } from "./transforms/utils/op-utils.ts";
|
|
20
|
+
|
|
21
|
+
const traverse = (traverseImport.default ||
|
|
22
|
+
traverseImport) as typeof traverseImport.default;
|
|
23
|
+
|
|
24
|
+
const readVMRuntimeFile = () => {
|
|
25
|
+
let code;
|
|
26
|
+
try {
|
|
27
|
+
code = readFileSync(join(import.meta.dirname, "./runtime.ts"), "utf-8");
|
|
28
|
+
} catch (e) {
|
|
29
|
+
code = readFileSync(join(import.meta.dirname, "./runtime.js"), "utf-8");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return stripTypeScriptTypes?.(code) || code;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const VM_RUNTIME = readVMRuntimeFile().split("@START")[1];
|
|
36
|
+
export const SOURCE_NODE_SYM = Symbol("SOURCE_NODE"); // Attach source node location to pseudo bytecode instructions
|
|
37
|
+
|
|
38
|
+
// Opcodes
|
|
39
|
+
export const OP_ORIGINAL = {
|
|
40
|
+
LOAD_CONST: 0,
|
|
41
|
+
LOAD_LOCAL: 1,
|
|
42
|
+
STORE_LOCAL: 2,
|
|
43
|
+
LOAD_GLOBAL: 3,
|
|
44
|
+
STORE_GLOBAL: 4,
|
|
45
|
+
GET_PROP: 5,
|
|
46
|
+
ADD: 6, // a + b (both are popped)
|
|
47
|
+
SUB: 7, // a - b
|
|
48
|
+
MUL: 8, // a * b
|
|
49
|
+
DIV: 9, // a / b
|
|
50
|
+
MAKE_CLOSURE: 10,
|
|
51
|
+
CALL: 11,
|
|
52
|
+
CALL_METHOD: 12,
|
|
53
|
+
RETURN: 13,
|
|
54
|
+
POP: 14, // discard top of stack
|
|
55
|
+
LT: 15, // pop b, pop a -> push (a < b)
|
|
56
|
+
GT: 16, // pop b, pop a -> push (a > b)
|
|
57
|
+
EQ: 17, // pop b, pop a -> push (a === b)
|
|
58
|
+
JUMP: 18, // unconditional - operand = absolute bytecode index
|
|
59
|
+
JUMP_IF_FALSE: 19, // pop value; jump if falsy
|
|
60
|
+
LTE: 20, // a <= b
|
|
61
|
+
GTE: 21, // a >= b
|
|
62
|
+
NEQ: 22, // a !== b
|
|
63
|
+
LOAD_UPVALUE: 23, // push frame.closure.upvalues[operand].read()
|
|
64
|
+
STORE_UPVALUE: 24, // frame.closure.upvalues[operand].write(pop())
|
|
65
|
+
|
|
66
|
+
// Unary
|
|
67
|
+
UNARY_NEG: 25, // -x
|
|
68
|
+
UNARY_POS: 26, // +x
|
|
69
|
+
UNARY_NOT: 27, // !x
|
|
70
|
+
UNARY_BITNOT: 28, // ~x
|
|
71
|
+
TYPEOF: 29, // typeof x
|
|
72
|
+
VOID: 30, // void x -> always undefined
|
|
73
|
+
|
|
74
|
+
TYPEOF_SAFE: 31, // operand = name constIdx - typeof guard for undeclared globals
|
|
75
|
+
BUILD_ARRAY: 32, // operand = element count - pops N values -> pushes array
|
|
76
|
+
BUILD_OBJECT: 33, // operand = pair count - pops N*2 (key,val) -> pushes object
|
|
77
|
+
SET_PROP: 34, // pop val, pop key, peek obj -> obj[key] = val (obj stays on stack)
|
|
78
|
+
GET_PROP_COMPUTED: 35, // pop key, peek obj -> push obj[key] (computed: nums[i])
|
|
79
|
+
|
|
80
|
+
MOD: 36, // a % b
|
|
81
|
+
BAND: 37, // a & b
|
|
82
|
+
BOR: 38, // a | b
|
|
83
|
+
BXOR: 39, // a ^ b
|
|
84
|
+
SHL: 40, // a << b
|
|
85
|
+
SHR: 41, // a >> b
|
|
86
|
+
USHR: 42, // a >>> b
|
|
87
|
+
|
|
88
|
+
JUMP_IF_FALSE_OR_POP: 43, // && - if top falsy: jump (keep), else: pop, eval RHS
|
|
89
|
+
JUMP_IF_TRUE_OR_POP: 44, // || - if top truthy: jump (keep), else: pop, eval RHS
|
|
90
|
+
|
|
91
|
+
DELETE_PROP: 45,
|
|
92
|
+
IN: 46, // a in b
|
|
93
|
+
INSTANCEOF: 47, // a instanceof b
|
|
94
|
+
|
|
95
|
+
// NEW
|
|
96
|
+
LOAD_THIS: 48, // push frame.thisVal
|
|
97
|
+
NEW: 49, // operand = argCount - construct a new object
|
|
98
|
+
DUP: 50, // duplicate top of stack
|
|
99
|
+
THROW: 51, // pop value, throw it
|
|
100
|
+
LOOSE_EQ: 52, // a == b (abstract equality)
|
|
101
|
+
LOOSE_NEQ: 53, // a != b (abstract inequality)
|
|
102
|
+
|
|
103
|
+
FOR_IN_SETUP: 54, // pop obj -> build enumerable-key iterator -> push {keys,i}
|
|
104
|
+
FOR_IN_NEXT: 55, // operand=exit_pc; pop iter; if done->jump; else push next key
|
|
105
|
+
|
|
106
|
+
// Self-modifying bytecode
|
|
107
|
+
PATCH: 56, // pop destPc; constants[operand]=word[]; write words into bytecode[destPc..]
|
|
108
|
+
|
|
109
|
+
// Try-Catch
|
|
110
|
+
TRY_SETUP: 57, // operand = catch_pc; push exception handler onto frame._handlerStack
|
|
111
|
+
TRY_END: 58, // pop exception handler (normal exit from try body)
|
|
112
|
+
|
|
113
|
+
// Getter / Setter (ES5 object literal accessor syntax)
|
|
114
|
+
DEFINE_GETTER: 59, // pop fn, pop key, pop obj -> Object.defineProperty(obj, key, {get: fn})
|
|
115
|
+
DEFINE_SETTER: 60, // pop fn, pop key, pop obj -> Object.defineProperty(obj, key, {set: fn})
|
|
116
|
+
|
|
117
|
+
DEBUGGER: 61, // emits a "debugger" statement
|
|
118
|
+
|
|
119
|
+
// Push the raw integer operand directly onto the stack (no constant pool lookup).
|
|
120
|
+
// Identical pipeline to JUMP ops: {type:"label"} pseudo-operands resolve to a
|
|
121
|
+
// raw PC number that becomes the operand, which is pushed as-is at runtime.
|
|
122
|
+
LOAD_INT: 62,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Scope
|
|
126
|
+
// Each function call gets its own Scope. Locals are resolved to
|
|
127
|
+
// numeric slots at compile time -- zero name lookups at runtime.
|
|
128
|
+
class Scope {
|
|
129
|
+
parent: Scope | null;
|
|
130
|
+
_locals: Map<string, number>;
|
|
131
|
+
_next: number;
|
|
132
|
+
|
|
133
|
+
constructor(parent = null) {
|
|
134
|
+
this.parent = parent;
|
|
135
|
+
this._locals = new Map(); // name -> slot index
|
|
136
|
+
this._next = 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
define(name) {
|
|
140
|
+
if (!this._locals.has(name)) {
|
|
141
|
+
this._locals.set(name, this._next++);
|
|
142
|
+
}
|
|
143
|
+
return this._locals.get(name);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Walk up scope chain. If we fall off the top -> global.
|
|
147
|
+
resolve(name) {
|
|
148
|
+
if (this._locals.has(name)) {
|
|
149
|
+
return { kind: "local", slot: this._locals.get(name) };
|
|
150
|
+
}
|
|
151
|
+
if (this.parent) return this.parent.resolve(name);
|
|
152
|
+
return { kind: "global" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get localCount() {
|
|
156
|
+
return this._next;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// FnContext
|
|
161
|
+
// Compiler-side state for the function currently being compiled.
|
|
162
|
+
// Distinct from runtime Frame -- this is compile-time only.
|
|
163
|
+
class FnContext {
|
|
164
|
+
upvalues: { name: string; isLocal: number; index: number }[];
|
|
165
|
+
parentCtx: FnContext | null;
|
|
166
|
+
scope: Scope;
|
|
167
|
+
compiler: Compiler;
|
|
168
|
+
bc: b.Instruction[];
|
|
169
|
+
|
|
170
|
+
constructor(compiler, parentCtx = null) {
|
|
171
|
+
this.compiler = compiler;
|
|
172
|
+
this.parentCtx = parentCtx;
|
|
173
|
+
this.scope = new Scope();
|
|
174
|
+
|
|
175
|
+
this.bc = [];
|
|
176
|
+
this.upvalues = []; // { name, isLocal, index }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Find or register a captured variable as an upvalue.
|
|
180
|
+
// isLocal=true -> captured directly from parent's locals[index]
|
|
181
|
+
// isLocal=false -> relayed from parent's own upvalue list[index]
|
|
182
|
+
addUpvalue(name, isLocal, index) {
|
|
183
|
+
const existing = this.upvalues.findIndex((u) => u.name === name);
|
|
184
|
+
if (existing !== -1) return existing;
|
|
185
|
+
const idx = this.upvalues.length;
|
|
186
|
+
this.upvalues.push({ name, isLocal, index: index });
|
|
187
|
+
return idx;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Compiler
|
|
192
|
+
export class Compiler {
|
|
193
|
+
fnDescriptors: any[];
|
|
194
|
+
bytecode: b.Bytecode;
|
|
195
|
+
mainStartPc: number;
|
|
196
|
+
|
|
197
|
+
_currentCtx: FnContext | null;
|
|
198
|
+
_pendingLabel: string | null;
|
|
199
|
+
_forInCount: number;
|
|
200
|
+
_labelCount: number;
|
|
201
|
+
_loopStack: {
|
|
202
|
+
type: "loop" | "switch" | "block";
|
|
203
|
+
label: string | null;
|
|
204
|
+
// Label that break statements targeting this entry should jump to.
|
|
205
|
+
breakLabel: string;
|
|
206
|
+
// Label that continue statements targeting this entry should jump to.
|
|
207
|
+
continueLabel: string;
|
|
208
|
+
}[];
|
|
209
|
+
|
|
210
|
+
options: Options;
|
|
211
|
+
serializer: Serializer;
|
|
212
|
+
|
|
213
|
+
OP: Partial<typeof OP_ORIGINAL>;
|
|
214
|
+
MACRO_OPS: Record<number, number[]>;
|
|
215
|
+
SPECIALIZED_OPS: Record<
|
|
216
|
+
number,
|
|
217
|
+
{
|
|
218
|
+
originalOp: number;
|
|
219
|
+
operand: b.InstrOperand;
|
|
220
|
+
resolvedOperand?: b.InstrOperand;
|
|
221
|
+
}
|
|
222
|
+
>;
|
|
223
|
+
|
|
224
|
+
OP_NAME: Record<number, string>;
|
|
225
|
+
JUMP_OPS: Set<number>;
|
|
226
|
+
|
|
227
|
+
emit(bc: b.Bytecode, instr: b.Instruction, node: t.Node) {
|
|
228
|
+
bc.push(instr);
|
|
229
|
+
|
|
230
|
+
instr[SOURCE_NODE_SYM] = node;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// DO NOT USE THIS KEY UNLESS YOU ARE "RESOLVE CONSTANTS"
|
|
234
|
+
// CONSTANTS DURING COMPILATION MUST BE USED BY REFERENCE WITH b.constantOperand("myConstantHere")
|
|
235
|
+
constants: any[];
|
|
236
|
+
|
|
237
|
+
constructor(options: Options = DEFAULT_OPTIONS) {
|
|
238
|
+
this.options = options;
|
|
239
|
+
this.fnDescriptors = []; // populated in pass 1
|
|
240
|
+
this.bytecode = [];
|
|
241
|
+
this.mainStartPc = 0;
|
|
242
|
+
this._currentCtx = null; // FnContext of the function being compiled, null at top-level
|
|
243
|
+
this._loopStack = []; // per active loop/switch/block/try
|
|
244
|
+
this._pendingLabel = null;
|
|
245
|
+
this._forInCount = 0; // counter for synthetic for-in iterator global names
|
|
246
|
+
this._labelCount = 0; // monotonically increasing counter for unique label names
|
|
247
|
+
|
|
248
|
+
this.serializer = new Serializer(this);
|
|
249
|
+
this.MACRO_OPS = {};
|
|
250
|
+
this.SPECIALIZED_OPS = {};
|
|
251
|
+
|
|
252
|
+
this.OP = { ...OP_ORIGINAL };
|
|
253
|
+
|
|
254
|
+
// Construct randomized opcode mapping
|
|
255
|
+
if (this.options.randomizeOpcodes) {
|
|
256
|
+
let usedNumbers = new Set<number>();
|
|
257
|
+
for (const key in this.OP) {
|
|
258
|
+
let val;
|
|
259
|
+
do {
|
|
260
|
+
val = getRandomInt(0, U16_MAX);
|
|
261
|
+
} while (usedNumbers.has(val));
|
|
262
|
+
usedNumbers.add(val);
|
|
263
|
+
this.OP[key] = val;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Reverse map for comment generation
|
|
268
|
+
this.OP_NAME = Object.fromEntries(
|
|
269
|
+
Object.entries(this.OP).map(([k, v]) => [v, k]),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
this.JUMP_OPS = new Set([
|
|
273
|
+
this.OP.JUMP,
|
|
274
|
+
this.OP.JUMP_IF_FALSE,
|
|
275
|
+
this.OP.JUMP_IF_TRUE_OR_POP,
|
|
276
|
+
this.OP.JUMP_IF_FALSE_OR_POP,
|
|
277
|
+
this.OP.FOR_IN_NEXT,
|
|
278
|
+
this.OP.TRY_SETUP, // catch_pc operand needs offset adjustment like jump targets
|
|
279
|
+
]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Generate a globally unique label string with an optional hint for readability.
|
|
283
|
+
_makeLabel(hint = ""): string {
|
|
284
|
+
var id = this._labelCount++;
|
|
285
|
+
return `${hint || "L"}_${id}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Variable resolution
|
|
289
|
+
// Walks up the FnContext chain. Crossing a context boundary means
|
|
290
|
+
// we're capturing from an outer function - register an upvalue.
|
|
291
|
+
_resolve(name, ctx) {
|
|
292
|
+
if (!ctx) return { kind: "global" };
|
|
293
|
+
|
|
294
|
+
// 1. Own locals
|
|
295
|
+
if (ctx.scope._locals.has(name)) {
|
|
296
|
+
return { kind: "local", slot: ctx.scope._locals.get(name) };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 2. No parent context -> must be global
|
|
300
|
+
if (!ctx.parentCtx) return { kind: "global" };
|
|
301
|
+
|
|
302
|
+
// 3. Ask parent -- recurse up the chain
|
|
303
|
+
const parentResult = this._resolve(name, ctx.parentCtx);
|
|
304
|
+
if (parentResult.kind === "global") return { kind: "global" };
|
|
305
|
+
|
|
306
|
+
// 4. Parent has it (as local or upvalue) -- register an upvalue here.
|
|
307
|
+
// isLocal=true means "take it straight from parent's locals[index]"
|
|
308
|
+
// isLocal=false means "relay parent's upvalue[index]" (multi-level capture)
|
|
309
|
+
const isLocal = parentResult.kind === "local";
|
|
310
|
+
const index = isLocal ? parentResult.slot : parentResult.index;
|
|
311
|
+
const uvIdx = ctx.addUpvalue(name, isLocal, index);
|
|
312
|
+
return { kind: "upvalue", index: uvIdx };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Entry point
|
|
316
|
+
compile(source: string) {
|
|
317
|
+
const ast = parse(source, { sourceType: "script" });
|
|
318
|
+
|
|
319
|
+
return this.compileAST(ast);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
compileAST(ast: t.File) {
|
|
323
|
+
// Pass 1 - compile every FunctionDeclaration into a descriptor.
|
|
324
|
+
// Traverse finds them regardless of nesting depth.
|
|
325
|
+
traverse(ast, {
|
|
326
|
+
FunctionDeclaration: (path) => {
|
|
327
|
+
// Only handle top-level functions for this MVP.
|
|
328
|
+
// (Parent is Program node)
|
|
329
|
+
if (path.parent.type !== "Program") return;
|
|
330
|
+
this._compileFunctionDecl(path.node);
|
|
331
|
+
path.skip(); // don't recurse into nested functions
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Pass 2 -- compile top-level statements into BYTECODE.
|
|
336
|
+
this._compileMain(ast.program.body);
|
|
337
|
+
|
|
338
|
+
return this.bytecode;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Function Declaration
|
|
342
|
+
|
|
343
|
+
_compileFunctionDecl(node: t.FunctionDeclaration | t.FunctionExpression) {
|
|
344
|
+
// Reserve a slot in fnDescriptors NOW, before compiling the body, so that
|
|
345
|
+
// any nested _compileFunctionDecl calls see the correct .length and get a
|
|
346
|
+
// distinct _fnIdx. The placeholder object is mutated in-place below once
|
|
347
|
+
// the body and header are ready.
|
|
348
|
+
var fnIdx = this.fnDescriptors.length;
|
|
349
|
+
const entryLabel = this._makeLabel(`fn_${fnIdx}`);
|
|
350
|
+
var desc: any = {}; // placeholder — filled in after compilation
|
|
351
|
+
this.fnDescriptors.push(desc);
|
|
352
|
+
|
|
353
|
+
// Create a context whose parent is whatever we're currently compiling.
|
|
354
|
+
// This is what lets _resolve cross function boundaries correctly.
|
|
355
|
+
const ctx = new FnContext(this, this._currentCtx);
|
|
356
|
+
const savedCtx = this._currentCtx;
|
|
357
|
+
this._currentCtx = ctx;
|
|
358
|
+
|
|
359
|
+
// Isolate the loop stack so that try/loop entries from the outer scope
|
|
360
|
+
// don't cause spurious TRY_END / extra jumps inside this function body.
|
|
361
|
+
const savedLoopStack = this._loopStack;
|
|
362
|
+
this._loopStack = [];
|
|
363
|
+
|
|
364
|
+
// Params occupy the first N local slots (args are copied in on CALL)
|
|
365
|
+
for (const param of node.params) {
|
|
366
|
+
let identifier = param.type === "AssignmentPattern" ? param.left : param;
|
|
367
|
+
ok(
|
|
368
|
+
identifier.type === "Identifier",
|
|
369
|
+
"Only simple identifiers allowed as parameters",
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
ctx.scope.define(identifier.name);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Reserve the next slot for the implicit `arguments` object.
|
|
376
|
+
// Slot index will always equal paramCount (params are 0..paramCount-1).
|
|
377
|
+
ctx.scope.define("arguments");
|
|
378
|
+
|
|
379
|
+
// Pass 2: emit default-value guards at top of fn body
|
|
380
|
+
// Mirrors what JS engines do: if the caller passed undefined (or
|
|
381
|
+
// nothing), evaluate the default expression and overwrite the slot.
|
|
382
|
+
for (const param of node.params) {
|
|
383
|
+
if (param.type !== "AssignmentPattern") continue;
|
|
384
|
+
|
|
385
|
+
const slot = ctx.scope._locals.get((param.left as t.Identifier).name);
|
|
386
|
+
const skipLabel = this._makeLabel("param_skip");
|
|
387
|
+
|
|
388
|
+
// if (param === undefined) param = <default expr>
|
|
389
|
+
this.emit(ctx.bc, [this.OP.LOAD_LOCAL, slot], param);
|
|
390
|
+
this.emit(
|
|
391
|
+
ctx.bc,
|
|
392
|
+
[this.OP.LOAD_CONST, b.constantOperand(undefined)],
|
|
393
|
+
param,
|
|
394
|
+
);
|
|
395
|
+
this.emit(ctx.bc, [this.OP.EQ], param);
|
|
396
|
+
this.emit(
|
|
397
|
+
ctx.bc,
|
|
398
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: skipLabel }],
|
|
399
|
+
param,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
this._compileExpr(param.right, ctx.scope, ctx.bc); // eval default
|
|
403
|
+
this.emit(ctx.bc, [this.OP.STORE_LOCAL, slot], param);
|
|
404
|
+
|
|
405
|
+
this.emit(
|
|
406
|
+
ctx.bc,
|
|
407
|
+
[null, { type: "defineLabel", label: skipLabel }],
|
|
408
|
+
param,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const stmt of node.body.body) {
|
|
413
|
+
this._compileStatement(stmt, ctx.scope, ctx.bc);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// If we fall off the end of the function, implicitly return undefined.
|
|
417
|
+
this.emit(ctx.bc, [this.OP.LOAD_CONST, b.constantOperand(undefined)], node);
|
|
418
|
+
this.emit(ctx.bc, [this.OP.RETURN], node);
|
|
419
|
+
|
|
420
|
+
this._currentCtx = savedCtx; // restore before touching fnDescriptors
|
|
421
|
+
this._loopStack = savedLoopStack;
|
|
422
|
+
|
|
423
|
+
(node as any)._fnIdx = fnIdx;
|
|
424
|
+
|
|
425
|
+
// Fill the placeholder that was reserved at the top of this function.
|
|
426
|
+
// Metadata (paramCount, localCount, upvalues) is stored on desc and emitted
|
|
427
|
+
// as inline operands on the MAKE_CLOSURE instruction via _emitMakeClosure.
|
|
428
|
+
desc.name = node.id?.name || "<anonymous>";
|
|
429
|
+
desc.entryLabel = entryLabel;
|
|
430
|
+
desc.bytecode = ctx.bc as b.Bytecode;
|
|
431
|
+
desc._fnIdx = fnIdx;
|
|
432
|
+
desc.paramCount = node.params.length;
|
|
433
|
+
desc.localCount = ctx.scope.localCount;
|
|
434
|
+
desc.upvalues = ctx.upvalues.slice();
|
|
435
|
+
|
|
436
|
+
return desc;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Emit a single MAKE_CLOSURE instruction with all closure metadata packed
|
|
440
|
+
// as inline operands. The runtime reads them via _operand() — no stack
|
|
441
|
+
// shuffling needed.
|
|
442
|
+
//
|
|
443
|
+
// Flat operand layout: startPc, paramCount, localCount, uvCount,
|
|
444
|
+
// [isLocal_0, idx_0, isLocal_1, idx_1, ...]
|
|
445
|
+
_emitMakeClosure(desc: any, node: t.Node, bc: b.Bytecode) {
|
|
446
|
+
const uvOperands: (number | b.InstrOperand)[] = [];
|
|
447
|
+
for (const uv of desc.upvalues) {
|
|
448
|
+
uvOperands.push(uv.isLocal ? 1 : 0);
|
|
449
|
+
uvOperands.push(uv.index);
|
|
450
|
+
}
|
|
451
|
+
this.emit(
|
|
452
|
+
bc,
|
|
453
|
+
[
|
|
454
|
+
this.OP.MAKE_CLOSURE,
|
|
455
|
+
{ type: "label", label: desc.entryLabel },
|
|
456
|
+
desc.paramCount,
|
|
457
|
+
desc.localCount,
|
|
458
|
+
desc.upvalues.length,
|
|
459
|
+
...uvOperands,
|
|
460
|
+
] as b.Instruction,
|
|
461
|
+
node,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Main (top-level)
|
|
466
|
+
_compileMain(body: t.Statement[]) {
|
|
467
|
+
const bc = this.bytecode;
|
|
468
|
+
|
|
469
|
+
// Hoist all FunctionDeclarations: MAKE_CLOSURE -> STORE_GLOBAL
|
|
470
|
+
// (mirrors JS hoisting -- functions are available before other code)
|
|
471
|
+
for (const node of body) {
|
|
472
|
+
if (node.type !== "FunctionDeclaration") continue;
|
|
473
|
+
const desc = this.fnDescriptors.find(
|
|
474
|
+
(d) => d._fnIdx === (node as any)._fnIdx,
|
|
475
|
+
);
|
|
476
|
+
const nameRef = b.constantOperand(node.id.name);
|
|
477
|
+
this._emitMakeClosure(desc, node, bc);
|
|
478
|
+
this.emit(bc, [this.OP.STORE_GLOBAL, nameRef], node);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Compile everything else in order
|
|
482
|
+
for (const node of body) {
|
|
483
|
+
if (node.type === "FunctionDeclaration") continue;
|
|
484
|
+
this._compileStatement(node, null, bc); // null scope -> global context
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
this.emit(bc, [this.OP.RETURN], null); // end program
|
|
488
|
+
|
|
489
|
+
// Append all function bodies. Each function's entryLabel (already generated
|
|
490
|
+
// in _compileFunctionDecl) points directly to the first body instruction;
|
|
491
|
+
// metadata is pushed onto the stack at each call site, not stored inline.
|
|
492
|
+
for (const descriptor of this.fnDescriptors) {
|
|
493
|
+
this.bytecode.push([
|
|
494
|
+
null,
|
|
495
|
+
{ type: "defineLabel", label: descriptor.entryLabel },
|
|
496
|
+
]);
|
|
497
|
+
for (const instr of descriptor.bytecode) {
|
|
498
|
+
this.bytecode.push(instr);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Statements
|
|
504
|
+
_compileStatement(node: t.Statement, scope: Scope, bc: b.Bytecode) {
|
|
505
|
+
switch (node.type) {
|
|
506
|
+
case "EmptyStatement": {
|
|
507
|
+
// nothing to emit -- bare semicolon is a no-op
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
case "DebuggerStatement":
|
|
512
|
+
this.emit(bc, [this.OP.DEBUGGER], node);
|
|
513
|
+
break;
|
|
514
|
+
|
|
515
|
+
case "BlockStatement": {
|
|
516
|
+
for (const stmt of node.body) {
|
|
517
|
+
this._compileStatement(stmt, scope, bc);
|
|
518
|
+
}
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case "FunctionDeclaration": {
|
|
523
|
+
// Nested function -- compile it into a descriptor, then emit
|
|
524
|
+
// MAKE_CLOSURE so it's captured as a live closure at runtime.
|
|
525
|
+
// (_compileFunctionDecl pushes/pops _currentCtx internally)
|
|
526
|
+
const desc = this._compileFunctionDecl(node);
|
|
527
|
+
this._emitMakeClosure(desc, node, bc);
|
|
528
|
+
if (scope) {
|
|
529
|
+
const slot = scope.define(node.id.name);
|
|
530
|
+
this.emit(bc, [this.OP.STORE_LOCAL, slot], node);
|
|
531
|
+
} else {
|
|
532
|
+
this.emit(
|
|
533
|
+
bc,
|
|
534
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(node.id.name)],
|
|
535
|
+
node,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case "ThrowStatement": {
|
|
542
|
+
this._compileExpr(node.argument, scope, bc);
|
|
543
|
+
this.emit(bc, [this.OP.THROW], node);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
case "ReturnStatement": {
|
|
548
|
+
if (node.argument) {
|
|
549
|
+
this._compileExpr(node.argument, scope, bc);
|
|
550
|
+
} else {
|
|
551
|
+
this.emit(
|
|
552
|
+
bc,
|
|
553
|
+
[this.OP.LOAD_CONST, b.constantOperand(undefined)],
|
|
554
|
+
node,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
// Disarm any open try handlers before leaving the function.
|
|
558
|
+
// TRY_END only touches frame._handlerStack, not the value stack,
|
|
559
|
+
// so the return value sitting on top is safe.
|
|
560
|
+
for (let _ri = this._loopStack.length - 1; _ri >= 0; _ri--) {
|
|
561
|
+
if ((this._loopStack[_ri].type as any) === "try") {
|
|
562
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
this.emit(bc, [this.OP.RETURN], node);
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
case "ExpressionStatement": {
|
|
570
|
+
this._compileExpr(node.expression, scope, bc);
|
|
571
|
+
this.emit(bc, [this.OP.POP], node); // discard return value of statement-level expressions
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
case "VariableDeclaration": {
|
|
576
|
+
for (const decl of node.declarations) {
|
|
577
|
+
// Push the initialiser (or undefined if absent)
|
|
578
|
+
if (decl.init) {
|
|
579
|
+
this._compileExpr(decl.init, scope, bc);
|
|
580
|
+
} else {
|
|
581
|
+
this.emit(
|
|
582
|
+
bc,
|
|
583
|
+
[this.OP.LOAD_CONST, b.constantOperand(undefined)],
|
|
584
|
+
node,
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
ok(
|
|
589
|
+
decl.id.type === "Identifier",
|
|
590
|
+
"Only simple identifiers can be declared",
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// Store: local slot if inside a function, global name otherwise
|
|
594
|
+
if (scope) {
|
|
595
|
+
const slot = scope.define(decl.id.name);
|
|
596
|
+
this.emit(bc, [this.OP.STORE_LOCAL, slot], node);
|
|
597
|
+
} else {
|
|
598
|
+
this.emit(
|
|
599
|
+
bc,
|
|
600
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(decl.id.name)],
|
|
601
|
+
node,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
case "IfStatement": {
|
|
609
|
+
const elseOrEndLabel = this._makeLabel("if_else");
|
|
610
|
+
// 1. Compile the test expression -> leaves a value on the stack
|
|
611
|
+
this._compileExpr(node.test, scope, bc);
|
|
612
|
+
// 2. Emit JUMP_IF_FALSE to the else branch (or end if no else)
|
|
613
|
+
this.emit(
|
|
614
|
+
bc,
|
|
615
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: elseOrEndLabel }],
|
|
616
|
+
node,
|
|
617
|
+
);
|
|
618
|
+
// 3. Compile the consequent block (the "then" branch)
|
|
619
|
+
const consequentBody =
|
|
620
|
+
node.consequent.type === "BlockStatement"
|
|
621
|
+
? node.consequent.body
|
|
622
|
+
: [node.consequent];
|
|
623
|
+
for (const stmt of consequentBody) {
|
|
624
|
+
this._compileStatement(stmt, scope, bc);
|
|
625
|
+
}
|
|
626
|
+
if (node.alternate) {
|
|
627
|
+
// 4a. Consequent needs to jump OVER the else block when done
|
|
628
|
+
const endLabel = this._makeLabel("if_end");
|
|
629
|
+
this.emit(
|
|
630
|
+
bc,
|
|
631
|
+
[this.OP.JUMP, { type: "label", label: endLabel }],
|
|
632
|
+
node,
|
|
633
|
+
);
|
|
634
|
+
// Mark start of else
|
|
635
|
+
this.emit(
|
|
636
|
+
bc,
|
|
637
|
+
[null, { type: "defineLabel", label: elseOrEndLabel }],
|
|
638
|
+
node,
|
|
639
|
+
);
|
|
640
|
+
// 5. Compile the alternate (else) block
|
|
641
|
+
const altBody =
|
|
642
|
+
node.alternate.type === "BlockStatement"
|
|
643
|
+
? node.alternate.body
|
|
644
|
+
: [node.alternate]; // handles `else if` -- it's just a nested IfStatement
|
|
645
|
+
for (const stmt of altBody) {
|
|
646
|
+
this._compileStatement(stmt, scope, bc);
|
|
647
|
+
}
|
|
648
|
+
// Mark end (consequent's jump lands here)
|
|
649
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
650
|
+
} else {
|
|
651
|
+
// 4b. No else -- label lands right after the then block
|
|
652
|
+
this.emit(
|
|
653
|
+
bc,
|
|
654
|
+
[null, { type: "defineLabel", label: elseOrEndLabel }],
|
|
655
|
+
node,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
case "WhileStatement": {
|
|
662
|
+
const _wLabel = this._pendingLabel;
|
|
663
|
+
this._pendingLabel = null;
|
|
664
|
+
|
|
665
|
+
const loopTopLabel = this._makeLabel("while_top");
|
|
666
|
+
const exitLabel = this._makeLabel("while_exit");
|
|
667
|
+
|
|
668
|
+
this._loopStack.push({
|
|
669
|
+
type: "loop",
|
|
670
|
+
label: _wLabel,
|
|
671
|
+
breakLabel: exitLabel,
|
|
672
|
+
continueLabel: loopTopLabel, // continue re-evaluates the test
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
this.emit(
|
|
676
|
+
bc,
|
|
677
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
678
|
+
node,
|
|
679
|
+
);
|
|
680
|
+
this._compileExpr(node.test, scope, bc);
|
|
681
|
+
this.emit(
|
|
682
|
+
bc,
|
|
683
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: exitLabel }],
|
|
684
|
+
node,
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
const whileBody =
|
|
688
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
689
|
+
for (const stmt of whileBody) {
|
|
690
|
+
this._compileStatement(stmt, scope, bc);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
this.emit(
|
|
694
|
+
bc,
|
|
695
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
696
|
+
node,
|
|
697
|
+
);
|
|
698
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
699
|
+
|
|
700
|
+
this._loopStack.pop();
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
case "DoWhileStatement": {
|
|
705
|
+
const _dwLabel = this._pendingLabel;
|
|
706
|
+
this._pendingLabel = null;
|
|
707
|
+
|
|
708
|
+
const loopTopLabel = this._makeLabel("dowhile_top");
|
|
709
|
+
const continueLabel = this._makeLabel("dowhile_cont");
|
|
710
|
+
const exitLabel = this._makeLabel("dowhile_exit");
|
|
711
|
+
|
|
712
|
+
this._loopStack.push({
|
|
713
|
+
type: "loop",
|
|
714
|
+
label: _dwLabel,
|
|
715
|
+
breakLabel: exitLabel,
|
|
716
|
+
continueLabel: continueLabel, // continue falls to the test
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
this.emit(
|
|
720
|
+
bc,
|
|
721
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
722
|
+
node,
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const doWhileBody =
|
|
726
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
727
|
+
for (const stmt of doWhileBody) {
|
|
728
|
+
this._compileStatement(stmt, scope, bc);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// continue -> skip rest of body, fall through to test
|
|
732
|
+
this.emit(
|
|
733
|
+
bc,
|
|
734
|
+
[null, { type: "defineLabel", label: continueLabel }],
|
|
735
|
+
node,
|
|
736
|
+
);
|
|
737
|
+
this._compileExpr(node.test, scope, bc);
|
|
738
|
+
this.emit(
|
|
739
|
+
bc,
|
|
740
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: exitLabel }],
|
|
741
|
+
node,
|
|
742
|
+
);
|
|
743
|
+
this.emit(
|
|
744
|
+
bc,
|
|
745
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
746
|
+
node,
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
750
|
+
|
|
751
|
+
this._loopStack.pop();
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
case "ForStatement": {
|
|
756
|
+
const _fLabel = this._pendingLabel;
|
|
757
|
+
this._pendingLabel = null;
|
|
758
|
+
|
|
759
|
+
const loopTopLabel = this._makeLabel("for_top");
|
|
760
|
+
const exitLabel = this._makeLabel("for_exit");
|
|
761
|
+
// continue jumps to the update clause if present, else straight to test
|
|
762
|
+
const updateLabel = node.update
|
|
763
|
+
? this._makeLabel("for_update")
|
|
764
|
+
: loopTopLabel;
|
|
765
|
+
|
|
766
|
+
this._loopStack.push({
|
|
767
|
+
type: "loop",
|
|
768
|
+
label: _fLabel,
|
|
769
|
+
breakLabel: exitLabel,
|
|
770
|
+
continueLabel: updateLabel,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
if (node.init) {
|
|
774
|
+
if (node.init.type === "VariableDeclaration") {
|
|
775
|
+
this._compileStatement(node.init, scope, bc);
|
|
776
|
+
} else {
|
|
777
|
+
this._compileExpr(node.init, scope, bc);
|
|
778
|
+
this.emit(bc, [this.OP.POP], node);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
this.emit(
|
|
783
|
+
bc,
|
|
784
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
785
|
+
node,
|
|
786
|
+
);
|
|
787
|
+
if (node.test) {
|
|
788
|
+
this._compileExpr(node.test, scope, bc);
|
|
789
|
+
this.emit(
|
|
790
|
+
bc,
|
|
791
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: exitLabel }],
|
|
792
|
+
node,
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const forBody =
|
|
797
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
798
|
+
for (const stmt of forBody) {
|
|
799
|
+
this._compileStatement(stmt, scope, bc);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// continue -> run update (if any) then back to test
|
|
803
|
+
if (node.update) {
|
|
804
|
+
this.emit(
|
|
805
|
+
bc,
|
|
806
|
+
[null, { type: "defineLabel", label: updateLabel }],
|
|
807
|
+
node,
|
|
808
|
+
);
|
|
809
|
+
this._compileExpr(node.update, scope, bc);
|
|
810
|
+
this.emit(bc, [this.OP.POP], node);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
this.emit(
|
|
814
|
+
bc,
|
|
815
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
816
|
+
node,
|
|
817
|
+
);
|
|
818
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
819
|
+
|
|
820
|
+
this._loopStack.pop();
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
case "BreakStatement": {
|
|
825
|
+
// Find the jump target in the loop stack.
|
|
826
|
+
let _bTargetIdx = -1;
|
|
827
|
+
if (node.label) {
|
|
828
|
+
const _bLabelName = node.label.name;
|
|
829
|
+
for (let _bi = this._loopStack.length - 1; _bi >= 0; _bi--) {
|
|
830
|
+
if (this._loopStack[_bi].label === _bLabelName) {
|
|
831
|
+
_bTargetIdx = _bi;
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (_bTargetIdx === -1)
|
|
836
|
+
throw new Error(`Label '${node.label.name}' not found`);
|
|
837
|
+
} else {
|
|
838
|
+
// Find innermost loop/switch/block (skip "try" entries)
|
|
839
|
+
for (let _bi = this._loopStack.length - 1; _bi >= 0; _bi--) {
|
|
840
|
+
if ((this._loopStack[_bi].type as any) !== "try") {
|
|
841
|
+
_bTargetIdx = _bi;
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (_bTargetIdx === -1) throw new Error("break outside loop");
|
|
846
|
+
}
|
|
847
|
+
// Emit TRY_END for every open try block between here and the target.
|
|
848
|
+
for (let _bi = this._loopStack.length - 1; _bi > _bTargetIdx; _bi--) {
|
|
849
|
+
if ((this._loopStack[_bi].type as any) === "try") {
|
|
850
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
this.emit(
|
|
854
|
+
bc,
|
|
855
|
+
[
|
|
856
|
+
this.OP.JUMP,
|
|
857
|
+
{ type: "label", label: this._loopStack[_bTargetIdx].breakLabel },
|
|
858
|
+
],
|
|
859
|
+
node,
|
|
860
|
+
);
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
case "ContinueStatement": {
|
|
865
|
+
// Find the target loop in the loop stack.
|
|
866
|
+
let _cTargetIdx = -1;
|
|
867
|
+
if (node.label) {
|
|
868
|
+
const _cLabelName = node.label.name;
|
|
869
|
+
for (let _ci = this._loopStack.length - 1; _ci >= 0; _ci--) {
|
|
870
|
+
if (
|
|
871
|
+
this._loopStack[_ci].label === _cLabelName &&
|
|
872
|
+
this._loopStack[_ci].type === "loop"
|
|
873
|
+
) {
|
|
874
|
+
_cTargetIdx = _ci;
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (_cTargetIdx === -1)
|
|
879
|
+
throw new Error(
|
|
880
|
+
`Label '${node.label.name}' not found for continue`,
|
|
881
|
+
);
|
|
882
|
+
} else {
|
|
883
|
+
// Find the innermost loop (skip switch, block, and try contexts)
|
|
884
|
+
for (let _ci = this._loopStack.length - 1; _ci >= 0; _ci--) {
|
|
885
|
+
if (this._loopStack[_ci].type === "loop") {
|
|
886
|
+
_cTargetIdx = _ci;
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (_cTargetIdx === -1) throw new Error("continue outside loop");
|
|
891
|
+
}
|
|
892
|
+
// Emit TRY_END for every open try block between here and the target loop.
|
|
893
|
+
for (let _ci = this._loopStack.length - 1; _ci > _cTargetIdx; _ci--) {
|
|
894
|
+
if ((this._loopStack[_ci].type as any) === "try") {
|
|
895
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
this.emit(
|
|
899
|
+
bc,
|
|
900
|
+
[
|
|
901
|
+
this.OP.JUMP,
|
|
902
|
+
{
|
|
903
|
+
type: "label",
|
|
904
|
+
label: this._loopStack[_cTargetIdx].continueLabel,
|
|
905
|
+
},
|
|
906
|
+
],
|
|
907
|
+
node,
|
|
908
|
+
);
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
case "SwitchStatement": {
|
|
913
|
+
const _swLabel = this._pendingLabel;
|
|
914
|
+
this._pendingLabel = null;
|
|
915
|
+
|
|
916
|
+
const switchBreakLabel = this._makeLabel("sw_break");
|
|
917
|
+
|
|
918
|
+
this._loopStack.push({
|
|
919
|
+
type: "switch",
|
|
920
|
+
label: _swLabel,
|
|
921
|
+
breakLabel: switchBreakLabel,
|
|
922
|
+
continueLabel: switchBreakLabel, // not used for switch
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// Compile the discriminant and leave it on the stack
|
|
926
|
+
this._compileExpr(node.discriminant, scope, bc);
|
|
927
|
+
|
|
928
|
+
const cases = node.cases;
|
|
929
|
+
const defaultIdx = cases.findIndex((c) => c.test === null);
|
|
930
|
+
|
|
931
|
+
// Pre-allocate a label for each case body so dispatch can reference them
|
|
932
|
+
const caseLabels = cases.map((_, i) => this._makeLabel(`sw_case_${i}`));
|
|
933
|
+
|
|
934
|
+
// Dispatch section: for each non-default case, check and jump to its body
|
|
935
|
+
for (let i = 0; i < cases.length; i++) {
|
|
936
|
+
const cas = cases[i];
|
|
937
|
+
if (cas.test === null) continue; // skip default in dispatch
|
|
938
|
+
|
|
939
|
+
const nextCheckLabel = this._makeLabel("sw_next");
|
|
940
|
+
this.emit(bc, [this.OP.DUP], node);
|
|
941
|
+
this._compileExpr(cas.test, scope, bc);
|
|
942
|
+
this.emit(bc, [this.OP.EQ], node);
|
|
943
|
+
// If not matched, fall through to the next check
|
|
944
|
+
this.emit(
|
|
945
|
+
bc,
|
|
946
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: nextCheckLabel }],
|
|
947
|
+
node,
|
|
948
|
+
);
|
|
949
|
+
// If matched, jump directly to this case's body
|
|
950
|
+
this.emit(
|
|
951
|
+
bc,
|
|
952
|
+
[this.OP.JUMP, { type: "label", label: caseLabels[i] }],
|
|
953
|
+
node,
|
|
954
|
+
);
|
|
955
|
+
this.emit(
|
|
956
|
+
bc,
|
|
957
|
+
[null, { type: "defineLabel", label: nextCheckLabel }],
|
|
958
|
+
node,
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// No case matched: jump to default body or exit (which pops discriminant)
|
|
963
|
+
this.emit(
|
|
964
|
+
bc,
|
|
965
|
+
[
|
|
966
|
+
this.OP.JUMP,
|
|
967
|
+
{
|
|
968
|
+
type: "label",
|
|
969
|
+
label:
|
|
970
|
+
defaultIdx !== -1 ? caseLabels[defaultIdx] : switchBreakLabel,
|
|
971
|
+
},
|
|
972
|
+
],
|
|
973
|
+
node,
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// Body section: compile all case bodies in source order (fallthrough intact)
|
|
977
|
+
for (let i = 0; i < cases.length; i++) {
|
|
978
|
+
this.emit(
|
|
979
|
+
bc,
|
|
980
|
+
[null, { type: "defineLabel", label: caseLabels[i] }],
|
|
981
|
+
node,
|
|
982
|
+
);
|
|
983
|
+
for (const stmt of cases[i].consequent) {
|
|
984
|
+
this._compileStatement(stmt, scope, bc);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// break label lands here; pop the discriminant and continue after switch
|
|
989
|
+
this.emit(
|
|
990
|
+
bc,
|
|
991
|
+
[null, { type: "defineLabel", label: switchBreakLabel }],
|
|
992
|
+
node,
|
|
993
|
+
);
|
|
994
|
+
this.emit(bc, [this.OP.POP], node);
|
|
995
|
+
|
|
996
|
+
this._loopStack.pop();
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
case "LabeledStatement": {
|
|
1001
|
+
const _lName = node.label.name;
|
|
1002
|
+
const _lBody = node.body;
|
|
1003
|
+
const _lIsLoop =
|
|
1004
|
+
_lBody.type === "ForStatement" ||
|
|
1005
|
+
_lBody.type === "WhileStatement" ||
|
|
1006
|
+
_lBody.type === "DoWhileStatement" ||
|
|
1007
|
+
_lBody.type === "ForInStatement";
|
|
1008
|
+
const _lIsSwitch = _lBody.type === "SwitchStatement";
|
|
1009
|
+
|
|
1010
|
+
if (_lIsLoop || _lIsSwitch) {
|
|
1011
|
+
// Pass label down to the loop/switch handler via _pendingLabel
|
|
1012
|
+
this._pendingLabel = _lName;
|
|
1013
|
+
this._compileStatement(_lBody, scope, bc);
|
|
1014
|
+
this._pendingLabel = null; // safety clear if handler didn't consume it
|
|
1015
|
+
} else {
|
|
1016
|
+
// Non-loop labeled statement (e.g. labeled block) -- only break is valid
|
|
1017
|
+
const blockBreakLabel = this._makeLabel("block_break");
|
|
1018
|
+
this._loopStack.push({
|
|
1019
|
+
type: "block",
|
|
1020
|
+
label: _lName,
|
|
1021
|
+
breakLabel: blockBreakLabel,
|
|
1022
|
+
continueLabel: blockBreakLabel, // unused
|
|
1023
|
+
});
|
|
1024
|
+
this._compileStatement(_lBody, scope, bc);
|
|
1025
|
+
this._loopStack.pop();
|
|
1026
|
+
this.emit(
|
|
1027
|
+
bc,
|
|
1028
|
+
[null, { type: "defineLabel", label: blockBreakLabel }],
|
|
1029
|
+
node,
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
case "ForInStatement": {
|
|
1036
|
+
const _fiLabel = this._pendingLabel;
|
|
1037
|
+
this._pendingLabel = null;
|
|
1038
|
+
|
|
1039
|
+
// Evaluate the object expression -> on stack
|
|
1040
|
+
this._compileExpr(node.right, scope, bc);
|
|
1041
|
+
// FOR_IN_SETUP: pops obj, pushes iterator {keys, i}
|
|
1042
|
+
this.emit(bc, [this.OP.FOR_IN_SETUP], node);
|
|
1043
|
+
|
|
1044
|
+
// Store iterator in a hidden slot so break/continue need no cleanup
|
|
1045
|
+
let emitLoadIter: () => void;
|
|
1046
|
+
let emitStoreIter: () => void;
|
|
1047
|
+
if (scope) {
|
|
1048
|
+
// Reserve a hidden local slot (no name mapping needed)
|
|
1049
|
+
const iterSlot = scope._next++;
|
|
1050
|
+
emitLoadIter = () =>
|
|
1051
|
+
this.emit(bc, [this.OP.LOAD_LOCAL, iterSlot], node);
|
|
1052
|
+
emitStoreIter = () =>
|
|
1053
|
+
this.emit(bc, [this.OP.STORE_LOCAL, iterSlot], node);
|
|
1054
|
+
} else {
|
|
1055
|
+
// Top level -- use a synthetic global that won't collide with user code
|
|
1056
|
+
const iterNameIdx = b.constantOperand("__fi" + this._forInCount++);
|
|
1057
|
+
emitLoadIter = () =>
|
|
1058
|
+
this.emit(bc, [this.OP.LOAD_GLOBAL, iterNameIdx], node);
|
|
1059
|
+
emitStoreIter = () =>
|
|
1060
|
+
this.emit(bc, [this.OP.STORE_GLOBAL, iterNameIdx], node);
|
|
1061
|
+
}
|
|
1062
|
+
emitStoreIter();
|
|
1063
|
+
|
|
1064
|
+
const loopTopLabel = this._makeLabel("forin_top");
|
|
1065
|
+
const exitLabel = this._makeLabel("forin_exit");
|
|
1066
|
+
|
|
1067
|
+
this._loopStack.push({
|
|
1068
|
+
type: "loop",
|
|
1069
|
+
label: _fiLabel,
|
|
1070
|
+
breakLabel: exitLabel,
|
|
1071
|
+
continueLabel: loopTopLabel, // continue re-checks the iterator
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
this.emit(
|
|
1075
|
+
bc,
|
|
1076
|
+
[null, { type: "defineLabel", label: loopTopLabel }],
|
|
1077
|
+
node,
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
// Load iterator, attempt to get next key
|
|
1081
|
+
emitLoadIter();
|
|
1082
|
+
this.emit(
|
|
1083
|
+
bc,
|
|
1084
|
+
[this.OP.FOR_IN_NEXT, { type: "label", label: exitLabel }],
|
|
1085
|
+
node,
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
// Assign the key (now on top of stack) to the loop variable
|
|
1089
|
+
if (node.left.type === "VariableDeclaration") {
|
|
1090
|
+
const identifier = node.left.declarations[0].id;
|
|
1091
|
+
ok(
|
|
1092
|
+
identifier.type === "Identifier",
|
|
1093
|
+
"Only simple identifiers can be declared in for-in loops",
|
|
1094
|
+
);
|
|
1095
|
+
const name = identifier.name;
|
|
1096
|
+
if (scope) {
|
|
1097
|
+
const slot = scope.define(name);
|
|
1098
|
+
this.emit(bc, [this.OP.STORE_LOCAL, slot], node);
|
|
1099
|
+
} else {
|
|
1100
|
+
this.emit(
|
|
1101
|
+
bc,
|
|
1102
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name)],
|
|
1103
|
+
node,
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
} else if (node.left.type === "Identifier") {
|
|
1107
|
+
const res = this._resolve(node.left.name, this._currentCtx);
|
|
1108
|
+
if (res.kind === "local") {
|
|
1109
|
+
this.emit(bc, [this.OP.STORE_LOCAL, res.slot], node);
|
|
1110
|
+
} else if (res.kind === "upvalue") {
|
|
1111
|
+
this.emit(bc, [this.OP.STORE_UPVALUE, res.index], node);
|
|
1112
|
+
} else {
|
|
1113
|
+
this.emit(
|
|
1114
|
+
bc,
|
|
1115
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(node.left.name)],
|
|
1116
|
+
node,
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
} else {
|
|
1120
|
+
const src = generate(node.left).code;
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`Unsupported for-in left-hand side: ${node.left.type}\n -> ${src}`,
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// Compile the loop body
|
|
1127
|
+
const fiBody =
|
|
1128
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
1129
|
+
for (const stmt of fiBody) {
|
|
1130
|
+
this._compileStatement(stmt, scope, bc);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
this.emit(
|
|
1134
|
+
bc,
|
|
1135
|
+
[this.OP.JUMP, { type: "label", label: loopTopLabel }],
|
|
1136
|
+
node,
|
|
1137
|
+
);
|
|
1138
|
+
this.emit(bc, [null, { type: "defineLabel", label: exitLabel }], node);
|
|
1139
|
+
|
|
1140
|
+
this._loopStack.pop();
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
case "TryStatement": {
|
|
1145
|
+
if (node.finalizer) {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
"try..finally is not supported. Use a helper function instead",
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
if (!node.handler) {
|
|
1151
|
+
// try without catch requires finally — not supported
|
|
1152
|
+
throw new Error(
|
|
1153
|
+
"try without catch is not supported (requires finally).",
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const catchLabel = this._makeLabel("catch");
|
|
1158
|
+
const afterCatchLabel = this._makeLabel("after_catch");
|
|
1159
|
+
|
|
1160
|
+
// Emit TRY_SETUP with the catch block's label as the handler PC.
|
|
1161
|
+
// At runtime: saves stack depth + frame stack depth, pushes handler.
|
|
1162
|
+
this.emit(
|
|
1163
|
+
bc,
|
|
1164
|
+
[this.OP.TRY_SETUP, { type: "label", label: catchLabel }],
|
|
1165
|
+
node,
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
// Track the open try block so that break/continue/return inside the
|
|
1169
|
+
// try body can emit the matching TRY_END before their jump.
|
|
1170
|
+
this._loopStack.push({
|
|
1171
|
+
type: "try" as any,
|
|
1172
|
+
label: null,
|
|
1173
|
+
breakLabel: "", // unused
|
|
1174
|
+
continueLabel: "", // unused
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
// Compile try body
|
|
1178
|
+
for (const stmt of node.block.body) {
|
|
1179
|
+
this._compileStatement(stmt, scope, bc);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Done compiling the try body — pop the tracking entry.
|
|
1183
|
+
this._loopStack.pop();
|
|
1184
|
+
|
|
1185
|
+
// Normal exit: disarm the exception handler.
|
|
1186
|
+
this.emit(bc, [this.OP.TRY_END], node);
|
|
1187
|
+
|
|
1188
|
+
// Jump over the catch block on normal path.
|
|
1189
|
+
this.emit(
|
|
1190
|
+
bc,
|
|
1191
|
+
[this.OP.JUMP, { type: "label", label: afterCatchLabel }],
|
|
1192
|
+
node,
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
// Catch block: exception is on top of the stack (pushed by the VM).
|
|
1196
|
+
this.emit(bc, [null, { type: "defineLabel", label: catchLabel }], node);
|
|
1197
|
+
|
|
1198
|
+
const handler = node.handler;
|
|
1199
|
+
if (handler.param) {
|
|
1200
|
+
// Bind the exception value to the catch variable.
|
|
1201
|
+
const name = (handler.param as t.Identifier).name;
|
|
1202
|
+
if (scope) {
|
|
1203
|
+
const slot = scope.define(name);
|
|
1204
|
+
this.emit(bc, [this.OP.STORE_LOCAL, slot], node);
|
|
1205
|
+
} else {
|
|
1206
|
+
this.emit(
|
|
1207
|
+
bc,
|
|
1208
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(name)],
|
|
1209
|
+
node,
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
} else {
|
|
1213
|
+
// Optional catch binding (catch without a variable — ES2019+)
|
|
1214
|
+
this.emit(bc, [this.OP.POP], node);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// Compile catch body
|
|
1218
|
+
for (const stmt of handler.body.body) {
|
|
1219
|
+
this._compileStatement(stmt, scope, bc);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Normal-path jump lands here (after the catch block).
|
|
1223
|
+
this.emit(
|
|
1224
|
+
bc,
|
|
1225
|
+
[null, { type: "defineLabel", label: afterCatchLabel }],
|
|
1226
|
+
node,
|
|
1227
|
+
);
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
default: {
|
|
1232
|
+
// Use @babel/generator to reproduce the source of unsupported nodes
|
|
1233
|
+
// so we can emit a clear error with context.
|
|
1234
|
+
const src = generate(node).code;
|
|
1235
|
+
throw new Error(`Unsupported statement: ${node.type}\n -> ${src}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Expressions
|
|
1241
|
+
_compileExpr(node, scope, bc) {
|
|
1242
|
+
switch (node.type) {
|
|
1243
|
+
case "NumericLiteral":
|
|
1244
|
+
case "StringLiteral": {
|
|
1245
|
+
this.emit(
|
|
1246
|
+
bc,
|
|
1247
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.value)],
|
|
1248
|
+
node,
|
|
1249
|
+
);
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
case "BooleanLiteral": {
|
|
1254
|
+
this.emit(
|
|
1255
|
+
bc,
|
|
1256
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.value)],
|
|
1257
|
+
node,
|
|
1258
|
+
);
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
case "NullLiteral": {
|
|
1263
|
+
this.emit(bc, [this.OP.LOAD_CONST, b.constantOperand(null)], node);
|
|
1264
|
+
break;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
case "Identifier": {
|
|
1268
|
+
// scope=null means we're at the top-level -> always global
|
|
1269
|
+
const res = this._resolve(node.name, this._currentCtx);
|
|
1270
|
+
if (res.kind === "local") {
|
|
1271
|
+
this.emit(bc, [this.OP.LOAD_LOCAL, res.slot], node);
|
|
1272
|
+
} else if (res.kind === "upvalue") {
|
|
1273
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, res.index], node);
|
|
1274
|
+
} else {
|
|
1275
|
+
this.emit(
|
|
1276
|
+
bc,
|
|
1277
|
+
[this.OP.LOAD_GLOBAL, b.constantOperand(node.name)],
|
|
1278
|
+
node,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
case "ThisExpression": {
|
|
1285
|
+
this.emit(bc, [this.OP.LOAD_THIS], node);
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
case "NewExpression": {
|
|
1290
|
+
// Push callee, then args -- identical layout to CALL but uses NEW opcode
|
|
1291
|
+
this._compileExpr(node.callee, scope, bc);
|
|
1292
|
+
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
1293
|
+
this.emit(bc, [this.OP.NEW, node.arguments.length], node);
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
case "SequenceExpression": {
|
|
1298
|
+
// (a, b, c) -> eval a -> POP, eval b -> POP, eval c -> leave on stack
|
|
1299
|
+
for (let i = 0; i < node.expressions.length - 1; i++) {
|
|
1300
|
+
this._compileExpr(node.expressions[i], scope, bc);
|
|
1301
|
+
this.emit(bc, [this.OP.POP], node); // discard intermediate result
|
|
1302
|
+
}
|
|
1303
|
+
// Last expression -- its value is the result of the whole sequence
|
|
1304
|
+
this._compileExpr(
|
|
1305
|
+
node.expressions[node.expressions.length - 1],
|
|
1306
|
+
scope,
|
|
1307
|
+
bc,
|
|
1308
|
+
);
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
case "ConditionalExpression": {
|
|
1313
|
+
// test ? consequent : alternate
|
|
1314
|
+
const elseLabel = this._makeLabel("ternary_else");
|
|
1315
|
+
const endLabel = this._makeLabel("ternary_end");
|
|
1316
|
+
|
|
1317
|
+
this._compileExpr(node.test, scope, bc);
|
|
1318
|
+
this.emit(
|
|
1319
|
+
bc,
|
|
1320
|
+
[this.OP.JUMP_IF_FALSE, { type: "label", label: elseLabel }],
|
|
1321
|
+
node,
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
this._compileExpr(node.consequent, scope, bc);
|
|
1325
|
+
this.emit(bc, [this.OP.JUMP, { type: "label", label: endLabel }], node);
|
|
1326
|
+
|
|
1327
|
+
this.emit(bc, [null, { type: "defineLabel", label: elseLabel }], node);
|
|
1328
|
+
this._compileExpr(node.alternate, scope, bc);
|
|
1329
|
+
|
|
1330
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
case "LogicalExpression": {
|
|
1335
|
+
// Pattern (CPython-style):
|
|
1336
|
+
// eval LHS
|
|
1337
|
+
// JUMP_IF_*_OR_POP -> target (past RHS)
|
|
1338
|
+
// eval RHS ← only reached if LHS didn't short-circuit
|
|
1339
|
+
// [target lands here, stack top is the result either way]
|
|
1340
|
+
|
|
1341
|
+
this._compileExpr(node.left, scope, bc);
|
|
1342
|
+
|
|
1343
|
+
if (node.operator === "||") {
|
|
1344
|
+
// Short-circuit if LHS is TRUTHY -- keep it, skip RHS
|
|
1345
|
+
const endLabel = this._makeLabel("or_end");
|
|
1346
|
+
this.emit(
|
|
1347
|
+
bc,
|
|
1348
|
+
[this.OP.JUMP_IF_TRUE_OR_POP, { type: "label", label: endLabel }],
|
|
1349
|
+
node,
|
|
1350
|
+
);
|
|
1351
|
+
this._compileExpr(node.right, scope, bc);
|
|
1352
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
1353
|
+
} else if (node.operator === "&&") {
|
|
1354
|
+
// Short-circuit if LHS is FALSY -- keep it, skip RHS
|
|
1355
|
+
const endLabel = this._makeLabel("and_end");
|
|
1356
|
+
this.emit(
|
|
1357
|
+
bc,
|
|
1358
|
+
[this.OP.JUMP_IF_FALSE_OR_POP, { type: "label", label: endLabel }],
|
|
1359
|
+
node,
|
|
1360
|
+
);
|
|
1361
|
+
this._compileExpr(node.right, scope, bc);
|
|
1362
|
+
this.emit(bc, [null, { type: "defineLabel", label: endLabel }], node);
|
|
1363
|
+
} else {
|
|
1364
|
+
throw new Error(`Unsupported logical operator: ${node.operator}`);
|
|
1365
|
+
}
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
case "BinaryExpression": {
|
|
1370
|
+
this._compileExpr(node.left, scope, bc);
|
|
1371
|
+
this._compileExpr(node.right, scope, bc);
|
|
1372
|
+
const arithOp = {
|
|
1373
|
+
"+": this.OP.ADD,
|
|
1374
|
+
"-": this.OP.SUB,
|
|
1375
|
+
"*": this.OP.MUL,
|
|
1376
|
+
"/": this.OP.DIV,
|
|
1377
|
+
"%": this.OP.MOD,
|
|
1378
|
+
"&": this.OP.BAND,
|
|
1379
|
+
"|": this.OP.BOR,
|
|
1380
|
+
"^": this.OP.BXOR,
|
|
1381
|
+
"<<": this.OP.SHL,
|
|
1382
|
+
">>": this.OP.SHR,
|
|
1383
|
+
">>>": this.OP.USHR,
|
|
1384
|
+
}[node.operator];
|
|
1385
|
+
|
|
1386
|
+
const cmpOp = {
|
|
1387
|
+
"<": this.OP.LT,
|
|
1388
|
+
">": this.OP.GT,
|
|
1389
|
+
"===": this.OP.EQ,
|
|
1390
|
+
"==": this.OP.LOOSE_EQ,
|
|
1391
|
+
"<=": this.OP.LTE,
|
|
1392
|
+
">=": this.OP.GTE,
|
|
1393
|
+
"!==": this.OP.NEQ,
|
|
1394
|
+
"!=": this.OP.LOOSE_NEQ,
|
|
1395
|
+
in: this.OP.IN, // ← add
|
|
1396
|
+
instanceof: this.OP.INSTANCEOF, // ← add
|
|
1397
|
+
}[node.operator];
|
|
1398
|
+
const resolvedOp = arithOp ?? cmpOp;
|
|
1399
|
+
if (resolvedOp === undefined)
|
|
1400
|
+
throw new Error(`Unsupported operator: ${node.operator}`);
|
|
1401
|
+
this.emit(bc, [resolvedOp], node);
|
|
1402
|
+
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
case "UpdateExpression": {
|
|
1407
|
+
const res = this._resolve(node.argument.name, this._currentCtx);
|
|
1408
|
+
const bumpOp = node.operator === "++" ? this.OP.ADD : this.OP.SUB;
|
|
1409
|
+
const one = b.constantOperand(1);
|
|
1410
|
+
|
|
1411
|
+
// Helper closures: emit load / store for whichever resolution kind we have
|
|
1412
|
+
const emitLoad = () => {
|
|
1413
|
+
if (res.kind === "local")
|
|
1414
|
+
this.emit(bc, [this.OP.LOAD_LOCAL, res.slot], node);
|
|
1415
|
+
else if (res.kind === "upvalue")
|
|
1416
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, res.index], node);
|
|
1417
|
+
else
|
|
1418
|
+
this.emit(
|
|
1419
|
+
bc,
|
|
1420
|
+
[this.OP.LOAD_GLOBAL, b.constantOperand(node.argument.name)],
|
|
1421
|
+
node,
|
|
1422
|
+
);
|
|
1423
|
+
};
|
|
1424
|
+
const emitStore = () => {
|
|
1425
|
+
if (res.kind === "local")
|
|
1426
|
+
this.emit(bc, [this.OP.STORE_LOCAL, res.slot], node);
|
|
1427
|
+
else if (res.kind === "upvalue")
|
|
1428
|
+
this.emit(bc, [this.OP.STORE_UPVALUE, res.index], node);
|
|
1429
|
+
else
|
|
1430
|
+
this.emit(
|
|
1431
|
+
bc,
|
|
1432
|
+
[this.OP.STORE_GLOBAL, b.constantOperand(node.argument.name)],
|
|
1433
|
+
node,
|
|
1434
|
+
);
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
emitLoad();
|
|
1438
|
+
if (!node.prefix) this.emit(bc, [this.OP.DUP], node); // post: save old value before mutating
|
|
1439
|
+
this.emit(bc, [this.OP.LOAD_CONST, one], node);
|
|
1440
|
+
this.emit(bc, [bumpOp], node);
|
|
1441
|
+
emitStore();
|
|
1442
|
+
if (node.prefix) emitLoad(); // pre: reload new value as result
|
|
1443
|
+
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
case "AssignmentExpression": {
|
|
1448
|
+
const compoundOp = {
|
|
1449
|
+
"+=": this.OP.ADD,
|
|
1450
|
+
"-=": this.OP.SUB,
|
|
1451
|
+
"*=": this.OP.MUL,
|
|
1452
|
+
"/=": this.OP.DIV,
|
|
1453
|
+
"%=": this.OP.MOD,
|
|
1454
|
+
"&=": this.OP.BAND,
|
|
1455
|
+
"|=": this.OP.BOR,
|
|
1456
|
+
"^=": this.OP.BXOR,
|
|
1457
|
+
"<<=": this.OP.SHL,
|
|
1458
|
+
">>=": this.OP.SHR,
|
|
1459
|
+
">>>=": this.OP.USHR,
|
|
1460
|
+
}[node.operator];
|
|
1461
|
+
|
|
1462
|
+
const isCompound = compoundOp !== undefined;
|
|
1463
|
+
|
|
1464
|
+
if (node.operator !== "=" && !isCompound) {
|
|
1465
|
+
throw new Error(`Unsupported assignment operator: ${node.operator}`);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Member assignment: obj.x = val or arr[i] = val
|
|
1469
|
+
if (node.left.type === "MemberExpression") {
|
|
1470
|
+
this._compileExpr(node.left.object, scope, bc); // push obj
|
|
1471
|
+
|
|
1472
|
+
if (node.left.computed) {
|
|
1473
|
+
this._compileExpr(node.left.property, scope, bc); // push key (runtime)
|
|
1474
|
+
} else {
|
|
1475
|
+
this.emit(
|
|
1476
|
+
bc,
|
|
1477
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.left.property.name)],
|
|
1478
|
+
node,
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
if (isCompound) {
|
|
1483
|
+
// Duplicate obj+key on the stack so we can read before we write.
|
|
1484
|
+
// Stack before DUP2: [..., obj, key]
|
|
1485
|
+
// We need: [..., obj, key, obj, key] -> GET_PROP_COMPUTED -> [..., obj, key, currentVal]
|
|
1486
|
+
// Cheapest approach without a DUP opcode: re-compile the member read.
|
|
1487
|
+
// (emits obj + key again; a future peephole pass could DUP instead)
|
|
1488
|
+
this._compileExpr(node.left.object, scope, bc);
|
|
1489
|
+
if (node.left.computed) {
|
|
1490
|
+
this._compileExpr(node.left.property, scope, bc);
|
|
1491
|
+
} else {
|
|
1492
|
+
this.emit(
|
|
1493
|
+
bc,
|
|
1494
|
+
[
|
|
1495
|
+
this.OP.LOAD_CONST,
|
|
1496
|
+
b.constantOperand(node.left.property.name),
|
|
1497
|
+
],
|
|
1498
|
+
node,
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
this.emit(bc, [this.OP.GET_PROP_COMPUTED], node); // [..., obj, key, currentVal]
|
|
1502
|
+
this._compileExpr(node.right, scope, bc); // [..., obj, key, currentVal, rhs]
|
|
1503
|
+
this.emit(bc, [compoundOp], node); // [..., obj, key, newVal]
|
|
1504
|
+
} else {
|
|
1505
|
+
this._compileExpr(node.right, scope, bc); // [..., obj, key, val]
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
this.emit(bc, [this.OP.SET_PROP], node); // obj[key] = val, leaves val on stack
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// Plain identifier assignment
|
|
1513
|
+
const res = this._resolve(node.left.name, this._currentCtx);
|
|
1514
|
+
|
|
1515
|
+
if (isCompound) {
|
|
1516
|
+
// Load the current value of the target first
|
|
1517
|
+
if (res.kind === "local") {
|
|
1518
|
+
this.emit(bc, [this.OP.LOAD_LOCAL, res.slot], node);
|
|
1519
|
+
} else if (res.kind === "upvalue") {
|
|
1520
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, res.index], node);
|
|
1521
|
+
} else {
|
|
1522
|
+
this.emit(
|
|
1523
|
+
bc,
|
|
1524
|
+
[this.OP.LOAD_GLOBAL, b.constantOperand(node.left.name)],
|
|
1525
|
+
node,
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
this._compileExpr(node.right, scope, bc); // push RHS
|
|
1531
|
+
|
|
1532
|
+
if (isCompound) {
|
|
1533
|
+
this.emit(bc, [compoundOp], node); // apply binary op -> leaves newVal on stack
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Store & leave value on stack (assignment is an expression)
|
|
1537
|
+
if (res.kind === "local") {
|
|
1538
|
+
this.emit(bc, [this.OP.STORE_LOCAL, res.slot], node);
|
|
1539
|
+
this.emit(bc, [this.OP.LOAD_LOCAL, res.slot], node);
|
|
1540
|
+
} else if (res.kind === "upvalue") {
|
|
1541
|
+
this.emit(bc, [this.OP.STORE_UPVALUE, res.index], node);
|
|
1542
|
+
this.emit(bc, [this.OP.LOAD_UPVALUE, res.index], node);
|
|
1543
|
+
} else {
|
|
1544
|
+
const nameIdx = b.constantOperand(node.left.name);
|
|
1545
|
+
this.emit(bc, [this.OP.STORE_GLOBAL, nameIdx], node);
|
|
1546
|
+
this.emit(bc, [this.OP.LOAD_GLOBAL, nameIdx], node);
|
|
1547
|
+
}
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
case "CallExpression": {
|
|
1552
|
+
if (node.callee.type === "MemberExpression") {
|
|
1553
|
+
// ── Method call: console.log(...)
|
|
1554
|
+
// Push receiver first (GET_PROP leaves it; CALL_METHOD pops it as `this`)
|
|
1555
|
+
this._compileExpr(node.callee.object, scope, bc);
|
|
1556
|
+
const prop = node.callee.property.name;
|
|
1557
|
+
const propIdx = b.constantOperand(prop);
|
|
1558
|
+
this.emit(bc, [this.OP.LOAD_CONST, propIdx], node);
|
|
1559
|
+
this.emit(bc, [this.OP.GET_PROP], node);
|
|
1560
|
+
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
1561
|
+
this.emit(bc, [this.OP.CALL_METHOD, node.arguments.length], node);
|
|
1562
|
+
} else {
|
|
1563
|
+
// ── Plain call: add(5, 10)
|
|
1564
|
+
this._compileExpr(node.callee, scope, bc);
|
|
1565
|
+
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
1566
|
+
this.emit(bc, [this.OP.CALL, node.arguments.length], node);
|
|
1567
|
+
}
|
|
1568
|
+
break;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
case "UnaryExpression": {
|
|
1572
|
+
// Special case: typeof on a bare identifier must not throw if undeclared.
|
|
1573
|
+
// We emit TYPEOF_SAFE (operand = name constant index) instead of
|
|
1574
|
+
// compiling the argument first. The VM does the guard itself.
|
|
1575
|
+
if (node.operator === "typeof" && node.argument.type === "Identifier") {
|
|
1576
|
+
const res = this._resolve(node.argument.name, this._currentCtx);
|
|
1577
|
+
if (res.kind === "global") {
|
|
1578
|
+
// Potentially undeclared -- let VM guard it
|
|
1579
|
+
this.emit(
|
|
1580
|
+
bc,
|
|
1581
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.argument.name)],
|
|
1582
|
+
node,
|
|
1583
|
+
);
|
|
1584
|
+
this.emit(bc, [this.OP.TYPEOF_SAFE], node);
|
|
1585
|
+
break;
|
|
1586
|
+
}
|
|
1587
|
+
// Known local or upvalue -- safe to load first, then typeof
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Special case: delete -- argument must NOT be pre-evaluated.
|
|
1591
|
+
if (node.operator === "delete") {
|
|
1592
|
+
const arg = node.argument;
|
|
1593
|
+
if (arg.type === "MemberExpression") {
|
|
1594
|
+
this._compileExpr(arg.object, scope, bc);
|
|
1595
|
+
if (arg.computed) {
|
|
1596
|
+
this._compileExpr(arg.property, scope, bc);
|
|
1597
|
+
} else {
|
|
1598
|
+
this.emit(
|
|
1599
|
+
bc,
|
|
1600
|
+
[this.OP.LOAD_CONST, b.constantOperand(arg.property.name)],
|
|
1601
|
+
node,
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
this.emit(bc, [this.OP.DELETE_PROP], node);
|
|
1605
|
+
} else {
|
|
1606
|
+
// delete x, delete 0, etc. -- always true in non-strict, just push true
|
|
1607
|
+
this.emit(bc, [this.OP.LOAD_CONST, b.constantOperand(true)], node);
|
|
1608
|
+
}
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// All other unary ops: compile argument first, then apply operator
|
|
1613
|
+
this._compileExpr(node.argument, scope, bc);
|
|
1614
|
+
switch (node.operator) {
|
|
1615
|
+
case "-":
|
|
1616
|
+
this.emit(bc, [this.OP.UNARY_NEG], node);
|
|
1617
|
+
break;
|
|
1618
|
+
case "+":
|
|
1619
|
+
this.emit(bc, [this.OP.UNARY_POS], node);
|
|
1620
|
+
break;
|
|
1621
|
+
case "!":
|
|
1622
|
+
this.emit(bc, [this.OP.UNARY_NOT], node);
|
|
1623
|
+
break;
|
|
1624
|
+
case "~":
|
|
1625
|
+
this.emit(bc, [this.OP.UNARY_BITNOT], node);
|
|
1626
|
+
break;
|
|
1627
|
+
case "typeof":
|
|
1628
|
+
this.emit(bc, [this.OP.TYPEOF], node);
|
|
1629
|
+
break;
|
|
1630
|
+
case "void":
|
|
1631
|
+
this.emit(bc, [this.OP.VOID], node);
|
|
1632
|
+
break;
|
|
1633
|
+
|
|
1634
|
+
default:
|
|
1635
|
+
throw new Error(`Unsupported unary operator: ${node.operator}`);
|
|
1636
|
+
}
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
case "RegExpLiteral": {
|
|
1641
|
+
// Emit: new RegExp(pattern, flags)
|
|
1642
|
+
// Fresh object per evaluation -- correct for stateful g/y flags.
|
|
1643
|
+
this.emit(bc, [this.OP.LOAD_GLOBAL, b.constantOperand("RegExp")], node);
|
|
1644
|
+
this.emit(
|
|
1645
|
+
bc,
|
|
1646
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.pattern)],
|
|
1647
|
+
node,
|
|
1648
|
+
);
|
|
1649
|
+
this.emit(
|
|
1650
|
+
bc,
|
|
1651
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.flags)],
|
|
1652
|
+
node,
|
|
1653
|
+
);
|
|
1654
|
+
this.emit(bc, [this.OP.NEW, 2], node);
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
case "FunctionExpression": {
|
|
1659
|
+
// Compile into a descriptor exactly like a declaration,
|
|
1660
|
+
// but leave the resulting closure ON THE STACK -- no store.
|
|
1661
|
+
// The surrounding expression (assignment, call arg, return) consumes it.
|
|
1662
|
+
const desc = this._compileFunctionDecl(node);
|
|
1663
|
+
this._emitMakeClosure(desc, node, bc);
|
|
1664
|
+
break;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
case "MemberExpression": {
|
|
1668
|
+
this._compileExpr(node.object, scope, bc);
|
|
1669
|
+
if (node.computed) {
|
|
1670
|
+
// nums[i] -- key is runtime value
|
|
1671
|
+
this._compileExpr(node.property, scope, bc);
|
|
1672
|
+
} else {
|
|
1673
|
+
// point.x -- push key as string, same opcode handles both
|
|
1674
|
+
this.emit(
|
|
1675
|
+
bc,
|
|
1676
|
+
[this.OP.LOAD_CONST, b.constantOperand(node.property.name)],
|
|
1677
|
+
node,
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// GET_PROP_COMPUTED pops the object -- correct for value access.
|
|
1682
|
+
// GET_PROP (peek) is only used in CallExpression's method call path
|
|
1683
|
+
// where the receiver must survive on the stack for CALL_METHOD.
|
|
1684
|
+
this.emit(bc, [this.OP.GET_PROP_COMPUTED], node);
|
|
1685
|
+
break;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
case "ArrayExpression": {
|
|
1689
|
+
// Compile each element left->right, then BUILD_ARRAY collapses them.
|
|
1690
|
+
// Sparse arrays (holes) get explicit undefined per slot.
|
|
1691
|
+
for (const el of node.elements) {
|
|
1692
|
+
if (el === null) {
|
|
1693
|
+
// hole: e.g. [1,,3]
|
|
1694
|
+
this.emit(
|
|
1695
|
+
bc,
|
|
1696
|
+
[this.OP.LOAD_CONST, b.constantOperand(undefined)],
|
|
1697
|
+
node,
|
|
1698
|
+
);
|
|
1699
|
+
} else {
|
|
1700
|
+
this._compileExpr(el, scope, bc);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
this.emit(bc, [this.OP.BUILD_ARRAY, node.elements.length], node);
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
case "ObjectExpression": {
|
|
1707
|
+
// Separate regular data properties from ES5 accessor methods (get/set).
|
|
1708
|
+
const regularProps: t.ObjectProperty[] = [];
|
|
1709
|
+
const accessorProps: t.ObjectMethod[] = [];
|
|
1710
|
+
|
|
1711
|
+
for (const prop of node.properties) {
|
|
1712
|
+
if (prop.type === "SpreadElement") {
|
|
1713
|
+
throw new Error("Object spread not supported");
|
|
1714
|
+
}
|
|
1715
|
+
if (prop.type === "ObjectMethod") {
|
|
1716
|
+
if (prop.kind === "get" || prop.kind === "set") {
|
|
1717
|
+
if (prop.computed) {
|
|
1718
|
+
throw new Error(
|
|
1719
|
+
"Computed getter/setter keys are not supported",
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
accessorProps.push(prop);
|
|
1723
|
+
} else {
|
|
1724
|
+
throw new Error(`Shorthand method syntax is not supported`);
|
|
1725
|
+
}
|
|
1726
|
+
} else {
|
|
1727
|
+
regularProps.push(prop as t.ObjectProperty);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// Build the base object from data properties.
|
|
1732
|
+
for (const prop of regularProps) {
|
|
1733
|
+
const key = prop.key;
|
|
1734
|
+
let keyStr: string;
|
|
1735
|
+
if (key.type === "Identifier") {
|
|
1736
|
+
keyStr = key.name;
|
|
1737
|
+
} else if (
|
|
1738
|
+
key.type === "StringLiteral" ||
|
|
1739
|
+
key.type === "NumericLiteral"
|
|
1740
|
+
) {
|
|
1741
|
+
keyStr = String(key.value);
|
|
1742
|
+
} else {
|
|
1743
|
+
throw new Error(`Unsupported object key type: ${key.type}`);
|
|
1744
|
+
}
|
|
1745
|
+
this.emit(bc, [this.OP.LOAD_CONST, b.constantOperand(keyStr)], node);
|
|
1746
|
+
this._compileExpr(prop.value, scope, bc);
|
|
1747
|
+
}
|
|
1748
|
+
this.emit(bc, [this.OP.BUILD_OBJECT, regularProps.length], node);
|
|
1749
|
+
|
|
1750
|
+
// Define each accessor on the object that is now on top of the stack.
|
|
1751
|
+
// Stack after BUILD_OBJECT: [..., obj]
|
|
1752
|
+
// For each accessor: DUP obj, push key, compile fn, DEFINE_GETTER/DEFINE_SETTER
|
|
1753
|
+
// DEFINE_GETTER/DEFINE_SETTER pops fn+key+obj, leaving the original obj.
|
|
1754
|
+
for (const prop of accessorProps) {
|
|
1755
|
+
const key = prop.key;
|
|
1756
|
+
let keyStr: string;
|
|
1757
|
+
if (key.type === "Identifier") {
|
|
1758
|
+
keyStr = key.name;
|
|
1759
|
+
} else if (
|
|
1760
|
+
key.type === "StringLiteral" ||
|
|
1761
|
+
key.type === "NumericLiteral"
|
|
1762
|
+
) {
|
|
1763
|
+
keyStr = String(key.value);
|
|
1764
|
+
} else {
|
|
1765
|
+
throw new Error(`Unsupported object key type: ${key.type}`);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
this.emit(bc, [this.OP.DUP], node); // dup so the original obj stays after the define
|
|
1769
|
+
this.emit(bc, [this.OP.LOAD_CONST, b.constantOperand(keyStr)], node);
|
|
1770
|
+
|
|
1771
|
+
// Compile the accessor body as an anonymous function descriptor.
|
|
1772
|
+
const desc = this._compileFunctionDecl(prop as any);
|
|
1773
|
+
this._emitMakeClosure(desc, prop as any, bc);
|
|
1774
|
+
|
|
1775
|
+
this.emit(
|
|
1776
|
+
bc,
|
|
1777
|
+
[
|
|
1778
|
+
prop.kind === "get"
|
|
1779
|
+
? this.OP.DEFINE_GETTER
|
|
1780
|
+
: this.OP.DEFINE_SETTER,
|
|
1781
|
+
],
|
|
1782
|
+
node,
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
default: {
|
|
1790
|
+
throw new Error(`Unsupported expression: ${node.type}`);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// Serializer
|
|
1797
|
+
// Turns the compiled output into a commented JS source string.
|
|
1798
|
+
// Expects fully-resolved bytecode (all label refs and constant refs already
|
|
1799
|
+
// converted to plain integers by resolveLabels + resolveConstants passes).
|
|
1800
|
+
class Serializer {
|
|
1801
|
+
compiler: Compiler;
|
|
1802
|
+
|
|
1803
|
+
constructor(compiler: Compiler) {
|
|
1804
|
+
this.compiler = compiler;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
get options() {
|
|
1808
|
+
return this.compiler.options;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
get OP() {
|
|
1812
|
+
return this.compiler.OP;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
get OP_NAME() {
|
|
1816
|
+
return this.compiler.OP_NAME;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
get JUMP_OPS() {
|
|
1820
|
+
return this.compiler.JUMP_OPS;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// Produce a JS literal for a constant pool entry
|
|
1824
|
+
_serializeConst(val) {
|
|
1825
|
+
if (val === null) return "null";
|
|
1826
|
+
if (val === undefined) return "undefined";
|
|
1827
|
+
return JSON.stringify(val); // number / string / bool
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// One instruction -> "[op, op1, op2, ...] // MNEMONIC description"
|
|
1831
|
+
// Expects a fully-resolved instruction: all operands are plain numbers.
|
|
1832
|
+
// Returns { text, values } where values is the flat u16 slots for this
|
|
1833
|
+
// instruction (opcode first, then one entry per operand).
|
|
1834
|
+
_serializeInstr(
|
|
1835
|
+
instr: b.Instruction,
|
|
1836
|
+
constants: any[],
|
|
1837
|
+
): { text: string; values: number[] } {
|
|
1838
|
+
const op = instr[0] as number;
|
|
1839
|
+
const operands = instr.slice(1) as number[];
|
|
1840
|
+
|
|
1841
|
+
const resolvedOperands = operands
|
|
1842
|
+
.filter((operand) => (operand as any)?.placeholder !== true)
|
|
1843
|
+
.map((o) => (o as any)?.resolvedValue ?? o);
|
|
1844
|
+
|
|
1845
|
+
for (const o of resolvedOperands) {
|
|
1846
|
+
ok(typeof o === "number", "Unresolved operand: " + JSON.stringify(o));
|
|
1847
|
+
ok(o >= 0 && o <= 0xffff, `Operand overflow (max 0xFFFF u16): ${o}`);
|
|
1848
|
+
}
|
|
1849
|
+
ok(op >= 0 && op <= 0xffff, `Opcode overflow (max 0xFFFF u16): ${op}`);
|
|
1850
|
+
|
|
1851
|
+
const operand = resolvedOperands[0]; // first operand for single-operand comment cases
|
|
1852
|
+
const name = this.OP_NAME[op] || `OP_${op}`;
|
|
1853
|
+
let comment = name;
|
|
1854
|
+
|
|
1855
|
+
const sourceNode = instr[SOURCE_NODE_SYM];
|
|
1856
|
+
const sourceLocation = sourceNode
|
|
1857
|
+
? sourceNode.loc.start?.line +
|
|
1858
|
+
":" +
|
|
1859
|
+
sourceNode.loc.start?.column +
|
|
1860
|
+
"-" +
|
|
1861
|
+
(sourceNode.loc.end?.line + ":" + sourceNode.loc.end?.column)
|
|
1862
|
+
: "";
|
|
1863
|
+
|
|
1864
|
+
// Annotate with human-readable operand meaning
|
|
1865
|
+
if (resolvedOperands.length > 0) {
|
|
1866
|
+
switch (op) {
|
|
1867
|
+
case this.OP.LOAD_CONST: {
|
|
1868
|
+
const val = constants[operand];
|
|
1869
|
+
comment += ` ${this._serializeConst(val)}`;
|
|
1870
|
+
break;
|
|
1871
|
+
}
|
|
1872
|
+
case this.OP.MAKE_CLOSURE: {
|
|
1873
|
+
comment += ` PC ${operand} (params=${resolvedOperands[1]} locals=${resolvedOperands[2]} upvalues=${resolvedOperands[3]})`;
|
|
1874
|
+
break;
|
|
1875
|
+
}
|
|
1876
|
+
case this.OP.LOAD_LOCAL:
|
|
1877
|
+
case this.OP.STORE_LOCAL:
|
|
1878
|
+
comment += ` slot[${operand}]`;
|
|
1879
|
+
break;
|
|
1880
|
+
case this.OP.LOAD_UPVALUE:
|
|
1881
|
+
case this.OP.STORE_UPVALUE:
|
|
1882
|
+
comment += ` upvalue[${operand}]`;
|
|
1883
|
+
break;
|
|
1884
|
+
case this.OP.LOAD_GLOBAL:
|
|
1885
|
+
case this.OP.STORE_GLOBAL:
|
|
1886
|
+
comment += ` "${constants[operand]}"`;
|
|
1887
|
+
break;
|
|
1888
|
+
case this.OP.CALL:
|
|
1889
|
+
case this.OP.CALL_METHOD:
|
|
1890
|
+
comment += ` (${operand} args)`;
|
|
1891
|
+
break;
|
|
1892
|
+
case this.OP.BUILD_ARRAY:
|
|
1893
|
+
comment += ` (${operand} elements)`;
|
|
1894
|
+
break;
|
|
1895
|
+
case this.OP.BUILD_OBJECT:
|
|
1896
|
+
comment += ` (${operand} pairs)`;
|
|
1897
|
+
break;
|
|
1898
|
+
case this.OP.NEW:
|
|
1899
|
+
comment += ` (${operand} args)`;
|
|
1900
|
+
break;
|
|
1901
|
+
default:
|
|
1902
|
+
comment +=
|
|
1903
|
+
resolvedOperands.length === 1
|
|
1904
|
+
? ` ${operand}`
|
|
1905
|
+
: ` [${resolvedOperands.join(", ")}]`;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
comment = comment.padEnd(40) + sourceLocation;
|
|
1910
|
+
|
|
1911
|
+
const values = [op, ...resolvedOperands];
|
|
1912
|
+
const instrText = `[${values.join(", ")}]`;
|
|
1913
|
+
const text = `${(instrText + ",").padEnd(12)} ${comment}`;
|
|
1914
|
+
|
|
1915
|
+
return { text, values };
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// Serialize the CONSTANTS array
|
|
1919
|
+
_serializeConstants(constants: any[]) {
|
|
1920
|
+
const lines = ["var CONSTANTS = ["];
|
|
1921
|
+
constants.forEach((val, idx) => {
|
|
1922
|
+
lines.push(` /* ${idx} */ ${this._serializeConst(val)},`);
|
|
1923
|
+
});
|
|
1924
|
+
lines.push("];");
|
|
1925
|
+
return lines.join("\n");
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// Filter out any remaining null-opcode pseudo-instructions.
|
|
1929
|
+
// (defineLabel pseudo-ops are already stripped by resolveLabels.)
|
|
1930
|
+
_serializeBytecode(
|
|
1931
|
+
bytecode: b.Bytecode,
|
|
1932
|
+
compiler: Compiler,
|
|
1933
|
+
): { bytecode: b.Bytecode } {
|
|
1934
|
+
const serialized = [];
|
|
1935
|
+
for (const instr of bytecode) {
|
|
1936
|
+
if (instr[0] === null) continue;
|
|
1937
|
+
|
|
1938
|
+
const specializedOpInfo = compiler.SPECIALIZED_OPS[instr[0]];
|
|
1939
|
+
if (specializedOpInfo) {
|
|
1940
|
+
const resolvedValue = (instr[1] as any)?.resolvedValue ?? instr[1];
|
|
1941
|
+
const originalName = compiler.OP_NAME[specializedOpInfo.originalOp];
|
|
1942
|
+
|
|
1943
|
+
compiler.OP_NAME[instr[0]] = `${originalName}_${resolvedValue}`;
|
|
1944
|
+
specializedOpInfo.resolvedOperand = instr[1];
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
serialized.push(instr);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
return {
|
|
1951
|
+
bytecode: serialized,
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
_encodeBytecode(flat: number[]) {
|
|
1956
|
+
// Encode as little-endian Uint16Array -> base64.
|
|
1957
|
+
const buf = new Uint8Array(flat.length * 2);
|
|
1958
|
+
flat.forEach((w, i) => {
|
|
1959
|
+
buf[i * 2] = w & 0xff;
|
|
1960
|
+
buf[i * 2 + 1] = (w >>> 8) & 0xff;
|
|
1961
|
+
});
|
|
1962
|
+
return Buffer.from(buf).toString("base64");
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
serialize(bytecode: b.Bytecode, constants: any[], compiler: Compiler) {
|
|
1966
|
+
const mainStartPc = compiler.mainStartPc;
|
|
1967
|
+
let sections = [];
|
|
1968
|
+
|
|
1969
|
+
var textForm = [];
|
|
1970
|
+
var initBody = [];
|
|
1971
|
+
|
|
1972
|
+
var bytecodeResult = this._serializeBytecode(bytecode, compiler);
|
|
1973
|
+
|
|
1974
|
+
for (const instr of bytecodeResult.bytecode) {
|
|
1975
|
+
const serialized = this._serializeInstr(instr, constants);
|
|
1976
|
+
textForm.push(serialized.text);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
initBody.push(textForm.map((line) => `// ${line}`).join("\n"));
|
|
1980
|
+
|
|
1981
|
+
const flat = bytecodeResult.bytecode.flatMap((instr) => {
|
|
1982
|
+
let filtered = instr.filter((x) => (x as any)?.placeholder !== true);
|
|
1983
|
+
let resolved = filtered.map((x) => (x as any)?.resolvedValue ?? x);
|
|
1984
|
+
|
|
1985
|
+
return resolved as number[];
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
if (this.options.encodeBytecode) {
|
|
1989
|
+
sections.push(`var BYTECODE = "${this._encodeBytecode(flat)}";`);
|
|
1990
|
+
} else {
|
|
1991
|
+
// Flatten each [op, ...operands] instruction into individual u16 slots.
|
|
1992
|
+
|
|
1993
|
+
sections.push(`var BYTECODE = [${flat.join(",")}]`);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// MAIN_START_PC
|
|
1997
|
+
sections.push(`var MAIN_START_PC = ${mainStartPc};`);
|
|
1998
|
+
sections.push(`var ENCODE_BYTECODE = ${!!this.options.encodeBytecode};`);
|
|
1999
|
+
sections.push(`var TIMING_CHECKS = ${!!this.options.timingChecks};`);
|
|
2000
|
+
// Opcodes
|
|
2001
|
+
const object = t.objectExpression(
|
|
2002
|
+
Object.entries(this.OP).map(([name, value]) =>
|
|
2003
|
+
t.objectProperty(t.identifier(name), t.numericLiteral(value)),
|
|
2004
|
+
),
|
|
2005
|
+
);
|
|
2006
|
+
sections.push(`var OP = ${generate(object).code};`);
|
|
2007
|
+
|
|
2008
|
+
// Constants must be defined before the bytecode
|
|
2009
|
+
initBody.push(this._serializeConstants(constants));
|
|
2010
|
+
|
|
2011
|
+
sections = [...initBody, ...sections];
|
|
2012
|
+
|
|
2013
|
+
// VM runtime
|
|
2014
|
+
sections.push(VM_RUNTIME);
|
|
2015
|
+
|
|
2016
|
+
return sections.join("\n\n");
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
export async function compileAndSerialize(
|
|
2021
|
+
sourceCode: string,
|
|
2022
|
+
options: Options,
|
|
2023
|
+
) {
|
|
2024
|
+
const compiler = new Compiler(options);
|
|
2025
|
+
let bytecode = compiler.compile(sourceCode);
|
|
2026
|
+
|
|
2027
|
+
// User transform passes (operate on unresolved IR with label/constant refs)
|
|
2028
|
+
// macroOpcodes must run after selfModifying (so PATCH-stub bodies are in place)
|
|
2029
|
+
const passes = [];
|
|
2030
|
+
|
|
2031
|
+
// Due to current implementation, specialized must run BEFORE macroOpcodes
|
|
2032
|
+
if (options.specializedOpcodes) {
|
|
2033
|
+
passes.push(specializedOpcodes);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
if (options.macroOpcodes) {
|
|
2037
|
+
passes.push(macroOpcodes);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
if (options.selfModifying) {
|
|
2041
|
+
passes.push(selfModifying);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
for (const pass of passes) {
|
|
2045
|
+
const passResult = pass(bytecode, compiler);
|
|
2046
|
+
bytecode = passResult.bytecode;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Assembler phases: resolve IR operands to plain integers before printing
|
|
2050
|
+
const { bytecode: labelResolved } = resolveLabels(bytecode, compiler);
|
|
2051
|
+
let { bytecode: finalBytecode, constants } = resolveConstants(labelResolved);
|
|
2052
|
+
|
|
2053
|
+
const output = compiler.serializer.serialize(
|
|
2054
|
+
finalBytecode,
|
|
2055
|
+
constants,
|
|
2056
|
+
compiler,
|
|
2057
|
+
);
|
|
2058
|
+
|
|
2059
|
+
const finalOutput = await obfuscateRuntime(
|
|
2060
|
+
output,
|
|
2061
|
+
finalBytecode,
|
|
2062
|
+
options,
|
|
2063
|
+
compiler,
|
|
2064
|
+
);
|
|
2065
|
+
|
|
2066
|
+
return {
|
|
2067
|
+
code: finalOutput,
|
|
2068
|
+
};
|
|
2069
|
+
}
|