porffor 0.0.0-745e995 → 0.0.0-7d5ae9c
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/README.md +49 -15
- package/c +0 -0
- package/c.exe +0 -0
- package/compiler/2c.js +350 -0
- package/compiler/builtins.js +6 -1
- package/compiler/codeGen.js +639 -186
- package/compiler/decompile.js +3 -3
- package/compiler/embedding.js +9 -5
- package/compiler/encoding.js +4 -2
- package/compiler/index.js +55 -4
- package/compiler/opt.js +49 -23
- package/compiler/parse.js +1 -0
- package/compiler/prototype.js +172 -34
- package/compiler/sections.js +47 -6
- package/compiler/wrap.js +19 -1
- package/cool.exe +0 -0
- package/g +0 -0
- package/g.exe +0 -0
- package/hi.c +37 -0
- package/out +0 -0
- package/out.exe +0 -0
- package/package.json +1 -1
- package/r.js +39 -0
- package/rhemyn/README.md +37 -0
- package/rhemyn/compile.js +214 -0
- package/rhemyn/parse.js +321 -0
- package/rhemyn/test/parse.js +59 -0
- package/runner/index.js +54 -33
- package/runner/info.js +37 -2
- package/runner/repl.js +6 -11
- package/runner/transform.js +5 -26
- package/runner/version.js +10 -0
- package/t.js +31 -0
- package/tmp.c +58 -0
package/compiler/codeGen.js
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
6
|
-
import { number, i32x4 } from "./embedding.js";
|
6
|
+
import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
|
7
7
|
import parse from "./parse.js";
|
8
|
+
import * as Rhemyn from "../rhemyn/compile.js";
|
8
9
|
|
9
10
|
let globals = {};
|
10
11
|
let globalInd = 0;
|
@@ -35,7 +36,14 @@ const debug = str => {
|
|
35
36
|
};
|
36
37
|
|
37
38
|
const todo = msg => {
|
38
|
-
|
39
|
+
class TodoError extends Error {
|
40
|
+
constructor(message) {
|
41
|
+
super(message);
|
42
|
+
this.name = 'TodoError';
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
throw new TodoError(`todo: ${msg}`);
|
39
47
|
|
40
48
|
const code = [];
|
41
49
|
|
@@ -101,6 +109,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
109
|
case 'WhileStatement':
|
102
110
|
return generateWhile(scope, decl);
|
103
111
|
|
112
|
+
case 'ForOfStatement':
|
113
|
+
return generateForOf(scope, decl);
|
114
|
+
|
104
115
|
case 'BreakStatement':
|
105
116
|
return generateBreak(scope, decl);
|
106
117
|
|
@@ -141,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
141
152
|
|
142
153
|
return [];
|
143
154
|
|
144
|
-
case 'TaggedTemplateExpression':
|
145
|
-
|
146
|
-
|
155
|
+
case 'TaggedTemplateExpression': {
|
156
|
+
const funcs = {
|
157
|
+
asm: str => {
|
158
|
+
let out = [];
|
147
159
|
|
148
|
-
|
149
|
-
|
160
|
+
for (const line of str.split('\n')) {
|
161
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
162
|
+
if (asm[0] === '') continue; // blank
|
150
163
|
|
151
|
-
|
152
|
-
|
153
|
-
|
164
|
+
if (asm[0] === 'local') {
|
165
|
+
const [ name, idx, type ] = asm.slice(1);
|
166
|
+
scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
|
167
|
+
continue;
|
168
|
+
}
|
154
169
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
}
|
170
|
+
if (asm[0] === 'returns') {
|
171
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
172
|
+
continue;
|
173
|
+
}
|
160
174
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
175
|
+
if (asm[0] === 'memory') {
|
176
|
+
allocPage('asm instrinsic');
|
177
|
+
// todo: add to store/load offset insts
|
178
|
+
continue;
|
179
|
+
}
|
165
180
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
}
|
181
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
182
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
183
|
+
|
184
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
185
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
172
186
|
|
173
|
-
|
174
|
-
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
189
|
+
|
190
|
+
return out;
|
191
|
+
},
|
192
|
+
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
175
195
|
|
176
|
-
|
177
|
-
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
178
199
|
|
179
|
-
|
200
|
+
// newline
|
201
|
+
...number(10),
|
202
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
203
|
+
];
|
204
|
+
}
|
180
205
|
}
|
181
206
|
|
182
|
-
|
207
|
+
const name = decl.tag.name;
|
208
|
+
// hack for inline asm
|
209
|
+
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
210
|
+
|
211
|
+
const str = decl.quasi.quasis[0].value.raw;
|
212
|
+
return funcs[name](str);
|
213
|
+
}
|
183
214
|
|
184
215
|
default:
|
185
216
|
return todo(`no generation for ${decl.type}!`);
|
@@ -278,7 +309,7 @@ const generateReturn = (scope, decl) => {
|
|
278
309
|
];
|
279
310
|
}
|
280
311
|
|
281
|
-
|
312
|
+
scope.returnType = getNodeType(scope, decl.argument);
|
282
313
|
|
283
314
|
return [
|
284
315
|
...generate(scope, decl.argument),
|
@@ -295,11 +326,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
326
|
return idx;
|
296
327
|
};
|
297
328
|
|
298
|
-
const
|
329
|
+
const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
|
330
|
+
|
331
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
332
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
333
|
+
'||': falsy,
|
334
|
+
'&&': truthy,
|
335
|
+
'??': nullish
|
303
336
|
};
|
304
337
|
|
305
338
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -307,10 +340,37 @@ const performLogicOp = (scope, op, left, right) => {
|
|
307
340
|
// generic structure for {a} OP {b}
|
308
341
|
// -->
|
309
342
|
// _ = {a}; if (OP_CHECK) {b} else _
|
343
|
+
|
344
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
345
|
+
// (like if we are in an if condition - very common)
|
346
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
347
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
348
|
+
|
349
|
+
const canInt = leftIsInt && rightIsInt;
|
350
|
+
|
351
|
+
if (canInt) {
|
352
|
+
// remove int -> float conversions from left and right
|
353
|
+
left.pop();
|
354
|
+
right.pop();
|
355
|
+
|
356
|
+
return [
|
357
|
+
...left,
|
358
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
359
|
+
...checks[op](scope, [], leftType, true),
|
360
|
+
[ Opcodes.if, Valtype.i32 ],
|
361
|
+
...right,
|
362
|
+
[ Opcodes.else ],
|
363
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
364
|
+
[ Opcodes.end ],
|
365
|
+
Opcodes.i32_from
|
366
|
+
];
|
367
|
+
}
|
368
|
+
|
310
369
|
return [
|
311
370
|
...left,
|
312
371
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
372
|
+
...checks[op](scope, [], leftType),
|
373
|
+
Opcodes.i32_to,
|
314
374
|
[ Opcodes.if, valtypeBinary ],
|
315
375
|
...right,
|
316
376
|
[ Opcodes.else ],
|
@@ -325,12 +385,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
385
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
386
|
// todo: optimize this if using literals/known lengths?
|
327
387
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
388
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
331
389
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
332
390
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
333
391
|
|
392
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
393
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
394
|
+
|
334
395
|
if (assign) {
|
335
396
|
const pointer = arrays.get(name ?? '$undeclared');
|
336
397
|
|
@@ -455,38 +516,37 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
455
516
|
];
|
456
517
|
};
|
457
518
|
|
458
|
-
const compareStrings = (scope, left, right
|
519
|
+
const compareStrings = (scope, left, right) => {
|
459
520
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
460
521
|
// todo: convert left and right to strings if not
|
461
522
|
// todo: optimize by looking up names in arrays and using that if exists?
|
462
523
|
// todo: optimize this if using literals/known lengths?
|
463
524
|
|
464
|
-
|
525
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
526
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
527
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
528
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
465
529
|
|
466
|
-
const
|
467
|
-
const
|
468
|
-
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
469
|
-
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
470
|
-
|
471
|
-
// alloc/assign array
|
472
|
-
const [ , pointer ] = makeArray(scope, {
|
473
|
-
rawElements: new Array(0)
|
474
|
-
}, global, name, true, 'i16');
|
530
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
531
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
475
532
|
|
476
533
|
return [
|
477
534
|
// setup left
|
478
535
|
...left,
|
479
536
|
Opcodes.i32_to_u,
|
480
|
-
[ Opcodes.
|
537
|
+
[ Opcodes.local_tee, leftPointer ],
|
481
538
|
|
482
539
|
// setup right
|
483
540
|
...right,
|
484
541
|
Opcodes.i32_to_u,
|
485
|
-
[ Opcodes.
|
542
|
+
[ Opcodes.local_tee, rightPointer ],
|
486
543
|
|
487
|
-
//
|
488
|
-
|
544
|
+
// fast path: check leftPointer == rightPointer
|
545
|
+
// use if (block) for everything after to "return" a value early
|
546
|
+
[ Opcodes.i32_ne ],
|
547
|
+
[ Opcodes.if, Valtype.i32 ],
|
489
548
|
|
549
|
+
// get lengths
|
490
550
|
[ Opcodes.local_get, leftPointer ],
|
491
551
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
492
552
|
[ Opcodes.local_tee, leftLength ],
|
@@ -495,154 +555,219 @@ const compareStrings = (scope, left, right, global, name) => {
|
|
495
555
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
496
556
|
[ Opcodes.local_tee, rightLength ],
|
497
557
|
|
498
|
-
|
558
|
+
// fast path: check leftLength != rightLength
|
559
|
+
[ Opcodes.i32_ne ],
|
560
|
+
[ Opcodes.if, Blocktype.void ],
|
561
|
+
...number(0, Valtype.i32),
|
562
|
+
[ Opcodes.br, 1 ],
|
563
|
+
[ Opcodes.end ],
|
499
564
|
|
500
|
-
//
|
501
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
565
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
502
566
|
|
503
|
-
//
|
504
|
-
//
|
505
|
-
|
567
|
+
// setup index end as length * sizeof i16 (2)
|
568
|
+
// we do this instead of having to do mul/div each iter for perf™
|
569
|
+
[ Opcodes.local_get, leftLength ],
|
570
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
571
|
+
[ Opcodes.i32_mul ],
|
572
|
+
[ Opcodes.local_set, indexEnd ],
|
506
573
|
|
507
|
-
//
|
574
|
+
// iterate over each char and check if eq
|
575
|
+
[ Opcodes.loop, Blocktype.void ],
|
576
|
+
|
577
|
+
// fetch left
|
578
|
+
[ Opcodes.local_get, index ],
|
508
579
|
[ Opcodes.local_get, leftPointer ],
|
509
|
-
...number(ValtypeSize.i32, Valtype.i32),
|
510
580
|
[ Opcodes.i32_add ],
|
581
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
511
582
|
|
512
|
-
//
|
513
|
-
|
514
|
-
[
|
583
|
+
// fetch right
|
584
|
+
[ Opcodes.local_get, index ],
|
585
|
+
[ Opcodes.local_get, rightPointer ],
|
586
|
+
[ Opcodes.i32_add ],
|
587
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
515
588
|
|
516
|
-
//
|
517
|
-
|
518
|
-
|
589
|
+
// not equal, "return" false
|
590
|
+
[ Opcodes.i32_ne ],
|
591
|
+
[ Opcodes.if, Blocktype.void ],
|
592
|
+
...number(0, Valtype.i32),
|
593
|
+
[ Opcodes.br, 2 ],
|
594
|
+
[ Opcodes.end ],
|
519
595
|
|
520
|
-
|
596
|
+
// index += sizeof i16 (2)
|
597
|
+
[ Opcodes.local_get, index ],
|
521
598
|
...number(ValtypeSize.i16, Valtype.i32),
|
522
|
-
[ Opcodes.i32_mul ],
|
523
599
|
[ Opcodes.i32_add ],
|
600
|
+
[ Opcodes.local_tee, index ],
|
524
601
|
|
525
|
-
//
|
526
|
-
[ Opcodes.local_get,
|
527
|
-
|
528
|
-
[ Opcodes.
|
602
|
+
// if index != index end (length * sizeof 16), loop
|
603
|
+
[ Opcodes.local_get, indexEnd ],
|
604
|
+
[ Opcodes.i32_ne ],
|
605
|
+
[ Opcodes.br_if, 0 ],
|
606
|
+
[ Opcodes.end ],
|
529
607
|
|
530
|
-
//
|
531
|
-
|
532
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
533
|
-
[ Opcodes.i32_mul ],
|
608
|
+
// no failed checks, so true!
|
609
|
+
...number(1, Valtype.i32),
|
534
610
|
|
535
|
-
|
611
|
+
// pointers match, so true
|
612
|
+
[ Opcodes.else ],
|
613
|
+
...number(1, Valtype.i32),
|
614
|
+
[ Opcodes.end ],
|
536
615
|
|
537
|
-
//
|
538
|
-
|
616
|
+
// convert i32 result to valtype
|
617
|
+
// do not do as automatically added by binary exp gen for equality ops
|
618
|
+
// Opcodes.i32_from_u
|
539
619
|
];
|
540
620
|
};
|
541
621
|
|
542
|
-
const
|
622
|
+
const truthy = (scope, wasm, type, int = false) => {
|
543
623
|
// arrays are always truthy
|
544
624
|
if (type === TYPES._array) return [
|
545
625
|
...wasm,
|
546
626
|
[ Opcodes.drop ],
|
547
|
-
number(
|
627
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
548
628
|
];
|
549
629
|
|
550
630
|
if (type === TYPES.string) {
|
551
|
-
// if "" (length = 0)
|
631
|
+
// if not "" (length = 0)
|
552
632
|
return [
|
553
633
|
// pointer
|
554
634
|
...wasm,
|
635
|
+
Opcodes.i32_to_u,
|
555
636
|
|
556
637
|
// get length
|
557
638
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
558
639
|
|
559
|
-
// if length
|
560
|
-
[ Opcodes.i32_eqz ],
|
561
|
-
Opcodes.
|
562
|
-
|
640
|
+
// if length != 0
|
641
|
+
/* [ Opcodes.i32_eqz ],
|
642
|
+
[ Opcodes.i32_eqz ], */
|
643
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
644
|
+
];
|
563
645
|
}
|
564
646
|
|
565
|
-
// if
|
647
|
+
// if != 0
|
566
648
|
return [
|
567
649
|
...wasm,
|
568
650
|
|
569
|
-
|
570
|
-
Opcodes.
|
651
|
+
/* Opcodes.eqz,
|
652
|
+
[ Opcodes.i32_eqz ],
|
653
|
+
Opcodes.i32_from */
|
571
654
|
];
|
572
655
|
};
|
573
656
|
|
574
|
-
const
|
657
|
+
const falsy = (scope, wasm, type, int = false) => {
|
575
658
|
// arrays are always truthy
|
576
659
|
if (type === TYPES._array) return [
|
577
660
|
...wasm,
|
578
661
|
[ Opcodes.drop ],
|
579
|
-
number(
|
662
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
580
663
|
];
|
581
664
|
|
582
665
|
if (type === TYPES.string) {
|
583
|
-
// if
|
666
|
+
// if "" (length = 0)
|
584
667
|
return [
|
585
668
|
// pointer
|
586
669
|
...wasm,
|
670
|
+
Opcodes.i32_to_u,
|
587
671
|
|
588
672
|
// get length
|
589
673
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
590
674
|
|
591
|
-
// if length
|
592
|
-
|
593
|
-
[ Opcodes.
|
594
|
-
Opcodes.i32_from_u
|
675
|
+
// if length == 0
|
676
|
+
[ Opcodes.i32_eqz ],
|
677
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
595
678
|
]
|
596
679
|
}
|
597
680
|
|
598
|
-
// if
|
681
|
+
// if = 0
|
599
682
|
return [
|
600
683
|
...wasm,
|
601
684
|
|
602
|
-
|
603
|
-
|
604
|
-
|
685
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
686
|
+
];
|
687
|
+
};
|
688
|
+
|
689
|
+
const nullish = (scope, wasm, type, int = false) => {
|
690
|
+
// undefined
|
691
|
+
if (type === TYPES.undefined) return [
|
692
|
+
...wasm,
|
693
|
+
[ Opcodes.drop ],
|
694
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
695
|
+
];
|
696
|
+
|
697
|
+
// null (if object and = "0")
|
698
|
+
if (type === TYPES.object) return [
|
699
|
+
...wasm,
|
700
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
701
|
+
];
|
702
|
+
|
703
|
+
// not
|
704
|
+
return [
|
705
|
+
...wasm,
|
706
|
+
[ Opcodes.drop ],
|
707
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
605
708
|
];
|
606
709
|
};
|
607
710
|
|
608
711
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
609
712
|
if (op === '||' || op === '&&' || op === '??') {
|
610
|
-
return performLogicOp(scope, op, left, right);
|
713
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
611
714
|
}
|
612
715
|
|
613
716
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
614
717
|
|
615
|
-
|
616
|
-
|
718
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
719
|
+
|
720
|
+
if (leftType && rightType && (
|
721
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
722
|
+
((op === '===' || op === '!==') && leftType !== rightType) ||
|
723
|
+
|
724
|
+
// if equality op and an operand is undefined, return false
|
725
|
+
(eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
|
726
|
+
)) {
|
727
|
+
// undefined == null
|
728
|
+
if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
|
729
|
+
...(leftType === TYPES.object ? left : right),
|
730
|
+
...Opcodes.eqz,
|
731
|
+
...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
|
732
|
+
];
|
733
|
+
|
617
734
|
return [
|
618
735
|
...left,
|
619
|
-
...right,
|
620
|
-
|
621
|
-
// drop values
|
622
736
|
[ Opcodes.drop ],
|
737
|
+
|
738
|
+
...right,
|
623
739
|
[ Opcodes.drop ],
|
624
740
|
|
625
|
-
// return
|
626
|
-
...number(op === '===' ?
|
741
|
+
// return true (!=/!==) or false (else)
|
742
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
627
743
|
];
|
628
744
|
}
|
629
745
|
|
746
|
+
// todo: niche null hell with 0
|
747
|
+
|
630
748
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
631
749
|
if (op === '+') {
|
632
750
|
// string concat (a + b)
|
633
751
|
return concatStrings(scope, left, right, _global, _name, assign);
|
634
752
|
}
|
635
753
|
|
636
|
-
//
|
637
|
-
if (!
|
754
|
+
// not an equality op, NaN
|
755
|
+
if (!eqOp) return number(NaN);
|
638
756
|
|
639
757
|
// else leave bool ops
|
640
758
|
// todo: convert string to number if string and number/bool
|
641
759
|
// todo: string (>|>=|<|<=) string
|
642
760
|
|
643
|
-
// string
|
644
|
-
if (op === '===') {
|
761
|
+
// string comparison
|
762
|
+
if (op === '===' || op === '==') {
|
763
|
+
return compareStrings(scope, left, right);
|
764
|
+
}
|
645
765
|
|
766
|
+
if (op === '!==' || op === '!=') {
|
767
|
+
return [
|
768
|
+
...compareStrings(scope, left, right),
|
769
|
+
[ Opcodes.i32_eqz ]
|
770
|
+
];
|
646
771
|
}
|
647
772
|
}
|
648
773
|
|
@@ -672,21 +797,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
672
797
|
];
|
673
798
|
};
|
674
799
|
|
675
|
-
let binaryExpDepth = 0;
|
676
800
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
677
|
-
|
678
|
-
|
679
|
-
const out = [
|
680
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
681
|
-
];
|
801
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
682
802
|
|
683
803
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
684
804
|
|
685
|
-
binaryExpDepth--;
|
686
805
|
return out;
|
687
806
|
};
|
688
807
|
|
689
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
808
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
690
809
|
const existing = funcs.find(x => x.name === name);
|
691
810
|
if (existing) return existing;
|
692
811
|
|
@@ -722,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
722
841
|
returns,
|
723
842
|
returnType: TYPES[returnType ?? 'number'],
|
724
843
|
wasm,
|
725
|
-
memory,
|
726
844
|
internal: true,
|
727
845
|
index: currentFuncIndex++
|
728
846
|
};
|
@@ -741,7 +859,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
741
859
|
};
|
742
860
|
|
743
861
|
const generateLogicExp = (scope, decl) => {
|
744
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
862
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
745
863
|
};
|
746
864
|
|
747
865
|
const TYPES = {
|
@@ -755,7 +873,8 @@ const TYPES = {
|
|
755
873
|
bigint: 0xffffffffffff7,
|
756
874
|
|
757
875
|
// these are not "typeof" types but tracked internally
|
758
|
-
_array:
|
876
|
+
_array: 0xfffffffffff0f,
|
877
|
+
_regexp: 0xfffffffffff1f
|
759
878
|
};
|
760
879
|
|
761
880
|
const TYPE_NAMES = {
|
@@ -789,6 +908,9 @@ const getType = (scope, _name) => {
|
|
789
908
|
|
790
909
|
const getNodeType = (scope, node) => {
|
791
910
|
if (node.type === 'Literal') {
|
911
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
912
|
+
if (node.regex) return TYPES._regexp;
|
913
|
+
|
792
914
|
return TYPES[typeof node.value];
|
793
915
|
}
|
794
916
|
|
@@ -822,6 +944,11 @@ const getNodeType = (scope, node) => {
|
|
822
944
|
|
823
945
|
// literal.func()
|
824
946
|
if (!name && node.callee.type === 'MemberExpression') {
|
947
|
+
if (node.callee.object.regex) {
|
948
|
+
const funcName = node.callee.property.name;
|
949
|
+
return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
950
|
+
}
|
951
|
+
|
825
952
|
const baseType = getNodeType(scope, node.callee.object);
|
826
953
|
|
827
954
|
const func = node.callee.property.name;
|
@@ -865,6 +992,11 @@ const getNodeType = (scope, node) => {
|
|
865
992
|
const generateLiteral = (scope, decl, global, name) => {
|
866
993
|
if (decl.value === null) return number(NULL);
|
867
994
|
|
995
|
+
if (decl.regex) {
|
996
|
+
scope.regex[name] = decl.regex;
|
997
|
+
return number(1);
|
998
|
+
}
|
999
|
+
|
868
1000
|
switch (typeof decl.value) {
|
869
1001
|
case 'number':
|
870
1002
|
return number(decl.value);
|
@@ -886,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
886
1018
|
case 'bigint': return number(TYPES.bigint);
|
887
1019
|
}
|
888
1020
|
|
1021
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1022
|
+
let wellFormed = aotWFA ? true : undefined;
|
1023
|
+
|
889
1024
|
const str = decl.value;
|
890
1025
|
const rawElements = new Array(str.length);
|
1026
|
+
let j = 0;
|
891
1027
|
for (let i = 0; i < str.length; i++) {
|
892
1028
|
rawElements[i] = str.charCodeAt(i);
|
1029
|
+
|
1030
|
+
if (wellFormed) {
|
1031
|
+
// check if surrogate
|
1032
|
+
if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
|
1033
|
+
// unpaired trailing surrogate
|
1034
|
+
if (str.charCodeAt(j) >= 0xDC00) {
|
1035
|
+
wellFormed = false;
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
// unpaired leading surrogate
|
1039
|
+
// if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
|
1040
|
+
if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
|
1041
|
+
wellFormed = false;
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
j++;
|
1046
|
+
}
|
893
1047
|
}
|
894
1048
|
|
1049
|
+
// console.log(wellFormed, str);
|
1050
|
+
|
1051
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1052
|
+
|
895
1053
|
return makeArray(scope, {
|
896
1054
|
rawElements
|
897
1055
|
}, global, name, false, 'i16')[0];
|
@@ -904,7 +1062,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
904
1062
|
const countLeftover = wasm => {
|
905
1063
|
let count = 0, depth = 0;
|
906
1064
|
|
907
|
-
for (
|
1065
|
+
for (let i = 0; i < wasm.length; i++) {
|
1066
|
+
const inst = wasm[i];
|
908
1067
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
909
1068
|
if (inst[0] === Opcodes.if) count--;
|
910
1069
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -925,6 +1084,8 @@ const countLeftover = wasm => {
|
|
925
1084
|
} else count--;
|
926
1085
|
if (func) count += func.returns.length;
|
927
1086
|
} else count--;
|
1087
|
+
|
1088
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
928
1089
|
}
|
929
1090
|
|
930
1091
|
return count;
|
@@ -1008,7 +1169,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1008
1169
|
}
|
1009
1170
|
|
1010
1171
|
let out = [];
|
1011
|
-
let protoFunc, protoName, baseType, baseName
|
1172
|
+
let protoFunc, protoName, baseType, baseName;
|
1012
1173
|
// ident.func()
|
1013
1174
|
if (name && name.startsWith('__')) {
|
1014
1175
|
const spl = name.slice(2).split('_');
|
@@ -1023,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1023
1184
|
|
1024
1185
|
// literal.func()
|
1025
1186
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1187
|
+
// megahack for /regex/.func()
|
1188
|
+
if (decl.callee.object.regex) {
|
1189
|
+
const funcName = decl.callee.property.name;
|
1190
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1191
|
+
|
1192
|
+
funcIndex[func.name] = func.index;
|
1193
|
+
funcs.push(func);
|
1194
|
+
|
1195
|
+
return [
|
1196
|
+
// make string arg
|
1197
|
+
...generate(scope, decl.arguments[0]),
|
1198
|
+
|
1199
|
+
// call regex func
|
1200
|
+
Opcodes.i32_to_u,
|
1201
|
+
[ Opcodes.call, func.index ],
|
1202
|
+
Opcodes.i32_from
|
1203
|
+
];
|
1204
|
+
}
|
1205
|
+
|
1026
1206
|
baseType = getNodeType(scope, decl.callee.object);
|
1027
1207
|
|
1028
1208
|
const func = decl.callee.property.name;
|
@@ -1031,11 +1211,36 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1031
1211
|
|
1032
1212
|
out = generate(scope, decl.callee.object);
|
1033
1213
|
out.push([ Opcodes.drop ]);
|
1214
|
+
|
1215
|
+
baseName = [...arrays.keys()].pop();
|
1034
1216
|
}
|
1035
1217
|
|
1036
|
-
if (
|
1037
|
-
|
1218
|
+
if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1219
|
+
const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
1220
|
+
|
1221
|
+
funcIndex[func.name] = func.index;
|
1222
|
+
funcs.push(func);
|
1223
|
+
|
1224
|
+
const pointer = arrays.get(baseName);
|
1225
|
+
const [ local, isGlobal ] = lookupName(scope, baseName);
|
1226
|
+
|
1227
|
+
return [
|
1228
|
+
...out,
|
1229
|
+
|
1230
|
+
...(pointer == null ? [
|
1231
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1232
|
+
Opcodes.i32_to_u,
|
1233
|
+
] : [
|
1234
|
+
...number(pointer, Valtype.i32)
|
1235
|
+
]),
|
1038
1236
|
|
1237
|
+
// call regex func
|
1238
|
+
[ Opcodes.call, func.index ],
|
1239
|
+
Opcodes.i32_from
|
1240
|
+
];
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
if (protoFunc) {
|
1039
1244
|
let pointer = arrays.get(baseName);
|
1040
1245
|
|
1041
1246
|
if (pointer == null) {
|
@@ -1043,7 +1248,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1043
1248
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1044
1249
|
|
1045
1250
|
// register array
|
1046
|
-
|
1251
|
+
0, [ , pointer ] = makeArray(scope, {
|
1047
1252
|
rawElements: new Array(0)
|
1048
1253
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1049
1254
|
|
@@ -1063,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1063
1268
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1064
1269
|
|
1065
1270
|
let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1271
|
+
let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1066
1272
|
|
1067
1273
|
// use local for cached i32 length as commonly used
|
1068
1274
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1069
1275
|
|
1276
|
+
let lengthI32CacheUsed = false;
|
1277
|
+
|
1278
|
+
const protoOut = protoFunc(pointer, {
|
1279
|
+
getCachedI32: () => {
|
1280
|
+
lengthI32CacheUsed = true;
|
1281
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1282
|
+
},
|
1283
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1284
|
+
get: () => arrayUtil.getLength(pointer),
|
1285
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1286
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1287
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1288
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1289
|
+
return makeArray(scope, {
|
1290
|
+
rawElements: new Array(length)
|
1291
|
+
}, _global, _name, true, itemType);
|
1292
|
+
}, varMetadata.get(baseName));
|
1293
|
+
|
1070
1294
|
return [
|
1071
1295
|
...out,
|
1072
1296
|
|
1073
|
-
...
|
1074
|
-
|
1297
|
+
...(!lengthI32CacheUsed ? [] : [
|
1298
|
+
...arrayUtil.getLengthI32(pointer),
|
1299
|
+
[ Opcodes.local_set, lengthLocal ],
|
1300
|
+
]),
|
1075
1301
|
|
1076
1302
|
[ Opcodes.block, valtypeBinary ],
|
1077
|
-
...
|
1078
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1079
|
-
get: arrayUtil.getLength(pointer),
|
1080
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1081
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1082
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1083
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1084
|
-
return makeArray(scope, {
|
1085
|
-
rawElements: new Array(length)
|
1086
|
-
}, _global, _name, true, itemType);
|
1087
|
-
}),
|
1303
|
+
...protoOut,
|
1088
1304
|
[ Opcodes.end ]
|
1089
1305
|
];
|
1090
1306
|
}
|
@@ -1141,11 +1357,10 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1141
1357
|
args = args.slice(0, func.params.length);
|
1142
1358
|
}
|
1143
1359
|
|
1144
|
-
if (func && func.memory) scope.memory = true;
|
1145
1360
|
if (func && func.throws) scope.throws = true;
|
1146
1361
|
|
1147
1362
|
for (const arg of args) {
|
1148
|
-
out.
|
1363
|
+
out = out.concat(generate(scope, arg));
|
1149
1364
|
}
|
1150
1365
|
|
1151
1366
|
out.push([ Opcodes.call, idx ]);
|
@@ -1156,8 +1371,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1156
1371
|
const generateNew = (scope, decl, _global, _name) => {
|
1157
1372
|
// hack: basically treat this as a normal call for builtins for now
|
1158
1373
|
const name = mapName(decl.callee.name);
|
1159
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1160
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1374
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1375
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1161
1376
|
|
1162
1377
|
return generateCall(scope, decl, _global, _name);
|
1163
1378
|
};
|
@@ -1174,12 +1389,12 @@ const unhackName = name => {
|
|
1174
1389
|
};
|
1175
1390
|
|
1176
1391
|
const generateVar = (scope, decl) => {
|
1177
|
-
|
1392
|
+
let out = [];
|
1178
1393
|
|
1179
1394
|
const topLevel = scope.name === 'main';
|
1180
1395
|
|
1181
1396
|
// global variable if in top scope (main) and var ..., or if wanted
|
1182
|
-
const global = decl.kind === 'var';
|
1397
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1183
1398
|
const target = global ? globals : scope.locals;
|
1184
1399
|
|
1185
1400
|
for (const x of decl.declarations) {
|
@@ -1216,7 +1431,7 @@ const generateVar = (scope, decl) => {
|
|
1216
1431
|
|
1217
1432
|
// x.init ??= DEFAULT_VALUE;
|
1218
1433
|
if (x.init) {
|
1219
|
-
out.
|
1434
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1220
1435
|
|
1221
1436
|
// if our value is the result of a function, infer the type from that func's return value
|
1222
1437
|
if (out[out.length - 1][0] === Opcodes.call) {
|
@@ -1265,8 +1480,6 @@ const generateAssign = (scope, decl) => {
|
|
1265
1480
|
const name = decl.left.object.name;
|
1266
1481
|
const pointer = arrays.get(name);
|
1267
1482
|
|
1268
|
-
scope.memory = true;
|
1269
|
-
|
1270
1483
|
const aotPointer = pointer != null;
|
1271
1484
|
|
1272
1485
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1287,13 +1500,60 @@ const generateAssign = (scope, decl) => {
|
|
1287
1500
|
];
|
1288
1501
|
}
|
1289
1502
|
|
1503
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1504
|
+
|
1505
|
+
// arr[i] | str[i]
|
1506
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1507
|
+
const name = decl.left.object.name;
|
1508
|
+
const pointer = arrays.get(name);
|
1509
|
+
|
1510
|
+
const aotPointer = pointer != null;
|
1511
|
+
|
1512
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1513
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1514
|
+
|
1515
|
+
const parentType = getNodeType(scope, decl.left.object);
|
1516
|
+
|
1517
|
+
return [
|
1518
|
+
...(aotPointer ? [] : [
|
1519
|
+
...generate(scope, decl.left.object),
|
1520
|
+
Opcodes.i32_to_u
|
1521
|
+
]),
|
1522
|
+
|
1523
|
+
// get index as valtype
|
1524
|
+
...generate(scope, decl.left.property),
|
1525
|
+
|
1526
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1527
|
+
Opcodes.i32_to_u,
|
1528
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1529
|
+
[ Opcodes.i32_mul ],
|
1530
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1531
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1532
|
+
|
1533
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1534
|
+
[ Opcodes.local_get, pointerTmp ],
|
1535
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1536
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1537
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1538
|
+
|
1539
|
+
...(parentType === TYPES._array ? [
|
1540
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1541
|
+
] : [
|
1542
|
+
Opcodes.i32_to_u,
|
1543
|
+
[ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1544
|
+
]),
|
1545
|
+
|
1546
|
+
[ Opcodes.local_get, newValueTmp ]
|
1547
|
+
];
|
1548
|
+
}
|
1549
|
+
|
1290
1550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1291
1551
|
|
1292
1552
|
if (local === undefined) {
|
1293
|
-
// todo: this should be a
|
1553
|
+
// todo: this should be a sloppy mode only thing
|
1294
1554
|
|
1295
1555
|
// only allow = for this
|
1296
|
-
if (
|
1556
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1297
1557
|
|
1298
1558
|
if (builtinVars[name]) {
|
1299
1559
|
// just return rhs (eg `NaN = 2`)
|
@@ -1302,13 +1562,15 @@ const generateAssign = (scope, decl) => {
|
|
1302
1562
|
|
1303
1563
|
// set global and return (eg a = 2)
|
1304
1564
|
return [
|
1305
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1565
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1306
1566
|
[ Opcodes.global_get, globals[name].idx ]
|
1307
1567
|
];
|
1308
1568
|
}
|
1309
1569
|
|
1310
|
-
|
1311
|
-
|
1570
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1571
|
+
|
1572
|
+
if (op === '=') {
|
1573
|
+
// typeStates[name] = getNodeType(scope, decl.right);
|
1312
1574
|
|
1313
1575
|
return [
|
1314
1576
|
...generate(scope, decl.right, isGlobal, name),
|
@@ -1317,8 +1579,26 @@ const generateAssign = (scope, decl) => {
|
|
1317
1579
|
];
|
1318
1580
|
}
|
1319
1581
|
|
1582
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1583
|
+
// todo: is this needed?
|
1584
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1585
|
+
// instead, left @ (left = right)
|
1586
|
+
// eg, x &&= y ~= x && (x = y)
|
1587
|
+
|
1588
|
+
return [
|
1589
|
+
...performOp(scope, op, [
|
1590
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1591
|
+
], [
|
1592
|
+
...generate(scope, decl.right),
|
1593
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1594
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1595
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1596
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1597
|
+
];
|
1598
|
+
}
|
1599
|
+
|
1320
1600
|
return [
|
1321
|
-
...performOp(scope,
|
1601
|
+
...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1322
1602
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1323
1603
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1324
1604
|
];
|
@@ -1345,13 +1625,14 @@ const generateUnary = (scope, decl) => {
|
|
1345
1625
|
|
1346
1626
|
case '!':
|
1347
1627
|
// !=
|
1348
|
-
return falsy(scope, generate(scope, decl.argument));
|
1628
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1349
1629
|
|
1350
1630
|
case '~':
|
1631
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1351
1632
|
return [
|
1352
1633
|
...generate(scope, decl.argument),
|
1353
1634
|
Opcodes.i32_to,
|
1354
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1635
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1355
1636
|
[ Opcodes.i32_xor ],
|
1356
1637
|
Opcodes.i32_from
|
1357
1638
|
];
|
@@ -1432,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
|
|
1432
1713
|
};
|
1433
1714
|
|
1434
1715
|
const generateIf = (scope, decl) => {
|
1435
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1716
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
|
1436
1717
|
|
1437
1718
|
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1438
1719
|
depth.push('if');
|
@@ -1522,9 +1803,116 @@ const generateWhile = (scope, decl) => {
|
|
1522
1803
|
return out;
|
1523
1804
|
};
|
1524
1805
|
|
1806
|
+
const generateForOf = (scope, decl) => {
|
1807
|
+
const out = [];
|
1808
|
+
|
1809
|
+
const rightType = getNodeType(scope, decl.right);
|
1810
|
+
const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
|
1811
|
+
|
1812
|
+
// todo: for of inside for of might fuck up?
|
1813
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
1814
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
1815
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
1816
|
+
|
1817
|
+
out.push(
|
1818
|
+
// set pointer as right
|
1819
|
+
...generate(scope, decl.right),
|
1820
|
+
Opcodes.i32_to_u,
|
1821
|
+
[ Opcodes.local_set, pointer ],
|
1822
|
+
|
1823
|
+
// get length
|
1824
|
+
[ Opcodes.local_get, pointer ],
|
1825
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1826
|
+
[ Opcodes.local_set, length ]
|
1827
|
+
);
|
1828
|
+
|
1829
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1830
|
+
depth.push('forof');
|
1831
|
+
|
1832
|
+
// setup local for left
|
1833
|
+
generate(scope, decl.left);
|
1834
|
+
|
1835
|
+
const leftName = decl.left.declarations[0].id.name;
|
1836
|
+
|
1837
|
+
// set type for local
|
1838
|
+
typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
|
1839
|
+
|
1840
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
1841
|
+
|
1842
|
+
if (rightType === TYPES._array) { // array
|
1843
|
+
out.push(
|
1844
|
+
[ Opcodes.local_get, pointer ],
|
1845
|
+
[ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
1846
|
+
);
|
1847
|
+
} else { // string
|
1848
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1849
|
+
rawElements: new Array(1)
|
1850
|
+
}, isGlobal, leftName, true, 'i16');
|
1851
|
+
|
1852
|
+
out.push(
|
1853
|
+
// setup new/out array
|
1854
|
+
...newOut,
|
1855
|
+
[ Opcodes.drop ],
|
1856
|
+
|
1857
|
+
...number(0, Valtype.i32), // base 0 for store after
|
1858
|
+
|
1859
|
+
// load current string ind {arg}
|
1860
|
+
[ Opcodes.local_get, pointer ],
|
1861
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
1862
|
+
|
1863
|
+
// store to new string ind 0
|
1864
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1865
|
+
|
1866
|
+
// return new string (page)
|
1867
|
+
...number(newPointer)
|
1868
|
+
);
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
// set left value
|
1872
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
|
1873
|
+
|
1874
|
+
out.push(
|
1875
|
+
[ Opcodes.block, Blocktype.void ],
|
1876
|
+
[ Opcodes.block, Blocktype.void ]
|
1877
|
+
);
|
1878
|
+
depth.push('block');
|
1879
|
+
depth.push('block');
|
1880
|
+
|
1881
|
+
out.push(
|
1882
|
+
...generate(scope, decl.body),
|
1883
|
+
[ Opcodes.end ]
|
1884
|
+
);
|
1885
|
+
depth.pop();
|
1886
|
+
|
1887
|
+
out.push(
|
1888
|
+
// increment iter pointer by valtype size
|
1889
|
+
[ Opcodes.local_get, pointer ],
|
1890
|
+
...number(valtypeSize, Valtype.i32),
|
1891
|
+
[ Opcodes.i32_add ],
|
1892
|
+
[ Opcodes.local_set, pointer ],
|
1893
|
+
|
1894
|
+
// increment counter by 1
|
1895
|
+
[ Opcodes.local_get, counter ],
|
1896
|
+
...number(1, Valtype.i32),
|
1897
|
+
[ Opcodes.i32_add ],
|
1898
|
+
[ Opcodes.local_tee, counter ],
|
1899
|
+
|
1900
|
+
// loop if counter != length
|
1901
|
+
[ Opcodes.local_get, length ],
|
1902
|
+
[ Opcodes.i32_ne ],
|
1903
|
+
[ Opcodes.br_if, 1 ],
|
1904
|
+
|
1905
|
+
[ Opcodes.end ], [ Opcodes.end ]
|
1906
|
+
);
|
1907
|
+
depth.pop();
|
1908
|
+
depth.pop();
|
1909
|
+
|
1910
|
+
return out;
|
1911
|
+
};
|
1912
|
+
|
1525
1913
|
const getNearestLoop = () => {
|
1526
1914
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1527
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1915
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1528
1916
|
}
|
1529
1917
|
|
1530
1918
|
return -1;
|
@@ -1608,13 +1996,22 @@ const generateAssignPat = (scope, decl) => {
|
|
1608
1996
|
};
|
1609
1997
|
|
1610
1998
|
let pages = new Map();
|
1611
|
-
const allocPage = reason => {
|
1612
|
-
if (pages.has(reason)) return pages.get(reason);
|
1999
|
+
const allocPage = (reason, type) => {
|
2000
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
1613
2001
|
|
1614
|
-
|
1615
|
-
pages.set(reason, ind);
|
2002
|
+
const ind = pages.size;
|
2003
|
+
pages.set(reason, { ind, type });
|
1616
2004
|
|
1617
|
-
if (
|
2005
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2006
|
+
|
2007
|
+
return ind;
|
2008
|
+
};
|
2009
|
+
|
2010
|
+
const freePage = reason => {
|
2011
|
+
const { ind } = pages.get(reason);
|
2012
|
+
pages.delete(reason);
|
2013
|
+
|
2014
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1618
2015
|
|
1619
2016
|
return ind;
|
1620
2017
|
};
|
@@ -1628,7 +2025,7 @@ const itemTypeToValtype = {
|
|
1628
2025
|
i16: 'i32'
|
1629
2026
|
};
|
1630
2027
|
|
1631
|
-
const
|
2028
|
+
const StoreOps = {
|
1632
2029
|
i32: Opcodes.i32_store,
|
1633
2030
|
i64: Opcodes.i64_store,
|
1634
2031
|
f64: Opcodes.f64_store,
|
@@ -1637,13 +2034,31 @@ const storeOps = {
|
|
1637
2034
|
i16: Opcodes.i32_store16
|
1638
2035
|
};
|
1639
2036
|
|
2037
|
+
let data = [];
|
2038
|
+
|
2039
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2040
|
+
switch (itemType) {
|
2041
|
+
case 'i8': return [ val % 256 ];
|
2042
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2043
|
+
|
2044
|
+
case 'i32':
|
2045
|
+
case 'i64':
|
2046
|
+
return enforceFourBytes(signedLEB128(val));
|
2047
|
+
|
2048
|
+
case 'f64': return ieee754_binary64(val);
|
2049
|
+
}
|
2050
|
+
};
|
2051
|
+
|
1640
2052
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1641
2053
|
const out = [];
|
1642
2054
|
|
2055
|
+
let firstAssign = false;
|
1643
2056
|
if (!arrays.has(name) || name === '$undeclared') {
|
2057
|
+
firstAssign = true;
|
2058
|
+
|
1644
2059
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1645
2060
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1646
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2061
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1647
2062
|
}
|
1648
2063
|
|
1649
2064
|
const pointer = arrays.get(name);
|
@@ -1651,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1651
2066
|
const useRawElements = !!decl.rawElements;
|
1652
2067
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1653
2068
|
|
2069
|
+
const valtype = itemTypeToValtype[itemType];
|
1654
2070
|
const length = elements.length;
|
1655
2071
|
|
2072
|
+
if (firstAssign && useRawElements) {
|
2073
|
+
let bytes = compileBytes(length, 'i32');
|
2074
|
+
|
2075
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2076
|
+
if (elements[i] == null) continue;
|
2077
|
+
|
2078
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2079
|
+
}
|
2080
|
+
|
2081
|
+
data.push({
|
2082
|
+
offset: pointer,
|
2083
|
+
bytes
|
2084
|
+
});
|
2085
|
+
|
2086
|
+
// local value as pointer
|
2087
|
+
out.push(...number(pointer));
|
2088
|
+
|
2089
|
+
return [ out, pointer ];
|
2090
|
+
}
|
2091
|
+
|
1656
2092
|
// store length as 0th array
|
1657
2093
|
out.push(
|
1658
2094
|
...number(0, Valtype.i32),
|
@@ -1660,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1660
2096
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1661
2097
|
);
|
1662
2098
|
|
1663
|
-
const storeOp =
|
1664
|
-
const valtype = itemTypeToValtype[itemType];
|
2099
|
+
const storeOp = StoreOps[itemType];
|
1665
2100
|
|
1666
2101
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1667
2102
|
if (elements[i] == null) continue;
|
@@ -1676,8 +2111,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1676
2111
|
// local value as pointer
|
1677
2112
|
out.push(...number(pointer));
|
1678
2113
|
|
1679
|
-
scope.memory = true;
|
1680
|
-
|
1681
2114
|
return [ out, pointer ];
|
1682
2115
|
};
|
1683
2116
|
|
@@ -1686,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
|
|
1686
2119
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1687
2120
|
};
|
1688
2121
|
|
2122
|
+
let varMetadata = new Map();
|
2123
|
+
const addVarMeta = (_name, obj) => {
|
2124
|
+
const name = _name ?? '$undeclared';
|
2125
|
+
if (!varMetadata.has(name)) varMetadata.set(name, {});
|
2126
|
+
|
2127
|
+
const meta = varMetadata.get(name);
|
2128
|
+
for (const k in obj) {
|
2129
|
+
meta[k] = obj[k];
|
2130
|
+
}
|
2131
|
+
};
|
2132
|
+
|
1689
2133
|
export const generateMember = (scope, decl, _global, _name) => {
|
1690
2134
|
const type = getNodeType(scope, decl.object);
|
1691
2135
|
|
@@ -1696,8 +2140,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1696
2140
|
const name = decl.object.name;
|
1697
2141
|
const pointer = arrays.get(name);
|
1698
2142
|
|
1699
|
-
scope.memory = true;
|
1700
|
-
|
1701
2143
|
const aotPointer = pointer != null;
|
1702
2144
|
|
1703
2145
|
return [
|
@@ -1717,8 +2159,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1717
2159
|
const name = decl.object.name;
|
1718
2160
|
const pointer = arrays.get(name);
|
1719
2161
|
|
1720
|
-
scope.memory = true;
|
1721
|
-
|
1722
2162
|
const aotPointer = pointer != null;
|
1723
2163
|
|
1724
2164
|
if (type === TYPES._array) {
|
@@ -1828,7 +2268,7 @@ const generateFunc = (scope, decl) => {
|
|
1828
2268
|
locals: {},
|
1829
2269
|
localInd: 0,
|
1830
2270
|
returns: [ valtypeBinary ],
|
1831
|
-
|
2271
|
+
returnType: null,
|
1832
2272
|
throws: false,
|
1833
2273
|
name
|
1834
2274
|
};
|
@@ -1854,7 +2294,6 @@ const generateFunc = (scope, decl) => {
|
|
1854
2294
|
returns: innerScope.returns,
|
1855
2295
|
returnType: innerScope.returnType,
|
1856
2296
|
locals: innerScope.locals,
|
1857
|
-
memory: innerScope.memory,
|
1858
2297
|
throws: innerScope.throws,
|
1859
2298
|
index: currentFuncIndex++
|
1860
2299
|
};
|
@@ -1869,6 +2308,8 @@ const generateFunc = (scope, decl) => {
|
|
1869
2308
|
|
1870
2309
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1871
2310
|
wasm.push(...number(0), [ Opcodes.return ]);
|
2311
|
+
|
2312
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1872
2313
|
}
|
1873
2314
|
|
1874
2315
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1879,9 +2320,7 @@ const generateFunc = (scope, decl) => {
|
|
1879
2320
|
if (local.type === Valtype.v128) {
|
1880
2321
|
vecParams++;
|
1881
2322
|
|
1882
|
-
/*
|
1883
|
-
|
1884
|
-
wasm.unshift( // add v128 load for param
|
2323
|
+
/* wasm.unshift( // add v128 load for param
|
1885
2324
|
[ Opcodes.i32_const, 0 ],
|
1886
2325
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1887
2326
|
[ Opcodes.local_set, local.idx ]
|
@@ -1992,10 +2431,10 @@ const generateFunc = (scope, decl) => {
|
|
1992
2431
|
};
|
1993
2432
|
|
1994
2433
|
const generateCode = (scope, decl) => {
|
1995
|
-
|
2434
|
+
let out = [];
|
1996
2435
|
|
1997
2436
|
for (const x of decl.body) {
|
1998
|
-
out.
|
2437
|
+
out = out.concat(generate(scope, x));
|
1999
2438
|
}
|
2000
2439
|
|
2001
2440
|
return out;
|
@@ -2031,6 +2470,18 @@ const internalConstrs = {
|
|
2031
2470
|
];
|
2032
2471
|
},
|
2033
2472
|
type: TYPES._array
|
2473
|
+
},
|
2474
|
+
|
2475
|
+
__Array_of: {
|
2476
|
+
// this is not a constructor but best fits internal structure here
|
2477
|
+
generate: (scope, decl, global, name) => {
|
2478
|
+
// Array.of(i0, i1, ...)
|
2479
|
+
return generateArray(scope, {
|
2480
|
+
elements: decl.arguments
|
2481
|
+
}, global, name);
|
2482
|
+
},
|
2483
|
+
type: TYPES._array,
|
2484
|
+
notConstr: true
|
2034
2485
|
}
|
2035
2486
|
};
|
2036
2487
|
|
@@ -2044,7 +2495,9 @@ export default program => {
|
|
2044
2495
|
depth = [];
|
2045
2496
|
typeStates = {};
|
2046
2497
|
arrays = new Map();
|
2498
|
+
varMetadata = new Map();
|
2047
2499
|
pages = new Map();
|
2500
|
+
data = [];
|
2048
2501
|
currentFuncIndex = importedFuncs.length;
|
2049
2502
|
|
2050
2503
|
globalThis.valtype = 'f64';
|
@@ -2121,5 +2574,5 @@ export default program => {
|
|
2121
2574
|
// if blank main func and other exports, remove it
|
2122
2575
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2123
2576
|
|
2124
|
-
return { funcs, globals, tags, exceptions, pages };
|
2577
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2125
2578
|
};
|