porffor 0.0.0-650350 → 0.0.0-679c4ea
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 +631 -151
- 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 +94 -33
- package/compiler/sections.js +45 -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 +7 -12
- 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,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
326
|
return idx;
|
296
327
|
};
|
297
328
|
|
298
|
-
const performLogicOp = (scope, op, left, right) => {
|
329
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
330
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
331
|
+
'||': falsy,
|
332
|
+
'&&': truthy,
|
333
|
+
'??': nullish
|
303
334
|
};
|
304
335
|
|
305
336
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -310,7 +341,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
310
341
|
return [
|
311
342
|
...left,
|
312
343
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
344
|
+
...checks[op](scope, [], leftType),
|
345
|
+
Opcodes.i32_to,
|
314
346
|
[ Opcodes.if, valtypeBinary ],
|
315
347
|
...right,
|
316
348
|
[ Opcodes.else ],
|
@@ -325,15 +357,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
357
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
358
|
// todo: optimize this if using literals/known lengths?
|
327
359
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
360
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
361
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
362
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
363
|
|
336
364
|
if (assign) {
|
365
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
366
|
+
|
337
367
|
return [
|
338
368
|
// setup right
|
339
369
|
...right,
|
@@ -384,15 +414,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
414
|
|
385
415
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
416
|
|
387
|
-
|
417
|
+
// alloc/assign array
|
418
|
+
const [ , pointer ] = makeArray(scope, {
|
388
419
|
rawElements: new Array(0)
|
389
420
|
}, global, name, true, 'i16');
|
390
421
|
|
391
422
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
423
|
// setup left
|
397
424
|
...left,
|
398
425
|
Opcodes.i32_to_u,
|
@@ -458,89 +485,261 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
485
|
];
|
459
486
|
};
|
460
487
|
|
461
|
-
const
|
488
|
+
const compareStrings = (scope, left, right) => {
|
489
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
490
|
+
// todo: convert left and right to strings if not
|
491
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
492
|
+
// todo: optimize this if using literals/known lengths?
|
493
|
+
|
494
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
495
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
496
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
497
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
498
|
+
|
499
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
500
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
501
|
+
|
502
|
+
return [
|
503
|
+
// setup left
|
504
|
+
...left,
|
505
|
+
Opcodes.i32_to_u,
|
506
|
+
[ Opcodes.local_tee, leftPointer ],
|
507
|
+
|
508
|
+
// setup right
|
509
|
+
...right,
|
510
|
+
Opcodes.i32_to_u,
|
511
|
+
[ Opcodes.local_tee, rightPointer ],
|
512
|
+
|
513
|
+
// fast path: check leftPointer == rightPointer
|
514
|
+
// use if (block) for everything after to "return" a value early
|
515
|
+
[ Opcodes.i32_ne ],
|
516
|
+
[ Opcodes.if, Valtype.i32 ],
|
517
|
+
|
518
|
+
// get lengths
|
519
|
+
[ Opcodes.local_get, leftPointer ],
|
520
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
521
|
+
[ Opcodes.local_tee, leftLength ],
|
522
|
+
|
523
|
+
[ Opcodes.local_get, rightPointer ],
|
524
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
525
|
+
[ Opcodes.local_tee, rightLength ],
|
526
|
+
|
527
|
+
// fast path: check leftLength != rightLength
|
528
|
+
[ Opcodes.i32_ne ],
|
529
|
+
[ Opcodes.if, Blocktype.void ],
|
530
|
+
...number(0, Valtype.i32),
|
531
|
+
[ Opcodes.br, 1 ],
|
532
|
+
[ Opcodes.end ],
|
533
|
+
|
534
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
535
|
+
|
536
|
+
// setup index end as length * sizeof i16 (2)
|
537
|
+
// we do this instead of having to do mul/div each iter for perf™
|
538
|
+
[ Opcodes.local_get, leftLength ],
|
539
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
540
|
+
[ Opcodes.i32_mul ],
|
541
|
+
[ Opcodes.local_set, indexEnd ],
|
542
|
+
|
543
|
+
// iterate over each char and check if eq
|
544
|
+
[ Opcodes.loop, Blocktype.void ],
|
545
|
+
|
546
|
+
// fetch left
|
547
|
+
[ Opcodes.local_get, index ],
|
548
|
+
[ Opcodes.local_get, leftPointer ],
|
549
|
+
[ Opcodes.i32_add ],
|
550
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
551
|
+
|
552
|
+
// fetch right
|
553
|
+
[ Opcodes.local_get, index ],
|
554
|
+
[ Opcodes.local_get, rightPointer ],
|
555
|
+
[ Opcodes.i32_add ],
|
556
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
557
|
+
|
558
|
+
// not equal, "return" false
|
559
|
+
[ Opcodes.i32_ne ],
|
560
|
+
[ Opcodes.if, Blocktype.void ],
|
561
|
+
...number(0, Valtype.i32),
|
562
|
+
[ Opcodes.br, 2 ],
|
563
|
+
[ Opcodes.end ],
|
564
|
+
|
565
|
+
// index += sizeof i16 (2)
|
566
|
+
[ Opcodes.local_get, index ],
|
567
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
568
|
+
[ Opcodes.i32_add ],
|
569
|
+
[ Opcodes.local_tee, index ],
|
570
|
+
|
571
|
+
// if index != index end (length * sizeof 16), loop
|
572
|
+
[ Opcodes.local_get, indexEnd ],
|
573
|
+
[ Opcodes.i32_ne ],
|
574
|
+
[ Opcodes.br_if, 0 ],
|
575
|
+
[ Opcodes.end ],
|
576
|
+
|
577
|
+
// no failed checks, so true!
|
578
|
+
...number(1, Valtype.i32),
|
579
|
+
|
580
|
+
// pointers match, so true
|
581
|
+
[ Opcodes.else ],
|
582
|
+
...number(1, Valtype.i32),
|
583
|
+
[ Opcodes.end ],
|
584
|
+
|
585
|
+
// convert i32 result to valtype
|
586
|
+
// do not do as automatically added by binary exp gen for equality ops
|
587
|
+
// Opcodes.i32_from_u
|
588
|
+
];
|
589
|
+
};
|
590
|
+
|
591
|
+
const truthy = (scope, wasm, type) => {
|
462
592
|
// arrays are always truthy
|
463
593
|
if (type === TYPES._array) return [
|
464
594
|
...wasm,
|
465
595
|
[ Opcodes.drop ],
|
466
|
-
number(
|
596
|
+
...number(1)
|
467
597
|
];
|
468
598
|
|
469
599
|
if (type === TYPES.string) {
|
470
|
-
// if "" (length = 0)
|
600
|
+
// if not "" (length = 0)
|
471
601
|
return [
|
472
602
|
// pointer
|
473
603
|
...wasm,
|
604
|
+
Opcodes.i32_to_u,
|
474
605
|
|
475
606
|
// get length
|
476
607
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
477
608
|
|
478
|
-
// if length
|
479
|
-
[ Opcodes.i32_eqz ],
|
609
|
+
// if length != 0
|
610
|
+
/* [ Opcodes.i32_eqz ],
|
611
|
+
[ Opcodes.i32_eqz ], */
|
480
612
|
Opcodes.i32_from_u
|
481
613
|
]
|
482
614
|
}
|
483
615
|
|
484
|
-
// if
|
616
|
+
// if != 0
|
485
617
|
return [
|
486
618
|
...wasm,
|
487
619
|
|
488
|
-
|
489
|
-
Opcodes.
|
620
|
+
/* Opcodes.eqz,
|
621
|
+
[ Opcodes.i32_eqz ],
|
622
|
+
Opcodes.i32_from */
|
490
623
|
];
|
491
624
|
};
|
492
625
|
|
493
|
-
const
|
626
|
+
const falsy = (scope, wasm, type) => {
|
494
627
|
// arrays are always truthy
|
495
628
|
if (type === TYPES._array) return [
|
496
629
|
...wasm,
|
497
630
|
[ Opcodes.drop ],
|
498
|
-
number(
|
631
|
+
...number(0)
|
499
632
|
];
|
500
633
|
|
501
634
|
if (type === TYPES.string) {
|
502
|
-
// if
|
635
|
+
// if "" (length = 0)
|
503
636
|
return [
|
504
637
|
// pointer
|
505
638
|
...wasm,
|
639
|
+
Opcodes.i32_to_u,
|
506
640
|
|
507
641
|
// get length
|
508
642
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
509
643
|
|
510
|
-
// if length
|
511
|
-
|
512
|
-
[ Opcodes.i32_eqz ], */
|
644
|
+
// if length == 0
|
645
|
+
[ Opcodes.i32_eqz ],
|
513
646
|
Opcodes.i32_from_u
|
514
647
|
]
|
515
648
|
}
|
516
649
|
|
517
|
-
// if
|
650
|
+
// if = 0
|
518
651
|
return [
|
519
652
|
...wasm,
|
520
653
|
|
521
|
-
|
522
|
-
|
523
|
-
Opcodes.i32_from */
|
654
|
+
...Opcodes.eqz,
|
655
|
+
Opcodes.i32_from_u
|
524
656
|
];
|
525
657
|
};
|
526
658
|
|
527
|
-
const
|
659
|
+
const nullish = (scope, wasm, type) => {
|
660
|
+
// undefined
|
661
|
+
if (type === TYPES.undefined) return [
|
662
|
+
...wasm,
|
663
|
+
[ Opcodes.drop ],
|
664
|
+
...number(1)
|
665
|
+
];
|
666
|
+
|
667
|
+
// null (if object and = "0")
|
668
|
+
if (type === TYPES.object) return [
|
669
|
+
...wasm,
|
670
|
+
...Opcodes.eqz,
|
671
|
+
Opcodes.i32_from_u
|
672
|
+
];
|
673
|
+
|
674
|
+
// not
|
675
|
+
return [
|
676
|
+
...wasm,
|
677
|
+
[ Opcodes.drop ],
|
678
|
+
...number(0)
|
679
|
+
];
|
680
|
+
};
|
681
|
+
|
682
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
683
|
if (op === '||' || op === '&&' || op === '??') {
|
529
|
-
return performLogicOp(scope, op, left, right);
|
684
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
685
|
+
}
|
686
|
+
|
687
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
688
|
+
|
689
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
690
|
+
|
691
|
+
if (leftType && rightType && (
|
692
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
693
|
+
((op === '===' || op === '!==') && leftType !== rightType) ||
|
694
|
+
|
695
|
+
// if equality op and an operand is undefined, return false
|
696
|
+
(eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
|
697
|
+
)) {
|
698
|
+
// undefined == null
|
699
|
+
if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
|
700
|
+
...(leftType === TYPES.object ? left : right),
|
701
|
+
...Opcodes.eqz,
|
702
|
+
...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
|
703
|
+
];
|
704
|
+
|
705
|
+
return [
|
706
|
+
...left,
|
707
|
+
[ Opcodes.drop ],
|
708
|
+
|
709
|
+
...right,
|
710
|
+
[ Opcodes.drop ],
|
711
|
+
|
712
|
+
// return true (!=/!==) or false (else)
|
713
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
714
|
+
];
|
530
715
|
}
|
531
716
|
|
717
|
+
// todo: niche null hell with 0
|
718
|
+
|
532
719
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
533
720
|
if (op === '+') {
|
534
721
|
// string concat (a + b)
|
535
722
|
return concatStrings(scope, left, right, _global, _name, assign);
|
536
723
|
}
|
537
724
|
|
538
|
-
//
|
539
|
-
if (!
|
725
|
+
// not an equality op, NaN
|
726
|
+
if (!eqOp) return number(NaN);
|
540
727
|
|
541
728
|
// else leave bool ops
|
542
|
-
// todo: convert string to number if string and number
|
543
|
-
// todo: string
|
729
|
+
// todo: convert string to number if string and number/bool
|
730
|
+
// todo: string (>|>=|<|<=) string
|
731
|
+
|
732
|
+
// string comparison
|
733
|
+
if (op === '===' || op === '==') {
|
734
|
+
return compareStrings(scope, left, right);
|
735
|
+
}
|
736
|
+
|
737
|
+
if (op === '!==' || op === '!=') {
|
738
|
+
return [
|
739
|
+
...compareStrings(scope, left, right),
|
740
|
+
[ Opcodes.i32_eqz ]
|
741
|
+
];
|
742
|
+
}
|
544
743
|
}
|
545
744
|
|
546
745
|
let ops = operatorOpcode[valtype][op];
|
@@ -570,16 +769,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
570
769
|
};
|
571
770
|
|
572
771
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
573
|
-
const out =
|
574
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
|
-
];
|
772
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
576
773
|
|
577
774
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
775
|
|
579
776
|
return out;
|
580
777
|
};
|
581
778
|
|
582
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
779
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
583
780
|
const existing = funcs.find(x => x.name === name);
|
584
781
|
if (existing) return existing;
|
585
782
|
|
@@ -615,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
615
812
|
returns,
|
616
813
|
returnType: TYPES[returnType ?? 'number'],
|
617
814
|
wasm,
|
618
|
-
memory,
|
619
815
|
internal: true,
|
620
816
|
index: currentFuncIndex++
|
621
817
|
};
|
@@ -634,7 +830,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
634
830
|
};
|
635
831
|
|
636
832
|
const generateLogicExp = (scope, decl) => {
|
637
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
833
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
638
834
|
};
|
639
835
|
|
640
836
|
const TYPES = {
|
@@ -648,7 +844,8 @@ const TYPES = {
|
|
648
844
|
bigint: 0xffffffffffff7,
|
649
845
|
|
650
846
|
// these are not "typeof" types but tracked internally
|
651
|
-
_array:
|
847
|
+
_array: 0xfffffffffff0f,
|
848
|
+
_regexp: 0xfffffffffff1f
|
652
849
|
};
|
653
850
|
|
654
851
|
const TYPE_NAMES = {
|
@@ -682,6 +879,9 @@ const getType = (scope, _name) => {
|
|
682
879
|
|
683
880
|
const getNodeType = (scope, node) => {
|
684
881
|
if (node.type === 'Literal') {
|
882
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
883
|
+
if (node.regex) return TYPES._regexp;
|
884
|
+
|
685
885
|
return TYPES[typeof node.value];
|
686
886
|
}
|
687
887
|
|
@@ -696,7 +896,7 @@ const getNodeType = (scope, node) => {
|
|
696
896
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
697
897
|
const name = node.callee.name;
|
698
898
|
const func = funcs.find(x => x.name === name);
|
699
|
-
if (func) return func.returnType
|
899
|
+
if (func) return func.returnType;
|
700
900
|
|
701
901
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
702
902
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -715,15 +915,18 @@ const getNodeType = (scope, node) => {
|
|
715
915
|
|
716
916
|
// literal.func()
|
717
917
|
if (!name && node.callee.type === 'MemberExpression') {
|
918
|
+
if (node.callee.object.regex) {
|
919
|
+
const funcName = node.callee.property.name;
|
920
|
+
return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
921
|
+
}
|
922
|
+
|
718
923
|
const baseType = getNodeType(scope, node.callee.object);
|
719
924
|
|
720
925
|
const func = node.callee.property.name;
|
721
926
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
722
927
|
}
|
723
928
|
|
724
|
-
if (protoFunc) return protoFunc.returnType
|
725
|
-
|
726
|
-
return TYPES.number;
|
929
|
+
if (protoFunc) return protoFunc.returnType;
|
727
930
|
}
|
728
931
|
|
729
932
|
if (node.type === 'ExpressionStatement') {
|
@@ -755,14 +958,16 @@ const getNodeType = (scope, node) => {
|
|
755
958
|
|
756
959
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
757
960
|
}
|
758
|
-
|
759
|
-
// default to number
|
760
|
-
return TYPES.number;
|
761
961
|
};
|
762
962
|
|
763
963
|
const generateLiteral = (scope, decl, global, name) => {
|
764
964
|
if (decl.value === null) return number(NULL);
|
765
965
|
|
966
|
+
if (decl.regex) {
|
967
|
+
scope.regex[name] = decl.regex;
|
968
|
+
return number(1);
|
969
|
+
}
|
970
|
+
|
766
971
|
switch (typeof decl.value) {
|
767
972
|
case 'number':
|
768
973
|
return number(decl.value);
|
@@ -792,7 +997,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
792
997
|
|
793
998
|
return makeArray(scope, {
|
794
999
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
1000
|
+
}, global, name, false, 'i16')[0];
|
796
1001
|
|
797
1002
|
default:
|
798
1003
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -802,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
802
1007
|
const countLeftover = wasm => {
|
803
1008
|
let count = 0, depth = 0;
|
804
1009
|
|
805
|
-
for (
|
1010
|
+
for (let i = 0; i < wasm.length; i++) {
|
1011
|
+
const inst = wasm[i];
|
806
1012
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
807
1013
|
if (inst[0] === Opcodes.if) count--;
|
808
1014
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -823,6 +1029,8 @@ const countLeftover = wasm => {
|
|
823
1029
|
} else count--;
|
824
1030
|
if (func) count += func.returns.length;
|
825
1031
|
} else count--;
|
1032
|
+
|
1033
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
826
1034
|
}
|
827
1035
|
|
828
1036
|
return count;
|
@@ -906,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
906
1114
|
}
|
907
1115
|
|
908
1116
|
let out = [];
|
909
|
-
let protoFunc, protoName, baseType, baseName
|
1117
|
+
let protoFunc, protoName, baseType, baseName;
|
910
1118
|
// ident.func()
|
911
1119
|
if (name && name.startsWith('__')) {
|
912
1120
|
const spl = name.slice(2).split('_');
|
@@ -921,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
921
1129
|
|
922
1130
|
// literal.func()
|
923
1131
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1132
|
+
// megahack for /regex/.func()
|
1133
|
+
if (decl.callee.object.regex) {
|
1134
|
+
const funcName = decl.callee.property.name;
|
1135
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1136
|
+
|
1137
|
+
funcIndex[func.name] = func.index;
|
1138
|
+
funcs.push(func);
|
1139
|
+
|
1140
|
+
return [
|
1141
|
+
// make string arg
|
1142
|
+
...generate(scope, decl.arguments[0]),
|
1143
|
+
|
1144
|
+
// call regex func
|
1145
|
+
Opcodes.i32_to_u,
|
1146
|
+
[ Opcodes.call, func.index ],
|
1147
|
+
Opcodes.i32_from
|
1148
|
+
];
|
1149
|
+
}
|
1150
|
+
|
924
1151
|
baseType = getNodeType(scope, decl.callee.object);
|
925
1152
|
|
926
1153
|
const func = decl.callee.property.name;
|
@@ -929,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
929
1156
|
|
930
1157
|
out = generate(scope, decl.callee.object);
|
931
1158
|
out.push([ Opcodes.drop ]);
|
1159
|
+
|
1160
|
+
baseName = [...arrays.keys()].pop();
|
932
1161
|
}
|
933
1162
|
|
934
|
-
if (
|
935
|
-
|
1163
|
+
if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1164
|
+
const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
1165
|
+
|
1166
|
+
funcIndex[func.name] = func.index;
|
1167
|
+
funcs.push(func);
|
1168
|
+
|
1169
|
+
const pointer = arrays.get(baseName);
|
1170
|
+
const [ local, isGlobal ] = lookupName(scope, baseName);
|
1171
|
+
|
1172
|
+
return [
|
1173
|
+
...out,
|
1174
|
+
|
1175
|
+
...(pointer == null ? [
|
1176
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1177
|
+
Opcodes.i32_to_u,
|
1178
|
+
] : [
|
1179
|
+
...number(pointer, Valtype.i32)
|
1180
|
+
]),
|
936
1181
|
|
1182
|
+
// call regex func
|
1183
|
+
[ Opcodes.call, func.index ],
|
1184
|
+
Opcodes.i32_from
|
1185
|
+
];
|
1186
|
+
}
|
1187
|
+
|
1188
|
+
if (protoFunc) {
|
937
1189
|
let pointer = arrays.get(baseName);
|
938
1190
|
|
939
1191
|
if (pointer == null) {
|
@@ -941,10 +1193,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
941
1193
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
942
1194
|
|
943
1195
|
// register array
|
944
|
-
makeArray(scope, {
|
1196
|
+
0, [ , pointer ] = makeArray(scope, {
|
945
1197
|
rawElements: new Array(0)
|
946
1198
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
947
|
-
pointer = arrays.get(baseName);
|
948
1199
|
|
949
1200
|
const [ local, isGlobal ] = lookupName(scope, baseName);
|
950
1201
|
|
@@ -966,25 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
966
1217
|
// use local for cached i32 length as commonly used
|
967
1218
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
968
1219
|
|
1220
|
+
let lengthI32CacheUsed = false;
|
1221
|
+
|
1222
|
+
const protoOut = protoFunc(pointer, {
|
1223
|
+
getCachedI32: () => {
|
1224
|
+
lengthI32CacheUsed = true;
|
1225
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1226
|
+
},
|
1227
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1228
|
+
get: () => arrayUtil.getLength(pointer),
|
1229
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1230
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1231
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1232
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1233
|
+
return makeArray(scope, {
|
1234
|
+
rawElements: new Array(length)
|
1235
|
+
}, _global, _name, true, itemType);
|
1236
|
+
});
|
1237
|
+
|
969
1238
|
return [
|
970
1239
|
...out,
|
971
1240
|
|
972
|
-
...
|
973
|
-
|
1241
|
+
...(!lengthI32CacheUsed ? [] : [
|
1242
|
+
...arrayUtil.getLengthI32(pointer),
|
1243
|
+
[ Opcodes.local_set, lengthLocal ],
|
1244
|
+
]),
|
974
1245
|
|
975
1246
|
[ Opcodes.block, valtypeBinary ],
|
976
|
-
...
|
977
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
978
|
-
get: arrayUtil.getLength(pointer),
|
979
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
980
|
-
set: value => arrayUtil.setLength(pointer, value),
|
981
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
982
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
-
const out = makeArray(scope, {
|
984
|
-
rawElements: new Array(length)
|
985
|
-
}, _global, _name, true, itemType);
|
986
|
-
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
|
-
}),
|
1247
|
+
...protoOut,
|
988
1248
|
[ Opcodes.end ]
|
989
1249
|
];
|
990
1250
|
}
|
@@ -1041,11 +1301,10 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1041
1301
|
args = args.slice(0, func.params.length);
|
1042
1302
|
}
|
1043
1303
|
|
1044
|
-
if (func && func.memory) scope.memory = true;
|
1045
1304
|
if (func && func.throws) scope.throws = true;
|
1046
1305
|
|
1047
1306
|
for (const arg of args) {
|
1048
|
-
out.
|
1307
|
+
out = out.concat(generate(scope, arg));
|
1049
1308
|
}
|
1050
1309
|
|
1051
1310
|
out.push([ Opcodes.call, idx ]);
|
@@ -1056,8 +1315,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1056
1315
|
const generateNew = (scope, decl, _global, _name) => {
|
1057
1316
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1317
|
const name = mapName(decl.callee.name);
|
1059
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1060
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1318
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1319
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1320
|
|
1062
1321
|
return generateCall(scope, decl, _global, _name);
|
1063
1322
|
};
|
@@ -1165,8 +1424,6 @@ const generateAssign = (scope, decl) => {
|
|
1165
1424
|
const name = decl.left.object.name;
|
1166
1425
|
const pointer = arrays.get(name);
|
1167
1426
|
|
1168
|
-
scope.memory = true;
|
1169
|
-
|
1170
1427
|
const aotPointer = pointer != null;
|
1171
1428
|
|
1172
1429
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1187,13 +1444,60 @@ const generateAssign = (scope, decl) => {
|
|
1187
1444
|
];
|
1188
1445
|
}
|
1189
1446
|
|
1447
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1448
|
+
|
1449
|
+
// arr[i] | str[i]
|
1450
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1451
|
+
const name = decl.left.object.name;
|
1452
|
+
const pointer = arrays.get(name);
|
1453
|
+
|
1454
|
+
const aotPointer = pointer != null;
|
1455
|
+
|
1456
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1457
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1458
|
+
|
1459
|
+
const parentType = getNodeType(scope, decl.left.object);
|
1460
|
+
|
1461
|
+
return [
|
1462
|
+
...(aotPointer ? [] : [
|
1463
|
+
...generate(scope, decl.left.object),
|
1464
|
+
Opcodes.i32_to_u
|
1465
|
+
]),
|
1466
|
+
|
1467
|
+
// get index as valtype
|
1468
|
+
...generate(scope, decl.left.property),
|
1469
|
+
|
1470
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1471
|
+
Opcodes.i32_to_u,
|
1472
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1473
|
+
[ Opcodes.i32_mul ],
|
1474
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1475
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1476
|
+
|
1477
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1478
|
+
[ Opcodes.local_get, pointerTmp ],
|
1479
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1480
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1481
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1482
|
+
|
1483
|
+
...(parentType === TYPES._array ? [
|
1484
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1485
|
+
] : [
|
1486
|
+
Opcodes.i32_to_u,
|
1487
|
+
[ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1488
|
+
]),
|
1489
|
+
|
1490
|
+
[ Opcodes.local_get, newValueTmp ]
|
1491
|
+
];
|
1492
|
+
}
|
1493
|
+
|
1190
1494
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1191
1495
|
|
1192
1496
|
if (local === undefined) {
|
1193
1497
|
// todo: this should be a devtools/repl/??? only thing
|
1194
1498
|
|
1195
1499
|
// only allow = for this
|
1196
|
-
if (
|
1500
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1197
1501
|
|
1198
1502
|
if (builtinVars[name]) {
|
1199
1503
|
// just return rhs (eg `NaN = 2`)
|
@@ -1207,8 +1511,10 @@ const generateAssign = (scope, decl) => {
|
|
1207
1511
|
];
|
1208
1512
|
}
|
1209
1513
|
|
1210
|
-
|
1211
|
-
|
1514
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1515
|
+
|
1516
|
+
if (op === '=') {
|
1517
|
+
// typeStates[name] = getNodeType(scope, decl.right);
|
1212
1518
|
|
1213
1519
|
return [
|
1214
1520
|
...generate(scope, decl.right, isGlobal, name),
|
@@ -1217,8 +1523,26 @@ const generateAssign = (scope, decl) => {
|
|
1217
1523
|
];
|
1218
1524
|
}
|
1219
1525
|
|
1526
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1527
|
+
// todo: is this needed?
|
1528
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1529
|
+
// instead, left @ (left = right)
|
1530
|
+
// eg, x &&= y ~= x && (x = y)
|
1531
|
+
|
1532
|
+
return [
|
1533
|
+
...performOp(scope, op, [
|
1534
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1535
|
+
], [
|
1536
|
+
...generate(scope, decl.right),
|
1537
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1538
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1539
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1540
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1541
|
+
];
|
1542
|
+
}
|
1543
|
+
|
1220
1544
|
return [
|
1221
|
-
...performOp(scope,
|
1545
|
+
...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),
|
1222
1546
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1223
1547
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1224
1548
|
];
|
@@ -1245,13 +1569,14 @@ const generateUnary = (scope, decl) => {
|
|
1245
1569
|
|
1246
1570
|
case '!':
|
1247
1571
|
// !=
|
1248
|
-
return falsy(scope, generate(scope, decl.argument));
|
1572
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1249
1573
|
|
1250
1574
|
case '~':
|
1575
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1251
1576
|
return [
|
1252
1577
|
...generate(scope, decl.argument),
|
1253
1578
|
Opcodes.i32_to,
|
1254
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1579
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1255
1580
|
[ Opcodes.i32_xor ],
|
1256
1581
|
Opcodes.i32_from
|
1257
1582
|
];
|
@@ -1289,7 +1614,7 @@ const generateUnary = (scope, decl) => {
|
|
1289
1614
|
return out;
|
1290
1615
|
|
1291
1616
|
case 'typeof':
|
1292
|
-
const type = getNodeType(scope, decl.argument);
|
1617
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1293
1618
|
|
1294
1619
|
// for custom types, just return object
|
1295
1620
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1332,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
|
|
1332
1657
|
};
|
1333
1658
|
|
1334
1659
|
const generateIf = (scope, decl) => {
|
1335
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1660
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
|
1336
1661
|
|
1337
1662
|
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1338
1663
|
depth.push('if');
|
@@ -1422,9 +1747,116 @@ const generateWhile = (scope, decl) => {
|
|
1422
1747
|
return out;
|
1423
1748
|
};
|
1424
1749
|
|
1750
|
+
const generateForOf = (scope, decl) => {
|
1751
|
+
const out = [];
|
1752
|
+
|
1753
|
+
const rightType = getNodeType(scope, decl.right);
|
1754
|
+
const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
|
1755
|
+
|
1756
|
+
// todo: for of inside for of might fuck up?
|
1757
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
1758
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
1759
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
1760
|
+
|
1761
|
+
out.push(
|
1762
|
+
// set pointer as right
|
1763
|
+
...generate(scope, decl.right),
|
1764
|
+
Opcodes.i32_to_u,
|
1765
|
+
[ Opcodes.local_set, pointer ],
|
1766
|
+
|
1767
|
+
// get length
|
1768
|
+
[ Opcodes.local_get, pointer ],
|
1769
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1770
|
+
[ Opcodes.local_set, length ]
|
1771
|
+
);
|
1772
|
+
|
1773
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1774
|
+
depth.push('forof');
|
1775
|
+
|
1776
|
+
// setup local for left
|
1777
|
+
generate(scope, decl.left);
|
1778
|
+
|
1779
|
+
const leftName = decl.left.declarations[0].id.name;
|
1780
|
+
|
1781
|
+
// set type for local
|
1782
|
+
typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
|
1783
|
+
|
1784
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
1785
|
+
|
1786
|
+
if (rightType === TYPES._array) { // array
|
1787
|
+
out.push(
|
1788
|
+
[ Opcodes.local_get, pointer ],
|
1789
|
+
[ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
1790
|
+
);
|
1791
|
+
} else { // string
|
1792
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1793
|
+
rawElements: new Array(1)
|
1794
|
+
}, isGlobal, leftName, true, 'i16');
|
1795
|
+
|
1796
|
+
out.push(
|
1797
|
+
// setup new/out array
|
1798
|
+
...newOut,
|
1799
|
+
[ Opcodes.drop ],
|
1800
|
+
|
1801
|
+
...number(0, Valtype.i32), // base 0 for store after
|
1802
|
+
|
1803
|
+
// load current string ind {arg}
|
1804
|
+
[ Opcodes.local_get, pointer ],
|
1805
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
1806
|
+
|
1807
|
+
// store to new string ind 0
|
1808
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1809
|
+
|
1810
|
+
// return new string (page)
|
1811
|
+
...number(newPointer)
|
1812
|
+
);
|
1813
|
+
}
|
1814
|
+
|
1815
|
+
// set left value
|
1816
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
|
1817
|
+
|
1818
|
+
out.push(
|
1819
|
+
[ Opcodes.block, Blocktype.void ],
|
1820
|
+
[ Opcodes.block, Blocktype.void ]
|
1821
|
+
);
|
1822
|
+
depth.push('block');
|
1823
|
+
depth.push('block');
|
1824
|
+
|
1825
|
+
out.push(
|
1826
|
+
...generate(scope, decl.body),
|
1827
|
+
[ Opcodes.end ]
|
1828
|
+
);
|
1829
|
+
depth.pop();
|
1830
|
+
|
1831
|
+
out.push(
|
1832
|
+
// increment iter pointer by valtype size
|
1833
|
+
[ Opcodes.local_get, pointer ],
|
1834
|
+
...number(valtypeSize, Valtype.i32),
|
1835
|
+
[ Opcodes.i32_add ],
|
1836
|
+
[ Opcodes.local_set, pointer ],
|
1837
|
+
|
1838
|
+
// increment counter by 1
|
1839
|
+
[ Opcodes.local_get, counter ],
|
1840
|
+
...number(1, Valtype.i32),
|
1841
|
+
[ Opcodes.i32_add ],
|
1842
|
+
[ Opcodes.local_tee, counter ],
|
1843
|
+
|
1844
|
+
// loop if counter != length
|
1845
|
+
[ Opcodes.local_get, length ],
|
1846
|
+
[ Opcodes.i32_ne ],
|
1847
|
+
[ Opcodes.br_if, 1 ],
|
1848
|
+
|
1849
|
+
[ Opcodes.end ], [ Opcodes.end ]
|
1850
|
+
);
|
1851
|
+
depth.pop();
|
1852
|
+
depth.pop();
|
1853
|
+
|
1854
|
+
return out;
|
1855
|
+
};
|
1856
|
+
|
1425
1857
|
const getNearestLoop = () => {
|
1426
1858
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1859
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
1860
|
}
|
1429
1861
|
|
1430
1862
|
return -1;
|
@@ -1508,13 +1940,22 @@ const generateAssignPat = (scope, decl) => {
|
|
1508
1940
|
};
|
1509
1941
|
|
1510
1942
|
let pages = new Map();
|
1511
|
-
const allocPage = reason => {
|
1512
|
-
if (pages.has(reason)) return pages.get(reason);
|
1943
|
+
const allocPage = (reason, type) => {
|
1944
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
1945
|
+
|
1946
|
+
const ind = pages.size;
|
1947
|
+
pages.set(reason, { ind, type });
|
1513
1948
|
|
1514
|
-
|
1515
|
-
pages.set(reason, ind);
|
1949
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
1516
1950
|
|
1517
|
-
|
1951
|
+
return ind;
|
1952
|
+
};
|
1953
|
+
|
1954
|
+
const freePage = reason => {
|
1955
|
+
const { ind } = pages.get(reason);
|
1956
|
+
pages.delete(reason);
|
1957
|
+
|
1958
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
1959
|
|
1519
1960
|
return ind;
|
1520
1961
|
};
|
@@ -1528,7 +1969,7 @@ const itemTypeToValtype = {
|
|
1528
1969
|
i16: 'i32'
|
1529
1970
|
};
|
1530
1971
|
|
1531
|
-
const
|
1972
|
+
const StoreOps = {
|
1532
1973
|
i32: Opcodes.i32_store,
|
1533
1974
|
i64: Opcodes.i64_store,
|
1534
1975
|
f64: Opcodes.f64_store,
|
@@ -1537,13 +1978,28 @@ const storeOps = {
|
|
1537
1978
|
i16: Opcodes.i32_store16
|
1538
1979
|
};
|
1539
1980
|
|
1981
|
+
let data = [];
|
1982
|
+
|
1983
|
+
const compileBytes = (val, itemType, signed = true) => {
|
1984
|
+
switch (itemType) {
|
1985
|
+
case 'i8': return enforceOneByte(unsignedLEB128(val));
|
1986
|
+
case 'i16': return enforceTwoBytes(unsignedLEB128(val));
|
1987
|
+
case 'i32': return enforceFourBytes(signedLEB128(val));
|
1988
|
+
case 'i64': return enforceEightBytes(signedLEB128(val));
|
1989
|
+
case 'f64': return enforceEightBytes(ieee754_binary64(val));
|
1990
|
+
}
|
1991
|
+
};
|
1992
|
+
|
1540
1993
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1541
1994
|
const out = [];
|
1542
1995
|
|
1996
|
+
let firstAssign = false;
|
1543
1997
|
if (!arrays.has(name) || name === '$undeclared') {
|
1998
|
+
firstAssign = true;
|
1999
|
+
|
1544
2000
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1545
2001
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1546
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2002
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1547
2003
|
}
|
1548
2004
|
|
1549
2005
|
const pointer = arrays.get(name);
|
@@ -1551,8 +2007,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1551
2007
|
const useRawElements = !!decl.rawElements;
|
1552
2008
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1553
2009
|
|
2010
|
+
const valtype = itemTypeToValtype[itemType];
|
1554
2011
|
const length = elements.length;
|
1555
2012
|
|
2013
|
+
if (firstAssign && useRawElements) {
|
2014
|
+
let bytes = compileBytes(length, 'i32');
|
2015
|
+
|
2016
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2017
|
+
if (elements[i] == null) continue;
|
2018
|
+
|
2019
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2020
|
+
}
|
2021
|
+
|
2022
|
+
data.push({
|
2023
|
+
offset: pointer,
|
2024
|
+
bytes
|
2025
|
+
});
|
2026
|
+
|
2027
|
+
// local value as pointer
|
2028
|
+
out.push(...number(pointer));
|
2029
|
+
|
2030
|
+
return [ out, pointer ];
|
2031
|
+
}
|
2032
|
+
|
1556
2033
|
// store length as 0th array
|
1557
2034
|
out.push(
|
1558
2035
|
...number(0, Valtype.i32),
|
@@ -1560,8 +2037,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1560
2037
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1561
2038
|
);
|
1562
2039
|
|
1563
|
-
const storeOp =
|
1564
|
-
const valtype = itemTypeToValtype[itemType];
|
2040
|
+
const storeOp = StoreOps[itemType];
|
1565
2041
|
|
1566
2042
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1567
2043
|
if (elements[i] == null) continue;
|
@@ -1576,14 +2052,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1576
2052
|
// local value as pointer
|
1577
2053
|
out.push(...number(pointer));
|
1578
2054
|
|
1579
|
-
|
1580
|
-
|
1581
|
-
return out;
|
2055
|
+
return [ out, pointer ];
|
1582
2056
|
};
|
1583
2057
|
|
1584
2058
|
let arrays = new Map();
|
1585
2059
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
2060
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
2061
|
};
|
1588
2062
|
|
1589
2063
|
export const generateMember = (scope, decl, _global, _name) => {
|
@@ -1596,8 +2070,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1596
2070
|
const name = decl.object.name;
|
1597
2071
|
const pointer = arrays.get(name);
|
1598
2072
|
|
1599
|
-
scope.memory = true;
|
1600
|
-
|
1601
2073
|
const aotPointer = pointer != null;
|
1602
2074
|
|
1603
2075
|
return [
|
@@ -1617,8 +2089,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1617
2089
|
const name = decl.object.name;
|
1618
2090
|
const pointer = arrays.get(name);
|
1619
2091
|
|
1620
|
-
scope.memory = true;
|
1621
|
-
|
1622
2092
|
const aotPointer = pointer != null;
|
1623
2093
|
|
1624
2094
|
if (type === TYPES._array) {
|
@@ -1644,10 +2114,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1644
2114
|
|
1645
2115
|
// string
|
1646
2116
|
|
1647
|
-
const newOut = makeArray(scope, {
|
2117
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1648
2118
|
rawElements: new Array(1)
|
1649
2119
|
}, _global, _name, true, 'i16');
|
1650
|
-
const newPointer = arrays.get(_name ?? '$undeclared');
|
1651
2120
|
|
1652
2121
|
return [
|
1653
2122
|
// setup new/out array
|
@@ -1729,7 +2198,7 @@ const generateFunc = (scope, decl) => {
|
|
1729
2198
|
locals: {},
|
1730
2199
|
localInd: 0,
|
1731
2200
|
returns: [ valtypeBinary ],
|
1732
|
-
|
2201
|
+
returnType: null,
|
1733
2202
|
throws: false,
|
1734
2203
|
name
|
1735
2204
|
};
|
@@ -1753,9 +2222,8 @@ const generateFunc = (scope, decl) => {
|
|
1753
2222
|
name,
|
1754
2223
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1755
2224
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType
|
2225
|
+
returnType: innerScope.returnType,
|
1757
2226
|
locals: innerScope.locals,
|
1758
|
-
memory: innerScope.memory,
|
1759
2227
|
throws: innerScope.throws,
|
1760
2228
|
index: currentFuncIndex++
|
1761
2229
|
};
|
@@ -1770,6 +2238,8 @@ const generateFunc = (scope, decl) => {
|
|
1770
2238
|
|
1771
2239
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1772
2240
|
wasm.push(...number(0), [ Opcodes.return ]);
|
2241
|
+
|
2242
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1773
2243
|
}
|
1774
2244
|
|
1775
2245
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1780,9 +2250,7 @@ const generateFunc = (scope, decl) => {
|
|
1780
2250
|
if (local.type === Valtype.v128) {
|
1781
2251
|
vecParams++;
|
1782
2252
|
|
1783
|
-
/*
|
1784
|
-
|
1785
|
-
wasm.unshift( // add v128 load for param
|
2253
|
+
/* wasm.unshift( // add v128 load for param
|
1786
2254
|
[ Opcodes.i32_const, 0 ],
|
1787
2255
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1788
2256
|
[ Opcodes.local_set, local.idx ]
|
@@ -1893,10 +2361,10 @@ const generateFunc = (scope, decl) => {
|
|
1893
2361
|
};
|
1894
2362
|
|
1895
2363
|
const generateCode = (scope, decl) => {
|
1896
|
-
|
2364
|
+
let out = [];
|
1897
2365
|
|
1898
2366
|
for (const x of decl.body) {
|
1899
|
-
out.
|
2367
|
+
out = out.concat(generate(scope, x));
|
1900
2368
|
}
|
1901
2369
|
|
1902
2370
|
return out;
|
@@ -1912,10 +2380,9 @@ const internalConstrs = {
|
|
1912
2380
|
|
1913
2381
|
// new Array(n)
|
1914
2382
|
|
1915
|
-
makeArray(scope, {
|
2383
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2384
|
rawElements: new Array(0)
|
1917
2385
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2386
|
|
1920
2387
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2388
|
|
@@ -1933,6 +2400,18 @@ const internalConstrs = {
|
|
1933
2400
|
];
|
1934
2401
|
},
|
1935
2402
|
type: TYPES._array
|
2403
|
+
},
|
2404
|
+
|
2405
|
+
__Array_of: {
|
2406
|
+
// this is not a constructor but best fits internal structure here
|
2407
|
+
generate: (scope, decl, global, name) => {
|
2408
|
+
// Array.of(i0, i1, ...)
|
2409
|
+
return generateArray(scope, {
|
2410
|
+
elements: decl.arguments
|
2411
|
+
}, global, name);
|
2412
|
+
},
|
2413
|
+
type: TYPES._array,
|
2414
|
+
notConstr: true
|
1936
2415
|
}
|
1937
2416
|
};
|
1938
2417
|
|
@@ -1947,6 +2426,7 @@ export default program => {
|
|
1947
2426
|
typeStates = {};
|
1948
2427
|
arrays = new Map();
|
1949
2428
|
pages = new Map();
|
2429
|
+
data = [];
|
1950
2430
|
currentFuncIndex = importedFuncs.length;
|
1951
2431
|
|
1952
2432
|
globalThis.valtype = 'f64';
|
@@ -2023,5 +2503,5 @@ export default program => {
|
|
2023
2503
|
// if blank main func and other exports, remove it
|
2024
2504
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2025
2505
|
|
2026
|
-
return { funcs, globals, tags, exceptions, pages };
|
2506
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2027
2507
|
};
|