porffor 0.0.0-828ee15 → 0.0.0-a2afb57
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 +354 -0
- package/compiler/builtins.js +0 -1
- package/compiler/codeGen.js +699 -148
- 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 +12 -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 +61 -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
|
-
allocPage('asm instrinsic');
|
169
|
-
// todo: add to store/load offset insts
|
170
|
-
continue;
|
171
|
-
}
|
181
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
182
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
172
183
|
|
173
|
-
|
174
|
-
|
184
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
185
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
186
|
+
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
175
189
|
|
176
|
-
|
177
|
-
|
190
|
+
return out;
|
191
|
+
},
|
178
192
|
|
179
|
-
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
195
|
+
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
199
|
+
|
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,14 +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
|
-
const getLocalTmp = name => localTmp(scope, name + binaryExpDepth);
|
331
|
-
|
332
388
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
389
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
390
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
391
|
|
392
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
393
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
394
|
+
|
336
395
|
if (assign) {
|
337
396
|
const pointer = arrays.get(name ?? '$undeclared');
|
338
397
|
|
@@ -457,90 +516,259 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
457
516
|
];
|
458
517
|
};
|
459
518
|
|
460
|
-
const
|
519
|
+
const compareStrings = (scope, left, right) => {
|
520
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
521
|
+
// todo: convert left and right to strings if not
|
522
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
523
|
+
// todo: optimize this if using literals/known lengths?
|
524
|
+
|
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);
|
529
|
+
|
530
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
531
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
532
|
+
|
533
|
+
return [
|
534
|
+
// setup left
|
535
|
+
...left,
|
536
|
+
Opcodes.i32_to_u,
|
537
|
+
[ Opcodes.local_tee, leftPointer ],
|
538
|
+
|
539
|
+
// setup right
|
540
|
+
...right,
|
541
|
+
Opcodes.i32_to_u,
|
542
|
+
[ Opcodes.local_tee, rightPointer ],
|
543
|
+
|
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 ],
|
548
|
+
|
549
|
+
// get lengths
|
550
|
+
[ Opcodes.local_get, leftPointer ],
|
551
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
552
|
+
[ Opcodes.local_tee, leftLength ],
|
553
|
+
|
554
|
+
[ Opcodes.local_get, rightPointer ],
|
555
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
556
|
+
[ Opcodes.local_tee, rightLength ],
|
557
|
+
|
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 ],
|
564
|
+
|
565
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
566
|
+
|
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 ],
|
573
|
+
|
574
|
+
// iterate over each char and check if eq
|
575
|
+
[ Opcodes.loop, Blocktype.void ],
|
576
|
+
|
577
|
+
// fetch left
|
578
|
+
[ Opcodes.local_get, index ],
|
579
|
+
[ Opcodes.local_get, leftPointer ],
|
580
|
+
[ Opcodes.i32_add ],
|
581
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
582
|
+
|
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) ],
|
588
|
+
|
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 ],
|
595
|
+
|
596
|
+
// index += sizeof i16 (2)
|
597
|
+
[ Opcodes.local_get, index ],
|
598
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
599
|
+
[ Opcodes.i32_add ],
|
600
|
+
[ Opcodes.local_tee, index ],
|
601
|
+
|
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 ],
|
607
|
+
|
608
|
+
// no failed checks, so true!
|
609
|
+
...number(1, Valtype.i32),
|
610
|
+
|
611
|
+
// pointers match, so true
|
612
|
+
[ Opcodes.else ],
|
613
|
+
...number(1, Valtype.i32),
|
614
|
+
[ Opcodes.end ],
|
615
|
+
|
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
|
619
|
+
];
|
620
|
+
};
|
621
|
+
|
622
|
+
const truthy = (scope, wasm, type, int = false) => {
|
461
623
|
// arrays are always truthy
|
462
624
|
if (type === TYPES._array) return [
|
463
625
|
...wasm,
|
464
626
|
[ Opcodes.drop ],
|
465
|
-
number(
|
627
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
466
628
|
];
|
467
629
|
|
468
630
|
if (type === TYPES.string) {
|
469
|
-
// if "" (length = 0)
|
631
|
+
// if not "" (length = 0)
|
470
632
|
return [
|
471
633
|
// pointer
|
472
634
|
...wasm,
|
635
|
+
Opcodes.i32_to_u,
|
473
636
|
|
474
637
|
// get length
|
475
638
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
476
639
|
|
477
|
-
// if length
|
478
|
-
[ Opcodes.i32_eqz ],
|
479
|
-
Opcodes.
|
480
|
-
|
640
|
+
// if length != 0
|
641
|
+
/* [ Opcodes.i32_eqz ],
|
642
|
+
[ Opcodes.i32_eqz ], */
|
643
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
644
|
+
];
|
481
645
|
}
|
482
646
|
|
483
|
-
// if
|
647
|
+
// if != 0
|
484
648
|
return [
|
485
649
|
...wasm,
|
486
650
|
|
487
|
-
|
488
|
-
Opcodes.
|
651
|
+
/* Opcodes.eqz,
|
652
|
+
[ Opcodes.i32_eqz ],
|
653
|
+
Opcodes.i32_from */
|
489
654
|
];
|
490
655
|
};
|
491
656
|
|
492
|
-
const
|
657
|
+
const falsy = (scope, wasm, type, int = false) => {
|
493
658
|
// arrays are always truthy
|
494
659
|
if (type === TYPES._array) return [
|
495
660
|
...wasm,
|
496
661
|
[ Opcodes.drop ],
|
497
|
-
number(
|
662
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
498
663
|
];
|
499
664
|
|
500
665
|
if (type === TYPES.string) {
|
501
|
-
// if
|
666
|
+
// if "" (length = 0)
|
502
667
|
return [
|
503
668
|
// pointer
|
504
669
|
...wasm,
|
670
|
+
Opcodes.i32_to_u,
|
505
671
|
|
506
672
|
// get length
|
507
673
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
508
674
|
|
509
|
-
// if length
|
510
|
-
|
511
|
-
[ Opcodes.
|
512
|
-
Opcodes.i32_from_u
|
675
|
+
// if length == 0
|
676
|
+
[ Opcodes.i32_eqz ],
|
677
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
513
678
|
]
|
514
679
|
}
|
515
680
|
|
516
|
-
// if
|
681
|
+
// if = 0
|
517
682
|
return [
|
518
683
|
...wasm,
|
519
684
|
|
520
|
-
|
521
|
-
|
522
|
-
|
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)
|
523
708
|
];
|
524
709
|
};
|
525
710
|
|
526
711
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
527
712
|
if (op === '||' || op === '&&' || op === '??') {
|
528
|
-
return performLogicOp(scope, op, left, right);
|
713
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
714
|
+
}
|
715
|
+
|
716
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
717
|
+
|
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
|
+
|
734
|
+
return [
|
735
|
+
...left,
|
736
|
+
[ Opcodes.drop ],
|
737
|
+
|
738
|
+
...right,
|
739
|
+
[ Opcodes.drop ],
|
740
|
+
|
741
|
+
// return true (!=/!==) or false (else)
|
742
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
743
|
+
];
|
529
744
|
}
|
530
745
|
|
746
|
+
// todo: niche null hell with 0
|
747
|
+
|
531
748
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
532
749
|
if (op === '+') {
|
533
750
|
// string concat (a + b)
|
534
751
|
return concatStrings(scope, left, right, _global, _name, assign);
|
535
752
|
}
|
536
753
|
|
537
|
-
//
|
538
|
-
if (!
|
754
|
+
// not an equality op, NaN
|
755
|
+
if (!eqOp) return number(NaN);
|
539
756
|
|
540
757
|
// else leave bool ops
|
541
|
-
// todo: convert string to number if string and number
|
758
|
+
// todo: convert string to number if string and number/bool
|
542
759
|
// todo: string (>|>=|<|<=) string
|
543
|
-
|
760
|
+
|
761
|
+
// string comparison
|
762
|
+
if (op === '===' || op === '==') {
|
763
|
+
return compareStrings(scope, left, right);
|
764
|
+
}
|
765
|
+
|
766
|
+
if (op === '!==' || op === '!=') {
|
767
|
+
return [
|
768
|
+
...compareStrings(scope, left, right),
|
769
|
+
[ Opcodes.i32_eqz ]
|
770
|
+
];
|
771
|
+
}
|
544
772
|
}
|
545
773
|
|
546
774
|
let ops = operatorOpcode[valtype][op];
|
@@ -569,21 +797,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
569
797
|
];
|
570
798
|
};
|
571
799
|
|
572
|
-
let binaryExpDepth = 0;
|
573
800
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
574
|
-
|
575
|
-
|
576
|
-
const out = [
|
577
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
578
|
-
];
|
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);
|
579
802
|
|
580
803
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
581
804
|
|
582
|
-
binaryExpDepth--;
|
583
805
|
return out;
|
584
806
|
};
|
585
807
|
|
586
|
-
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 = [] }) => {
|
587
809
|
const existing = funcs.find(x => x.name === name);
|
588
810
|
if (existing) return existing;
|
589
811
|
|
@@ -619,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
619
841
|
returns,
|
620
842
|
returnType: TYPES[returnType ?? 'number'],
|
621
843
|
wasm,
|
622
|
-
memory,
|
623
844
|
internal: true,
|
624
845
|
index: currentFuncIndex++
|
625
846
|
};
|
@@ -638,7 +859,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
638
859
|
};
|
639
860
|
|
640
861
|
const generateLogicExp = (scope, decl) => {
|
641
|
-
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));
|
642
863
|
};
|
643
864
|
|
644
865
|
const TYPES = {
|
@@ -652,7 +873,8 @@ const TYPES = {
|
|
652
873
|
bigint: 0xffffffffffff7,
|
653
874
|
|
654
875
|
// these are not "typeof" types but tracked internally
|
655
|
-
_array:
|
876
|
+
_array: 0xfffffffffff0f,
|
877
|
+
_regexp: 0xfffffffffff1f
|
656
878
|
};
|
657
879
|
|
658
880
|
const TYPE_NAMES = {
|
@@ -686,6 +908,9 @@ const getType = (scope, _name) => {
|
|
686
908
|
|
687
909
|
const getNodeType = (scope, node) => {
|
688
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
|
+
|
689
914
|
return TYPES[typeof node.value];
|
690
915
|
}
|
691
916
|
|
@@ -700,7 +925,7 @@ const getNodeType = (scope, node) => {
|
|
700
925
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
701
926
|
const name = node.callee.name;
|
702
927
|
const func = funcs.find(x => x.name === name);
|
703
|
-
if (func) return func.returnType
|
928
|
+
if (func) return func.returnType;
|
704
929
|
|
705
930
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
706
931
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -719,15 +944,18 @@ const getNodeType = (scope, node) => {
|
|
719
944
|
|
720
945
|
// literal.func()
|
721
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
|
+
|
722
952
|
const baseType = getNodeType(scope, node.callee.object);
|
723
953
|
|
724
954
|
const func = node.callee.property.name;
|
725
955
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
726
956
|
}
|
727
957
|
|
728
|
-
if (protoFunc) return protoFunc.returnType
|
729
|
-
|
730
|
-
return TYPES.number;
|
958
|
+
if (protoFunc) return protoFunc.returnType;
|
731
959
|
}
|
732
960
|
|
733
961
|
if (node.type === 'ExpressionStatement') {
|
@@ -759,14 +987,16 @@ const getNodeType = (scope, node) => {
|
|
759
987
|
|
760
988
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
761
989
|
}
|
762
|
-
|
763
|
-
// default to number
|
764
|
-
return TYPES.number;
|
765
990
|
};
|
766
991
|
|
767
992
|
const generateLiteral = (scope, decl, global, name) => {
|
768
993
|
if (decl.value === null) return number(NULL);
|
769
994
|
|
995
|
+
if (decl.regex) {
|
996
|
+
scope.regex[name] = decl.regex;
|
997
|
+
return number(1);
|
998
|
+
}
|
999
|
+
|
770
1000
|
switch (typeof decl.value) {
|
771
1001
|
case 'number':
|
772
1002
|
return number(decl.value);
|
@@ -788,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
788
1018
|
case 'bigint': return number(TYPES.bigint);
|
789
1019
|
}
|
790
1020
|
|
1021
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1022
|
+
let wellFormed = aotWFA ? true : undefined;
|
1023
|
+
|
791
1024
|
const str = decl.value;
|
792
1025
|
const rawElements = new Array(str.length);
|
1026
|
+
let j = 0;
|
793
1027
|
for (let i = 0; i < str.length; i++) {
|
794
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
|
+
}
|
795
1047
|
}
|
796
1048
|
|
1049
|
+
// console.log(wellFormed, str);
|
1050
|
+
|
1051
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1052
|
+
|
797
1053
|
return makeArray(scope, {
|
798
1054
|
rawElements
|
799
1055
|
}, global, name, false, 'i16')[0];
|
@@ -806,7 +1062,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
806
1062
|
const countLeftover = wasm => {
|
807
1063
|
let count = 0, depth = 0;
|
808
1064
|
|
809
|
-
for (
|
1065
|
+
for (let i = 0; i < wasm.length; i++) {
|
1066
|
+
const inst = wasm[i];
|
810
1067
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
811
1068
|
if (inst[0] === Opcodes.if) count--;
|
812
1069
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -827,6 +1084,8 @@ const countLeftover = wasm => {
|
|
827
1084
|
} else count--;
|
828
1085
|
if (func) count += func.returns.length;
|
829
1086
|
} else count--;
|
1087
|
+
|
1088
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
830
1089
|
}
|
831
1090
|
|
832
1091
|
return count;
|
@@ -910,7 +1169,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
910
1169
|
}
|
911
1170
|
|
912
1171
|
let out = [];
|
913
|
-
let protoFunc, protoName, baseType, baseName
|
1172
|
+
let protoFunc, protoName, baseType, baseName;
|
914
1173
|
// ident.func()
|
915
1174
|
if (name && name.startsWith('__')) {
|
916
1175
|
const spl = name.slice(2).split('_');
|
@@ -925,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
925
1184
|
|
926
1185
|
// literal.func()
|
927
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
|
+
|
928
1206
|
baseType = getNodeType(scope, decl.callee.object);
|
929
1207
|
|
930
1208
|
const func = decl.callee.property.name;
|
@@ -933,11 +1211,36 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
933
1211
|
|
934
1212
|
out = generate(scope, decl.callee.object);
|
935
1213
|
out.push([ Opcodes.drop ]);
|
1214
|
+
|
1215
|
+
baseName = [...arrays.keys()].pop();
|
936
1216
|
}
|
937
1217
|
|
938
|
-
if (
|
939
|
-
|
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);
|
940
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
|
+
]),
|
1236
|
+
|
1237
|
+
// call regex func
|
1238
|
+
[ Opcodes.call, func.index ],
|
1239
|
+
Opcodes.i32_from
|
1240
|
+
];
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
if (protoFunc) {
|
941
1244
|
let pointer = arrays.get(baseName);
|
942
1245
|
|
943
1246
|
if (pointer == null) {
|
@@ -945,7 +1248,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
945
1248
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
946
1249
|
|
947
1250
|
// register array
|
948
|
-
|
1251
|
+
0, [ , pointer ] = makeArray(scope, {
|
949
1252
|
rawElements: new Array(0)
|
950
1253
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
951
1254
|
|
@@ -965,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
965
1268
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
966
1269
|
|
967
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;
|
968
1272
|
|
969
1273
|
// use local for cached i32 length as commonly used
|
970
1274
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
971
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
|
+
|
972
1294
|
return [
|
973
1295
|
...out,
|
974
1296
|
|
975
|
-
...
|
976
|
-
|
1297
|
+
...(!lengthI32CacheUsed ? [] : [
|
1298
|
+
...arrayUtil.getLengthI32(pointer),
|
1299
|
+
[ Opcodes.local_set, lengthLocal ],
|
1300
|
+
]),
|
977
1301
|
|
978
1302
|
[ Opcodes.block, valtypeBinary ],
|
979
|
-
...
|
980
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
981
|
-
get: arrayUtil.getLength(pointer),
|
982
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
983
|
-
set: value => arrayUtil.setLength(pointer, value),
|
984
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
985
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
986
|
-
return makeArray(scope, {
|
987
|
-
rawElements: new Array(length)
|
988
|
-
}, _global, _name, true, itemType);
|
989
|
-
}),
|
1303
|
+
...protoOut,
|
990
1304
|
[ Opcodes.end ]
|
991
1305
|
];
|
992
1306
|
}
|
@@ -1043,11 +1357,10 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1043
1357
|
args = args.slice(0, func.params.length);
|
1044
1358
|
}
|
1045
1359
|
|
1046
|
-
if (func && func.memory) scope.memory = true;
|
1047
1360
|
if (func && func.throws) scope.throws = true;
|
1048
1361
|
|
1049
1362
|
for (const arg of args) {
|
1050
|
-
out.
|
1363
|
+
out = out.concat(generate(scope, arg));
|
1051
1364
|
}
|
1052
1365
|
|
1053
1366
|
out.push([ Opcodes.call, idx ]);
|
@@ -1058,8 +1371,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1058
1371
|
const generateNew = (scope, decl, _global, _name) => {
|
1059
1372
|
// hack: basically treat this as a normal call for builtins for now
|
1060
1373
|
const name = mapName(decl.callee.name);
|
1061
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1062
|
-
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)})`);
|
1063
1376
|
|
1064
1377
|
return generateCall(scope, decl, _global, _name);
|
1065
1378
|
};
|
@@ -1076,12 +1389,12 @@ const unhackName = name => {
|
|
1076
1389
|
};
|
1077
1390
|
|
1078
1391
|
const generateVar = (scope, decl) => {
|
1079
|
-
|
1392
|
+
let out = [];
|
1080
1393
|
|
1081
1394
|
const topLevel = scope.name === 'main';
|
1082
1395
|
|
1083
1396
|
// global variable if in top scope (main) and var ..., or if wanted
|
1084
|
-
const global = decl.kind === 'var';
|
1397
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1085
1398
|
const target = global ? globals : scope.locals;
|
1086
1399
|
|
1087
1400
|
for (const x of decl.declarations) {
|
@@ -1118,7 +1431,7 @@ const generateVar = (scope, decl) => {
|
|
1118
1431
|
|
1119
1432
|
// x.init ??= DEFAULT_VALUE;
|
1120
1433
|
if (x.init) {
|
1121
|
-
out.
|
1434
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1122
1435
|
|
1123
1436
|
// if our value is the result of a function, infer the type from that func's return value
|
1124
1437
|
if (out[out.length - 1][0] === Opcodes.call) {
|
@@ -1167,8 +1480,6 @@ const generateAssign = (scope, decl) => {
|
|
1167
1480
|
const name = decl.left.object.name;
|
1168
1481
|
const pointer = arrays.get(name);
|
1169
1482
|
|
1170
|
-
scope.memory = true;
|
1171
|
-
|
1172
1483
|
const aotPointer = pointer != null;
|
1173
1484
|
|
1174
1485
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1189,13 +1500,60 @@ const generateAssign = (scope, decl) => {
|
|
1189
1500
|
];
|
1190
1501
|
}
|
1191
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
|
+
|
1192
1550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1193
1551
|
|
1194
1552
|
if (local === undefined) {
|
1195
|
-
// todo: this should be a
|
1553
|
+
// todo: this should be a sloppy mode only thing
|
1196
1554
|
|
1197
1555
|
// only allow = for this
|
1198
|
-
if (
|
1556
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1199
1557
|
|
1200
1558
|
if (builtinVars[name]) {
|
1201
1559
|
// just return rhs (eg `NaN = 2`)
|
@@ -1204,13 +1562,15 @@ const generateAssign = (scope, decl) => {
|
|
1204
1562
|
|
1205
1563
|
// set global and return (eg a = 2)
|
1206
1564
|
return [
|
1207
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1565
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1208
1566
|
[ Opcodes.global_get, globals[name].idx ]
|
1209
1567
|
];
|
1210
1568
|
}
|
1211
1569
|
|
1212
|
-
|
1213
|
-
|
1570
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1571
|
+
|
1572
|
+
if (op === '=') {
|
1573
|
+
// typeStates[name] = getNodeType(scope, decl.right);
|
1214
1574
|
|
1215
1575
|
return [
|
1216
1576
|
...generate(scope, decl.right, isGlobal, name),
|
@@ -1219,8 +1579,26 @@ const generateAssign = (scope, decl) => {
|
|
1219
1579
|
];
|
1220
1580
|
}
|
1221
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
|
+
|
1222
1600
|
return [
|
1223
|
-
...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),
|
1224
1602
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1225
1603
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1226
1604
|
];
|
@@ -1247,13 +1625,14 @@ const generateUnary = (scope, decl) => {
|
|
1247
1625
|
|
1248
1626
|
case '!':
|
1249
1627
|
// !=
|
1250
|
-
return falsy(scope, generate(scope, decl.argument));
|
1628
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1251
1629
|
|
1252
1630
|
case '~':
|
1631
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1253
1632
|
return [
|
1254
1633
|
...generate(scope, decl.argument),
|
1255
1634
|
Opcodes.i32_to,
|
1256
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1635
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1257
1636
|
[ Opcodes.i32_xor ],
|
1258
1637
|
Opcodes.i32_from
|
1259
1638
|
];
|
@@ -1291,7 +1670,7 @@ const generateUnary = (scope, decl) => {
|
|
1291
1670
|
return out;
|
1292
1671
|
|
1293
1672
|
case 'typeof':
|
1294
|
-
const type = getNodeType(scope, decl.argument);
|
1673
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1295
1674
|
|
1296
1675
|
// for custom types, just return object
|
1297
1676
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1334,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
|
|
1334
1713
|
};
|
1335
1714
|
|
1336
1715
|
const generateIf = (scope, decl) => {
|
1337
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1716
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
|
1338
1717
|
|
1339
1718
|
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1340
1719
|
depth.push('if');
|
@@ -1424,9 +1803,116 @@ const generateWhile = (scope, decl) => {
|
|
1424
1803
|
return out;
|
1425
1804
|
};
|
1426
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
|
+
|
1427
1913
|
const getNearestLoop = () => {
|
1428
1914
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1429
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1915
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1430
1916
|
}
|
1431
1917
|
|
1432
1918
|
return -1;
|
@@ -1510,13 +1996,22 @@ const generateAssignPat = (scope, decl) => {
|
|
1510
1996
|
};
|
1511
1997
|
|
1512
1998
|
let pages = new Map();
|
1513
|
-
const allocPage = reason => {
|
1514
|
-
if (pages.has(reason)) return pages.get(reason);
|
1999
|
+
const allocPage = (reason, type) => {
|
2000
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
2001
|
+
|
2002
|
+
const ind = pages.size;
|
2003
|
+
pages.set(reason, { ind, type });
|
2004
|
+
|
2005
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2006
|
+
|
2007
|
+
return ind;
|
2008
|
+
};
|
1515
2009
|
|
1516
|
-
|
1517
|
-
pages.
|
2010
|
+
const freePage = reason => {
|
2011
|
+
const { ind } = pages.get(reason);
|
2012
|
+
pages.delete(reason);
|
1518
2013
|
|
1519
|
-
if (
|
2014
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1520
2015
|
|
1521
2016
|
return ind;
|
1522
2017
|
};
|
@@ -1530,7 +2025,7 @@ const itemTypeToValtype = {
|
|
1530
2025
|
i16: 'i32'
|
1531
2026
|
};
|
1532
2027
|
|
1533
|
-
const
|
2028
|
+
const StoreOps = {
|
1534
2029
|
i32: Opcodes.i32_store,
|
1535
2030
|
i64: Opcodes.i64_store,
|
1536
2031
|
f64: Opcodes.f64_store,
|
@@ -1539,13 +2034,31 @@ const storeOps = {
|
|
1539
2034
|
i16: Opcodes.i32_store16
|
1540
2035
|
};
|
1541
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
|
+
|
1542
2052
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1543
2053
|
const out = [];
|
1544
2054
|
|
2055
|
+
let firstAssign = false;
|
1545
2056
|
if (!arrays.has(name) || name === '$undeclared') {
|
2057
|
+
firstAssign = true;
|
2058
|
+
|
1546
2059
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1547
2060
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1548
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2061
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1549
2062
|
}
|
1550
2063
|
|
1551
2064
|
const pointer = arrays.get(name);
|
@@ -1553,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1553
2066
|
const useRawElements = !!decl.rawElements;
|
1554
2067
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1555
2068
|
|
2069
|
+
const valtype = itemTypeToValtype[itemType];
|
1556
2070
|
const length = elements.length;
|
1557
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
|
+
|
1558
2092
|
// store length as 0th array
|
1559
2093
|
out.push(
|
1560
2094
|
...number(0, Valtype.i32),
|
@@ -1562,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1562
2096
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1563
2097
|
);
|
1564
2098
|
|
1565
|
-
const storeOp =
|
1566
|
-
const valtype = itemTypeToValtype[itemType];
|
2099
|
+
const storeOp = StoreOps[itemType];
|
1567
2100
|
|
1568
2101
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1569
2102
|
if (elements[i] == null) continue;
|
@@ -1578,8 +2111,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1578
2111
|
// local value as pointer
|
1579
2112
|
out.push(...number(pointer));
|
1580
2113
|
|
1581
|
-
scope.memory = true;
|
1582
|
-
|
1583
2114
|
return [ out, pointer ];
|
1584
2115
|
};
|
1585
2116
|
|
@@ -1588,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
|
|
1588
2119
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1589
2120
|
};
|
1590
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
|
+
|
1591
2133
|
export const generateMember = (scope, decl, _global, _name) => {
|
1592
2134
|
const type = getNodeType(scope, decl.object);
|
1593
2135
|
|
@@ -1598,8 +2140,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1598
2140
|
const name = decl.object.name;
|
1599
2141
|
const pointer = arrays.get(name);
|
1600
2142
|
|
1601
|
-
scope.memory = true;
|
1602
|
-
|
1603
2143
|
const aotPointer = pointer != null;
|
1604
2144
|
|
1605
2145
|
return [
|
@@ -1619,8 +2159,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1619
2159
|
const name = decl.object.name;
|
1620
2160
|
const pointer = arrays.get(name);
|
1621
2161
|
|
1622
|
-
scope.memory = true;
|
1623
|
-
|
1624
2162
|
const aotPointer = pointer != null;
|
1625
2163
|
|
1626
2164
|
if (type === TYPES._array) {
|
@@ -1730,7 +2268,7 @@ const generateFunc = (scope, decl) => {
|
|
1730
2268
|
locals: {},
|
1731
2269
|
localInd: 0,
|
1732
2270
|
returns: [ valtypeBinary ],
|
1733
|
-
|
2271
|
+
returnType: null,
|
1734
2272
|
throws: false,
|
1735
2273
|
name
|
1736
2274
|
};
|
@@ -1754,9 +2292,8 @@ const generateFunc = (scope, decl) => {
|
|
1754
2292
|
name,
|
1755
2293
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1756
2294
|
returns: innerScope.returns,
|
1757
|
-
returnType: innerScope.returnType
|
2295
|
+
returnType: innerScope.returnType,
|
1758
2296
|
locals: innerScope.locals,
|
1759
|
-
memory: innerScope.memory,
|
1760
2297
|
throws: innerScope.throws,
|
1761
2298
|
index: currentFuncIndex++
|
1762
2299
|
};
|
@@ -1771,6 +2308,8 @@ const generateFunc = (scope, decl) => {
|
|
1771
2308
|
|
1772
2309
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1773
2310
|
wasm.push(...number(0), [ Opcodes.return ]);
|
2311
|
+
|
2312
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1774
2313
|
}
|
1775
2314
|
|
1776
2315
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1781,9 +2320,7 @@ const generateFunc = (scope, decl) => {
|
|
1781
2320
|
if (local.type === Valtype.v128) {
|
1782
2321
|
vecParams++;
|
1783
2322
|
|
1784
|
-
/*
|
1785
|
-
|
1786
|
-
wasm.unshift( // add v128 load for param
|
2323
|
+
/* wasm.unshift( // add v128 load for param
|
1787
2324
|
[ Opcodes.i32_const, 0 ],
|
1788
2325
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1789
2326
|
[ Opcodes.local_set, local.idx ]
|
@@ -1894,10 +2431,10 @@ const generateFunc = (scope, decl) => {
|
|
1894
2431
|
};
|
1895
2432
|
|
1896
2433
|
const generateCode = (scope, decl) => {
|
1897
|
-
|
2434
|
+
let out = [];
|
1898
2435
|
|
1899
2436
|
for (const x of decl.body) {
|
1900
|
-
out.
|
2437
|
+
out = out.concat(generate(scope, x));
|
1901
2438
|
}
|
1902
2439
|
|
1903
2440
|
return out;
|
@@ -1933,6 +2470,18 @@ const internalConstrs = {
|
|
1933
2470
|
];
|
1934
2471
|
},
|
1935
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
|
1936
2485
|
}
|
1937
2486
|
};
|
1938
2487
|
|
@@ -1946,7 +2495,9 @@ export default program => {
|
|
1946
2495
|
depth = [];
|
1947
2496
|
typeStates = {};
|
1948
2497
|
arrays = new Map();
|
2498
|
+
varMetadata = new Map();
|
1949
2499
|
pages = new Map();
|
2500
|
+
data = [];
|
1950
2501
|
currentFuncIndex = importedFuncs.length;
|
1951
2502
|
|
1952
2503
|
globalThis.valtype = 'f64';
|
@@ -2023,5 +2574,5 @@ export default program => {
|
|
2023
2574
|
// if blank main func and other exports, remove it
|
2024
2575
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2025
2576
|
|
2026
|
-
return { funcs, globals, tags, exceptions, pages };
|
2577
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2027
2578
|
};
|