porffor 0.0.0-44bc2d8 → 0.0.0-48403fd
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 +50 -15
- package/c +0 -0
- package/c.exe +0 -0
- package/compiler/2c.js +354 -0
- package/compiler/builtins.js +14 -12
- package/compiler/codeGen.js +859 -332
- package/compiler/decompile.js +21 -5
- package/compiler/embedding.js +9 -5
- package/compiler/encoding.js +4 -2
- package/compiler/index.js +56 -5
- package/compiler/opt.js +337 -251
- package/compiler/parse.js +1 -0
- package/compiler/prototype.js +172 -34
- package/compiler/sections.js +46 -5
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +11 -5
- package/cool.exe +0 -0
- package/fib.js +10 -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 -34
- package/runner/info.js +37 -2
- package/runner/repl.js +4 -11
- package/runner/results.json +1 -0
- package/runner/transform.js +2 -1
- package/runner/version.js +10 -0
- package/tmp.c +61 -0
- package/t.js +0 -31
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,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
109
|
case 'WhileStatement':
|
102
110
|
return generateWhile(scope, decl);
|
103
111
|
|
104
|
-
|
105
|
-
return generateForOf(scope, decl);
|
112
|
+
case 'ForOfStatement':
|
113
|
+
return generateForOf(scope, decl);
|
106
114
|
|
107
115
|
case 'BreakStatement':
|
108
116
|
return generateBreak(scope, decl);
|
@@ -144,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
144
152
|
|
145
153
|
return [];
|
146
154
|
|
147
|
-
case 'TaggedTemplateExpression':
|
148
|
-
|
149
|
-
|
155
|
+
case 'TaggedTemplateExpression': {
|
156
|
+
const funcs = {
|
157
|
+
asm: str => {
|
158
|
+
let out = [];
|
150
159
|
|
151
|
-
|
152
|
-
|
160
|
+
for (const line of str.split('\n')) {
|
161
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
162
|
+
if (asm[0] === '') continue; // blank
|
153
163
|
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
+
}
|
157
169
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
}
|
170
|
+
if (asm[0] === 'returns') {
|
171
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
172
|
+
continue;
|
173
|
+
}
|
163
174
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
175
|
+
if (asm[0] === 'memory') {
|
176
|
+
allocPage('asm instrinsic');
|
177
|
+
// todo: add to store/load offset insts
|
178
|
+
continue;
|
179
|
+
}
|
168
180
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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));
|
186
|
+
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
175
189
|
|
176
|
-
|
177
|
-
|
190
|
+
return out;
|
191
|
+
},
|
178
192
|
|
179
|
-
|
180
|
-
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
181
195
|
|
182
|
-
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
199
|
+
|
200
|
+
// newline
|
201
|
+
...number(10),
|
202
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
203
|
+
];
|
204
|
+
}
|
183
205
|
}
|
184
206
|
|
185
|
-
|
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
|
+
}
|
186
214
|
|
187
215
|
default:
|
188
216
|
return todo(`no generation for ${decl.type}!`);
|
@@ -272,19 +300,17 @@ const generateIdent = (scope, decl) => {
|
|
272
300
|
|
273
301
|
const generateReturn = (scope, decl) => {
|
274
302
|
if (decl.argument === null) {
|
275
|
-
if (!scope.returnType) scope.returnType = TYPES.undefined;
|
276
|
-
|
277
303
|
// just bare "return"
|
278
304
|
return [
|
279
305
|
...number(UNDEFINED), // "undefined" if func returns
|
306
|
+
...number(TYPES.undefined, Valtype.i32), // type undefined
|
280
307
|
[ Opcodes.return ]
|
281
308
|
];
|
282
309
|
}
|
283
310
|
|
284
|
-
if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
|
285
|
-
|
286
311
|
return [
|
287
312
|
...generate(scope, decl.argument),
|
313
|
+
...getNodeType(scope, decl.argument),
|
288
314
|
[ Opcodes.return ]
|
289
315
|
];
|
290
316
|
};
|
@@ -298,11 +324,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
298
324
|
return idx;
|
299
325
|
};
|
300
326
|
|
301
|
-
const
|
327
|
+
const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
|
328
|
+
|
329
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
302
330
|
const checks = {
|
303
|
-
'||':
|
304
|
-
'&&':
|
305
|
-
|
331
|
+
'||': falsy,
|
332
|
+
'&&': truthy,
|
333
|
+
'??': nullish
|
306
334
|
};
|
307
335
|
|
308
336
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -310,14 +338,52 @@ const performLogicOp = (scope, op, left, right) => {
|
|
310
338
|
// generic structure for {a} OP {b}
|
311
339
|
// -->
|
312
340
|
// _ = {a}; if (OP_CHECK) {b} else _
|
341
|
+
|
342
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
343
|
+
// (like if we are in an if condition - very common)
|
344
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
345
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
346
|
+
|
347
|
+
const canInt = leftIsInt && rightIsInt;
|
348
|
+
|
349
|
+
if (canInt) {
|
350
|
+
// remove int -> float conversions from left and right
|
351
|
+
left.pop();
|
352
|
+
right.pop();
|
353
|
+
|
354
|
+
return [
|
355
|
+
...left,
|
356
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
357
|
+
...checks[op](scope, [], leftType, true, true),
|
358
|
+
[ Opcodes.if, Valtype.i32 ],
|
359
|
+
...right,
|
360
|
+
// note type
|
361
|
+
...rightType,
|
362
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
363
|
+
[ Opcodes.else ],
|
364
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
365
|
+
// note type
|
366
|
+
...leftType,
|
367
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
368
|
+
[ Opcodes.end ],
|
369
|
+
Opcodes.i32_from
|
370
|
+
];
|
371
|
+
}
|
372
|
+
|
313
373
|
return [
|
314
374
|
...left,
|
315
375
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
316
|
-
...checks[op],
|
376
|
+
...checks[op](scope, [], leftType, false, true),
|
317
377
|
[ Opcodes.if, valtypeBinary ],
|
318
378
|
...right,
|
379
|
+
// note type
|
380
|
+
...rightType,
|
381
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
319
382
|
[ Opcodes.else ],
|
320
383
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
384
|
+
// note type
|
385
|
+
...leftType,
|
386
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
321
387
|
[ Opcodes.end ]
|
322
388
|
];
|
323
389
|
};
|
@@ -328,12 +394,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
328
394
|
// todo: optimize by looking up names in arrays and using that if exists?
|
329
395
|
// todo: optimize this if using literals/known lengths?
|
330
396
|
|
331
|
-
scope.memory = true;
|
332
|
-
|
333
397
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
334
398
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
335
399
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
336
400
|
|
401
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
402
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
403
|
+
|
337
404
|
if (assign) {
|
338
405
|
const pointer = arrays.get(name ?? '$undeclared');
|
339
406
|
|
@@ -464,8 +531,6 @@ const compareStrings = (scope, left, right) => {
|
|
464
531
|
// todo: optimize by looking up names in arrays and using that if exists?
|
465
532
|
// todo: optimize this if using literals/known lengths?
|
466
533
|
|
467
|
-
scope.memory = true;
|
468
|
-
|
469
534
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
470
535
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
471
536
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
@@ -563,102 +628,143 @@ const compareStrings = (scope, left, right) => {
|
|
563
628
|
];
|
564
629
|
};
|
565
630
|
|
566
|
-
const
|
567
|
-
|
568
|
-
if (type === TYPES._array) return [
|
631
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
632
|
+
if (isIntOp(wasm[wasm.length - 1])) return [
|
569
633
|
...wasm,
|
570
|
-
|
571
|
-
number(0)
|
634
|
+
...(!intIn && intOut ? Opcodes.i32_to_u : [])
|
572
635
|
];
|
573
636
|
|
574
|
-
|
575
|
-
// if "" (length = 0)
|
576
|
-
return [
|
577
|
-
// pointer
|
578
|
-
...wasm,
|
579
|
-
|
580
|
-
// get length
|
581
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
582
|
-
|
583
|
-
// if length == 0
|
584
|
-
[ Opcodes.i32_eqz ],
|
585
|
-
Opcodes.i32_from_u
|
586
|
-
]
|
587
|
-
}
|
588
|
-
|
589
|
-
// if = 0
|
637
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
590
638
|
return [
|
591
639
|
...wasm,
|
592
|
-
|
593
|
-
|
594
|
-
|
640
|
+
[ Opcodes.local_set, tmp ],
|
641
|
+
|
642
|
+
...typeSwitch(scope, type, {
|
643
|
+
[TYPES._array]: [
|
644
|
+
// arrays are always truthy
|
645
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
646
|
+
],
|
647
|
+
[TYPES.string]: [
|
648
|
+
[ Opcodes.local_get, tmp ],
|
649
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
650
|
+
|
651
|
+
// get length
|
652
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
653
|
+
|
654
|
+
// if length != 0
|
655
|
+
/* [ Opcodes.i32_eqz ],
|
656
|
+
[ Opcodes.i32_eqz ], */
|
657
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
658
|
+
],
|
659
|
+
default: [
|
660
|
+
// if value != 0
|
661
|
+
[ Opcodes.local_get, tmp ],
|
662
|
+
|
663
|
+
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
664
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
665
|
+
|
666
|
+
/* Opcodes.eqz,
|
667
|
+
[ Opcodes.i32_eqz ],
|
668
|
+
Opcodes.i32_from */
|
669
|
+
]
|
670
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
595
671
|
];
|
596
672
|
};
|
597
673
|
|
598
|
-
const
|
599
|
-
|
600
|
-
|
674
|
+
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
675
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
676
|
+
return [
|
601
677
|
...wasm,
|
602
|
-
[ Opcodes.
|
603
|
-
|
678
|
+
[ Opcodes.local_set, tmp ],
|
679
|
+
|
680
|
+
...typeSwitch(scope, type, {
|
681
|
+
[TYPES._array]: [
|
682
|
+
// arrays are always truthy
|
683
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
684
|
+
],
|
685
|
+
[TYPES.string]: [
|
686
|
+
[ Opcodes.local_get, tmp ],
|
687
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
688
|
+
|
689
|
+
// get length
|
690
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
691
|
+
|
692
|
+
// if length == 0
|
693
|
+
[ Opcodes.i32_eqz ],
|
694
|
+
[ Opcodes.i32_eqz ],
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
697
|
+
default: [
|
698
|
+
// if value == 0
|
699
|
+
[ Opcodes.local_get, tmp ],
|
700
|
+
|
701
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
702
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
703
|
+
]
|
704
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
604
705
|
];
|
706
|
+
};
|
605
707
|
|
606
|
-
|
607
|
-
|
608
|
-
return [
|
609
|
-
// pointer
|
610
|
-
...wasm,
|
611
|
-
|
612
|
-
// get length
|
613
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
614
|
-
|
615
|
-
// if length != 0
|
616
|
-
/* [ Opcodes.i32_eqz ],
|
617
|
-
[ Opcodes.i32_eqz ], */
|
618
|
-
Opcodes.i32_from_u
|
619
|
-
]
|
620
|
-
}
|
621
|
-
|
622
|
-
// if != 0
|
708
|
+
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
709
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
623
710
|
return [
|
624
711
|
...wasm,
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
712
|
+
[ Opcodes.local_set, tmp ],
|
713
|
+
|
714
|
+
...typeSwitch(scope, type, {
|
715
|
+
[TYPES.undefined]: [
|
716
|
+
// undefined
|
717
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
718
|
+
],
|
719
|
+
[TYPES.object]: [
|
720
|
+
// object, null if == 0
|
721
|
+
[ Opcodes.local_get, tmp ],
|
722
|
+
|
723
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
724
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
725
|
+
],
|
726
|
+
default: [
|
727
|
+
// not
|
728
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
729
|
+
]
|
730
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
629
731
|
];
|
630
732
|
};
|
631
733
|
|
632
734
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
633
735
|
if (op === '||' || op === '&&' || op === '??') {
|
634
|
-
return performLogicOp(scope, op, left, right);
|
736
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
635
737
|
}
|
636
738
|
|
637
|
-
|
739
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
638
740
|
|
639
|
-
|
640
|
-
|
641
|
-
return [
|
642
|
-
...left,
|
643
|
-
...right,
|
741
|
+
const startOut = [], endOut = [];
|
742
|
+
const finalise = out => startOut.concat(out, endOut);
|
644
743
|
|
645
|
-
|
646
|
-
|
647
|
-
|
744
|
+
// if strict (in)equal check types match and...
|
745
|
+
if (op === '===' || op === '!==') {
|
746
|
+
startOut.push(
|
747
|
+
...leftType,
|
748
|
+
...rightType,
|
749
|
+
[ Opcodes.i32_eq ]
|
750
|
+
);
|
648
751
|
|
649
|
-
|
650
|
-
|
651
|
-
|
752
|
+
endOut.push(
|
753
|
+
[ Opcodes.i32_and ]
|
754
|
+
);
|
652
755
|
}
|
653
756
|
|
757
|
+
// todo: if equality op and an operand is undefined, return false
|
758
|
+
// todo: niche null hell with 0
|
759
|
+
|
654
760
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
655
761
|
if (op === '+') {
|
656
762
|
// string concat (a + b)
|
657
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
763
|
+
return finalise(concatStrings(scope, left, right, _global, _name, assign));
|
658
764
|
}
|
659
765
|
|
660
|
-
//
|
661
|
-
if (!
|
766
|
+
// not an equality op, NaN
|
767
|
+
if (!eqOp) return finalise(number(NaN));
|
662
768
|
|
663
769
|
// else leave bool ops
|
664
770
|
// todo: convert string to number if string and number/bool
|
@@ -666,14 +772,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
666
772
|
|
667
773
|
// string comparison
|
668
774
|
if (op === '===' || op === '==') {
|
669
|
-
return compareStrings(scope, left, right);
|
775
|
+
return finalise(compareStrings(scope, left, right));
|
670
776
|
}
|
671
777
|
|
672
778
|
if (op === '!==' || op === '!=') {
|
673
|
-
return [
|
779
|
+
return finalise([
|
674
780
|
...compareStrings(scope, left, right),
|
675
781
|
[ Opcodes.i32_eqz ]
|
676
|
-
];
|
782
|
+
]);
|
677
783
|
}
|
678
784
|
}
|
679
785
|
|
@@ -685,39 +791,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
685
791
|
includeBuiltin(scope, builtinName);
|
686
792
|
const idx = funcIndex[builtinName];
|
687
793
|
|
688
|
-
return [
|
794
|
+
return finalise([
|
689
795
|
...left,
|
690
796
|
...right,
|
691
797
|
[ Opcodes.call, idx ]
|
692
|
-
];
|
798
|
+
]);
|
693
799
|
}
|
694
800
|
|
695
801
|
if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
|
696
802
|
|
697
803
|
if (!Array.isArray(ops)) ops = [ ops ];
|
698
804
|
|
699
|
-
return [
|
805
|
+
return finalise([
|
700
806
|
...left,
|
701
807
|
...right,
|
702
808
|
ops
|
703
|
-
];
|
809
|
+
]);
|
704
810
|
};
|
705
811
|
|
706
|
-
let binaryExpDepth = 0;
|
707
812
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
708
|
-
|
709
|
-
|
710
|
-
const out = [
|
711
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
712
|
-
];
|
813
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
713
814
|
|
714
815
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
715
816
|
|
716
|
-
binaryExpDepth--;
|
717
817
|
return out;
|
718
818
|
};
|
719
819
|
|
720
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
820
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
721
821
|
const existing = funcs.find(x => x.name === name);
|
722
822
|
if (existing) return existing;
|
723
823
|
|
@@ -753,7 +853,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
753
853
|
returns,
|
754
854
|
returnType: TYPES[returnType ?? 'number'],
|
755
855
|
wasm,
|
756
|
-
memory,
|
757
856
|
internal: true,
|
758
857
|
index: currentFuncIndex++
|
759
858
|
};
|
@@ -772,21 +871,22 @@ const includeBuiltin = (scope, builtin) => {
|
|
772
871
|
};
|
773
872
|
|
774
873
|
const generateLogicExp = (scope, decl) => {
|
775
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
874
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
776
875
|
};
|
777
876
|
|
778
877
|
const TYPES = {
|
779
|
-
number:
|
780
|
-
boolean:
|
781
|
-
string:
|
782
|
-
undefined:
|
783
|
-
object:
|
784
|
-
function:
|
785
|
-
symbol:
|
786
|
-
bigint:
|
878
|
+
number: 0x00,
|
879
|
+
boolean: 0x01,
|
880
|
+
string: 0x02,
|
881
|
+
undefined: 0x03,
|
882
|
+
object: 0x04,
|
883
|
+
function: 0x05,
|
884
|
+
symbol: 0x06,
|
885
|
+
bigint: 0x07,
|
787
886
|
|
788
887
|
// these are not "typeof" types but tracked internally
|
789
|
-
_array:
|
888
|
+
_array: 0x10,
|
889
|
+
_regexp: 0x11
|
790
890
|
};
|
791
891
|
|
792
892
|
const TYPE_NAMES = {
|
@@ -802,101 +902,156 @@ const TYPE_NAMES = {
|
|
802
902
|
[TYPES._array]: 'Array'
|
803
903
|
};
|
804
904
|
|
805
|
-
let typeStates = {};
|
806
|
-
|
807
905
|
const getType = (scope, _name) => {
|
808
906
|
const name = mapName(_name);
|
809
|
-
if (scope.locals[name]) return typeStates[name];
|
810
907
|
|
811
|
-
if (
|
812
|
-
if (
|
813
|
-
|
908
|
+
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
909
|
+
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
910
|
+
|
911
|
+
let type = TYPES.undefined;
|
912
|
+
if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
|
913
|
+
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
814
914
|
|
815
|
-
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]
|
816
|
-
|
915
|
+
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
|
916
|
+
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
817
917
|
|
818
|
-
return
|
918
|
+
return number(type, Valtype.i32);
|
819
919
|
};
|
820
920
|
|
921
|
+
const setType = (scope, _name, type) => {
|
922
|
+
const name = mapName(_name);
|
923
|
+
|
924
|
+
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
925
|
+
|
926
|
+
if (scope.locals[name]) return [
|
927
|
+
...out,
|
928
|
+
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
929
|
+
];
|
930
|
+
|
931
|
+
if (globals[name]) return [
|
932
|
+
...out,
|
933
|
+
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
934
|
+
];
|
935
|
+
|
936
|
+
throw new Error('could not find var');
|
937
|
+
}
|
938
|
+
|
821
939
|
const getNodeType = (scope, node) => {
|
822
|
-
|
823
|
-
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
824
|
-
return TYPES[typeof node.value];
|
825
|
-
}
|
940
|
+
// console.trace(node);
|
826
941
|
|
827
|
-
|
828
|
-
|
829
|
-
|
942
|
+
const inner = () => {
|
943
|
+
if (node.type === 'Literal') {
|
944
|
+
// if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
945
|
+
if (node.regex) return TYPES._regexp;
|
830
946
|
|
831
|
-
|
832
|
-
|
833
|
-
}
|
947
|
+
return TYPES[typeof node.value];
|
948
|
+
}
|
834
949
|
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
950
|
+
if (isFuncType(node.type)) {
|
951
|
+
return TYPES.function;
|
952
|
+
}
|
953
|
+
|
954
|
+
if (node.type === 'Identifier') {
|
955
|
+
return getType(scope, node.name);
|
956
|
+
}
|
957
|
+
|
958
|
+
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
959
|
+
const name = node.callee.name;
|
960
|
+
const func = funcs.find(x => x.name === name);
|
961
|
+
|
962
|
+
if (func) {
|
963
|
+
// console.log(scope, func, func.returnType);
|
964
|
+
if (func.returnType) return func.returnType;
|
965
|
+
|
966
|
+
if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
967
|
+
|
968
|
+
// presume
|
969
|
+
// todo: warn here?
|
970
|
+
return TYPES.number;
|
971
|
+
}
|
839
972
|
|
840
|
-
|
841
|
-
|
973
|
+
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
974
|
+
if (internalConstrs[name]) return internalConstrs[name].type;
|
842
975
|
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
976
|
+
let protoFunc;
|
977
|
+
// ident.func()
|
978
|
+
if (name && name.startsWith('__')) {
|
979
|
+
const spl = name.slice(2).split('_');
|
847
980
|
|
848
|
-
|
849
|
-
|
981
|
+
const baseName = spl.slice(0, -1).join('_');
|
982
|
+
const baseType = getType(scope, baseName);
|
850
983
|
|
851
|
-
|
852
|
-
|
984
|
+
const func = spl[spl.length - 1];
|
985
|
+
protoFunc = prototypeFuncs[baseType]?.[func];
|
986
|
+
}
|
987
|
+
|
988
|
+
// literal.func()
|
989
|
+
if (!name && node.callee.type === 'MemberExpression') {
|
990
|
+
if (node.callee.object.regex) {
|
991
|
+
const funcName = node.callee.property.name;
|
992
|
+
return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
993
|
+
}
|
994
|
+
|
995
|
+
const baseType = getNodeType(scope, node.callee.object);
|
996
|
+
|
997
|
+
const func = node.callee.property.name;
|
998
|
+
protoFunc = prototypeFuncs[baseType]?.[func];
|
999
|
+
}
|
1000
|
+
|
1001
|
+
if (protoFunc) return protoFunc.returnType;
|
853
1002
|
}
|
854
1003
|
|
855
|
-
|
856
|
-
|
857
|
-
|
1004
|
+
if (node.type === 'ExpressionStatement') {
|
1005
|
+
return getNodeType(scope, node.expression);
|
1006
|
+
}
|
858
1007
|
|
859
|
-
|
860
|
-
|
1008
|
+
if (node.type === 'AssignmentExpression') {
|
1009
|
+
return getNodeType(scope, node.right);
|
861
1010
|
}
|
862
1011
|
|
863
|
-
if (
|
864
|
-
|
1012
|
+
if (node.type === 'ArrayExpression') {
|
1013
|
+
return TYPES._array;
|
1014
|
+
}
|
865
1015
|
|
866
|
-
|
867
|
-
|
868
|
-
}
|
1016
|
+
if (node.type === 'BinaryExpression') {
|
1017
|
+
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
869
1018
|
|
870
|
-
|
871
|
-
|
872
|
-
}
|
1019
|
+
if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
|
1020
|
+
}
|
873
1021
|
|
874
|
-
|
875
|
-
|
876
|
-
|
1022
|
+
if (node.type === 'UnaryExpression') {
|
1023
|
+
if (node.operator === '!') return TYPES.boolean;
|
1024
|
+
if (node.operator === 'void') return TYPES.undefined;
|
1025
|
+
if (node.operator === 'delete') return TYPES.boolean;
|
1026
|
+
if (node.operator === 'typeof') return TYPES.string;
|
1027
|
+
}
|
877
1028
|
|
878
|
-
|
879
|
-
|
1029
|
+
if (node.type === 'MemberExpression') {
|
1030
|
+
const objectType = getNodeType(scope, node.object);
|
880
1031
|
|
881
|
-
|
882
|
-
|
1032
|
+
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
1033
|
+
}
|
883
1034
|
|
884
|
-
|
885
|
-
if (node.operator === '!') return TYPES.boolean;
|
886
|
-
if (node.operator === 'void') return TYPES.undefined;
|
887
|
-
if (node.operator === 'delete') return TYPES.boolean;
|
888
|
-
}
|
1035
|
+
if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
889
1036
|
|
890
|
-
|
891
|
-
|
1037
|
+
// presume
|
1038
|
+
// todo: warn here?
|
1039
|
+
return TYPES.number;
|
1040
|
+
};
|
892
1041
|
|
893
|
-
|
894
|
-
|
1042
|
+
const ret = inner();
|
1043
|
+
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1044
|
+
return ret;
|
895
1045
|
};
|
896
1046
|
|
897
1047
|
const generateLiteral = (scope, decl, global, name) => {
|
898
1048
|
if (decl.value === null) return number(NULL);
|
899
1049
|
|
1050
|
+
if (decl.regex) {
|
1051
|
+
scope.regex[name] = decl.regex;
|
1052
|
+
return number(1);
|
1053
|
+
}
|
1054
|
+
|
900
1055
|
switch (typeof decl.value) {
|
901
1056
|
case 'number':
|
902
1057
|
return number(decl.value);
|
@@ -907,23 +1062,49 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
907
1062
|
|
908
1063
|
case 'string':
|
909
1064
|
// this is a terrible hack which changes type strings ("number" etc) to known const number values
|
910
|
-
switch (decl.value) {
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
}
|
1065
|
+
// switch (decl.value) {
|
1066
|
+
// case 'number': return number(TYPES.number);
|
1067
|
+
// case 'boolean': return number(TYPES.boolean);
|
1068
|
+
// case 'string': return number(TYPES.string);
|
1069
|
+
// case 'undefined': return number(TYPES.undefined);
|
1070
|
+
// case 'object': return number(TYPES.object);
|
1071
|
+
// case 'function': return number(TYPES.function);
|
1072
|
+
// case 'symbol': return number(TYPES.symbol);
|
1073
|
+
// case 'bigint': return number(TYPES.bigint);
|
1074
|
+
// }
|
1075
|
+
|
1076
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1077
|
+
let wellFormed = aotWFA ? true : undefined;
|
920
1078
|
|
921
1079
|
const str = decl.value;
|
922
1080
|
const rawElements = new Array(str.length);
|
1081
|
+
let j = 0;
|
923
1082
|
for (let i = 0; i < str.length; i++) {
|
924
1083
|
rawElements[i] = str.charCodeAt(i);
|
1084
|
+
|
1085
|
+
if (wellFormed) {
|
1086
|
+
// check if surrogate
|
1087
|
+
if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
|
1088
|
+
// unpaired trailing surrogate
|
1089
|
+
if (str.charCodeAt(j) >= 0xDC00) {
|
1090
|
+
wellFormed = false;
|
1091
|
+
}
|
1092
|
+
|
1093
|
+
// unpaired leading surrogate
|
1094
|
+
// if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
|
1095
|
+
if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
|
1096
|
+
wellFormed = false;
|
1097
|
+
}
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
j++;
|
1101
|
+
}
|
925
1102
|
}
|
926
1103
|
|
1104
|
+
// console.log(wellFormed, str);
|
1105
|
+
|
1106
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1107
|
+
|
927
1108
|
return makeArray(scope, {
|
928
1109
|
rawElements
|
929
1110
|
}, global, name, false, 'i16')[0];
|
@@ -936,7 +1117,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
936
1117
|
const countLeftover = wasm => {
|
937
1118
|
let count = 0, depth = 0;
|
938
1119
|
|
939
|
-
|
1120
|
+
// console.trace(wasm.length);
|
1121
|
+
|
1122
|
+
for (let i = 0; i < wasm.length; i++) {
|
1123
|
+
const inst = wasm[i];
|
940
1124
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
941
1125
|
if (inst[0] === Opcodes.if) count--;
|
942
1126
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -945,11 +1129,12 @@ const countLeftover = wasm => {
|
|
945
1129
|
if (inst[0] === Opcodes.end) depth--;
|
946
1130
|
|
947
1131
|
if (depth === 0)
|
948
|
-
if ([Opcodes.throw,
|
1132
|
+
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
949
1133
|
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
950
1134
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
951
1135
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
952
1136
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1137
|
+
else if (inst[0] === Opcodes.return) count = 0;
|
953
1138
|
else if (inst[0] === Opcodes.call) {
|
954
1139
|
let func = funcs.find(x => x.index === inst[1]);
|
955
1140
|
if (func) {
|
@@ -957,6 +1142,8 @@ const countLeftover = wasm => {
|
|
957
1142
|
} else count--;
|
958
1143
|
if (func) count += func.returns.length;
|
959
1144
|
} else count--;
|
1145
|
+
|
1146
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
960
1147
|
}
|
961
1148
|
|
962
1149
|
return count;
|
@@ -968,6 +1155,13 @@ const disposeLeftover = wasm => {
|
|
968
1155
|
for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
|
969
1156
|
};
|
970
1157
|
|
1158
|
+
const disposeType = (scope, wasm) => {
|
1159
|
+
const lastOp = wasm[wasm.length - 1];
|
1160
|
+
if (lastOp[0] === Opcodes.i32_const || (lastOp[0] === Opcodes.local_get && lastOp[1] === scope.locals['#last_type']))
|
1161
|
+
|
1162
|
+
wasm.push([ Opcodes.drop ]);
|
1163
|
+
};
|
1164
|
+
|
971
1165
|
const generateExp = (scope, decl) => {
|
972
1166
|
const expression = decl.expression;
|
973
1167
|
|
@@ -1040,7 +1234,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1040
1234
|
}
|
1041
1235
|
|
1042
1236
|
let out = [];
|
1043
|
-
let protoFunc, protoName, baseType, baseName
|
1237
|
+
let protoFunc, protoName, baseType, baseName;
|
1044
1238
|
// ident.func()
|
1045
1239
|
if (name && name.startsWith('__')) {
|
1046
1240
|
const spl = name.slice(2).split('_');
|
@@ -1055,6 +1249,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1055
1249
|
|
1056
1250
|
// literal.func()
|
1057
1251
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1252
|
+
// megahack for /regex/.func()
|
1253
|
+
if (decl.callee.object.regex) {
|
1254
|
+
const funcName = decl.callee.property.name;
|
1255
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1256
|
+
|
1257
|
+
funcIndex[func.name] = func.index;
|
1258
|
+
funcs.push(func);
|
1259
|
+
|
1260
|
+
return [
|
1261
|
+
// make string arg
|
1262
|
+
...generate(scope, decl.arguments[0]),
|
1263
|
+
|
1264
|
+
// call regex func
|
1265
|
+
Opcodes.i32_to_u,
|
1266
|
+
[ Opcodes.call, func.index ],
|
1267
|
+
Opcodes.i32_from
|
1268
|
+
];
|
1269
|
+
}
|
1270
|
+
|
1058
1271
|
baseType = getNodeType(scope, decl.callee.object);
|
1059
1272
|
|
1060
1273
|
const func = decl.callee.property.name;
|
@@ -1063,11 +1276,36 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1063
1276
|
|
1064
1277
|
out = generate(scope, decl.callee.object);
|
1065
1278
|
out.push([ Opcodes.drop ]);
|
1279
|
+
|
1280
|
+
baseName = [...arrays.keys()].pop();
|
1066
1281
|
}
|
1067
1282
|
|
1068
|
-
if (
|
1069
|
-
|
1283
|
+
if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1284
|
+
const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
1285
|
+
|
1286
|
+
funcIndex[func.name] = func.index;
|
1287
|
+
funcs.push(func);
|
1288
|
+
|
1289
|
+
const pointer = arrays.get(baseName);
|
1290
|
+
const [ local, isGlobal ] = lookupName(scope, baseName);
|
1070
1291
|
|
1292
|
+
return [
|
1293
|
+
...out,
|
1294
|
+
|
1295
|
+
...(pointer == null ? [
|
1296
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1297
|
+
Opcodes.i32_to_u,
|
1298
|
+
] : [
|
1299
|
+
...number(pointer, Valtype.i32)
|
1300
|
+
]),
|
1301
|
+
|
1302
|
+
// call regex func
|
1303
|
+
[ Opcodes.call, func.index ],
|
1304
|
+
Opcodes.i32_from
|
1305
|
+
];
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
if (protoFunc) {
|
1071
1309
|
let pointer = arrays.get(baseName);
|
1072
1310
|
|
1073
1311
|
if (pointer == null) {
|
@@ -1075,7 +1313,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1075
1313
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1076
1314
|
|
1077
1315
|
// register array
|
1078
|
-
|
1316
|
+
0, [ , pointer ] = makeArray(scope, {
|
1079
1317
|
rawElements: new Array(0)
|
1080
1318
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1081
1319
|
|
@@ -1095,28 +1333,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1095
1333
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1096
1334
|
|
1097
1335
|
let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1336
|
+
let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1098
1337
|
|
1099
1338
|
// use local for cached i32 length as commonly used
|
1100
1339
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1101
1340
|
|
1341
|
+
let lengthI32CacheUsed = false;
|
1342
|
+
|
1343
|
+
const protoOut = protoFunc(pointer, {
|
1344
|
+
getCachedI32: () => {
|
1345
|
+
lengthI32CacheUsed = true;
|
1346
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1347
|
+
},
|
1348
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1349
|
+
get: () => arrayUtil.getLength(pointer),
|
1350
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1351
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1352
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1353
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1354
|
+
return makeArray(scope, {
|
1355
|
+
rawElements: new Array(length)
|
1356
|
+
}, _global, _name, true, itemType);
|
1357
|
+
}, varMetadata.get(baseName));
|
1358
|
+
|
1102
1359
|
return [
|
1103
1360
|
...out,
|
1104
1361
|
|
1105
|
-
...
|
1106
|
-
|
1362
|
+
...(!lengthI32CacheUsed ? [] : [
|
1363
|
+
...arrayUtil.getLengthI32(pointer),
|
1364
|
+
[ Opcodes.local_set, lengthLocal ],
|
1365
|
+
]),
|
1107
1366
|
|
1108
1367
|
[ Opcodes.block, valtypeBinary ],
|
1109
|
-
...
|
1110
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1111
|
-
get: arrayUtil.getLength(pointer),
|
1112
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1113
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1114
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1115
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1116
|
-
return makeArray(scope, {
|
1117
|
-
rawElements: new Array(length)
|
1118
|
-
}, _global, _name, true, itemType);
|
1119
|
-
}),
|
1368
|
+
...protoOut,
|
1120
1369
|
[ Opcodes.end ]
|
1121
1370
|
];
|
1122
1371
|
}
|
@@ -1162,34 +1411,40 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1162
1411
|
|
1163
1412
|
const func = funcs.find(x => x.index === idx);
|
1164
1413
|
|
1414
|
+
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1415
|
+
const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
|
1416
|
+
|
1165
1417
|
let args = decl.arguments;
|
1166
|
-
if (func && args.length <
|
1418
|
+
if (func && args.length < paramCount) {
|
1167
1419
|
// too little args, push undefineds
|
1168
|
-
args = args.concat(new Array(
|
1420
|
+
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1169
1421
|
}
|
1170
1422
|
|
1171
|
-
if (func && args.length >
|
1423
|
+
if (func && args.length > paramCount) {
|
1172
1424
|
// too many args, slice extras off
|
1173
|
-
args = args.slice(0,
|
1425
|
+
args = args.slice(0, paramCount);
|
1174
1426
|
}
|
1175
1427
|
|
1176
|
-
if (func && func.memory) scope.memory = true;
|
1177
1428
|
if (func && func.throws) scope.throws = true;
|
1178
1429
|
|
1179
1430
|
for (const arg of args) {
|
1180
|
-
out.
|
1431
|
+
out = out.concat(generate(scope, arg));
|
1432
|
+
if (userFunc) out = out.concat(getNodeType(scope, arg));
|
1181
1433
|
}
|
1182
1434
|
|
1183
1435
|
out.push([ Opcodes.call, idx ]);
|
1184
1436
|
|
1437
|
+
// if user func, they return [ value, type ]
|
1438
|
+
if (userFunc) out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
|
1439
|
+
|
1185
1440
|
return out;
|
1186
1441
|
};
|
1187
1442
|
|
1188
1443
|
const generateNew = (scope, decl, _global, _name) => {
|
1189
1444
|
// hack: basically treat this as a normal call for builtins for now
|
1190
1445
|
const name = mapName(decl.callee.name);
|
1191
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1192
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1446
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1447
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1193
1448
|
|
1194
1449
|
return generateCall(scope, decl, _global, _name);
|
1195
1450
|
};
|
@@ -1205,13 +1460,67 @@ const unhackName = name => {
|
|
1205
1460
|
return name;
|
1206
1461
|
};
|
1207
1462
|
|
1463
|
+
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1464
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1465
|
+
|
1466
|
+
const out = [
|
1467
|
+
...type,
|
1468
|
+
[ Opcodes.local_set, tmp ],
|
1469
|
+
[ Opcodes.block, returns ]
|
1470
|
+
];
|
1471
|
+
|
1472
|
+
// todo: use br_table?
|
1473
|
+
|
1474
|
+
for (const x in bc) {
|
1475
|
+
if (x === 'default') continue;
|
1476
|
+
|
1477
|
+
// if type == x
|
1478
|
+
out.push([ Opcodes.local_get, tmp ]);
|
1479
|
+
out.push(...number(x, Valtype.i32));
|
1480
|
+
out.push([ Opcodes.i32_eq ]);
|
1481
|
+
|
1482
|
+
out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
|
1483
|
+
out.push(...bc[x]);
|
1484
|
+
out.push([ Opcodes.br, 0 ]);
|
1485
|
+
out.push([ Opcodes.end ]);
|
1486
|
+
}
|
1487
|
+
|
1488
|
+
// default
|
1489
|
+
if (bc.default) out.push(...bc.default);
|
1490
|
+
else out.push(...number(0, returns));
|
1491
|
+
|
1492
|
+
out.push([ Opcodes.end, `TYPESWITCH_end` ]);
|
1493
|
+
|
1494
|
+
return out;
|
1495
|
+
};
|
1496
|
+
|
1497
|
+
const allocVar = (scope, name, global = false) => {
|
1498
|
+
const target = global ? globals : scope.locals;
|
1499
|
+
|
1500
|
+
// already declared
|
1501
|
+
if (target[name]) {
|
1502
|
+
// parser should catch this but sanity check anyway
|
1503
|
+
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1504
|
+
|
1505
|
+
return target[name].idx;
|
1506
|
+
}
|
1507
|
+
|
1508
|
+
let idx = global ? globalInd++ : scope.localInd++;
|
1509
|
+
target[name] = { idx, type: valtypeBinary };
|
1510
|
+
|
1511
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1512
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1513
|
+
|
1514
|
+
return idx;
|
1515
|
+
};
|
1516
|
+
|
1208
1517
|
const generateVar = (scope, decl) => {
|
1209
|
-
|
1518
|
+
let out = [];
|
1210
1519
|
|
1211
1520
|
const topLevel = scope.name === 'main';
|
1212
1521
|
|
1213
1522
|
// global variable if in top scope (main) and var ..., or if wanted
|
1214
|
-
const global = decl.kind === 'var';
|
1523
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1215
1524
|
const target = global ? globals : scope.locals;
|
1216
1525
|
|
1217
1526
|
for (const x of decl.declarations) {
|
@@ -1232,43 +1541,13 @@ const generateVar = (scope, decl) => {
|
|
1232
1541
|
continue; // always ignore
|
1233
1542
|
}
|
1234
1543
|
|
1235
|
-
let idx;
|
1236
|
-
// already declared
|
1237
|
-
if (target[name]) {
|
1238
|
-
// parser should catch this but sanity check anyway
|
1239
|
-
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1544
|
+
let idx = allocVar(scope, name, global);
|
1240
1545
|
|
1241
|
-
|
1242
|
-
} else {
|
1243
|
-
idx = global ? globalInd++ : scope.localInd++;
|
1244
|
-
target[name] = { idx, type: valtypeBinary };
|
1245
|
-
}
|
1246
|
-
|
1247
|
-
typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
|
1546
|
+
out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1248
1547
|
|
1249
1548
|
// x.init ??= DEFAULT_VALUE;
|
1250
1549
|
if (x.init) {
|
1251
|
-
out.
|
1252
|
-
|
1253
|
-
// if our value is the result of a function, infer the type from that func's return value
|
1254
|
-
if (out[out.length - 1][0] === Opcodes.call) {
|
1255
|
-
const ind = out[out.length - 1][1];
|
1256
|
-
if (ind >= importedFuncs.length) { // not an imported func
|
1257
|
-
const func = funcs.find(x => x.index === ind);
|
1258
|
-
if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
|
1259
|
-
|
1260
|
-
const returns = func.returns;
|
1261
|
-
if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
|
1262
|
-
|
1263
|
-
target[name].type = func.returns[0];
|
1264
|
-
if (target[name].type === Valtype.v128) {
|
1265
|
-
// specify vec subtype inferred from first vec type in function name
|
1266
|
-
target[name].vecType = func.name.split('_').find(x => x.includes('x'));
|
1267
|
-
}
|
1268
|
-
} else {
|
1269
|
-
// we do not have imports that return yet, ignore for now
|
1270
|
-
}
|
1271
|
-
}
|
1550
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1272
1551
|
|
1273
1552
|
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1274
1553
|
}
|
@@ -1297,8 +1576,6 @@ const generateAssign = (scope, decl) => {
|
|
1297
1576
|
const name = decl.left.object.name;
|
1298
1577
|
const pointer = arrays.get(name);
|
1299
1578
|
|
1300
|
-
scope.memory = true;
|
1301
|
-
|
1302
1579
|
const aotPointer = pointer != null;
|
1303
1580
|
|
1304
1581
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1319,13 +1596,60 @@ const generateAssign = (scope, decl) => {
|
|
1319
1596
|
];
|
1320
1597
|
}
|
1321
1598
|
|
1599
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1600
|
+
|
1601
|
+
// arr[i] | str[i]
|
1602
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1603
|
+
const name = decl.left.object.name;
|
1604
|
+
const pointer = arrays.get(name);
|
1605
|
+
|
1606
|
+
const aotPointer = pointer != null;
|
1607
|
+
|
1608
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1609
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1610
|
+
|
1611
|
+
const parentType = getNodeType(scope, decl.left.object);
|
1612
|
+
|
1613
|
+
return [
|
1614
|
+
...(aotPointer ? [] : [
|
1615
|
+
...generate(scope, decl.left.object),
|
1616
|
+
Opcodes.i32_to_u
|
1617
|
+
]),
|
1618
|
+
|
1619
|
+
// get index as valtype
|
1620
|
+
...generate(scope, decl.left.property),
|
1621
|
+
|
1622
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1623
|
+
Opcodes.i32_to_u,
|
1624
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1625
|
+
[ Opcodes.i32_mul ],
|
1626
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1627
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1628
|
+
|
1629
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1630
|
+
[ Opcodes.local_get, pointerTmp ],
|
1631
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1632
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1633
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1634
|
+
|
1635
|
+
...(parentType === TYPES._array ? [
|
1636
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1637
|
+
] : [
|
1638
|
+
Opcodes.i32_to_u,
|
1639
|
+
[ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1640
|
+
]),
|
1641
|
+
|
1642
|
+
[ Opcodes.local_get, newValueTmp ]
|
1643
|
+
];
|
1644
|
+
}
|
1645
|
+
|
1322
1646
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1323
1647
|
|
1324
1648
|
if (local === undefined) {
|
1325
|
-
// todo: this should be a
|
1649
|
+
// todo: this should be a sloppy mode only thing
|
1326
1650
|
|
1327
1651
|
// only allow = for this
|
1328
|
-
if (
|
1652
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1329
1653
|
|
1330
1654
|
if (builtinVars[name]) {
|
1331
1655
|
// just return rhs (eg `NaN = 2`)
|
@@ -1334,14 +1658,12 @@ const generateAssign = (scope, decl) => {
|
|
1334
1658
|
|
1335
1659
|
// set global and return (eg a = 2)
|
1336
1660
|
return [
|
1337
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1661
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1338
1662
|
[ Opcodes.global_get, globals[name].idx ]
|
1339
1663
|
];
|
1340
1664
|
}
|
1341
1665
|
|
1342
|
-
if (
|
1343
|
-
typeStates[name] = getNodeType(scope, decl.right);
|
1344
|
-
|
1666
|
+
if (op === '=') {
|
1345
1667
|
return [
|
1346
1668
|
...generate(scope, decl.right, isGlobal, name),
|
1347
1669
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
@@ -1349,8 +1671,26 @@ const generateAssign = (scope, decl) => {
|
|
1349
1671
|
];
|
1350
1672
|
}
|
1351
1673
|
|
1674
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1675
|
+
// todo: is this needed?
|
1676
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1677
|
+
// instead, left @ (left = right)
|
1678
|
+
// eg, x &&= y ~= x && (x = y)
|
1679
|
+
|
1680
|
+
return [
|
1681
|
+
...performOp(scope, op, [
|
1682
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1683
|
+
], [
|
1684
|
+
...generate(scope, decl.right),
|
1685
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1686
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1687
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1688
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1689
|
+
];
|
1690
|
+
}
|
1691
|
+
|
1352
1692
|
return [
|
1353
|
-
...performOp(scope,
|
1693
|
+
...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),
|
1354
1694
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1355
1695
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1356
1696
|
];
|
@@ -1377,13 +1717,14 @@ const generateUnary = (scope, decl) => {
|
|
1377
1717
|
|
1378
1718
|
case '!':
|
1379
1719
|
// !=
|
1380
|
-
return falsy(scope, generate(scope, decl.argument));
|
1720
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
1381
1721
|
|
1382
1722
|
case '~':
|
1723
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1383
1724
|
return [
|
1384
1725
|
...generate(scope, decl.argument),
|
1385
1726
|
Opcodes.i32_to,
|
1386
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1727
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1387
1728
|
[ Opcodes.i32_xor ],
|
1388
1729
|
Opcodes.i32_from
|
1389
1730
|
];
|
@@ -1421,11 +1762,16 @@ const generateUnary = (scope, decl) => {
|
|
1421
1762
|
return out;
|
1422
1763
|
|
1423
1764
|
case 'typeof':
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1765
|
+
// todo: do not use typeswitch, use custom bc instead
|
1766
|
+
// todo: _array & _regexp -> object. others when impl'd
|
1767
|
+
return typeSwitch(scope, getNodeType(scope, decl.argument), {
|
1768
|
+
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1769
|
+
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1770
|
+
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1771
|
+
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1772
|
+
[TYPES.object]: makeString(scope, 'object', false, '#typeof_result'),
|
1773
|
+
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1774
|
+
});
|
1429
1775
|
|
1430
1776
|
default:
|
1431
1777
|
return todo(`unary operator ${decl.operator} not implemented yet`);
|
@@ -1464,9 +1810,9 @@ const generateUpdate = (scope, decl) => {
|
|
1464
1810
|
};
|
1465
1811
|
|
1466
1812
|
const generateIf = (scope, decl) => {
|
1467
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1813
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
|
1468
1814
|
|
1469
|
-
out.push(
|
1815
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
1470
1816
|
depth.push('if');
|
1471
1817
|
|
1472
1818
|
const consOut = generate(scope, decl.consequent);
|
@@ -1495,9 +1841,21 @@ const generateConditional = (scope, decl) => {
|
|
1495
1841
|
|
1496
1842
|
out.push(...generate(scope, decl.consequent));
|
1497
1843
|
|
1844
|
+
// note type
|
1845
|
+
out.push(
|
1846
|
+
...getNodeType(scope, decl.consequent),
|
1847
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1848
|
+
);
|
1849
|
+
|
1498
1850
|
out.push([ Opcodes.else ]);
|
1499
1851
|
out.push(...generate(scope, decl.alternate));
|
1500
1852
|
|
1853
|
+
// note type
|
1854
|
+
out.push(
|
1855
|
+
...getNodeType(scope, decl.alternate),
|
1856
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1857
|
+
);
|
1858
|
+
|
1501
1859
|
out.push([ Opcodes.end ]);
|
1502
1860
|
depth.pop();
|
1503
1861
|
|
@@ -1557,18 +1915,106 @@ const generateWhile = (scope, decl) => {
|
|
1557
1915
|
const generateForOf = (scope, decl) => {
|
1558
1916
|
const out = [];
|
1559
1917
|
|
1918
|
+
const rightType = getNodeType(scope, decl.right);
|
1919
|
+
const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
|
1920
|
+
|
1921
|
+
// todo: for of inside for of might fuck up?
|
1922
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
1923
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
1924
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
1925
|
+
|
1926
|
+
out.push(
|
1927
|
+
// set pointer as right
|
1928
|
+
...generate(scope, decl.right),
|
1929
|
+
Opcodes.i32_to_u,
|
1930
|
+
[ Opcodes.local_set, pointer ],
|
1931
|
+
|
1932
|
+
// get length
|
1933
|
+
[ Opcodes.local_get, pointer ],
|
1934
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1935
|
+
[ Opcodes.local_set, length ]
|
1936
|
+
);
|
1937
|
+
|
1560
1938
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
1561
|
-
depth.push('
|
1939
|
+
depth.push('forof');
|
1562
1940
|
|
1563
|
-
|
1564
|
-
|
1565
|
-
depth.push('if');
|
1941
|
+
// setup local for left
|
1942
|
+
generate(scope, decl.left);
|
1566
1943
|
|
1567
|
-
|
1944
|
+
const leftName = decl.left.declarations[0].id.name;
|
1568
1945
|
|
1569
|
-
|
1570
|
-
out.push(
|
1571
|
-
|
1946
|
+
// set type for local
|
1947
|
+
out.push(...setType(scope, leftName, rightType === TYPES._array ? TYPES.number : TYPES.string));
|
1948
|
+
|
1949
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
1950
|
+
|
1951
|
+
if (rightType === TYPES._array) { // array
|
1952
|
+
out.push(
|
1953
|
+
[ Opcodes.local_get, pointer ],
|
1954
|
+
[ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
1955
|
+
);
|
1956
|
+
} else { // string
|
1957
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1958
|
+
rawElements: new Array(1)
|
1959
|
+
}, isGlobal, leftName, true, 'i16');
|
1960
|
+
|
1961
|
+
out.push(
|
1962
|
+
// setup new/out array
|
1963
|
+
...newOut,
|
1964
|
+
[ Opcodes.drop ],
|
1965
|
+
|
1966
|
+
...number(0, Valtype.i32), // base 0 for store after
|
1967
|
+
|
1968
|
+
// load current string ind {arg}
|
1969
|
+
[ Opcodes.local_get, pointer ],
|
1970
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
1971
|
+
|
1972
|
+
// store to new string ind 0
|
1973
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1974
|
+
|
1975
|
+
// return new string (page)
|
1976
|
+
...number(newPointer)
|
1977
|
+
);
|
1978
|
+
}
|
1979
|
+
|
1980
|
+
// set left value
|
1981
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
|
1982
|
+
|
1983
|
+
out.push(
|
1984
|
+
[ Opcodes.block, Blocktype.void ],
|
1985
|
+
[ Opcodes.block, Blocktype.void ]
|
1986
|
+
);
|
1987
|
+
depth.push('block');
|
1988
|
+
depth.push('block');
|
1989
|
+
|
1990
|
+
out.push(
|
1991
|
+
...generate(scope, decl.body),
|
1992
|
+
[ Opcodes.end ]
|
1993
|
+
);
|
1994
|
+
depth.pop();
|
1995
|
+
|
1996
|
+
out.push(
|
1997
|
+
// increment iter pointer by valtype size
|
1998
|
+
[ Opcodes.local_get, pointer ],
|
1999
|
+
...number(valtypeSize, Valtype.i32),
|
2000
|
+
[ Opcodes.i32_add ],
|
2001
|
+
[ Opcodes.local_set, pointer ],
|
2002
|
+
|
2003
|
+
// increment counter by 1
|
2004
|
+
[ Opcodes.local_get, counter ],
|
2005
|
+
...number(1, Valtype.i32),
|
2006
|
+
[ Opcodes.i32_add ],
|
2007
|
+
[ Opcodes.local_tee, counter ],
|
2008
|
+
|
2009
|
+
// loop if counter != length
|
2010
|
+
[ Opcodes.local_get, length ],
|
2011
|
+
[ Opcodes.i32_ne ],
|
2012
|
+
[ Opcodes.br_if, 1 ],
|
2013
|
+
|
2014
|
+
[ Opcodes.end ], [ Opcodes.end ]
|
2015
|
+
);
|
2016
|
+
depth.pop();
|
2017
|
+
depth.pop();
|
1572
2018
|
|
1573
2019
|
return out;
|
1574
2020
|
};
|
@@ -1659,13 +2105,22 @@ const generateAssignPat = (scope, decl) => {
|
|
1659
2105
|
};
|
1660
2106
|
|
1661
2107
|
let pages = new Map();
|
1662
|
-
const allocPage = reason => {
|
1663
|
-
if (pages.has(reason)) return pages.get(reason);
|
2108
|
+
const allocPage = (reason, type) => {
|
2109
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
2110
|
+
|
2111
|
+
const ind = pages.size;
|
2112
|
+
pages.set(reason, { ind, type });
|
2113
|
+
|
2114
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2115
|
+
|
2116
|
+
return ind;
|
2117
|
+
};
|
1664
2118
|
|
1665
|
-
|
1666
|
-
pages.
|
2119
|
+
const freePage = reason => {
|
2120
|
+
const { ind } = pages.get(reason);
|
2121
|
+
pages.delete(reason);
|
1667
2122
|
|
1668
|
-
if (
|
2123
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1669
2124
|
|
1670
2125
|
return ind;
|
1671
2126
|
};
|
@@ -1679,7 +2134,7 @@ const itemTypeToValtype = {
|
|
1679
2134
|
i16: 'i32'
|
1680
2135
|
};
|
1681
2136
|
|
1682
|
-
const
|
2137
|
+
const StoreOps = {
|
1683
2138
|
i32: Opcodes.i32_store,
|
1684
2139
|
i64: Opcodes.i64_store,
|
1685
2140
|
f64: Opcodes.f64_store,
|
@@ -1688,13 +2143,31 @@ const storeOps = {
|
|
1688
2143
|
i16: Opcodes.i32_store16
|
1689
2144
|
};
|
1690
2145
|
|
2146
|
+
let data = [];
|
2147
|
+
|
2148
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2149
|
+
switch (itemType) {
|
2150
|
+
case 'i8': return [ val % 256 ];
|
2151
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2152
|
+
|
2153
|
+
case 'i32':
|
2154
|
+
case 'i64':
|
2155
|
+
return enforceFourBytes(signedLEB128(val));
|
2156
|
+
|
2157
|
+
case 'f64': return ieee754_binary64(val);
|
2158
|
+
}
|
2159
|
+
};
|
2160
|
+
|
1691
2161
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1692
2162
|
const out = [];
|
1693
2163
|
|
2164
|
+
let firstAssign = false;
|
1694
2165
|
if (!arrays.has(name) || name === '$undeclared') {
|
2166
|
+
firstAssign = true;
|
2167
|
+
|
1695
2168
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1696
2169
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1697
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2170
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1698
2171
|
}
|
1699
2172
|
|
1700
2173
|
const pointer = arrays.get(name);
|
@@ -1702,8 +2175,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1702
2175
|
const useRawElements = !!decl.rawElements;
|
1703
2176
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1704
2177
|
|
2178
|
+
const valtype = itemTypeToValtype[itemType];
|
1705
2179
|
const length = elements.length;
|
1706
2180
|
|
2181
|
+
if (firstAssign && useRawElements) {
|
2182
|
+
let bytes = compileBytes(length, 'i32');
|
2183
|
+
|
2184
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2185
|
+
if (elements[i] == null) continue;
|
2186
|
+
|
2187
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2188
|
+
}
|
2189
|
+
|
2190
|
+
data.push({
|
2191
|
+
offset: pointer,
|
2192
|
+
bytes
|
2193
|
+
});
|
2194
|
+
|
2195
|
+
// local value as pointer
|
2196
|
+
out.push(...number(pointer));
|
2197
|
+
|
2198
|
+
return [ out, pointer ];
|
2199
|
+
}
|
2200
|
+
|
1707
2201
|
// store length as 0th array
|
1708
2202
|
out.push(
|
1709
2203
|
...number(0, Valtype.i32),
|
@@ -1711,8 +2205,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1711
2205
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1712
2206
|
);
|
1713
2207
|
|
1714
|
-
const storeOp =
|
1715
|
-
const valtype = itemTypeToValtype[itemType];
|
2208
|
+
const storeOp = StoreOps[itemType];
|
1716
2209
|
|
1717
2210
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1718
2211
|
if (elements[i] == null) continue;
|
@@ -1727,16 +2220,36 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1727
2220
|
// local value as pointer
|
1728
2221
|
out.push(...number(pointer));
|
1729
2222
|
|
1730
|
-
scope.memory = true;
|
1731
|
-
|
1732
2223
|
return [ out, pointer ];
|
1733
2224
|
};
|
1734
2225
|
|
2226
|
+
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2227
|
+
const rawElements = new Array(str.length);
|
2228
|
+
for (let i = 0; i < str.length; i++) {
|
2229
|
+
rawElements[i] = str.charCodeAt(i);
|
2230
|
+
}
|
2231
|
+
|
2232
|
+
return makeArray(scope, {
|
2233
|
+
rawElements
|
2234
|
+
}, global, name, false, 'i16')[0];
|
2235
|
+
};
|
2236
|
+
|
1735
2237
|
let arrays = new Map();
|
1736
2238
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1737
2239
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1738
2240
|
};
|
1739
2241
|
|
2242
|
+
let varMetadata = new Map();
|
2243
|
+
const addVarMeta = (_name, obj) => {
|
2244
|
+
const name = _name ?? '$undeclared';
|
2245
|
+
if (!varMetadata.has(name)) varMetadata.set(name, {});
|
2246
|
+
|
2247
|
+
const meta = varMetadata.get(name);
|
2248
|
+
for (const k in obj) {
|
2249
|
+
meta[k] = obj[k];
|
2250
|
+
}
|
2251
|
+
};
|
2252
|
+
|
1740
2253
|
export const generateMember = (scope, decl, _global, _name) => {
|
1741
2254
|
const type = getNodeType(scope, decl.object);
|
1742
2255
|
|
@@ -1747,8 +2260,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1747
2260
|
const name = decl.object.name;
|
1748
2261
|
const pointer = arrays.get(name);
|
1749
2262
|
|
1750
|
-
scope.memory = true;
|
1751
|
-
|
1752
2263
|
const aotPointer = pointer != null;
|
1753
2264
|
|
1754
2265
|
return [
|
@@ -1768,8 +2279,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1768
2279
|
const name = decl.object.name;
|
1769
2280
|
const pointer = arrays.get(name);
|
1770
2281
|
|
1771
|
-
scope.memory = true;
|
1772
|
-
|
1773
2282
|
const aotPointer = pointer != null;
|
1774
2283
|
|
1775
2284
|
if (type === TYPES._array) {
|
@@ -1878,15 +2387,14 @@ const generateFunc = (scope, decl) => {
|
|
1878
2387
|
const innerScope = {
|
1879
2388
|
locals: {},
|
1880
2389
|
localInd: 0,
|
1881
|
-
|
1882
|
-
|
2390
|
+
// value, type
|
2391
|
+
returns: [ valtypeBinary, Valtype.i32 ],
|
1883
2392
|
throws: false,
|
1884
2393
|
name
|
1885
2394
|
};
|
1886
2395
|
|
1887
2396
|
for (let i = 0; i < params.length; i++) {
|
1888
|
-
|
1889
|
-
innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
|
2397
|
+
allocVar(innerScope, params[i], false);
|
1890
2398
|
}
|
1891
2399
|
|
1892
2400
|
let body = objectHack(decl.body);
|
@@ -1901,11 +2409,9 @@ const generateFunc = (scope, decl) => {
|
|
1901
2409
|
const wasm = generate(innerScope, body);
|
1902
2410
|
const func = {
|
1903
2411
|
name,
|
1904
|
-
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
2412
|
+
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
1905
2413
|
returns: innerScope.returns,
|
1906
|
-
returnType: innerScope.returnType,
|
1907
2414
|
locals: innerScope.locals,
|
1908
|
-
memory: innerScope.memory,
|
1909
2415
|
throws: innerScope.throws,
|
1910
2416
|
index: currentFuncIndex++
|
1911
2417
|
};
|
@@ -1918,8 +2424,13 @@ const generateFunc = (scope, decl) => {
|
|
1918
2424
|
}
|
1919
2425
|
}
|
1920
2426
|
|
1921
|
-
|
1922
|
-
|
2427
|
+
// add end return if not found
|
2428
|
+
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2429
|
+
wasm.push(
|
2430
|
+
...number(0),
|
2431
|
+
...number(TYPES.undefined, Valtype.i32),
|
2432
|
+
[ Opcodes.return ]
|
2433
|
+
);
|
1923
2434
|
}
|
1924
2435
|
|
1925
2436
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1930,9 +2441,7 @@ const generateFunc = (scope, decl) => {
|
|
1930
2441
|
if (local.type === Valtype.v128) {
|
1931
2442
|
vecParams++;
|
1932
2443
|
|
1933
|
-
/*
|
1934
|
-
|
1935
|
-
wasm.unshift( // add v128 load for param
|
2444
|
+
/* wasm.unshift( // add v128 load for param
|
1936
2445
|
[ Opcodes.i32_const, 0 ],
|
1937
2446
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1938
2447
|
[ Opcodes.local_set, local.idx ]
|
@@ -2043,10 +2552,10 @@ const generateFunc = (scope, decl) => {
|
|
2043
2552
|
};
|
2044
2553
|
|
2045
2554
|
const generateCode = (scope, decl) => {
|
2046
|
-
|
2555
|
+
let out = [];
|
2047
2556
|
|
2048
2557
|
for (const x of decl.body) {
|
2049
|
-
out.
|
2558
|
+
out = out.concat(generate(scope, x));
|
2050
2559
|
}
|
2051
2560
|
|
2052
2561
|
return out;
|
@@ -2082,6 +2591,18 @@ const internalConstrs = {
|
|
2082
2591
|
];
|
2083
2592
|
},
|
2084
2593
|
type: TYPES._array
|
2594
|
+
},
|
2595
|
+
|
2596
|
+
__Array_of: {
|
2597
|
+
// this is not a constructor but best fits internal structure here
|
2598
|
+
generate: (scope, decl, global, name) => {
|
2599
|
+
// Array.of(i0, i1, ...)
|
2600
|
+
return generateArray(scope, {
|
2601
|
+
elements: decl.arguments
|
2602
|
+
}, global, name);
|
2603
|
+
},
|
2604
|
+
type: TYPES._array,
|
2605
|
+
notConstr: true
|
2085
2606
|
}
|
2086
2607
|
};
|
2087
2608
|
|
@@ -2093,9 +2614,10 @@ export default program => {
|
|
2093
2614
|
funcs = [];
|
2094
2615
|
funcIndex = {};
|
2095
2616
|
depth = [];
|
2096
|
-
typeStates = {};
|
2097
2617
|
arrays = new Map();
|
2618
|
+
varMetadata = new Map();
|
2098
2619
|
pages = new Map();
|
2620
|
+
data = [];
|
2099
2621
|
currentFuncIndex = importedFuncs.length;
|
2100
2622
|
|
2101
2623
|
globalThis.valtype = 'f64';
|
@@ -2145,18 +2667,23 @@ export default program => {
|
|
2145
2667
|
body: program.body
|
2146
2668
|
};
|
2147
2669
|
|
2670
|
+
if (process.argv.includes('-ast-log')) console.log(program.body.body);
|
2671
|
+
|
2148
2672
|
generateFunc(scope, program);
|
2149
2673
|
|
2150
2674
|
const main = funcs[funcs.length - 1];
|
2151
2675
|
main.export = true;
|
2152
|
-
main.returns = [ valtypeBinary ];
|
2676
|
+
main.returns = [ valtypeBinary, Valtype.i32 ];
|
2153
2677
|
|
2154
2678
|
const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
|
2155
2679
|
if (lastInst[0] === Opcodes.drop) {
|
2156
2680
|
main.wasm.splice(main.wasm.length - 1, 1);
|
2681
|
+
// main.wasm.splice(main.wasm.length - 2, 2);
|
2157
2682
|
|
2158
2683
|
const finalStatement = program.body.body[program.body.body.length - 1];
|
2159
|
-
main.returnType = getNodeType(main, finalStatement);
|
2684
|
+
// main.returnType = getNodeType(main, finalStatement);
|
2685
|
+
|
2686
|
+
main.wasm.push(...getNodeType(main, finalStatement));
|
2160
2687
|
}
|
2161
2688
|
|
2162
2689
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
@@ -2172,5 +2699,5 @@ export default program => {
|
|
2172
2699
|
// if blank main func and other exports, remove it
|
2173
2700
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2174
2701
|
|
2175
|
-
return { funcs, globals, tags, exceptions, pages };
|
2702
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2176
2703
|
};
|