porffor 0.0.0-425ea20 → 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 +37 -16
- 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 +806 -328
- package/compiler/decompile.js +19 -3
- package/compiler/embedding.js +9 -5
- package/compiler/index.js +48 -6
- package/compiler/opt.js +331 -275
- package/compiler/parse.js +1 -0
- package/compiler/prototype.js +170 -30
- 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 -40
- package/runner/info.js +37 -2
- package/runner/repl.js +1 -1
- package/runner/results.json +1 -0
- package/runner/transform.js +2 -1
- 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;
|
@@ -108,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
108
109
|
case 'WhileStatement':
|
109
110
|
return generateWhile(scope, decl);
|
110
111
|
|
111
|
-
|
112
|
-
return generateForOf(scope, decl);
|
112
|
+
case 'ForOfStatement':
|
113
|
+
return generateForOf(scope, decl);
|
113
114
|
|
114
115
|
case 'BreakStatement':
|
115
116
|
return generateBreak(scope, decl);
|
@@ -151,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
151
152
|
|
152
153
|
return [];
|
153
154
|
|
154
|
-
case 'TaggedTemplateExpression':
|
155
|
-
|
156
|
-
|
155
|
+
case 'TaggedTemplateExpression': {
|
156
|
+
const funcs = {
|
157
|
+
asm: str => {
|
158
|
+
let out = [];
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
+
for (const line of str.split('\n')) {
|
161
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
162
|
+
if (asm[0] === '') continue; // blank
|
160
163
|
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
+
}
|
164
169
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
}
|
170
|
+
if (asm[0] === 'returns') {
|
171
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
172
|
+
continue;
|
173
|
+
}
|
170
174
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
+
if (asm[0] === 'memory') {
|
176
|
+
allocPage('asm instrinsic');
|
177
|
+
// todo: add to store/load offset insts
|
178
|
+
continue;
|
179
|
+
}
|
175
180
|
|
176
|
-
|
177
|
-
|
178
|
-
// todo: add to store/load offset insts
|
179
|
-
continue;
|
180
|
-
}
|
181
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
182
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
181
183
|
|
182
|
-
|
183
|
-
|
184
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
185
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
184
186
|
|
185
|
-
|
186
|
-
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
187
189
|
|
188
|
-
|
190
|
+
return out;
|
191
|
+
},
|
192
|
+
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
195
|
+
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
199
|
+
|
200
|
+
// newline
|
201
|
+
...number(10),
|
202
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
203
|
+
];
|
204
|
+
}
|
189
205
|
}
|
190
206
|
|
191
|
-
|
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
|
+
}
|
192
214
|
|
193
215
|
default:
|
194
216
|
return todo(`no generation for ${decl.type}!`);
|
@@ -278,19 +300,17 @@ const generateIdent = (scope, decl) => {
|
|
278
300
|
|
279
301
|
const generateReturn = (scope, decl) => {
|
280
302
|
if (decl.argument === null) {
|
281
|
-
if (!scope.returnType) scope.returnType = TYPES.undefined;
|
282
|
-
|
283
303
|
// just bare "return"
|
284
304
|
return [
|
285
305
|
...number(UNDEFINED), // "undefined" if func returns
|
306
|
+
...number(TYPES.undefined, Valtype.i32), // type undefined
|
286
307
|
[ Opcodes.return ]
|
287
308
|
];
|
288
309
|
}
|
289
310
|
|
290
|
-
scope.returnType = getNodeType(scope, decl.argument);
|
291
|
-
|
292
311
|
return [
|
293
312
|
...generate(scope, decl.argument),
|
313
|
+
...getNodeType(scope, decl.argument),
|
294
314
|
[ Opcodes.return ]
|
295
315
|
];
|
296
316
|
};
|
@@ -304,6 +324,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
304
324
|
return idx;
|
305
325
|
};
|
306
326
|
|
327
|
+
const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
|
328
|
+
|
307
329
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
308
330
|
const checks = {
|
309
331
|
'||': falsy,
|
@@ -316,15 +338,52 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
316
338
|
// generic structure for {a} OP {b}
|
317
339
|
// -->
|
318
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
|
+
|
319
373
|
return [
|
320
374
|
...left,
|
321
375
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
322
|
-
...checks[op](scope, [], leftType),
|
323
|
-
Opcodes.i32_to,
|
376
|
+
...checks[op](scope, [], leftType, false, true),
|
324
377
|
[ Opcodes.if, valtypeBinary ],
|
325
378
|
...right,
|
379
|
+
// note type
|
380
|
+
...rightType,
|
381
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
326
382
|
[ Opcodes.else ],
|
327
383
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
384
|
+
// note type
|
385
|
+
...leftType,
|
386
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
328
387
|
[ Opcodes.end ]
|
329
388
|
];
|
330
389
|
};
|
@@ -339,6 +398,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
339
398
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
340
399
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
341
400
|
|
401
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
402
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
403
|
+
|
342
404
|
if (assign) {
|
343
405
|
const pointer = arrays.get(name ?? '$undeclared');
|
344
406
|
|
@@ -566,94 +628,106 @@ const compareStrings = (scope, left, right) => {
|
|
566
628
|
];
|
567
629
|
};
|
568
630
|
|
569
|
-
const truthy = (scope, wasm, type) => {
|
570
|
-
|
571
|
-
if (type === TYPES._array) return [
|
631
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
632
|
+
if (isIntOp(wasm[wasm.length - 1])) return [
|
572
633
|
...wasm,
|
573
|
-
|
574
|
-
...number(1)
|
634
|
+
...(!intIn && intOut ? Opcodes.i32_to_u : [])
|
575
635
|
];
|
576
636
|
|
577
|
-
|
578
|
-
// if not "" (length = 0)
|
579
|
-
return [
|
580
|
-
// pointer
|
581
|
-
...wasm,
|
582
|
-
Opcodes.i32_to_u,
|
583
|
-
|
584
|
-
// get length
|
585
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
586
|
-
|
587
|
-
// if length != 0
|
588
|
-
/* [ Opcodes.i32_eqz ],
|
589
|
-
[ Opcodes.i32_eqz ], */
|
590
|
-
Opcodes.i32_from_u
|
591
|
-
]
|
592
|
-
}
|
593
|
-
|
594
|
-
// if != 0
|
637
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
595
638
|
return [
|
596
639
|
...wasm,
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
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)
|
601
671
|
];
|
602
672
|
};
|
603
673
|
|
604
|
-
const falsy = (scope, wasm, type) => {
|
605
|
-
|
606
|
-
if (type === TYPES._array) return [
|
607
|
-
...wasm,
|
608
|
-
[ Opcodes.drop ],
|
609
|
-
...number(0)
|
610
|
-
];
|
611
|
-
|
612
|
-
if (type === TYPES.string) {
|
613
|
-
// if "" (length = 0)
|
614
|
-
return [
|
615
|
-
// pointer
|
616
|
-
...wasm,
|
617
|
-
Opcodes.i32_to_u,
|
618
|
-
|
619
|
-
// get length
|
620
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
621
|
-
|
622
|
-
// if length == 0
|
623
|
-
[ Opcodes.i32_eqz ],
|
624
|
-
Opcodes.i32_from_u
|
625
|
-
]
|
626
|
-
}
|
627
|
-
|
628
|
-
// if = 0
|
674
|
+
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
675
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
629
676
|
return [
|
630
677
|
...wasm,
|
631
|
-
|
632
|
-
|
633
|
-
|
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)
|
634
705
|
];
|
635
706
|
};
|
636
707
|
|
637
|
-
const nullish = (scope, wasm, type) => {
|
638
|
-
|
639
|
-
if (type === TYPES.undefined) return [
|
640
|
-
...wasm,
|
641
|
-
[ Opcodes.drop ],
|
642
|
-
...number(1)
|
643
|
-
];
|
644
|
-
|
645
|
-
// null (if object and = "0")
|
646
|
-
if (type === TYPES.object) return [
|
647
|
-
...wasm,
|
648
|
-
...Opcodes.eqz,
|
649
|
-
Opcodes.i32_from_u
|
650
|
-
];
|
651
|
-
|
652
|
-
// not
|
708
|
+
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
709
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
710
|
return [
|
654
711
|
...wasm,
|
655
|
-
[ Opcodes.
|
656
|
-
|
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)
|
657
731
|
];
|
658
732
|
};
|
659
733
|
|
@@ -662,31 +736,35 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
662
736
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
663
737
|
}
|
664
738
|
|
665
|
-
|
739
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
666
740
|
|
667
|
-
|
668
|
-
|
669
|
-
return [
|
670
|
-
...left,
|
671
|
-
...right,
|
741
|
+
const startOut = [], endOut = [];
|
742
|
+
const finalise = out => startOut.concat(out, endOut);
|
672
743
|
|
673
|
-
|
674
|
-
|
675
|
-
|
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
|
+
);
|
676
751
|
|
677
|
-
|
678
|
-
|
679
|
-
|
752
|
+
endOut.push(
|
753
|
+
[ Opcodes.i32_and ]
|
754
|
+
);
|
680
755
|
}
|
681
756
|
|
757
|
+
// todo: if equality op and an operand is undefined, return false
|
758
|
+
// todo: niche null hell with 0
|
759
|
+
|
682
760
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
683
761
|
if (op === '+') {
|
684
762
|
// string concat (a + b)
|
685
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
763
|
+
return finalise(concatStrings(scope, left, right, _global, _name, assign));
|
686
764
|
}
|
687
765
|
|
688
|
-
//
|
689
|
-
if (!
|
766
|
+
// not an equality op, NaN
|
767
|
+
if (!eqOp) return finalise(number(NaN));
|
690
768
|
|
691
769
|
// else leave bool ops
|
692
770
|
// todo: convert string to number if string and number/bool
|
@@ -694,14 +772,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
694
772
|
|
695
773
|
// string comparison
|
696
774
|
if (op === '===' || op === '==') {
|
697
|
-
return compareStrings(scope, left, right);
|
775
|
+
return finalise(compareStrings(scope, left, right));
|
698
776
|
}
|
699
777
|
|
700
778
|
if (op === '!==' || op === '!=') {
|
701
|
-
return [
|
779
|
+
return finalise([
|
702
780
|
...compareStrings(scope, left, right),
|
703
781
|
[ Opcodes.i32_eqz ]
|
704
|
-
];
|
782
|
+
]);
|
705
783
|
}
|
706
784
|
}
|
707
785
|
|
@@ -713,39 +791,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
713
791
|
includeBuiltin(scope, builtinName);
|
714
792
|
const idx = funcIndex[builtinName];
|
715
793
|
|
716
|
-
return [
|
794
|
+
return finalise([
|
717
795
|
...left,
|
718
796
|
...right,
|
719
797
|
[ Opcodes.call, idx ]
|
720
|
-
];
|
798
|
+
]);
|
721
799
|
}
|
722
800
|
|
723
801
|
if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
|
724
802
|
|
725
803
|
if (!Array.isArray(ops)) ops = [ ops ];
|
726
804
|
|
727
|
-
return [
|
805
|
+
return finalise([
|
728
806
|
...left,
|
729
807
|
...right,
|
730
808
|
ops
|
731
|
-
];
|
809
|
+
]);
|
732
810
|
};
|
733
811
|
|
734
|
-
let binaryExpDepth = 0;
|
735
812
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
736
|
-
|
737
|
-
|
738
|
-
const out = [
|
739
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
740
|
-
];
|
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);
|
741
814
|
|
742
815
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
743
816
|
|
744
|
-
binaryExpDepth--;
|
745
817
|
return out;
|
746
818
|
};
|
747
819
|
|
748
|
-
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 = [] }) => {
|
749
821
|
const existing = funcs.find(x => x.name === name);
|
750
822
|
if (existing) return existing;
|
751
823
|
|
@@ -781,7 +853,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
781
853
|
returns,
|
782
854
|
returnType: TYPES[returnType ?? 'number'],
|
783
855
|
wasm,
|
784
|
-
memory,
|
785
856
|
internal: true,
|
786
857
|
index: currentFuncIndex++
|
787
858
|
};
|
@@ -804,17 +875,18 @@ const generateLogicExp = (scope, decl) => {
|
|
804
875
|
};
|
805
876
|
|
806
877
|
const TYPES = {
|
807
|
-
number:
|
808
|
-
boolean:
|
809
|
-
string:
|
810
|
-
undefined:
|
811
|
-
object:
|
812
|
-
function:
|
813
|
-
symbol:
|
814
|
-
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,
|
815
886
|
|
816
887
|
// these are not "typeof" types but tracked internally
|
817
|
-
_array:
|
888
|
+
_array: 0x10,
|
889
|
+
_regexp: 0x11
|
818
890
|
};
|
819
891
|
|
820
892
|
const TYPE_NAMES = {
|
@@ -830,101 +902,156 @@ const TYPE_NAMES = {
|
|
830
902
|
[TYPES._array]: 'Array'
|
831
903
|
};
|
832
904
|
|
833
|
-
let typeStates = {};
|
834
|
-
|
835
905
|
const getType = (scope, _name) => {
|
836
906
|
const name = mapName(_name);
|
837
|
-
if (scope.locals[name]) return typeStates[name];
|
838
907
|
|
839
|
-
if (
|
840
|
-
if (
|
841
|
-
|
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;
|
842
914
|
|
843
|
-
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]
|
844
|
-
|
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;
|
845
917
|
|
846
|
-
return
|
918
|
+
return number(type, Valtype.i32);
|
847
919
|
};
|
848
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
|
+
|
849
939
|
const getNodeType = (scope, node) => {
|
850
|
-
|
851
|
-
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
852
|
-
return TYPES[typeof node.value];
|
853
|
-
}
|
940
|
+
// console.trace(node);
|
854
941
|
|
855
|
-
|
856
|
-
|
857
|
-
|
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;
|
858
946
|
|
859
|
-
|
860
|
-
|
861
|
-
}
|
947
|
+
return TYPES[typeof node.value];
|
948
|
+
}
|
862
949
|
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
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
|
+
}
|
867
972
|
|
868
|
-
|
869
|
-
|
973
|
+
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
974
|
+
if (internalConstrs[name]) return internalConstrs[name].type;
|
870
975
|
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
976
|
+
let protoFunc;
|
977
|
+
// ident.func()
|
978
|
+
if (name && name.startsWith('__')) {
|
979
|
+
const spl = name.slice(2).split('_');
|
875
980
|
|
876
|
-
|
877
|
-
|
981
|
+
const baseName = spl.slice(0, -1).join('_');
|
982
|
+
const baseType = getType(scope, baseName);
|
878
983
|
|
879
|
-
|
880
|
-
|
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;
|
881
1002
|
}
|
882
1003
|
|
883
|
-
|
884
|
-
|
885
|
-
|
1004
|
+
if (node.type === 'ExpressionStatement') {
|
1005
|
+
return getNodeType(scope, node.expression);
|
1006
|
+
}
|
886
1007
|
|
887
|
-
|
888
|
-
|
1008
|
+
if (node.type === 'AssignmentExpression') {
|
1009
|
+
return getNodeType(scope, node.right);
|
889
1010
|
}
|
890
1011
|
|
891
|
-
if (
|
892
|
-
|
1012
|
+
if (node.type === 'ArrayExpression') {
|
1013
|
+
return TYPES._array;
|
1014
|
+
}
|
893
1015
|
|
894
|
-
|
895
|
-
|
896
|
-
}
|
1016
|
+
if (node.type === 'BinaryExpression') {
|
1017
|
+
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
897
1018
|
|
898
|
-
|
899
|
-
|
900
|
-
}
|
1019
|
+
if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
|
1020
|
+
}
|
901
1021
|
|
902
|
-
|
903
|
-
|
904
|
-
|
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
|
+
}
|
905
1028
|
|
906
|
-
|
907
|
-
|
1029
|
+
if (node.type === 'MemberExpression') {
|
1030
|
+
const objectType = getNodeType(scope, node.object);
|
908
1031
|
|
909
|
-
|
910
|
-
|
1032
|
+
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
1033
|
+
}
|
911
1034
|
|
912
|
-
|
913
|
-
if (node.operator === '!') return TYPES.boolean;
|
914
|
-
if (node.operator === 'void') return TYPES.undefined;
|
915
|
-
if (node.operator === 'delete') return TYPES.boolean;
|
916
|
-
}
|
1035
|
+
if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
917
1036
|
|
918
|
-
|
919
|
-
|
1037
|
+
// presume
|
1038
|
+
// todo: warn here?
|
1039
|
+
return TYPES.number;
|
1040
|
+
};
|
920
1041
|
|
921
|
-
|
922
|
-
|
1042
|
+
const ret = inner();
|
1043
|
+
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1044
|
+
return ret;
|
923
1045
|
};
|
924
1046
|
|
925
1047
|
const generateLiteral = (scope, decl, global, name) => {
|
926
1048
|
if (decl.value === null) return number(NULL);
|
927
1049
|
|
1050
|
+
if (decl.regex) {
|
1051
|
+
scope.regex[name] = decl.regex;
|
1052
|
+
return number(1);
|
1053
|
+
}
|
1054
|
+
|
928
1055
|
switch (typeof decl.value) {
|
929
1056
|
case 'number':
|
930
1057
|
return number(decl.value);
|
@@ -935,23 +1062,49 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
935
1062
|
|
936
1063
|
case 'string':
|
937
1064
|
// this is a terrible hack which changes type strings ("number" etc) to known const number values
|
938
|
-
switch (decl.value) {
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
}
|
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;
|
948
1078
|
|
949
1079
|
const str = decl.value;
|
950
1080
|
const rawElements = new Array(str.length);
|
1081
|
+
let j = 0;
|
951
1082
|
for (let i = 0; i < str.length; i++) {
|
952
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
|
+
}
|
953
1102
|
}
|
954
1103
|
|
1104
|
+
// console.log(wellFormed, str);
|
1105
|
+
|
1106
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1107
|
+
|
955
1108
|
return makeArray(scope, {
|
956
1109
|
rawElements
|
957
1110
|
}, global, name, false, 'i16')[0];
|
@@ -964,6 +1117,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
964
1117
|
const countLeftover = wasm => {
|
965
1118
|
let count = 0, depth = 0;
|
966
1119
|
|
1120
|
+
// console.trace(wasm.length);
|
1121
|
+
|
967
1122
|
for (let i = 0; i < wasm.length; i++) {
|
968
1123
|
const inst = wasm[i];
|
969
1124
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
@@ -974,11 +1129,12 @@ const countLeftover = wasm => {
|
|
974
1129
|
if (inst[0] === Opcodes.end) depth--;
|
975
1130
|
|
976
1131
|
if (depth === 0)
|
977
|
-
if ([Opcodes.throw,
|
1132
|
+
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
978
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)) {}
|
979
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++;
|
980
1135
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
981
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;
|
982
1138
|
else if (inst[0] === Opcodes.call) {
|
983
1139
|
let func = funcs.find(x => x.index === inst[1]);
|
984
1140
|
if (func) {
|
@@ -986,6 +1142,8 @@ const countLeftover = wasm => {
|
|
986
1142
|
} else count--;
|
987
1143
|
if (func) count += func.returns.length;
|
988
1144
|
} else count--;
|
1145
|
+
|
1146
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
989
1147
|
}
|
990
1148
|
|
991
1149
|
return count;
|
@@ -997,6 +1155,13 @@ const disposeLeftover = wasm => {
|
|
997
1155
|
for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
|
998
1156
|
};
|
999
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
|
+
|
1000
1165
|
const generateExp = (scope, decl) => {
|
1001
1166
|
const expression = decl.expression;
|
1002
1167
|
|
@@ -1084,6 +1249,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1084
1249
|
|
1085
1250
|
// literal.func()
|
1086
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
|
+
|
1087
1271
|
baseType = getNodeType(scope, decl.callee.object);
|
1088
1272
|
|
1089
1273
|
const func = decl.callee.property.name;
|
@@ -1096,6 +1280,31 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1096
1280
|
baseName = [...arrays.keys()].pop();
|
1097
1281
|
}
|
1098
1282
|
|
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);
|
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
|
+
|
1099
1308
|
if (protoFunc) {
|
1100
1309
|
let pointer = arrays.get(baseName);
|
1101
1310
|
|
@@ -1124,28 +1333,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1124
1333
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1125
1334
|
|
1126
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;
|
1127
1337
|
|
1128
1338
|
// use local for cached i32 length as commonly used
|
1129
1339
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1130
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
|
+
|
1131
1359
|
return [
|
1132
1360
|
...out,
|
1133
1361
|
|
1134
|
-
...
|
1135
|
-
|
1362
|
+
...(!lengthI32CacheUsed ? [] : [
|
1363
|
+
...arrayUtil.getLengthI32(pointer),
|
1364
|
+
[ Opcodes.local_set, lengthLocal ],
|
1365
|
+
]),
|
1136
1366
|
|
1137
1367
|
[ Opcodes.block, valtypeBinary ],
|
1138
|
-
...
|
1139
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1140
|
-
get: arrayUtil.getLength(pointer),
|
1141
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1142
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1143
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1144
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1145
|
-
return makeArray(scope, {
|
1146
|
-
rawElements: new Array(length)
|
1147
|
-
}, _global, _name, true, itemType);
|
1148
|
-
}),
|
1368
|
+
...protoOut,
|
1149
1369
|
[ Opcodes.end ]
|
1150
1370
|
];
|
1151
1371
|
}
|
@@ -1191,32 +1411,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1191
1411
|
|
1192
1412
|
const func = funcs.find(x => x.index === idx);
|
1193
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
|
+
|
1194
1417
|
let args = decl.arguments;
|
1195
|
-
if (func && args.length <
|
1418
|
+
if (func && args.length < paramCount) {
|
1196
1419
|
// too little args, push undefineds
|
1197
|
-
args = args.concat(new Array(
|
1420
|
+
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1198
1421
|
}
|
1199
1422
|
|
1200
|
-
if (func && args.length >
|
1423
|
+
if (func && args.length > paramCount) {
|
1201
1424
|
// too many args, slice extras off
|
1202
|
-
args = args.slice(0,
|
1425
|
+
args = args.slice(0, paramCount);
|
1203
1426
|
}
|
1204
1427
|
|
1205
1428
|
if (func && func.throws) scope.throws = true;
|
1206
1429
|
|
1207
1430
|
for (const arg of args) {
|
1208
|
-
out.
|
1431
|
+
out = out.concat(generate(scope, arg));
|
1432
|
+
if (userFunc) out = out.concat(getNodeType(scope, arg));
|
1209
1433
|
}
|
1210
1434
|
|
1211
1435
|
out.push([ Opcodes.call, idx ]);
|
1212
1436
|
|
1437
|
+
// if user func, they return [ value, type ]
|
1438
|
+
if (userFunc) out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
|
1439
|
+
|
1213
1440
|
return out;
|
1214
1441
|
};
|
1215
1442
|
|
1216
1443
|
const generateNew = (scope, decl, _global, _name) => {
|
1217
1444
|
// hack: basically treat this as a normal call for builtins for now
|
1218
1445
|
const name = mapName(decl.callee.name);
|
1219
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1446
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1220
1447
|
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1221
1448
|
|
1222
1449
|
return generateCall(scope, decl, _global, _name);
|
@@ -1233,13 +1460,67 @@ const unhackName = name => {
|
|
1233
1460
|
return name;
|
1234
1461
|
};
|
1235
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
|
+
|
1236
1517
|
const generateVar = (scope, decl) => {
|
1237
|
-
|
1518
|
+
let out = [];
|
1238
1519
|
|
1239
1520
|
const topLevel = scope.name === 'main';
|
1240
1521
|
|
1241
1522
|
// global variable if in top scope (main) and var ..., or if wanted
|
1242
|
-
const global = decl.kind === 'var';
|
1523
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1243
1524
|
const target = global ? globals : scope.locals;
|
1244
1525
|
|
1245
1526
|
for (const x of decl.declarations) {
|
@@ -1260,43 +1541,13 @@ const generateVar = (scope, decl) => {
|
|
1260
1541
|
continue; // always ignore
|
1261
1542
|
}
|
1262
1543
|
|
1263
|
-
let idx;
|
1264
|
-
// already declared
|
1265
|
-
if (target[name]) {
|
1266
|
-
// parser should catch this but sanity check anyway
|
1267
|
-
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1544
|
+
let idx = allocVar(scope, name, global);
|
1268
1545
|
|
1269
|
-
|
1270
|
-
} else {
|
1271
|
-
idx = global ? globalInd++ : scope.localInd++;
|
1272
|
-
target[name] = { idx, type: valtypeBinary };
|
1273
|
-
}
|
1274
|
-
|
1275
|
-
typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
|
1546
|
+
out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1276
1547
|
|
1277
1548
|
// x.init ??= DEFAULT_VALUE;
|
1278
1549
|
if (x.init) {
|
1279
|
-
out.
|
1280
|
-
|
1281
|
-
// if our value is the result of a function, infer the type from that func's return value
|
1282
|
-
if (out[out.length - 1][0] === Opcodes.call) {
|
1283
|
-
const ind = out[out.length - 1][1];
|
1284
|
-
if (ind >= importedFuncs.length) { // not an imported func
|
1285
|
-
const func = funcs.find(x => x.index === ind);
|
1286
|
-
if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
|
1287
|
-
|
1288
|
-
const returns = func.returns;
|
1289
|
-
if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
|
1290
|
-
|
1291
|
-
target[name].type = func.returns[0];
|
1292
|
-
if (target[name].type === Valtype.v128) {
|
1293
|
-
// specify vec subtype inferred from first vec type in function name
|
1294
|
-
target[name].vecType = func.name.split('_').find(x => x.includes('x'));
|
1295
|
-
}
|
1296
|
-
} else {
|
1297
|
-
// we do not have imports that return yet, ignore for now
|
1298
|
-
}
|
1299
|
-
}
|
1550
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1300
1551
|
|
1301
1552
|
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1302
1553
|
}
|
@@ -1345,13 +1596,60 @@ const generateAssign = (scope, decl) => {
|
|
1345
1596
|
];
|
1346
1597
|
}
|
1347
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
|
+
|
1348
1646
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1349
1647
|
|
1350
1648
|
if (local === undefined) {
|
1351
|
-
// todo: this should be a
|
1649
|
+
// todo: this should be a sloppy mode only thing
|
1352
1650
|
|
1353
1651
|
// only allow = for this
|
1354
|
-
if (
|
1652
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1355
1653
|
|
1356
1654
|
if (builtinVars[name]) {
|
1357
1655
|
// just return rhs (eg `NaN = 2`)
|
@@ -1360,14 +1658,12 @@ const generateAssign = (scope, decl) => {
|
|
1360
1658
|
|
1361
1659
|
// set global and return (eg a = 2)
|
1362
1660
|
return [
|
1363
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1661
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1364
1662
|
[ Opcodes.global_get, globals[name].idx ]
|
1365
1663
|
];
|
1366
1664
|
}
|
1367
1665
|
|
1368
|
-
if (
|
1369
|
-
typeStates[name] = getNodeType(scope, decl.right);
|
1370
|
-
|
1666
|
+
if (op === '=') {
|
1371
1667
|
return [
|
1372
1668
|
...generate(scope, decl.right, isGlobal, name),
|
1373
1669
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
@@ -1375,7 +1671,6 @@ const generateAssign = (scope, decl) => {
|
|
1375
1671
|
];
|
1376
1672
|
}
|
1377
1673
|
|
1378
|
-
const op = decl.operator.slice(0, -1);
|
1379
1674
|
if (op === '||' || op === '&&' || op === '??') {
|
1380
1675
|
// todo: is this needed?
|
1381
1676
|
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
@@ -1422,7 +1717,7 @@ const generateUnary = (scope, decl) => {
|
|
1422
1717
|
|
1423
1718
|
case '!':
|
1424
1719
|
// !=
|
1425
|
-
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1720
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
1426
1721
|
|
1427
1722
|
case '~':
|
1428
1723
|
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
@@ -1467,11 +1762,16 @@ const generateUnary = (scope, decl) => {
|
|
1467
1762
|
return out;
|
1468
1763
|
|
1469
1764
|
case 'typeof':
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
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
|
+
});
|
1475
1775
|
|
1476
1776
|
default:
|
1477
1777
|
return todo(`unary operator ${decl.operator} not implemented yet`);
|
@@ -1510,9 +1810,9 @@ const generateUpdate = (scope, decl) => {
|
|
1510
1810
|
};
|
1511
1811
|
|
1512
1812
|
const generateIf = (scope, decl) => {
|
1513
|
-
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);
|
1514
1814
|
|
1515
|
-
out.push(
|
1815
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
1516
1816
|
depth.push('if');
|
1517
1817
|
|
1518
1818
|
const consOut = generate(scope, decl.consequent);
|
@@ -1541,9 +1841,21 @@ const generateConditional = (scope, decl) => {
|
|
1541
1841
|
|
1542
1842
|
out.push(...generate(scope, decl.consequent));
|
1543
1843
|
|
1844
|
+
// note type
|
1845
|
+
out.push(
|
1846
|
+
...getNodeType(scope, decl.consequent),
|
1847
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1848
|
+
);
|
1849
|
+
|
1544
1850
|
out.push([ Opcodes.else ]);
|
1545
1851
|
out.push(...generate(scope, decl.alternate));
|
1546
1852
|
|
1853
|
+
// note type
|
1854
|
+
out.push(
|
1855
|
+
...getNodeType(scope, decl.alternate),
|
1856
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1857
|
+
);
|
1858
|
+
|
1547
1859
|
out.push([ Opcodes.end ]);
|
1548
1860
|
depth.pop();
|
1549
1861
|
|
@@ -1603,18 +1915,106 @@ const generateWhile = (scope, decl) => {
|
|
1603
1915
|
const generateForOf = (scope, decl) => {
|
1604
1916
|
const out = [];
|
1605
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
|
+
|
1606
1938
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
1607
|
-
depth.push('
|
1939
|
+
depth.push('forof');
|
1608
1940
|
|
1609
|
-
|
1610
|
-
|
1611
|
-
depth.push('if');
|
1941
|
+
// setup local for left
|
1942
|
+
generate(scope, decl.left);
|
1612
1943
|
|
1613
|
-
|
1944
|
+
const leftName = decl.left.declarations[0].id.name;
|
1614
1945
|
|
1615
|
-
|
1616
|
-
out.push(
|
1617
|
-
|
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();
|
1618
2018
|
|
1619
2019
|
return out;
|
1620
2020
|
};
|
@@ -1705,19 +2105,19 @@ const generateAssignPat = (scope, decl) => {
|
|
1705
2105
|
};
|
1706
2106
|
|
1707
2107
|
let pages = new Map();
|
1708
|
-
const allocPage = reason => {
|
1709
|
-
if (pages.has(reason)) return pages.get(reason);
|
2108
|
+
const allocPage = (reason, type) => {
|
2109
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
1710
2110
|
|
1711
|
-
|
1712
|
-
pages.set(reason, ind);
|
2111
|
+
const ind = pages.size;
|
2112
|
+
pages.set(reason, { ind, type });
|
1713
2113
|
|
1714
|
-
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
2114
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
1715
2115
|
|
1716
2116
|
return ind;
|
1717
2117
|
};
|
1718
2118
|
|
1719
2119
|
const freePage = reason => {
|
1720
|
-
|
2120
|
+
const { ind } = pages.get(reason);
|
1721
2121
|
pages.delete(reason);
|
1722
2122
|
|
1723
2123
|
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
@@ -1734,7 +2134,7 @@ const itemTypeToValtype = {
|
|
1734
2134
|
i16: 'i32'
|
1735
2135
|
};
|
1736
2136
|
|
1737
|
-
const
|
2137
|
+
const StoreOps = {
|
1738
2138
|
i32: Opcodes.i32_store,
|
1739
2139
|
i64: Opcodes.i64_store,
|
1740
2140
|
f64: Opcodes.f64_store,
|
@@ -1743,13 +2143,31 @@ const storeOps = {
|
|
1743
2143
|
i16: Opcodes.i32_store16
|
1744
2144
|
};
|
1745
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
|
+
|
1746
2161
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1747
2162
|
const out = [];
|
1748
2163
|
|
2164
|
+
let firstAssign = false;
|
1749
2165
|
if (!arrays.has(name) || name === '$undeclared') {
|
2166
|
+
firstAssign = true;
|
2167
|
+
|
1750
2168
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1751
2169
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1752
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2170
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1753
2171
|
}
|
1754
2172
|
|
1755
2173
|
const pointer = arrays.get(name);
|
@@ -1757,8 +2175,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1757
2175
|
const useRawElements = !!decl.rawElements;
|
1758
2176
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1759
2177
|
|
2178
|
+
const valtype = itemTypeToValtype[itemType];
|
1760
2179
|
const length = elements.length;
|
1761
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
|
+
|
1762
2201
|
// store length as 0th array
|
1763
2202
|
out.push(
|
1764
2203
|
...number(0, Valtype.i32),
|
@@ -1766,8 +2205,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1766
2205
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1767
2206
|
);
|
1768
2207
|
|
1769
|
-
const storeOp =
|
1770
|
-
const valtype = itemTypeToValtype[itemType];
|
2208
|
+
const storeOp = StoreOps[itemType];
|
1771
2209
|
|
1772
2210
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1773
2211
|
if (elements[i] == null) continue;
|
@@ -1785,11 +2223,33 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1785
2223
|
return [ out, pointer ];
|
1786
2224
|
};
|
1787
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
|
+
|
1788
2237
|
let arrays = new Map();
|
1789
2238
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1790
2239
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1791
2240
|
};
|
1792
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
|
+
|
1793
2253
|
export const generateMember = (scope, decl, _global, _name) => {
|
1794
2254
|
const type = getNodeType(scope, decl.object);
|
1795
2255
|
|
@@ -1927,16 +2387,14 @@ const generateFunc = (scope, decl) => {
|
|
1927
2387
|
const innerScope = {
|
1928
2388
|
locals: {},
|
1929
2389
|
localInd: 0,
|
1930
|
-
|
1931
|
-
|
1932
|
-
memory: false,
|
2390
|
+
// value, type
|
2391
|
+
returns: [ valtypeBinary, Valtype.i32 ],
|
1933
2392
|
throws: false,
|
1934
2393
|
name
|
1935
2394
|
};
|
1936
2395
|
|
1937
2396
|
for (let i = 0; i < params.length; i++) {
|
1938
|
-
|
1939
|
-
innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
|
2397
|
+
allocVar(innerScope, params[i], false);
|
1940
2398
|
}
|
1941
2399
|
|
1942
2400
|
let body = objectHack(decl.body);
|
@@ -1951,9 +2409,8 @@ const generateFunc = (scope, decl) => {
|
|
1951
2409
|
const wasm = generate(innerScope, body);
|
1952
2410
|
const func = {
|
1953
2411
|
name,
|
1954
|
-
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),
|
1955
2413
|
returns: innerScope.returns,
|
1956
|
-
returnType: innerScope.returnType,
|
1957
2414
|
locals: innerScope.locals,
|
1958
2415
|
throws: innerScope.throws,
|
1959
2416
|
index: currentFuncIndex++
|
@@ -1967,10 +2424,13 @@ const generateFunc = (scope, decl) => {
|
|
1967
2424
|
}
|
1968
2425
|
}
|
1969
2426
|
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
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
|
+
);
|
1974
2434
|
}
|
1975
2435
|
|
1976
2436
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -2092,10 +2552,10 @@ const generateFunc = (scope, decl) => {
|
|
2092
2552
|
};
|
2093
2553
|
|
2094
2554
|
const generateCode = (scope, decl) => {
|
2095
|
-
|
2555
|
+
let out = [];
|
2096
2556
|
|
2097
2557
|
for (const x of decl.body) {
|
2098
|
-
out.
|
2558
|
+
out = out.concat(generate(scope, x));
|
2099
2559
|
}
|
2100
2560
|
|
2101
2561
|
return out;
|
@@ -2131,6 +2591,18 @@ const internalConstrs = {
|
|
2131
2591
|
];
|
2132
2592
|
},
|
2133
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
|
2134
2606
|
}
|
2135
2607
|
};
|
2136
2608
|
|
@@ -2142,9 +2614,10 @@ export default program => {
|
|
2142
2614
|
funcs = [];
|
2143
2615
|
funcIndex = {};
|
2144
2616
|
depth = [];
|
2145
|
-
typeStates = {};
|
2146
2617
|
arrays = new Map();
|
2618
|
+
varMetadata = new Map();
|
2147
2619
|
pages = new Map();
|
2620
|
+
data = [];
|
2148
2621
|
currentFuncIndex = importedFuncs.length;
|
2149
2622
|
|
2150
2623
|
globalThis.valtype = 'f64';
|
@@ -2194,18 +2667,23 @@ export default program => {
|
|
2194
2667
|
body: program.body
|
2195
2668
|
};
|
2196
2669
|
|
2670
|
+
if (process.argv.includes('-ast-log')) console.log(program.body.body);
|
2671
|
+
|
2197
2672
|
generateFunc(scope, program);
|
2198
2673
|
|
2199
2674
|
const main = funcs[funcs.length - 1];
|
2200
2675
|
main.export = true;
|
2201
|
-
main.returns = [ valtypeBinary ];
|
2676
|
+
main.returns = [ valtypeBinary, Valtype.i32 ];
|
2202
2677
|
|
2203
2678
|
const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
|
2204
2679
|
if (lastInst[0] === Opcodes.drop) {
|
2205
2680
|
main.wasm.splice(main.wasm.length - 1, 1);
|
2681
|
+
// main.wasm.splice(main.wasm.length - 2, 2);
|
2206
2682
|
|
2207
2683
|
const finalStatement = program.body.body[program.body.body.length - 1];
|
2208
|
-
main.returnType = getNodeType(main, finalStatement);
|
2684
|
+
// main.returnType = getNodeType(main, finalStatement);
|
2685
|
+
|
2686
|
+
main.wasm.push(...getNodeType(main, finalStatement));
|
2209
2687
|
}
|
2210
2688
|
|
2211
2689
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
@@ -2221,5 +2699,5 @@ export default program => {
|
|
2221
2699
|
// if blank main func and other exports, remove it
|
2222
2700
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2223
2701
|
|
2224
|
-
return { funcs, globals, tags, exceptions, pages };
|
2702
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2225
2703
|
};
|