porffor 0.2.0-47b4f2e → 0.2.0-4b72c49
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 +89 -47
- package/compiler/2c.js +321 -71
- package/compiler/builtins/base64.ts +153 -0
- package/compiler/builtins/porffor.d.ts +23 -0
- package/compiler/builtins.js +219 -204
- package/compiler/codeGen.js +635 -301
- package/compiler/decompile.js +11 -12
- package/compiler/encoding.js +2 -116
- package/compiler/generated_builtins.js +15 -0
- package/compiler/index.js +22 -22
- package/compiler/opt.js +61 -26
- package/compiler/parse.js +13 -12
- package/compiler/precompile.js +129 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +177 -21
- package/compiler/sections.js +8 -7
- package/compiler/wasmSpec.js +15 -3
- package/compiler/wrap.js +103 -9
- package/demo.js +15 -0
- package/package.json +1 -1
- package/porf +2 -0
- package/rhemyn/compile.js +2 -1
- package/runner/index.js +20 -3
- package/runner/repl.js +2 -2
- package/compiler/builtins/base64.js +0 -92
- package/tmp.c +0 -69
package/compiler/codeGen.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } 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";
|
@@ -7,6 +7,7 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
10
11
|
|
11
12
|
let globals = {};
|
12
13
|
let globalInd = 0;
|
@@ -55,7 +56,7 @@ const todo = msg => {
|
|
55
56
|
};
|
56
57
|
|
57
58
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
59
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
60
|
switch (decl.type) {
|
60
61
|
case 'BinaryExpression':
|
61
62
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +69,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
69
|
|
69
70
|
case 'ArrowFunctionExpression':
|
70
71
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
72
|
+
const func = generateFunc(scope, decl);
|
73
|
+
|
74
|
+
if (decl.type.endsWith('Expression')) {
|
75
|
+
return number(func.index);
|
76
|
+
}
|
77
|
+
|
72
78
|
return [];
|
73
79
|
|
74
80
|
case 'BlockStatement':
|
@@ -81,7 +87,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
87
|
return generateExp(scope, decl);
|
82
88
|
|
83
89
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
90
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
91
|
|
86
92
|
case 'NewExpression':
|
87
93
|
return generateNew(scope, decl, global, name);
|
@@ -155,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
155
161
|
|
156
162
|
case 'TaggedTemplateExpression': {
|
157
163
|
const funcs = {
|
158
|
-
|
164
|
+
__Porffor_wasm: str => {
|
159
165
|
let out = [];
|
160
166
|
|
161
167
|
for (const line of str.split('\n')) {
|
@@ -163,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
169
|
if (asm[0] === '') continue; // blank
|
164
170
|
|
165
171
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
172
|
+
const [ name, type ] = asm.slice(1);
|
173
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
174
|
continue;
|
169
175
|
}
|
170
176
|
|
@@ -174,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
180
|
}
|
175
181
|
|
176
182
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
183
|
+
allocPage(scope, 'asm instrinsic');
|
178
184
|
// todo: add to store/load offset insts
|
179
185
|
continue;
|
180
186
|
}
|
@@ -183,7 +189,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
183
189
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
190
|
|
185
191
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
const immediates = asm.slice(1).map(x =>
|
192
|
+
const immediates = asm.slice(1).map(x => {
|
193
|
+
const int = parseInt(x);
|
194
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
195
|
+
return int;
|
196
|
+
});
|
187
197
|
|
188
198
|
out.push([ ...inst, ...immediates ]);
|
189
199
|
}
|
@@ -191,29 +201,43 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
191
201
|
return out;
|
192
202
|
},
|
193
203
|
|
194
|
-
|
195
|
-
|
204
|
+
__Porffor_bs: str => [
|
205
|
+
...makeString(scope, str, undefined, undefined, true),
|
196
206
|
|
197
|
-
|
198
|
-
|
199
|
-
|
207
|
+
...number(TYPES._bytestring, Valtype.i32),
|
208
|
+
setLastType(scope)
|
209
|
+
],
|
210
|
+
__Porffor_s: str => [
|
211
|
+
...makeString(scope, str, undefined, undefined, false),
|
200
212
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
}
|
213
|
+
...number(TYPES.string, Valtype.i32),
|
214
|
+
setLastType(scope)
|
215
|
+
],
|
216
|
+
};
|
207
217
|
|
208
218
|
const name = decl.tag.name;
|
209
219
|
// hack for inline asm
|
210
220
|
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
211
221
|
|
212
|
-
const
|
222
|
+
const { quasis, expressions } = decl.quasi;
|
223
|
+
let str = quasis[0].value.raw;
|
224
|
+
|
225
|
+
for (let i = 0; i < expressions.length; i++) {
|
226
|
+
const e = expressions[i];
|
227
|
+
str += lookupName(scope, e.name)[0].idx;
|
228
|
+
str += quasis[i + 1].value.raw;
|
229
|
+
}
|
230
|
+
|
213
231
|
return funcs[name](str);
|
214
232
|
}
|
215
233
|
|
216
234
|
default:
|
235
|
+
// ignore typescript nodes
|
236
|
+
if (decl.type.startsWith('TS') ||
|
237
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
238
|
+
return [];
|
239
|
+
}
|
240
|
+
|
217
241
|
return todo(`no generation for ${decl.type}!`);
|
218
242
|
}
|
219
243
|
};
|
@@ -264,25 +288,25 @@ const generateIdent = (scope, decl) => {
|
|
264
288
|
const name = mapName(rawName);
|
265
289
|
let local = scope.locals[rawName];
|
266
290
|
|
267
|
-
if (builtinVars
|
291
|
+
if (Object.hasOwn(builtinVars, name)) {
|
268
292
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
269
293
|
return builtinVars[name];
|
270
294
|
}
|
271
295
|
|
272
|
-
if (builtinFuncs
|
296
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
273
297
|
// todo: return an actual something
|
274
298
|
return number(1);
|
275
299
|
}
|
276
300
|
|
277
|
-
if (local === undefined) {
|
301
|
+
if (local?.idx === undefined) {
|
278
302
|
// no local var with name
|
279
|
-
if (
|
280
|
-
if (funcIndex
|
303
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
304
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
281
305
|
|
282
|
-
if (globals
|
306
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
283
307
|
}
|
284
308
|
|
285
|
-
if (local === undefined && rawName.startsWith('__')) {
|
309
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
286
310
|
// return undefined if unknown key in already known var
|
287
311
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
288
312
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -291,7 +315,7 @@ const generateIdent = (scope, decl) => {
|
|
291
315
|
if (!parentLookup[1]) return number(UNDEFINED);
|
292
316
|
}
|
293
317
|
|
294
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
318
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
295
319
|
|
296
320
|
return [ [ Opcodes.local_get, local.idx ] ];
|
297
321
|
};
|
@@ -360,12 +384,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
384
|
...right,
|
361
385
|
// note type
|
362
386
|
...rightType,
|
363
|
-
|
387
|
+
setLastType(scope),
|
364
388
|
[ Opcodes.else ],
|
365
389
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
390
|
// note type
|
367
391
|
...leftType,
|
368
|
-
|
392
|
+
setLastType(scope),
|
369
393
|
[ Opcodes.end ],
|
370
394
|
Opcodes.i32_from
|
371
395
|
];
|
@@ -379,12 +403,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
403
|
...right,
|
380
404
|
// note type
|
381
405
|
...rightType,
|
382
|
-
|
406
|
+
setLastType(scope),
|
383
407
|
[ Opcodes.else ],
|
384
408
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
409
|
// note type
|
386
410
|
...leftType,
|
387
|
-
|
411
|
+
setLastType(scope),
|
388
412
|
[ Opcodes.end ]
|
389
413
|
];
|
390
414
|
};
|
@@ -399,9 +423,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
399
423
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
400
424
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
401
425
|
|
402
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
403
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
404
|
-
|
405
426
|
if (assign) {
|
406
427
|
const pointer = arrays.get(name ?? '$undeclared');
|
407
428
|
|
@@ -639,11 +660,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
639
660
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
640
661
|
];
|
641
662
|
|
642
|
-
const
|
663
|
+
const useTmp = knownType(scope, type) == null;
|
664
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
643
665
|
|
644
666
|
const def = [
|
645
667
|
// if value != 0
|
646
|
-
[ Opcodes.local_get, tmp ],
|
668
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
647
669
|
|
648
670
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
649
671
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -655,7 +677,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
655
677
|
|
656
678
|
return [
|
657
679
|
...wasm,
|
658
|
-
[ Opcodes.local_set, tmp ],
|
680
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
659
681
|
|
660
682
|
...typeSwitch(scope, type, {
|
661
683
|
// [TYPES.number]: def,
|
@@ -664,7 +686,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
664
686
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
665
687
|
],
|
666
688
|
[TYPES.string]: [
|
667
|
-
[ Opcodes.local_get, tmp ],
|
689
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
668
690
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
669
691
|
|
670
692
|
// get length
|
@@ -675,16 +697,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
675
697
|
[ Opcodes.i32_eqz ], */
|
676
698
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
699
|
],
|
700
|
+
[TYPES._bytestring]: [ // duplicate of string
|
701
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
702
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
703
|
+
|
704
|
+
// get length
|
705
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
706
|
+
|
707
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
708
|
+
],
|
678
709
|
default: def
|
679
710
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
711
|
];
|
681
712
|
};
|
682
713
|
|
683
714
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
684
|
-
const
|
715
|
+
const useTmp = knownType(scope, type) == null;
|
716
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
717
|
+
|
685
718
|
return [
|
686
719
|
...wasm,
|
687
|
-
[ Opcodes.local_set, tmp ],
|
720
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
688
721
|
|
689
722
|
...typeSwitch(scope, type, {
|
690
723
|
[TYPES._array]: [
|
@@ -692,7 +725,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
692
725
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
693
726
|
],
|
694
727
|
[TYPES.string]: [
|
695
|
-
[ Opcodes.local_get, tmp ],
|
728
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
729
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
730
|
+
|
731
|
+
// get length
|
732
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
733
|
+
|
734
|
+
// if length == 0
|
735
|
+
[ Opcodes.i32_eqz ],
|
736
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
737
|
+
],
|
738
|
+
[TYPES._bytestring]: [ // duplicate of string
|
739
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
696
740
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
697
741
|
|
698
742
|
// get length
|
@@ -704,7 +748,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
704
748
|
],
|
705
749
|
default: [
|
706
750
|
// if value == 0
|
707
|
-
[ Opcodes.local_get, tmp ],
|
751
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
708
752
|
|
709
753
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
710
754
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -714,10 +758,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
714
758
|
};
|
715
759
|
|
716
760
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
717
|
-
const
|
761
|
+
const useTmp = knownType(scope, type) == null;
|
762
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
763
|
+
|
718
764
|
return [
|
719
765
|
...wasm,
|
720
|
-
[ Opcodes.local_set, tmp ],
|
766
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
721
767
|
|
722
768
|
...typeSwitch(scope, type, {
|
723
769
|
[TYPES.undefined]: [
|
@@ -726,7 +772,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
726
772
|
],
|
727
773
|
[TYPES.object]: [
|
728
774
|
// object, null if == 0
|
729
|
-
[ Opcodes.local_get, tmp ],
|
775
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
730
776
|
|
731
777
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
732
778
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -755,11 +801,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
755
801
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
756
802
|
}
|
757
803
|
|
804
|
+
const knownLeft = knownType(scope, leftType);
|
805
|
+
const knownRight = knownType(scope, rightType);
|
806
|
+
|
758
807
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
759
808
|
const strictOp = op === '===' || op === '!==';
|
760
809
|
|
761
810
|
const startOut = [], endOut = [];
|
762
|
-
const
|
811
|
+
const finalize = out => startOut.concat(out, endOut);
|
763
812
|
|
764
813
|
// if strict (in)equal check types match
|
765
814
|
if (strictOp) {
|
@@ -804,31 +853,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
804
853
|
// todo: if equality op and an operand is undefined, return false
|
805
854
|
// todo: niche null hell with 0
|
806
855
|
|
807
|
-
//
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
856
|
+
// todo: this should be dynamic but for now only static
|
857
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
858
|
+
if (op === '+') {
|
859
|
+
// string concat (a + b)
|
860
|
+
return concatStrings(scope, left, right, _global, _name, assign);
|
861
|
+
}
|
862
|
+
|
863
|
+
// not an equality op, NaN
|
864
|
+
if (!eqOp) return number(NaN);
|
865
|
+
|
866
|
+
// else leave bool ops
|
867
|
+
// todo: convert string to number if string and number/bool
|
868
|
+
// todo: string (>|>=|<|<=) string
|
869
|
+
|
870
|
+
// string comparison
|
871
|
+
if (op === '===' || op === '==') {
|
872
|
+
return compareStrings(scope, left, right);
|
873
|
+
}
|
874
|
+
|
875
|
+
if (op === '!==' || op === '!=') {
|
876
|
+
return [
|
877
|
+
...compareStrings(scope, left, right),
|
878
|
+
[ Opcodes.i32_eqz ]
|
879
|
+
];
|
880
|
+
}
|
881
|
+
}
|
832
882
|
|
833
883
|
let ops = operatorOpcode[valtype][op];
|
834
884
|
|
@@ -838,7 +888,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
838
888
|
includeBuiltin(scope, builtinName);
|
839
889
|
const idx = funcIndex[builtinName];
|
840
890
|
|
841
|
-
return
|
891
|
+
return finalize([
|
842
892
|
...left,
|
843
893
|
...right,
|
844
894
|
[ Opcodes.call, idx ]
|
@@ -852,7 +902,13 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
852
902
|
|
853
903
|
let tmpLeft, tmpRight;
|
854
904
|
// if equal op, check if strings for compareStrings
|
855
|
-
if (op === '===' || op === '==' || op === '!==' || op === '!=') {
|
905
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
906
|
+
// todo: intelligent partial skip later
|
907
|
+
// if neither known are string, stop this madness
|
908
|
+
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
909
|
+
return;
|
910
|
+
}
|
911
|
+
|
856
912
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
913
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
914
|
|
@@ -886,7 +942,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
886
942
|
[ Opcodes.i32_or ],
|
887
943
|
[ Opcodes.if, Blocktype.void ],
|
888
944
|
...number(0, Valtype.i32),
|
889
|
-
[ Opcodes.br,
|
945
|
+
[ Opcodes.br, 2 ],
|
890
946
|
[ Opcodes.end ],
|
891
947
|
|
892
948
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -902,9 +958,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
902
958
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
903
959
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
904
960
|
// }
|
905
|
-
}
|
961
|
+
})();
|
906
962
|
|
907
|
-
return
|
963
|
+
return finalize([
|
908
964
|
...left,
|
909
965
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
910
966
|
...right,
|
@@ -921,7 +977,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
921
977
|
return out;
|
922
978
|
};
|
923
979
|
|
924
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
980
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
925
981
|
const existing = funcs.find(x => x.name === name);
|
926
982
|
if (existing) return existing;
|
927
983
|
|
@@ -933,6 +989,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
989
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
990
|
}
|
935
991
|
|
992
|
+
for (const x of _data) {
|
993
|
+
const copy = { ...x };
|
994
|
+
copy.offset += pages.size * pageSize;
|
995
|
+
data.push(copy);
|
996
|
+
}
|
997
|
+
|
998
|
+
if (typeof wasm === 'function') {
|
999
|
+
const scope = {
|
1000
|
+
name,
|
1001
|
+
params,
|
1002
|
+
locals,
|
1003
|
+
returns,
|
1004
|
+
localInd: allLocals.length,
|
1005
|
+
};
|
1006
|
+
|
1007
|
+
wasm = wasm(scope, {
|
1008
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1009
|
+
builtin: name => {
|
1010
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1011
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1012
|
+
includeBuiltin(scope, name);
|
1013
|
+
idx = funcIndex[name];
|
1014
|
+
}
|
1015
|
+
|
1016
|
+
return idx;
|
1017
|
+
}
|
1018
|
+
});
|
1019
|
+
}
|
1020
|
+
|
936
1021
|
let baseGlobalIdx, i = 0;
|
937
1022
|
for (const type of globalTypes) {
|
938
1023
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1013,7 +1098,8 @@ const TYPES = {
|
|
1013
1098
|
|
1014
1099
|
// these are not "typeof" types but tracked internally
|
1015
1100
|
_array: 0x10,
|
1016
|
-
_regexp: 0x11
|
1101
|
+
_regexp: 0x11,
|
1102
|
+
_bytestring: 0x12
|
1017
1103
|
};
|
1018
1104
|
|
1019
1105
|
const TYPE_NAMES = {
|
@@ -1027,13 +1113,17 @@ const TYPE_NAMES = {
|
|
1027
1113
|
[TYPES.bigint]: 'BigInt',
|
1028
1114
|
|
1029
1115
|
[TYPES._array]: 'Array',
|
1030
|
-
[TYPES._regexp]: 'RegExp'
|
1116
|
+
[TYPES._regexp]: 'RegExp',
|
1117
|
+
[TYPES._bytestring]: 'ByteString'
|
1031
1118
|
};
|
1032
1119
|
|
1033
1120
|
const getType = (scope, _name) => {
|
1034
1121
|
const name = mapName(_name);
|
1035
1122
|
|
1123
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1036
1124
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1125
|
+
|
1126
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1037
1127
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1038
1128
|
|
1039
1129
|
let type = TYPES.undefined;
|
@@ -1051,11 +1141,13 @@ const setType = (scope, _name, type) => {
|
|
1051
1141
|
|
1052
1142
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1053
1143
|
|
1144
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1054
1145
|
if (scope.locals[name]) return [
|
1055
1146
|
...out,
|
1056
1147
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
1148
|
];
|
1058
1149
|
|
1150
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1059
1151
|
if (globals[name]) return [
|
1060
1152
|
...out,
|
1061
1153
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
@@ -1064,11 +1156,22 @@ const setType = (scope, _name, type) => {
|
|
1064
1156
|
// throw new Error('could not find var');
|
1065
1157
|
};
|
1066
1158
|
|
1159
|
+
const getLastType = scope => {
|
1160
|
+
scope.gotLastType = true;
|
1161
|
+
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1162
|
+
};
|
1163
|
+
|
1164
|
+
const setLastType = scope => {
|
1165
|
+
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1166
|
+
};
|
1167
|
+
|
1067
1168
|
const getNodeType = (scope, node) => {
|
1068
1169
|
const inner = () => {
|
1069
1170
|
if (node.type === 'Literal') {
|
1070
1171
|
if (node.regex) return TYPES._regexp;
|
1071
1172
|
|
1173
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1174
|
+
|
1072
1175
|
return TYPES[typeof node.value];
|
1073
1176
|
}
|
1074
1177
|
|
@@ -1082,6 +1185,15 @@ const getNodeType = (scope, node) => {
|
|
1082
1185
|
|
1083
1186
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
1187
|
const name = node.callee.name;
|
1188
|
+
if (!name) {
|
1189
|
+
// iife
|
1190
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1191
|
+
|
1192
|
+
// presume
|
1193
|
+
// todo: warn here?
|
1194
|
+
return TYPES.number;
|
1195
|
+
}
|
1196
|
+
|
1085
1197
|
const func = funcs.find(x => x.name === name);
|
1086
1198
|
|
1087
1199
|
if (func) {
|
@@ -1089,10 +1201,27 @@ const getNodeType = (scope, node) => {
|
|
1089
1201
|
if (func.returnType) return func.returnType;
|
1090
1202
|
}
|
1091
1203
|
|
1092
|
-
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1204
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1093
1205
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
1206
|
|
1095
|
-
|
1207
|
+
// check if this is a prototype function
|
1208
|
+
// if so and there is only one impl (eg charCodeAt)
|
1209
|
+
// use that return type as that is the only possibility
|
1210
|
+
// (if non-matching type it would error out)
|
1211
|
+
if (name.startsWith('__')) {
|
1212
|
+
const spl = name.slice(2).split('_');
|
1213
|
+
|
1214
|
+
const func = spl[spl.length - 1];
|
1215
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1216
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1220
|
+
// todo: return undefined for non-returning ops
|
1221
|
+
return TYPES.number;
|
1222
|
+
}
|
1223
|
+
|
1224
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1096
1225
|
|
1097
1226
|
// presume
|
1098
1227
|
// todo: warn here?
|
@@ -1140,6 +1269,14 @@ const getNodeType = (scope, node) => {
|
|
1140
1269
|
|
1141
1270
|
if (node.type === 'BinaryExpression') {
|
1142
1271
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1272
|
+
if (node.operator !== '+') return TYPES.number;
|
1273
|
+
|
1274
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1275
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1276
|
+
|
1277
|
+
// todo: this should be dynamic but for now only static
|
1278
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1279
|
+
|
1143
1280
|
return TYPES.number;
|
1144
1281
|
|
1145
1282
|
// todo: string concat types
|
@@ -1164,7 +1301,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1301
|
if (node.operator === '!') return TYPES.boolean;
|
1165
1302
|
if (node.operator === 'void') return TYPES.undefined;
|
1166
1303
|
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1304
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1168
1305
|
|
1169
1306
|
return TYPES.number;
|
1170
1307
|
}
|
@@ -1173,11 +1310,17 @@ const getNodeType = (scope, node) => {
|
|
1173
1310
|
// hack: if something.length, number type
|
1174
1311
|
if (node.property.name === 'length') return TYPES.number;
|
1175
1312
|
|
1176
|
-
//
|
1313
|
+
// ts hack
|
1314
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1315
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1316
|
+
|
1317
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1318
|
+
|
1319
|
+
// presume
|
1177
1320
|
return TYPES.number;
|
1178
1321
|
}
|
1179
1322
|
|
1180
|
-
if (scope.locals['#last_type']) return [
|
1323
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1181
1324
|
|
1182
1325
|
// presume
|
1183
1326
|
// todo: warn here?
|
@@ -1193,8 +1336,8 @@ const getNodeType = (scope, node) => {
|
|
1193
1336
|
const generateLiteral = (scope, decl, global, name) => {
|
1194
1337
|
if (decl.value === null) return number(NULL);
|
1195
1338
|
|
1339
|
+
// hack: just return 1 for regex literals
|
1196
1340
|
if (decl.regex) {
|
1197
|
-
scope.regex[name] = decl.regex;
|
1198
1341
|
return number(1);
|
1199
1342
|
}
|
1200
1343
|
|
@@ -1207,16 +1350,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1207
1350
|
return number(decl.value ? 1 : 0);
|
1208
1351
|
|
1209
1352
|
case 'string':
|
1210
|
-
|
1211
|
-
const rawElements = new Array(str.length);
|
1212
|
-
let j = 0;
|
1213
|
-
for (let i = 0; i < str.length; i++) {
|
1214
|
-
rawElements[i] = str.charCodeAt(i);
|
1215
|
-
}
|
1216
|
-
|
1217
|
-
return makeArray(scope, {
|
1218
|
-
rawElements
|
1219
|
-
}, global, name, false, 'i16')[0];
|
1353
|
+
return makeString(scope, decl.value, global, name);
|
1220
1354
|
|
1221
1355
|
default:
|
1222
1356
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1237,9 +1371,9 @@ const countLeftover = wasm => {
|
|
1237
1371
|
|
1238
1372
|
if (depth === 0)
|
1239
1373
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1240
|
-
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)) {}
|
1374
|
+
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.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1241
1375
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1242
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1376
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1243
1377
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
1378
|
else if (inst[0] === Opcodes.return) count = 0;
|
1245
1379
|
else if (inst[0] === Opcodes.call) {
|
@@ -1265,7 +1399,7 @@ const disposeLeftover = wasm => {
|
|
1265
1399
|
const generateExp = (scope, decl) => {
|
1266
1400
|
const expression = decl.expression;
|
1267
1401
|
|
1268
|
-
const out = generate(scope, expression);
|
1402
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1269
1403
|
disposeLeftover(out);
|
1270
1404
|
|
1271
1405
|
return out;
|
@@ -1323,7 +1457,7 @@ const RTArrayUtil = {
|
|
1323
1457
|
]
|
1324
1458
|
};
|
1325
1459
|
|
1326
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1460
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1327
1461
|
/* const callee = decl.callee;
|
1328
1462
|
const args = decl.arguments;
|
1329
1463
|
|
@@ -1356,13 +1490,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1356
1490
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
1491
|
out.push(
|
1358
1492
|
...getNodeType(scope, finalStatement),
|
1359
|
-
|
1493
|
+
setLastType(scope)
|
1360
1494
|
);
|
1361
1495
|
} else if (countLeftover(out) === 0) {
|
1362
1496
|
out.push(...number(UNDEFINED));
|
1363
1497
|
out.push(
|
1364
1498
|
...number(TYPES.undefined, Valtype.i32),
|
1365
|
-
|
1499
|
+
setLastType(scope)
|
1366
1500
|
);
|
1367
1501
|
}
|
1368
1502
|
|
@@ -1380,8 +1514,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1380
1514
|
if (name && name.startsWith('__')) {
|
1381
1515
|
const spl = name.slice(2).split('_');
|
1382
1516
|
|
1383
|
-
|
1384
|
-
protoName = func;
|
1517
|
+
protoName = spl[spl.length - 1];
|
1385
1518
|
|
1386
1519
|
target = { ...decl.callee };
|
1387
1520
|
target.name = spl.slice(0, -1).join('_');
|
@@ -1390,8 +1523,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1390
1523
|
// literal.func()
|
1391
1524
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1392
1525
|
// megahack for /regex/.func()
|
1393
|
-
|
1394
|
-
|
1526
|
+
const funcName = decl.callee.property.name;
|
1527
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1395
1528
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1396
1529
|
|
1397
1530
|
funcIndex[func.name] = func.index;
|
@@ -1407,12 +1540,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1407
1540
|
Opcodes.i32_from_u,
|
1408
1541
|
|
1409
1542
|
...number(TYPES.boolean, Valtype.i32),
|
1410
|
-
|
1543
|
+
setLastType(scope)
|
1411
1544
|
];
|
1412
1545
|
}
|
1413
1546
|
|
1414
|
-
|
1415
|
-
protoName = func;
|
1547
|
+
protoName = decl.callee.property.name;
|
1416
1548
|
|
1417
1549
|
target = decl.callee.object;
|
1418
1550
|
}
|
@@ -1434,8 +1566,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1434
1566
|
|
1435
1567
|
if (protoName) {
|
1436
1568
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
-
|
1438
|
-
if (f) acc[x] = f;
|
1569
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1439
1570
|
return acc;
|
1440
1571
|
}, {});
|
1441
1572
|
|
@@ -1444,10 +1575,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1444
1575
|
// use local for cached i32 length as commonly used
|
1445
1576
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
1577
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1448
1578
|
|
1449
1579
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
1580
|
|
1581
|
+
const rawPointer = [
|
1582
|
+
...generate(scope, target),
|
1583
|
+
Opcodes.i32_to_u
|
1584
|
+
];
|
1585
|
+
|
1586
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1587
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1588
|
+
|
1589
|
+
let allOptUnused = true;
|
1451
1590
|
let lengthI32CacheUsed = false;
|
1452
1591
|
const protoBC = {};
|
1453
1592
|
for (const x in protoCands) {
|
@@ -1457,7 +1596,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1457
1596
|
...RTArrayUtil.getLength(getPointer),
|
1458
1597
|
|
1459
1598
|
...number(TYPES.number, Valtype.i32),
|
1460
|
-
|
1599
|
+
setLastType(scope)
|
1461
1600
|
];
|
1462
1601
|
continue;
|
1463
1602
|
}
|
@@ -1467,6 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1467
1606
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
1607
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
1608
|
|
1609
|
+
let optUnused = false;
|
1470
1610
|
const protoOut = protoFunc(getPointer, {
|
1471
1611
|
getCachedI32: () => {
|
1472
1612
|
lengthI32CacheUsed = true;
|
@@ -1481,23 +1621,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1481
1621
|
return makeArray(scope, {
|
1482
1622
|
rawElements: new Array(length)
|
1483
1623
|
}, _global, _name, true, itemType);
|
1624
|
+
}, () => {
|
1625
|
+
optUnused = true;
|
1626
|
+
return unusedValue;
|
1484
1627
|
});
|
1485
1628
|
|
1629
|
+
if (!optUnused) allOptUnused = false;
|
1630
|
+
|
1486
1631
|
protoBC[x] = [
|
1487
|
-
[ Opcodes.block, valtypeBinary ],
|
1632
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1488
1633
|
...protoOut,
|
1489
1634
|
|
1490
1635
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
-
|
1636
|
+
setLastType(scope),
|
1492
1637
|
[ Opcodes.end ]
|
1493
1638
|
];
|
1494
1639
|
}
|
1495
1640
|
|
1496
|
-
|
1497
|
-
...generate(scope, target),
|
1641
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1498
1642
|
|
1499
|
-
|
1500
|
-
|
1643
|
+
return [
|
1644
|
+
...(usePointerCache ? [
|
1645
|
+
...rawPointer,
|
1646
|
+
[ Opcodes.local_set, pointerLocal ],
|
1647
|
+
] : []),
|
1501
1648
|
|
1502
1649
|
...(!lengthI32CacheUsed ? [] : [
|
1503
1650
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1509,7 +1656,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1656
|
|
1510
1657
|
// TODO: error better
|
1511
1658
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
-
}, valtypeBinary),
|
1659
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1513
1660
|
];
|
1514
1661
|
}
|
1515
1662
|
}
|
@@ -1548,6 +1695,32 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1548
1695
|
idx = -1;
|
1549
1696
|
}
|
1550
1697
|
|
1698
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1699
|
+
const wasmOps = {
|
1700
|
+
// pointer, align, offset
|
1701
|
+
i32_load8_u: { imms: 2, args: 1 },
|
1702
|
+
// pointer, value, align, offset
|
1703
|
+
i32_store8: { imms: 2, args: 2 },
|
1704
|
+
};
|
1705
|
+
|
1706
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1707
|
+
|
1708
|
+
if (wasmOps[opName]) {
|
1709
|
+
const op = wasmOps[opName];
|
1710
|
+
|
1711
|
+
const argOut = [];
|
1712
|
+
for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
|
1713
|
+
|
1714
|
+
// literals only
|
1715
|
+
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1716
|
+
|
1717
|
+
return [
|
1718
|
+
...argOut,
|
1719
|
+
[ Opcodes[opName], ...imms ]
|
1720
|
+
];
|
1721
|
+
}
|
1722
|
+
}
|
1723
|
+
|
1551
1724
|
if (idx === undefined) {
|
1552
1725
|
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
|
1553
1726
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
@@ -1556,7 +1729,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1556
1729
|
const func = funcs.find(x => x.index === idx);
|
1557
1730
|
|
1558
1731
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
-
const
|
1732
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1733
|
+
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1734
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1560
1735
|
|
1561
1736
|
let args = decl.arguments;
|
1562
1737
|
if (func && args.length < paramCount) {
|
@@ -1572,14 +1747,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1572
1747
|
if (func && func.throws) scope.throws = true;
|
1573
1748
|
|
1574
1749
|
let out = [];
|
1575
|
-
for (
|
1750
|
+
for (let i = 0; i < args.length; i++) {
|
1751
|
+
const arg = args[i];
|
1576
1752
|
out = out.concat(generate(scope, arg));
|
1577
|
-
|
1753
|
+
|
1754
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1755
|
+
out.push(Opcodes.i32_to);
|
1756
|
+
}
|
1757
|
+
|
1758
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1578
1759
|
}
|
1579
1760
|
|
1580
1761
|
out.push([ Opcodes.call, idx ]);
|
1581
1762
|
|
1582
|
-
if (!
|
1763
|
+
if (!typedReturns) {
|
1583
1764
|
// let type;
|
1584
1765
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
1766
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1589,7 +1770,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1589
1770
|
// ...number(type, Valtype.i32),
|
1590
1771
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
1772
|
// );
|
1592
|
-
} else out.push(
|
1773
|
+
} else out.push(setLastType(scope));
|
1774
|
+
|
1775
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1776
|
+
out.push(Opcodes.i32_from);
|
1777
|
+
}
|
1593
1778
|
|
1594
1779
|
return out;
|
1595
1780
|
};
|
@@ -1614,9 +1799,118 @@ const unhackName = name => {
|
|
1614
1799
|
return name;
|
1615
1800
|
};
|
1616
1801
|
|
1802
|
+
const knownType = (scope, type) => {
|
1803
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1804
|
+
return type[0][1];
|
1805
|
+
}
|
1806
|
+
|
1807
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1808
|
+
const idx = type[0][1];
|
1809
|
+
|
1810
|
+
// type idx = var idx + 1
|
1811
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1812
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1813
|
+
}
|
1814
|
+
|
1815
|
+
return null;
|
1816
|
+
};
|
1817
|
+
|
1818
|
+
const brTable = (input, bc, returns) => {
|
1819
|
+
const out = [];
|
1820
|
+
const keys = Object.keys(bc);
|
1821
|
+
const count = keys.length;
|
1822
|
+
|
1823
|
+
if (count === 1) {
|
1824
|
+
// return [
|
1825
|
+
// ...input,
|
1826
|
+
// ...bc[keys[0]]
|
1827
|
+
// ];
|
1828
|
+
return bc[keys[0]];
|
1829
|
+
}
|
1830
|
+
|
1831
|
+
if (count === 2) {
|
1832
|
+
// just use if else
|
1833
|
+
const other = keys.find(x => x !== 'default');
|
1834
|
+
return [
|
1835
|
+
...input,
|
1836
|
+
...number(other, Valtype.i32),
|
1837
|
+
[ Opcodes.i32_eq ],
|
1838
|
+
[ Opcodes.if, returns ],
|
1839
|
+
...bc[other],
|
1840
|
+
[ Opcodes.else ],
|
1841
|
+
...bc.default,
|
1842
|
+
[ Opcodes.end ]
|
1843
|
+
];
|
1844
|
+
}
|
1845
|
+
|
1846
|
+
for (let i = 0; i < count; i++) {
|
1847
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1848
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1849
|
+
}
|
1850
|
+
|
1851
|
+
const nums = keys.filter(x => +x);
|
1852
|
+
const offset = Math.min(...nums);
|
1853
|
+
const max = Math.max(...nums);
|
1854
|
+
|
1855
|
+
const table = [];
|
1856
|
+
let br = 1;
|
1857
|
+
|
1858
|
+
for (let i = offset; i <= max; i++) {
|
1859
|
+
// if branch for this num, go to that block
|
1860
|
+
if (bc[i]) {
|
1861
|
+
table.push(br);
|
1862
|
+
br++;
|
1863
|
+
continue;
|
1864
|
+
}
|
1865
|
+
|
1866
|
+
// else default
|
1867
|
+
table.push(0);
|
1868
|
+
}
|
1869
|
+
|
1870
|
+
out.push(
|
1871
|
+
[ Opcodes.block, Blocktype.void ],
|
1872
|
+
...input,
|
1873
|
+
...(offset > 0 ? [
|
1874
|
+
...number(offset, Valtype.i32),
|
1875
|
+
[ Opcodes.i32_sub ]
|
1876
|
+
] : []),
|
1877
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1878
|
+
);
|
1879
|
+
|
1880
|
+
// if you can guess why we sort the wrong way and then reverse
|
1881
|
+
// (instead of just sorting the correct way)
|
1882
|
+
// dm me and if you are correct and the first person
|
1883
|
+
// I will somehow shout you out or something
|
1884
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1885
|
+
|
1886
|
+
br = count - 1;
|
1887
|
+
for (const x of orderedBc) {
|
1888
|
+
out.push(
|
1889
|
+
[ Opcodes.end ],
|
1890
|
+
...bc[x],
|
1891
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1892
|
+
);
|
1893
|
+
br--;
|
1894
|
+
}
|
1895
|
+
|
1896
|
+
return [
|
1897
|
+
...out,
|
1898
|
+
[ Opcodes.end, 'br table end' ]
|
1899
|
+
];
|
1900
|
+
};
|
1901
|
+
|
1617
1902
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1618
|
-
|
1903
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1619
1904
|
|
1905
|
+
const known = knownType(scope, type);
|
1906
|
+
if (known != null) {
|
1907
|
+
return bc[known] ?? bc.default;
|
1908
|
+
}
|
1909
|
+
|
1910
|
+
if (Prefs.typeswitchUseBrtable)
|
1911
|
+
return brTable(type, bc, returns);
|
1912
|
+
|
1913
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1620
1914
|
const out = [
|
1621
1915
|
...type,
|
1622
1916
|
[ Opcodes.local_set, tmp ],
|
@@ -1648,7 +1942,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1648
1942
|
return out;
|
1649
1943
|
};
|
1650
1944
|
|
1651
|
-
const allocVar = (scope, name, global = false) => {
|
1945
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1652
1946
|
const target = global ? globals : scope.locals;
|
1653
1947
|
|
1654
1948
|
// already declared
|
@@ -1662,12 +1956,60 @@ const allocVar = (scope, name, global = false) => {
|
|
1662
1956
|
let idx = global ? globalInd++ : scope.localInd++;
|
1663
1957
|
target[name] = { idx, type: valtypeBinary };
|
1664
1958
|
|
1665
|
-
|
1666
|
-
|
1959
|
+
if (type) {
|
1960
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1961
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1962
|
+
}
|
1667
1963
|
|
1668
1964
|
return idx;
|
1669
1965
|
};
|
1670
1966
|
|
1967
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
1968
|
+
const target = global ? globals : scope.locals;
|
1969
|
+
|
1970
|
+
target[name].metadata ??= {};
|
1971
|
+
for (const x in metadata) {
|
1972
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
1973
|
+
}
|
1974
|
+
};
|
1975
|
+
|
1976
|
+
const typeAnnoToPorfType = x => {
|
1977
|
+
if (TYPES[x]) return TYPES[x];
|
1978
|
+
if (TYPES['_' + x]) return TYPES['_' + x];
|
1979
|
+
|
1980
|
+
switch (x) {
|
1981
|
+
case 'i32':
|
1982
|
+
case 'i64':
|
1983
|
+
return TYPES.number;
|
1984
|
+
}
|
1985
|
+
|
1986
|
+
return null;
|
1987
|
+
};
|
1988
|
+
|
1989
|
+
const extractTypeAnnotation = decl => {
|
1990
|
+
let a = decl;
|
1991
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
1992
|
+
|
1993
|
+
let type, elementType;
|
1994
|
+
if (a.typeName) {
|
1995
|
+
type = a.typeName.name;
|
1996
|
+
} else if (a.type.endsWith('Keyword')) {
|
1997
|
+
type = a.type.slice(2, -7).toLowerCase();
|
1998
|
+
} else if (a.type === 'TSArrayType') {
|
1999
|
+
type = 'array';
|
2000
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
2001
|
+
}
|
2002
|
+
|
2003
|
+
const typeName = type;
|
2004
|
+
type = typeAnnoToPorfType(type);
|
2005
|
+
|
2006
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2007
|
+
|
2008
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
2009
|
+
|
2010
|
+
return { type, typeName, elementType };
|
2011
|
+
};
|
2012
|
+
|
1671
2013
|
const generateVar = (scope, decl) => {
|
1672
2014
|
let out = [];
|
1673
2015
|
|
@@ -1679,6 +2021,8 @@ const generateVar = (scope, decl) => {
|
|
1679
2021
|
for (const x of decl.declarations) {
|
1680
2022
|
const name = mapName(x.id.name);
|
1681
2023
|
|
2024
|
+
if (!name) return todo('destructuring is not supported yet');
|
2025
|
+
|
1682
2026
|
if (x.init && isFuncType(x.init.type)) {
|
1683
2027
|
// hack for let a = function () { ... }
|
1684
2028
|
x.init.id = { name };
|
@@ -1694,7 +2038,12 @@ const generateVar = (scope, decl) => {
|
|
1694
2038
|
continue; // always ignore
|
1695
2039
|
}
|
1696
2040
|
|
1697
|
-
let idx = allocVar(scope, name, global);
|
2041
|
+
let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
|
2042
|
+
|
2043
|
+
if (typedInput && x.id.typeAnnotation) {
|
2044
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2045
|
+
}
|
2046
|
+
|
1698
2047
|
if (x.init) {
|
1699
2048
|
out = out.concat(generate(scope, x.init, global, name));
|
1700
2049
|
|
@@ -1812,6 +2161,8 @@ const generateAssign = (scope, decl) => {
|
|
1812
2161
|
];
|
1813
2162
|
}
|
1814
2163
|
|
2164
|
+
if (!name) return todo('destructuring is not supported yet');
|
2165
|
+
|
1815
2166
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1816
2167
|
|
1817
2168
|
if (local === undefined) {
|
@@ -1858,9 +2209,7 @@ const generateAssign = (scope, decl) => {
|
|
1858
2209
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
2210
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
2211
|
|
1861
|
-
|
1862
|
-
// hack: type is idx+1
|
1863
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2212
|
+
...setType(scope, name, getLastType(scope))
|
1864
2213
|
];
|
1865
2214
|
}
|
1866
2215
|
|
@@ -1871,9 +2220,7 @@ const generateAssign = (scope, decl) => {
|
|
1871
2220
|
|
1872
2221
|
// todo: string concat types
|
1873
2222
|
|
1874
|
-
|
1875
|
-
...number(TYPES.number, Valtype.i32),
|
1876
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2223
|
+
...setType(scope, name, TYPES.number)
|
1877
2224
|
];
|
1878
2225
|
};
|
1879
2226
|
|
@@ -1950,6 +2297,8 @@ const generateUnary = (scope, decl) => {
|
|
1950
2297
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
2298
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1952
2299
|
|
2300
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2301
|
+
|
1953
2302
|
// object and internal types
|
1954
2303
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
2304
|
});
|
@@ -2025,7 +2374,7 @@ const generateConditional = (scope, decl) => {
|
|
2025
2374
|
// note type
|
2026
2375
|
out.push(
|
2027
2376
|
...getNodeType(scope, decl.consequent),
|
2028
|
-
|
2377
|
+
setLastType(scope)
|
2029
2378
|
);
|
2030
2379
|
|
2031
2380
|
out.push([ Opcodes.else ]);
|
@@ -2034,7 +2383,7 @@ const generateConditional = (scope, decl) => {
|
|
2034
2383
|
// note type
|
2035
2384
|
out.push(
|
2036
2385
|
...getNodeType(scope, decl.alternate),
|
2037
|
-
|
2386
|
+
setLastType(scope)
|
2038
2387
|
);
|
2039
2388
|
|
2040
2389
|
out.push([ Opcodes.end ]);
|
@@ -2055,8 +2404,10 @@ const generateFor = (scope, decl) => {
|
|
2055
2404
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2056
2405
|
depth.push('for');
|
2057
2406
|
|
2058
|
-
out.push(...generate(scope, decl.test));
|
2059
|
-
|
2407
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2408
|
+
else out.push(...number(1, Valtype.i32));
|
2409
|
+
|
2410
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2060
2411
|
depth.push('if');
|
2061
2412
|
|
2062
2413
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2064,8 +2415,7 @@ const generateFor = (scope, decl) => {
|
|
2064
2415
|
out.push(...generate(scope, decl.body));
|
2065
2416
|
out.push([ Opcodes.end ]);
|
2066
2417
|
|
2067
|
-
out.push(...generate(scope, decl.update));
|
2068
|
-
depth.pop();
|
2418
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2069
2419
|
|
2070
2420
|
out.push([ Opcodes.br, 1 ]);
|
2071
2421
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2122,7 +2472,13 @@ const generateForOf = (scope, decl) => {
|
|
2122
2472
|
// setup local for left
|
2123
2473
|
generate(scope, decl.left);
|
2124
2474
|
|
2125
|
-
|
2475
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2476
|
+
if (!leftName && decl.left.name) {
|
2477
|
+
leftName = decl.left.name;
|
2478
|
+
|
2479
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2480
|
+
}
|
2481
|
+
|
2126
2482
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2127
2483
|
|
2128
2484
|
depth.push('block');
|
@@ -2131,13 +2487,14 @@ const generateForOf = (scope, decl) => {
|
|
2131
2487
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
2488
|
// hack: this is naughty and will break things!
|
2133
2489
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
-
if (pages.
|
2490
|
+
if (pages.hasAnyString) {
|
2135
2491
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
2492
|
rawElements: new Array(1)
|
2137
2493
|
}, isGlobal, leftName, true, 'i16');
|
2138
2494
|
}
|
2139
2495
|
|
2140
2496
|
// set type for local
|
2497
|
+
// todo: optimize away counter and use end pointer
|
2141
2498
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
2499
|
[TYPES._array]: [
|
2143
2500
|
...setType(scope, leftName, TYPES.number),
|
@@ -2262,7 +2619,7 @@ const generateThrow = (scope, decl) => {
|
|
2262
2619
|
// hack: throw new X("...") -> throw "..."
|
2263
2620
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2264
2621
|
constructor = decl.argument.callee.name;
|
2265
|
-
message = decl.argument.arguments[0]
|
2622
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2266
2623
|
}
|
2267
2624
|
|
2268
2625
|
if (tags.length === 0) tags.push({
|
@@ -2274,6 +2631,9 @@ const generateThrow = (scope, decl) => {
|
|
2274
2631
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2275
2632
|
let tagIdx = tags[0].idx;
|
2276
2633
|
|
2634
|
+
scope.exceptions ??= [];
|
2635
|
+
scope.exceptions.push(exceptId);
|
2636
|
+
|
2277
2637
|
// todo: write a description of how this works lol
|
2278
2638
|
|
2279
2639
|
return [
|
@@ -2318,25 +2678,31 @@ const generateAssignPat = (scope, decl) => {
|
|
2318
2678
|
};
|
2319
2679
|
|
2320
2680
|
let pages = new Map();
|
2321
|
-
const allocPage = (reason, type) => {
|
2681
|
+
const allocPage = (scope, reason, type) => {
|
2322
2682
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2323
2683
|
|
2324
2684
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
2685
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2686
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2687
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2326
2688
|
|
2327
2689
|
const ind = pages.size;
|
2328
2690
|
pages.set(reason, { ind, type });
|
2329
2691
|
|
2330
|
-
|
2692
|
+
scope.pages ??= new Map();
|
2693
|
+
scope.pages.set(reason, { ind, type });
|
2694
|
+
|
2695
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2331
2696
|
|
2332
2697
|
return ind;
|
2333
2698
|
};
|
2334
2699
|
|
2700
|
+
// todo: add scope.pages
|
2335
2701
|
const freePage = reason => {
|
2336
2702
|
const { ind } = pages.get(reason);
|
2337
2703
|
pages.delete(reason);
|
2338
2704
|
|
2339
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2705
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2340
2706
|
|
2341
2707
|
return ind;
|
2342
2708
|
};
|
@@ -2356,25 +2722,34 @@ const StoreOps = {
|
|
2356
2722
|
f64: Opcodes.f64_store,
|
2357
2723
|
|
2358
2724
|
// expects i32 input!
|
2359
|
-
|
2725
|
+
i8: Opcodes.i32_store8,
|
2726
|
+
i16: Opcodes.i32_store16,
|
2360
2727
|
};
|
2361
2728
|
|
2362
2729
|
let data = [];
|
2363
2730
|
|
2364
|
-
const compileBytes = (val, itemType
|
2731
|
+
const compileBytes = (val, itemType) => {
|
2365
2732
|
// todo: this is a mess and needs confirming / ????
|
2366
2733
|
switch (itemType) {
|
2367
2734
|
case 'i8': return [ val % 256 ];
|
2368
|
-
case 'i16': return [ val % 256,
|
2369
|
-
|
2370
|
-
case 'i32':
|
2371
|
-
|
2372
|
-
return enforceFourBytes(signedLEB128(val));
|
2735
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2736
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2737
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2738
|
+
// todo: i64
|
2373
2739
|
|
2374
2740
|
case 'f64': return ieee754_binary64(val);
|
2375
2741
|
}
|
2376
2742
|
};
|
2377
2743
|
|
2744
|
+
const getAllocType = itemType => {
|
2745
|
+
switch (itemType) {
|
2746
|
+
case 'i8': return 'bytestring';
|
2747
|
+
case 'i16': return 'string';
|
2748
|
+
|
2749
|
+
default: return 'array';
|
2750
|
+
}
|
2751
|
+
};
|
2752
|
+
|
2378
2753
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2379
2754
|
const out = [];
|
2380
2755
|
|
@@ -2384,7 +2759,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2384
2759
|
|
2385
2760
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2386
2761
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2387
|
-
arrays.set(name, allocPage(`${itemType
|
2762
|
+
arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2388
2763
|
}
|
2389
2764
|
|
2390
2765
|
const pointer = arrays.get(name);
|
@@ -2395,19 +2770,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2395
2770
|
const valtype = itemTypeToValtype[itemType];
|
2396
2771
|
const length = elements.length;
|
2397
2772
|
|
2398
|
-
if (firstAssign && useRawElements) {
|
2399
|
-
|
2773
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
2774
|
+
// if length is 0 memory/data will just be 0000... anyway
|
2775
|
+
if (length !== 0) {
|
2776
|
+
let bytes = compileBytes(length, 'i32');
|
2400
2777
|
|
2401
|
-
|
2402
|
-
|
2778
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2779
|
+
if (elements[i] == null) continue;
|
2403
2780
|
|
2404
|
-
|
2405
|
-
|
2781
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2782
|
+
}
|
2406
2783
|
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2784
|
+
const ind = data.push({
|
2785
|
+
offset: pointer,
|
2786
|
+
bytes
|
2787
|
+
}) - 1;
|
2788
|
+
|
2789
|
+
scope.data ??= [];
|
2790
|
+
scope.data.push(ind);
|
2791
|
+
}
|
2411
2792
|
|
2412
2793
|
// local value as pointer
|
2413
2794
|
out.push(...number(pointer));
|
@@ -2430,7 +2811,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2430
2811
|
out.push(
|
2431
2812
|
...number(0, Valtype.i32),
|
2432
2813
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2433
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2814
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2434
2815
|
);
|
2435
2816
|
}
|
2436
2817
|
|
@@ -2440,15 +2821,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2440
2821
|
return [ out, pointer ];
|
2441
2822
|
};
|
2442
2823
|
|
2443
|
-
const
|
2824
|
+
const byteStringable = str => {
|
2825
|
+
if (!Prefs.bytestring) return false;
|
2826
|
+
|
2827
|
+
for (let i = 0; i < str.length; i++) {
|
2828
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2829
|
+
}
|
2830
|
+
|
2831
|
+
return true;
|
2832
|
+
};
|
2833
|
+
|
2834
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2444
2835
|
const rawElements = new Array(str.length);
|
2836
|
+
let byteStringable = Prefs.bytestring;
|
2445
2837
|
for (let i = 0; i < str.length; i++) {
|
2446
|
-
|
2838
|
+
const c = str.charCodeAt(i);
|
2839
|
+
rawElements[i] = c;
|
2840
|
+
|
2841
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2447
2842
|
}
|
2448
2843
|
|
2844
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2845
|
+
|
2449
2846
|
return makeArray(scope, {
|
2450
2847
|
rawElements
|
2451
|
-
}, global, name, false, 'i16')[0];
|
2848
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2452
2849
|
};
|
2453
2850
|
|
2454
2851
|
let arrays = new Map();
|
@@ -2476,10 +2873,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2476
2873
|
];
|
2477
2874
|
}
|
2478
2875
|
|
2876
|
+
const object = generate(scope, decl.object);
|
2877
|
+
const property = generate(scope, decl.property);
|
2878
|
+
|
2479
2879
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
2880
|
// hack: this is naughty and will break things!
|
2481
|
-
let newOut = number(0,
|
2482
|
-
if (pages.
|
2881
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2882
|
+
if (pages.hasAnyString) {
|
2483
2883
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
2884
|
rawElements: new Array(1)
|
2485
2885
|
}, _global, _name, true, 'i16');
|
@@ -2488,7 +2888,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2488
2888
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
2889
|
[TYPES._array]: [
|
2490
2890
|
// get index as valtype
|
2491
|
-
...
|
2891
|
+
...property,
|
2492
2892
|
|
2493
2893
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2494
2894
|
Opcodes.i32_to_u,
|
@@ -2496,7 +2896,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2496
2896
|
[ Opcodes.i32_mul ],
|
2497
2897
|
|
2498
2898
|
...(aotPointer ? [] : [
|
2499
|
-
...
|
2899
|
+
...object,
|
2500
2900
|
Opcodes.i32_to_u,
|
2501
2901
|
[ Opcodes.i32_add ]
|
2502
2902
|
]),
|
@@ -2505,7 +2905,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2505
2905
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2506
2906
|
|
2507
2907
|
...number(TYPES.number, Valtype.i32),
|
2508
|
-
|
2908
|
+
setLastType(scope)
|
2509
2909
|
],
|
2510
2910
|
|
2511
2911
|
[TYPES.string]: [
|
@@ -2515,14 +2915,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2515
2915
|
|
2516
2916
|
...number(0, Valtype.i32), // base 0 for store later
|
2517
2917
|
|
2518
|
-
...
|
2519
|
-
|
2918
|
+
...property,
|
2520
2919
|
Opcodes.i32_to_u,
|
2920
|
+
|
2521
2921
|
...number(ValtypeSize.i16, Valtype.i32),
|
2522
2922
|
[ Opcodes.i32_mul ],
|
2523
2923
|
|
2524
2924
|
...(aotPointer ? [] : [
|
2525
|
-
...
|
2925
|
+
...object,
|
2526
2926
|
Opcodes.i32_to_u,
|
2527
2927
|
[ Opcodes.i32_add ]
|
2528
2928
|
]),
|
@@ -2537,10 +2937,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2537
2937
|
...number(newPointer),
|
2538
2938
|
|
2539
2939
|
...number(TYPES.string, Valtype.i32),
|
2540
|
-
|
2940
|
+
setLastType(scope)
|
2941
|
+
],
|
2942
|
+
[TYPES._bytestring]: [
|
2943
|
+
// setup new/out array
|
2944
|
+
...newOut,
|
2945
|
+
[ Opcodes.drop ],
|
2946
|
+
|
2947
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2948
|
+
|
2949
|
+
...property,
|
2950
|
+
Opcodes.i32_to_u,
|
2951
|
+
|
2952
|
+
...(aotPointer ? [] : [
|
2953
|
+
...object,
|
2954
|
+
Opcodes.i32_to_u,
|
2955
|
+
[ Opcodes.i32_add ]
|
2956
|
+
]),
|
2957
|
+
|
2958
|
+
// load current string ind {arg}
|
2959
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2960
|
+
|
2961
|
+
// store to new string ind 0
|
2962
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2963
|
+
|
2964
|
+
// return new string (page)
|
2965
|
+
...number(newPointer),
|
2966
|
+
|
2967
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2968
|
+
setLastType(scope)
|
2541
2969
|
],
|
2542
2970
|
|
2543
|
-
default:
|
2971
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2544
2972
|
});
|
2545
2973
|
};
|
2546
2974
|
|
@@ -2557,13 +2985,16 @@ const objectHack = node => {
|
|
2557
2985
|
// if object is not identifier or another member exp, give up
|
2558
2986
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2559
2987
|
|
2560
|
-
if (!objectName) objectName = objectHack(node.object)
|
2988
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2561
2989
|
|
2562
2990
|
// if .length, give up (hack within a hack!)
|
2563
2991
|
if (node.property.name === 'length') return node;
|
2564
2992
|
|
2993
|
+
// no object name, give up
|
2994
|
+
if (!objectName) return node;
|
2995
|
+
|
2565
2996
|
const name = '__' + objectName + '_' + node.property.name;
|
2566
|
-
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2997
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2567
2998
|
|
2568
2999
|
return {
|
2569
3000
|
type: 'Identifier',
|
@@ -2586,7 +3017,7 @@ const generateFunc = (scope, decl) => {
|
|
2586
3017
|
if (decl.generator) return todo('generator functions are not supported');
|
2587
3018
|
|
2588
3019
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2589
|
-
const params = decl.params
|
3020
|
+
const params = decl.params ?? [];
|
2590
3021
|
|
2591
3022
|
// const innerScope = { ...scope };
|
2592
3023
|
// TODO: share scope/locals between !!!
|
@@ -2600,7 +3031,11 @@ const generateFunc = (scope, decl) => {
|
|
2600
3031
|
};
|
2601
3032
|
|
2602
3033
|
for (let i = 0; i < params.length; i++) {
|
2603
|
-
allocVar(innerScope, params[i], false);
|
3034
|
+
allocVar(innerScope, params[i].name, false);
|
3035
|
+
|
3036
|
+
if (typedInput && params[i].typeAnnotation) {
|
3037
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
3038
|
+
}
|
2604
3039
|
}
|
2605
3040
|
|
2606
3041
|
let body = objectHack(decl.body);
|
@@ -2616,13 +3051,13 @@ const generateFunc = (scope, decl) => {
|
|
2616
3051
|
const func = {
|
2617
3052
|
name,
|
2618
3053
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2619
|
-
|
2620
|
-
|
2621
|
-
throws: innerScope.throws,
|
2622
|
-
index: currentFuncIndex++
|
3054
|
+
index: currentFuncIndex++,
|
3055
|
+
...innerScope
|
2623
3056
|
};
|
2624
3057
|
funcIndex[name] = func.index;
|
2625
3058
|
|
3059
|
+
if (name === 'main') func.gotLastType = true;
|
3060
|
+
|
2626
3061
|
// quick hack fixes
|
2627
3062
|
for (const inst of wasm) {
|
2628
3063
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2639,117 +3074,6 @@ const generateFunc = (scope, decl) => {
|
|
2639
3074
|
);
|
2640
3075
|
}
|
2641
3076
|
|
2642
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2643
|
-
let offset = 0, vecParams = 0;
|
2644
|
-
for (let i = 0; i < params.length; i++) {
|
2645
|
-
const name = params[i];
|
2646
|
-
const local = func.locals[name];
|
2647
|
-
if (local.type === Valtype.v128) {
|
2648
|
-
vecParams++;
|
2649
|
-
|
2650
|
-
/* wasm.unshift( // add v128 load for param
|
2651
|
-
[ Opcodes.i32_const, 0 ],
|
2652
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2653
|
-
[ Opcodes.local_set, local.idx ]
|
2654
|
-
); */
|
2655
|
-
|
2656
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2657
|
-
|
2658
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2659
|
-
const { vecType } = local;
|
2660
|
-
let [ type, lanes ] = vecType.split('x');
|
2661
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2662
|
-
|
2663
|
-
lanes = parseInt(lanes);
|
2664
|
-
type = Valtype[type];
|
2665
|
-
|
2666
|
-
const name = params[i]; // get original param name
|
2667
|
-
|
2668
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2669
|
-
|
2670
|
-
// update index of original local
|
2671
|
-
// delete func.locals[name];
|
2672
|
-
|
2673
|
-
// add new locals for params
|
2674
|
-
for (let j = 0; j < lanes; j++) {
|
2675
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2676
|
-
}
|
2677
|
-
|
2678
|
-
// prepend wasm to generate expected v128 locals
|
2679
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2680
|
-
...i32x4(0, 0, 0, 0),
|
2681
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2682
|
-
[ Opcodes.local_get, offset + j ],
|
2683
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2684
|
-
]),
|
2685
|
-
[ Opcodes.local_set, i ]
|
2686
|
-
);
|
2687
|
-
|
2688
|
-
offset += lanes;
|
2689
|
-
|
2690
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2691
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2692
|
-
|
2693
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2694
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2695
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2696
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2697
|
-
}
|
2698
|
-
}
|
2699
|
-
|
2700
|
-
if (offset !== 0) {
|
2701
|
-
// bump local indexes for all other locals after
|
2702
|
-
for (const x in func.locals) {
|
2703
|
-
const local = func.locals[x];
|
2704
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2705
|
-
}
|
2706
|
-
|
2707
|
-
// bump local indexes in wasm local.get/set
|
2708
|
-
for (let j = 0; j < wasm.length; j++) {
|
2709
|
-
const inst = wasm[j];
|
2710
|
-
if (j < offset * 2 + vecParams * 2) {
|
2711
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2712
|
-
continue;
|
2713
|
-
}
|
2714
|
-
|
2715
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2716
|
-
}
|
2717
|
-
}
|
2718
|
-
|
2719
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2720
|
-
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
2721
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2722
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2723
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2724
|
-
const { vecType } = lastReturnLocal;
|
2725
|
-
let [ type, lanes ] = vecType.split('x');
|
2726
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2727
|
-
|
2728
|
-
lanes = parseInt(lanes);
|
2729
|
-
type = Valtype[type];
|
2730
|
-
|
2731
|
-
const vecIdx = lastReturnLocal.idx;
|
2732
|
-
|
2733
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2734
|
-
const tmpIdx = [];
|
2735
|
-
for (let i = 0; i < lanes; i++) {
|
2736
|
-
const idx = lastIdx + i + 1;
|
2737
|
-
tmpIdx.push(idx);
|
2738
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2739
|
-
}
|
2740
|
-
|
2741
|
-
wasm.splice(wasm.length - 1, 1,
|
2742
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2743
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2744
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2745
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2746
|
-
].filter(x => x !== null)),
|
2747
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2748
|
-
);
|
2749
|
-
|
2750
|
-
func.returns = new Array(lanes).fill(type);
|
2751
|
-
}
|
2752
|
-
|
2753
3077
|
func.wasm = wasm;
|
2754
3078
|
|
2755
3079
|
funcs.push(func);
|
@@ -2768,6 +3092,16 @@ const generateCode = (scope, decl) => {
|
|
2768
3092
|
};
|
2769
3093
|
|
2770
3094
|
const internalConstrs = {
|
3095
|
+
Boolean: {
|
3096
|
+
generate: (scope, decl) => {
|
3097
|
+
if (decl.arguments.length === 0) return number(0);
|
3098
|
+
|
3099
|
+
// should generate/run all args
|
3100
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3101
|
+
},
|
3102
|
+
type: TYPES.boolean
|
3103
|
+
},
|
3104
|
+
|
2771
3105
|
Array: {
|
2772
3106
|
generate: (scope, decl, global, name) => {
|
2773
3107
|
// new Array(i0, i1, ...)
|
@@ -2889,7 +3223,7 @@ export default program => {
|
|
2889
3223
|
body: program.body
|
2890
3224
|
};
|
2891
3225
|
|
2892
|
-
if (
|
3226
|
+
if (Prefs.astLog) console.log(program.body.body);
|
2893
3227
|
|
2894
3228
|
generateFunc(scope, program);
|
2895
3229
|
|