porffor 0.2.0-c7b7423 → 0.2.0-dcc06c8
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 +88 -0
- package/compiler/builtins/porffor.d.ts +10 -0
- package/compiler/builtins.js +178 -60
- package/compiler/codeGen.js +537 -271
- package/compiler/decompile.js +3 -3
- package/compiler/encoding.js +2 -116
- package/compiler/generated_builtins.js +3 -0
- package/compiler/index.js +22 -22
- package/compiler/opt.js +61 -26
- package/compiler/parse.js +13 -12
- package/compiler/precompile.js +79 -0
- package/compiler/prefs.js +22 -0
- package/compiler/prototype.js +176 -20
- package/compiler/sections.js +8 -7
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +103 -9
- package/demo.js +3 -0
- package/demo.ts +1 -0
- package/filesize.cmd +2 -0
- package/hello +0 -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/tmp.c +136 -53
- package/compiler/builtins/base64.js +0 -92
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')) {
|
@@ -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
|
}
|
@@ -191,29 +197,44 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
191
197
|
return out;
|
192
198
|
},
|
193
199
|
|
194
|
-
|
195
|
-
|
200
|
+
__Porffor_bs: str => [
|
201
|
+
...makeString(scope, str, undefined, undefined, true),
|
196
202
|
|
197
|
-
|
198
|
-
|
199
|
-
|
203
|
+
...number(TYPES._bytestring, Valtype.i32),
|
204
|
+
setLastType(scope)
|
205
|
+
],
|
206
|
+
__Porffor_s: str => [
|
207
|
+
...makeString(scope, str, undefined, undefined, false),
|
200
208
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
}
|
209
|
+
...number(TYPES.string, Valtype.i32),
|
210
|
+
setLastType(scope)
|
211
|
+
],
|
212
|
+
};
|
207
213
|
|
208
214
|
const name = decl.tag.name;
|
209
215
|
// hack for inline asm
|
210
216
|
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
211
217
|
|
212
|
-
const
|
218
|
+
const { quasis, expressions } = decl.quasi;
|
219
|
+
let str = quasis[0].value.raw;
|
220
|
+
|
221
|
+
for (let i = 0; i < expressions.length; i++) {
|
222
|
+
const e = expressions[i];
|
223
|
+
str += lookupName(scope, e.name)[0];
|
224
|
+
|
225
|
+
str += quasis[i + 1].value.raw;
|
226
|
+
}
|
227
|
+
|
213
228
|
return funcs[name](str);
|
214
229
|
}
|
215
230
|
|
216
231
|
default:
|
232
|
+
// ignore typescript nodes
|
233
|
+
if (decl.type.startsWith('TS') ||
|
234
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
235
|
+
return [];
|
236
|
+
}
|
237
|
+
|
217
238
|
return todo(`no generation for ${decl.type}!`);
|
218
239
|
}
|
219
240
|
};
|
@@ -264,25 +285,25 @@ const generateIdent = (scope, decl) => {
|
|
264
285
|
const name = mapName(rawName);
|
265
286
|
let local = scope.locals[rawName];
|
266
287
|
|
267
|
-
if (builtinVars
|
288
|
+
if (Object.hasOwn(builtinVars, name)) {
|
268
289
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
269
290
|
return builtinVars[name];
|
270
291
|
}
|
271
292
|
|
272
|
-
if (builtinFuncs
|
293
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
273
294
|
// todo: return an actual something
|
274
295
|
return number(1);
|
275
296
|
}
|
276
297
|
|
277
|
-
if (local === undefined) {
|
298
|
+
if (local?.idx === undefined) {
|
278
299
|
// no local var with name
|
279
|
-
if (
|
280
|
-
if (funcIndex
|
300
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
301
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
281
302
|
|
282
|
-
if (globals
|
303
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
283
304
|
}
|
284
305
|
|
285
|
-
if (local === undefined && rawName.startsWith('__')) {
|
306
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
286
307
|
// return undefined if unknown key in already known var
|
287
308
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
288
309
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -291,7 +312,7 @@ const generateIdent = (scope, decl) => {
|
|
291
312
|
if (!parentLookup[1]) return number(UNDEFINED);
|
292
313
|
}
|
293
314
|
|
294
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
315
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
295
316
|
|
296
317
|
return [ [ Opcodes.local_get, local.idx ] ];
|
297
318
|
};
|
@@ -360,12 +381,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
381
|
...right,
|
361
382
|
// note type
|
362
383
|
...rightType,
|
363
|
-
|
384
|
+
setLastType(scope),
|
364
385
|
[ Opcodes.else ],
|
365
386
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
387
|
// note type
|
367
388
|
...leftType,
|
368
|
-
|
389
|
+
setLastType(scope),
|
369
390
|
[ Opcodes.end ],
|
370
391
|
Opcodes.i32_from
|
371
392
|
];
|
@@ -379,12 +400,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
400
|
...right,
|
380
401
|
// note type
|
381
402
|
...rightType,
|
382
|
-
|
403
|
+
setLastType(scope),
|
383
404
|
[ Opcodes.else ],
|
384
405
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
406
|
// note type
|
386
407
|
...leftType,
|
387
|
-
|
408
|
+
setLastType(scope),
|
388
409
|
[ Opcodes.end ]
|
389
410
|
];
|
390
411
|
};
|
@@ -399,9 +420,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
399
420
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
400
421
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
401
422
|
|
402
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
403
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
404
|
-
|
405
423
|
if (assign) {
|
406
424
|
const pointer = arrays.get(name ?? '$undeclared');
|
407
425
|
|
@@ -675,6 +693,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
675
693
|
[ Opcodes.i32_eqz ], */
|
676
694
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
695
|
],
|
696
|
+
[TYPES._bytestring]: [ // duplicate of string
|
697
|
+
[ Opcodes.local_get, tmp ],
|
698
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
699
|
+
|
700
|
+
// get length
|
701
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
702
|
+
|
703
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
704
|
+
],
|
678
705
|
default: def
|
679
706
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
707
|
];
|
@@ -702,6 +729,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
702
729
|
[ Opcodes.i32_eqz ],
|
703
730
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
704
731
|
],
|
732
|
+
[TYPES._bytestring]: [ // duplicate of string
|
733
|
+
[ Opcodes.local_get, tmp ],
|
734
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
735
|
+
|
736
|
+
// get length
|
737
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
738
|
+
|
739
|
+
// if length == 0
|
740
|
+
[ Opcodes.i32_eqz ],
|
741
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
742
|
+
],
|
705
743
|
default: [
|
706
744
|
// if value == 0
|
707
745
|
[ Opcodes.local_get, tmp ],
|
@@ -755,11 +793,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
755
793
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
756
794
|
}
|
757
795
|
|
796
|
+
const knownLeft = knownType(scope, leftType);
|
797
|
+
const knownRight = knownType(scope, rightType);
|
798
|
+
|
758
799
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
759
800
|
const strictOp = op === '===' || op === '!==';
|
760
801
|
|
761
802
|
const startOut = [], endOut = [];
|
762
|
-
const
|
803
|
+
const finalize = out => startOut.concat(out, endOut);
|
763
804
|
|
764
805
|
// if strict (in)equal check types match
|
765
806
|
if (strictOp) {
|
@@ -804,31 +845,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
804
845
|
// todo: if equality op and an operand is undefined, return false
|
805
846
|
// todo: niche null hell with 0
|
806
847
|
|
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
|
-
|
848
|
+
// todo: this should be dynamic but for now only static
|
849
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
850
|
+
if (op === '+') {
|
851
|
+
// string concat (a + b)
|
852
|
+
return concatStrings(scope, left, right, _global, _name, assign);
|
853
|
+
}
|
854
|
+
|
855
|
+
// not an equality op, NaN
|
856
|
+
if (!eqOp) return number(NaN);
|
857
|
+
|
858
|
+
// else leave bool ops
|
859
|
+
// todo: convert string to number if string and number/bool
|
860
|
+
// todo: string (>|>=|<|<=) string
|
861
|
+
|
862
|
+
// string comparison
|
863
|
+
if (op === '===' || op === '==') {
|
864
|
+
return compareStrings(scope, left, right);
|
865
|
+
}
|
866
|
+
|
867
|
+
if (op === '!==' || op === '!=') {
|
868
|
+
return [
|
869
|
+
...compareStrings(scope, left, right),
|
870
|
+
[ Opcodes.i32_eqz ]
|
871
|
+
];
|
872
|
+
}
|
873
|
+
}
|
832
874
|
|
833
875
|
let ops = operatorOpcode[valtype][op];
|
834
876
|
|
@@ -838,7 +880,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
838
880
|
includeBuiltin(scope, builtinName);
|
839
881
|
const idx = funcIndex[builtinName];
|
840
882
|
|
841
|
-
return
|
883
|
+
return finalize([
|
842
884
|
...left,
|
843
885
|
...right,
|
844
886
|
[ Opcodes.call, idx ]
|
@@ -852,7 +894,13 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
852
894
|
|
853
895
|
let tmpLeft, tmpRight;
|
854
896
|
// if equal op, check if strings for compareStrings
|
855
|
-
if (op === '===' || op === '==' || op === '!==' || op === '!=') {
|
897
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
898
|
+
// todo: intelligent partial skip later
|
899
|
+
// if neither known are string, stop this madness
|
900
|
+
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
901
|
+
return;
|
902
|
+
}
|
903
|
+
|
856
904
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
905
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
906
|
|
@@ -886,7 +934,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
886
934
|
[ Opcodes.i32_or ],
|
887
935
|
[ Opcodes.if, Blocktype.void ],
|
888
936
|
...number(0, Valtype.i32),
|
889
|
-
[ Opcodes.br,
|
937
|
+
[ Opcodes.br, 2 ],
|
890
938
|
[ Opcodes.end ],
|
891
939
|
|
892
940
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -902,9 +950,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
902
950
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
903
951
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
904
952
|
// }
|
905
|
-
}
|
953
|
+
})();
|
906
954
|
|
907
|
-
return
|
955
|
+
return finalize([
|
908
956
|
...left,
|
909
957
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
910
958
|
...right,
|
@@ -933,6 +981,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
981
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
982
|
}
|
935
983
|
|
984
|
+
if (typeof wasm === 'function') {
|
985
|
+
const scope = {
|
986
|
+
name,
|
987
|
+
params,
|
988
|
+
locals,
|
989
|
+
returns,
|
990
|
+
localInd: allLocals.length,
|
991
|
+
};
|
992
|
+
|
993
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
994
|
+
}
|
995
|
+
|
936
996
|
let baseGlobalIdx, i = 0;
|
937
997
|
for (const type of globalTypes) {
|
938
998
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1013,7 +1073,8 @@ const TYPES = {
|
|
1013
1073
|
|
1014
1074
|
// these are not "typeof" types but tracked internally
|
1015
1075
|
_array: 0x10,
|
1016
|
-
_regexp: 0x11
|
1076
|
+
_regexp: 0x11,
|
1077
|
+
_bytestring: 0x12
|
1017
1078
|
};
|
1018
1079
|
|
1019
1080
|
const TYPE_NAMES = {
|
@@ -1027,13 +1088,17 @@ const TYPE_NAMES = {
|
|
1027
1088
|
[TYPES.bigint]: 'BigInt',
|
1028
1089
|
|
1029
1090
|
[TYPES._array]: 'Array',
|
1030
|
-
[TYPES._regexp]: 'RegExp'
|
1091
|
+
[TYPES._regexp]: 'RegExp',
|
1092
|
+
[TYPES._bytestring]: 'ByteString'
|
1031
1093
|
};
|
1032
1094
|
|
1033
1095
|
const getType = (scope, _name) => {
|
1034
1096
|
const name = mapName(_name);
|
1035
1097
|
|
1098
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1036
1099
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1100
|
+
|
1101
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1037
1102
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1038
1103
|
|
1039
1104
|
let type = TYPES.undefined;
|
@@ -1051,11 +1116,13 @@ const setType = (scope, _name, type) => {
|
|
1051
1116
|
|
1052
1117
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1053
1118
|
|
1119
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1054
1120
|
if (scope.locals[name]) return [
|
1055
1121
|
...out,
|
1056
1122
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
1123
|
];
|
1058
1124
|
|
1125
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1059
1126
|
if (globals[name]) return [
|
1060
1127
|
...out,
|
1061
1128
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
@@ -1064,11 +1131,22 @@ const setType = (scope, _name, type) => {
|
|
1064
1131
|
// throw new Error('could not find var');
|
1065
1132
|
};
|
1066
1133
|
|
1134
|
+
const getLastType = scope => {
|
1135
|
+
scope.gotLastType = true;
|
1136
|
+
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1137
|
+
};
|
1138
|
+
|
1139
|
+
const setLastType = scope => {
|
1140
|
+
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1141
|
+
};
|
1142
|
+
|
1067
1143
|
const getNodeType = (scope, node) => {
|
1068
1144
|
const inner = () => {
|
1069
1145
|
if (node.type === 'Literal') {
|
1070
1146
|
if (node.regex) return TYPES._regexp;
|
1071
1147
|
|
1148
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1149
|
+
|
1072
1150
|
return TYPES[typeof node.value];
|
1073
1151
|
}
|
1074
1152
|
|
@@ -1082,6 +1160,15 @@ const getNodeType = (scope, node) => {
|
|
1082
1160
|
|
1083
1161
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
1162
|
const name = node.callee.name;
|
1163
|
+
if (!name) {
|
1164
|
+
// iife
|
1165
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1166
|
+
|
1167
|
+
// presume
|
1168
|
+
// todo: warn here?
|
1169
|
+
return TYPES.number;
|
1170
|
+
}
|
1171
|
+
|
1085
1172
|
const func = funcs.find(x => x.name === name);
|
1086
1173
|
|
1087
1174
|
if (func) {
|
@@ -1092,7 +1179,19 @@ const getNodeType = (scope, node) => {
|
|
1092
1179
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1093
1180
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
1181
|
|
1095
|
-
|
1182
|
+
// check if this is a prototype function
|
1183
|
+
// if so and there is only one impl (eg charCodeAt)
|
1184
|
+
// use that return type as that is the only possibility
|
1185
|
+
// (if non-matching type it would error out)
|
1186
|
+
if (name.startsWith('__')) {
|
1187
|
+
const spl = name.slice(2).split('_');
|
1188
|
+
|
1189
|
+
const func = spl[spl.length - 1];
|
1190
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1191
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1192
|
+
}
|
1193
|
+
|
1194
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1096
1195
|
|
1097
1196
|
// presume
|
1098
1197
|
// todo: warn here?
|
@@ -1140,6 +1239,14 @@ const getNodeType = (scope, node) => {
|
|
1140
1239
|
|
1141
1240
|
if (node.type === 'BinaryExpression') {
|
1142
1241
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1242
|
+
if (node.operator !== '+') return TYPES.number;
|
1243
|
+
|
1244
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1245
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1246
|
+
|
1247
|
+
// todo: this should be dynamic but for now only static
|
1248
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1249
|
+
|
1143
1250
|
return TYPES.number;
|
1144
1251
|
|
1145
1252
|
// todo: string concat types
|
@@ -1164,7 +1271,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1271
|
if (node.operator === '!') return TYPES.boolean;
|
1165
1272
|
if (node.operator === 'void') return TYPES.undefined;
|
1166
1273
|
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1274
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1168
1275
|
|
1169
1276
|
return TYPES.number;
|
1170
1277
|
}
|
@@ -1173,11 +1280,17 @@ const getNodeType = (scope, node) => {
|
|
1173
1280
|
// hack: if something.length, number type
|
1174
1281
|
if (node.property.name === 'length') return TYPES.number;
|
1175
1282
|
|
1176
|
-
//
|
1283
|
+
// ts hack
|
1284
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1285
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1286
|
+
|
1287
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1288
|
+
|
1289
|
+
// presume
|
1177
1290
|
return TYPES.number;
|
1178
1291
|
}
|
1179
1292
|
|
1180
|
-
if (scope.locals['#last_type']) return [
|
1293
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1181
1294
|
|
1182
1295
|
// presume
|
1183
1296
|
// todo: warn here?
|
@@ -1193,8 +1306,8 @@ const getNodeType = (scope, node) => {
|
|
1193
1306
|
const generateLiteral = (scope, decl, global, name) => {
|
1194
1307
|
if (decl.value === null) return number(NULL);
|
1195
1308
|
|
1309
|
+
// hack: just return 1 for regex literals
|
1196
1310
|
if (decl.regex) {
|
1197
|
-
scope.regex[name] = decl.regex;
|
1198
1311
|
return number(1);
|
1199
1312
|
}
|
1200
1313
|
|
@@ -1207,16 +1320,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1207
1320
|
return number(decl.value ? 1 : 0);
|
1208
1321
|
|
1209
1322
|
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];
|
1323
|
+
return makeString(scope, decl.value, global, name);
|
1220
1324
|
|
1221
1325
|
default:
|
1222
1326
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1237,9 +1341,9 @@ const countLeftover = wasm => {
|
|
1237
1341
|
|
1238
1342
|
if (depth === 0)
|
1239
1343
|
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)) {}
|
1344
|
+
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
1345
|
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;
|
1346
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1243
1347
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
1348
|
else if (inst[0] === Opcodes.return) count = 0;
|
1245
1349
|
else if (inst[0] === Opcodes.call) {
|
@@ -1265,7 +1369,7 @@ const disposeLeftover = wasm => {
|
|
1265
1369
|
const generateExp = (scope, decl) => {
|
1266
1370
|
const expression = decl.expression;
|
1267
1371
|
|
1268
|
-
const out = generate(scope, expression);
|
1372
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1269
1373
|
disposeLeftover(out);
|
1270
1374
|
|
1271
1375
|
return out;
|
@@ -1323,7 +1427,7 @@ const RTArrayUtil = {
|
|
1323
1427
|
]
|
1324
1428
|
};
|
1325
1429
|
|
1326
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1430
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1327
1431
|
/* const callee = decl.callee;
|
1328
1432
|
const args = decl.arguments;
|
1329
1433
|
|
@@ -1356,13 +1460,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1356
1460
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
1461
|
out.push(
|
1358
1462
|
...getNodeType(scope, finalStatement),
|
1359
|
-
|
1463
|
+
setLastType(scope)
|
1360
1464
|
);
|
1361
1465
|
} else if (countLeftover(out) === 0) {
|
1362
1466
|
out.push(...number(UNDEFINED));
|
1363
1467
|
out.push(
|
1364
1468
|
...number(TYPES.undefined, Valtype.i32),
|
1365
|
-
|
1469
|
+
setLastType(scope)
|
1366
1470
|
);
|
1367
1471
|
}
|
1368
1472
|
|
@@ -1380,8 +1484,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1380
1484
|
if (name && name.startsWith('__')) {
|
1381
1485
|
const spl = name.slice(2).split('_');
|
1382
1486
|
|
1383
|
-
|
1384
|
-
protoName = func;
|
1487
|
+
protoName = spl[spl.length - 1];
|
1385
1488
|
|
1386
1489
|
target = { ...decl.callee };
|
1387
1490
|
target.name = spl.slice(0, -1).join('_');
|
@@ -1390,8 +1493,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1390
1493
|
// literal.func()
|
1391
1494
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1392
1495
|
// megahack for /regex/.func()
|
1393
|
-
|
1394
|
-
|
1496
|
+
const funcName = decl.callee.property.name;
|
1497
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1395
1498
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1396
1499
|
|
1397
1500
|
funcIndex[func.name] = func.index;
|
@@ -1407,12 +1510,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1407
1510
|
Opcodes.i32_from_u,
|
1408
1511
|
|
1409
1512
|
...number(TYPES.boolean, Valtype.i32),
|
1410
|
-
|
1513
|
+
setLastType(scope)
|
1411
1514
|
];
|
1412
1515
|
}
|
1413
1516
|
|
1414
|
-
|
1415
|
-
protoName = func;
|
1517
|
+
protoName = decl.callee.property.name;
|
1416
1518
|
|
1417
1519
|
target = decl.callee.object;
|
1418
1520
|
}
|
@@ -1434,8 +1536,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1434
1536
|
|
1435
1537
|
if (protoName) {
|
1436
1538
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
-
|
1438
|
-
if (f) acc[x] = f;
|
1539
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1439
1540
|
return acc;
|
1440
1541
|
}, {});
|
1441
1542
|
|
@@ -1444,10 +1545,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1444
1545
|
// use local for cached i32 length as commonly used
|
1445
1546
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
1547
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1448
1548
|
|
1449
1549
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
1550
|
|
1551
|
+
const rawPointer = [
|
1552
|
+
...generate(scope, target),
|
1553
|
+
Opcodes.i32_to_u
|
1554
|
+
];
|
1555
|
+
|
1556
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1557
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1558
|
+
|
1559
|
+
let allOptUnused = true;
|
1451
1560
|
let lengthI32CacheUsed = false;
|
1452
1561
|
const protoBC = {};
|
1453
1562
|
for (const x in protoCands) {
|
@@ -1457,7 +1566,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1457
1566
|
...RTArrayUtil.getLength(getPointer),
|
1458
1567
|
|
1459
1568
|
...number(TYPES.number, Valtype.i32),
|
1460
|
-
|
1569
|
+
setLastType(scope)
|
1461
1570
|
];
|
1462
1571
|
continue;
|
1463
1572
|
}
|
@@ -1467,6 +1576,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1467
1576
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
1577
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
1578
|
|
1579
|
+
let optUnused = false;
|
1470
1580
|
const protoOut = protoFunc(getPointer, {
|
1471
1581
|
getCachedI32: () => {
|
1472
1582
|
lengthI32CacheUsed = true;
|
@@ -1481,23 +1591,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1481
1591
|
return makeArray(scope, {
|
1482
1592
|
rawElements: new Array(length)
|
1483
1593
|
}, _global, _name, true, itemType);
|
1594
|
+
}, () => {
|
1595
|
+
optUnused = true;
|
1596
|
+
return unusedValue;
|
1484
1597
|
});
|
1485
1598
|
|
1599
|
+
if (!optUnused) allOptUnused = false;
|
1600
|
+
|
1486
1601
|
protoBC[x] = [
|
1487
|
-
[ Opcodes.block, valtypeBinary ],
|
1602
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1488
1603
|
...protoOut,
|
1489
1604
|
|
1490
1605
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
-
|
1606
|
+
setLastType(scope),
|
1492
1607
|
[ Opcodes.end ]
|
1493
1608
|
];
|
1494
1609
|
}
|
1495
1610
|
|
1496
|
-
|
1497
|
-
...generate(scope, target),
|
1611
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1498
1612
|
|
1499
|
-
|
1500
|
-
|
1613
|
+
return [
|
1614
|
+
...(usePointerCache ? [
|
1615
|
+
...rawPointer,
|
1616
|
+
[ Opcodes.local_set, pointerLocal ],
|
1617
|
+
] : []),
|
1501
1618
|
|
1502
1619
|
...(!lengthI32CacheUsed ? [] : [
|
1503
1620
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1509,7 +1626,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1626
|
|
1510
1627
|
// TODO: error better
|
1511
1628
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
-
}, valtypeBinary),
|
1629
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1513
1630
|
];
|
1514
1631
|
}
|
1515
1632
|
}
|
@@ -1556,7 +1673,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1556
1673
|
const func = funcs.find(x => x.index === idx);
|
1557
1674
|
|
1558
1675
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
-
const
|
1676
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1677
|
+
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1678
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1560
1679
|
|
1561
1680
|
let args = decl.arguments;
|
1562
1681
|
if (func && args.length < paramCount) {
|
@@ -1574,12 +1693,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1574
1693
|
let out = [];
|
1575
1694
|
for (const arg of args) {
|
1576
1695
|
out = out.concat(generate(scope, arg));
|
1577
|
-
if (
|
1696
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1578
1697
|
}
|
1579
1698
|
|
1580
1699
|
out.push([ Opcodes.call, idx ]);
|
1581
1700
|
|
1582
|
-
if (!
|
1701
|
+
if (!typedReturns) {
|
1583
1702
|
// let type;
|
1584
1703
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
1704
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1589,7 +1708,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1589
1708
|
// ...number(type, Valtype.i32),
|
1590
1709
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
1710
|
// );
|
1592
|
-
} else out.push(
|
1711
|
+
} else out.push(setLastType(scope));
|
1593
1712
|
|
1594
1713
|
return out;
|
1595
1714
|
};
|
@@ -1614,9 +1733,118 @@ const unhackName = name => {
|
|
1614
1733
|
return name;
|
1615
1734
|
};
|
1616
1735
|
|
1736
|
+
const knownType = (scope, type) => {
|
1737
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1738
|
+
return type[0][1];
|
1739
|
+
}
|
1740
|
+
|
1741
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1742
|
+
const idx = type[0][1];
|
1743
|
+
|
1744
|
+
// type idx = var idx + 1
|
1745
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1746
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1747
|
+
}
|
1748
|
+
|
1749
|
+
return null;
|
1750
|
+
};
|
1751
|
+
|
1752
|
+
const brTable = (input, bc, returns) => {
|
1753
|
+
const out = [];
|
1754
|
+
const keys = Object.keys(bc);
|
1755
|
+
const count = keys.length;
|
1756
|
+
|
1757
|
+
if (count === 1) {
|
1758
|
+
// return [
|
1759
|
+
// ...input,
|
1760
|
+
// ...bc[keys[0]]
|
1761
|
+
// ];
|
1762
|
+
return bc[keys[0]];
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
if (count === 2) {
|
1766
|
+
// just use if else
|
1767
|
+
const other = keys.find(x => x !== 'default');
|
1768
|
+
return [
|
1769
|
+
...input,
|
1770
|
+
...number(other, Valtype.i32),
|
1771
|
+
[ Opcodes.i32_eq ],
|
1772
|
+
[ Opcodes.if, returns ],
|
1773
|
+
...bc[other],
|
1774
|
+
[ Opcodes.else ],
|
1775
|
+
...bc.default,
|
1776
|
+
[ Opcodes.end ]
|
1777
|
+
];
|
1778
|
+
}
|
1779
|
+
|
1780
|
+
for (let i = 0; i < count; i++) {
|
1781
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1782
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1783
|
+
}
|
1784
|
+
|
1785
|
+
const nums = keys.filter(x => +x);
|
1786
|
+
const offset = Math.min(...nums);
|
1787
|
+
const max = Math.max(...nums);
|
1788
|
+
|
1789
|
+
const table = [];
|
1790
|
+
let br = 1;
|
1791
|
+
|
1792
|
+
for (let i = offset; i <= max; i++) {
|
1793
|
+
// if branch for this num, go to that block
|
1794
|
+
if (bc[i]) {
|
1795
|
+
table.push(br);
|
1796
|
+
br++;
|
1797
|
+
continue;
|
1798
|
+
}
|
1799
|
+
|
1800
|
+
// else default
|
1801
|
+
table.push(0);
|
1802
|
+
}
|
1803
|
+
|
1804
|
+
out.push(
|
1805
|
+
[ Opcodes.block, Blocktype.void ],
|
1806
|
+
...input,
|
1807
|
+
...(offset > 0 ? [
|
1808
|
+
...number(offset, Valtype.i32),
|
1809
|
+
[ Opcodes.i32_sub ]
|
1810
|
+
] : []),
|
1811
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1812
|
+
);
|
1813
|
+
|
1814
|
+
// if you can guess why we sort the wrong way and then reverse
|
1815
|
+
// (instead of just sorting the correct way)
|
1816
|
+
// dm me and if you are correct and the first person
|
1817
|
+
// I will somehow shout you out or something
|
1818
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1819
|
+
|
1820
|
+
br = count - 1;
|
1821
|
+
for (const x of orderedBc) {
|
1822
|
+
out.push(
|
1823
|
+
[ Opcodes.end ],
|
1824
|
+
...bc[x],
|
1825
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1826
|
+
);
|
1827
|
+
br--;
|
1828
|
+
}
|
1829
|
+
|
1830
|
+
return [
|
1831
|
+
...out,
|
1832
|
+
[ Opcodes.end, 'br table end' ]
|
1833
|
+
];
|
1834
|
+
};
|
1835
|
+
|
1617
1836
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1618
|
-
|
1837
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1838
|
+
|
1839
|
+
const known = knownType(scope, type);
|
1840
|
+
if (known != null) {
|
1841
|
+
return bc[known] ?? bc.default;
|
1842
|
+
}
|
1843
|
+
|
1844
|
+
if (Prefs.typeswitchUseBrtable)
|
1845
|
+
return brTable(type, bc, returns);
|
1619
1846
|
|
1847
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1620
1848
|
const out = [
|
1621
1849
|
...type,
|
1622
1850
|
[ Opcodes.local_set, tmp ],
|
@@ -1648,7 +1876,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1648
1876
|
return out;
|
1649
1877
|
};
|
1650
1878
|
|
1651
|
-
const allocVar = (scope, name, global = false) => {
|
1879
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1652
1880
|
const target = global ? globals : scope.locals;
|
1653
1881
|
|
1654
1882
|
// already declared
|
@@ -1662,12 +1890,59 @@ const allocVar = (scope, name, global = false) => {
|
|
1662
1890
|
let idx = global ? globalInd++ : scope.localInd++;
|
1663
1891
|
target[name] = { idx, type: valtypeBinary };
|
1664
1892
|
|
1665
|
-
|
1666
|
-
|
1893
|
+
if (type) {
|
1894
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1895
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1896
|
+
}
|
1667
1897
|
|
1668
1898
|
return idx;
|
1669
1899
|
};
|
1670
1900
|
|
1901
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
1902
|
+
const target = global ? globals : scope.locals;
|
1903
|
+
|
1904
|
+
target[name].metadata ??= {};
|
1905
|
+
for (const x in metadata) {
|
1906
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
1907
|
+
}
|
1908
|
+
};
|
1909
|
+
|
1910
|
+
const typeAnnoToPorfType = x => {
|
1911
|
+
if (TYPES[x]) return TYPES[x];
|
1912
|
+
if (TYPES['_' + x]) return TYPES['_' + x];
|
1913
|
+
|
1914
|
+
switch (x) {
|
1915
|
+
case 'i32':
|
1916
|
+
return TYPES.number;
|
1917
|
+
}
|
1918
|
+
|
1919
|
+
return null;
|
1920
|
+
};
|
1921
|
+
|
1922
|
+
const extractTypeAnnotation = decl => {
|
1923
|
+
let a = decl;
|
1924
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
1925
|
+
|
1926
|
+
let type, elementType;
|
1927
|
+
if (a.typeName) {
|
1928
|
+
type = a.typeName.name;
|
1929
|
+
} else if (a.type.endsWith('Keyword')) {
|
1930
|
+
type = a.type.slice(2, -7).toLowerCase();
|
1931
|
+
} else if (a.type === 'TSArrayType') {
|
1932
|
+
type = 'array';
|
1933
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
1934
|
+
}
|
1935
|
+
|
1936
|
+
const typeName = type;
|
1937
|
+
type = typeAnnoToPorfType(type);
|
1938
|
+
|
1939
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
1940
|
+
|
1941
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
1942
|
+
|
1943
|
+
return { type, typeName, elementType };
|
1944
|
+
};
|
1945
|
+
|
1671
1946
|
const generateVar = (scope, decl) => {
|
1672
1947
|
let out = [];
|
1673
1948
|
|
@@ -1679,6 +1954,8 @@ const generateVar = (scope, decl) => {
|
|
1679
1954
|
for (const x of decl.declarations) {
|
1680
1955
|
const name = mapName(x.id.name);
|
1681
1956
|
|
1957
|
+
if (!name) return todo('destructuring is not supported yet');
|
1958
|
+
|
1682
1959
|
if (x.init && isFuncType(x.init.type)) {
|
1683
1960
|
// hack for let a = function () { ... }
|
1684
1961
|
x.init.id = { name };
|
@@ -1694,7 +1971,12 @@ const generateVar = (scope, decl) => {
|
|
1694
1971
|
continue; // always ignore
|
1695
1972
|
}
|
1696
1973
|
|
1697
|
-
let idx = allocVar(scope, name, global);
|
1974
|
+
let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
|
1975
|
+
|
1976
|
+
if (typedInput && x.id.typeAnnotation) {
|
1977
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1978
|
+
}
|
1979
|
+
|
1698
1980
|
if (x.init) {
|
1699
1981
|
out = out.concat(generate(scope, x.init, global, name));
|
1700
1982
|
|
@@ -1812,6 +2094,8 @@ const generateAssign = (scope, decl) => {
|
|
1812
2094
|
];
|
1813
2095
|
}
|
1814
2096
|
|
2097
|
+
if (!name) return todo('destructuring is not supported yet');
|
2098
|
+
|
1815
2099
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1816
2100
|
|
1817
2101
|
if (local === undefined) {
|
@@ -1858,7 +2142,7 @@ const generateAssign = (scope, decl) => {
|
|
1858
2142
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
2143
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
2144
|
|
1861
|
-
|
2145
|
+
getLastType(scope),
|
1862
2146
|
// hack: type is idx+1
|
1863
2147
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1864
2148
|
];
|
@@ -1950,6 +2234,8 @@ const generateUnary = (scope, decl) => {
|
|
1950
2234
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
2235
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1952
2236
|
|
2237
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2238
|
+
|
1953
2239
|
// object and internal types
|
1954
2240
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
2241
|
});
|
@@ -2025,7 +2311,7 @@ const generateConditional = (scope, decl) => {
|
|
2025
2311
|
// note type
|
2026
2312
|
out.push(
|
2027
2313
|
...getNodeType(scope, decl.consequent),
|
2028
|
-
|
2314
|
+
setLastType(scope)
|
2029
2315
|
);
|
2030
2316
|
|
2031
2317
|
out.push([ Opcodes.else ]);
|
@@ -2034,7 +2320,7 @@ const generateConditional = (scope, decl) => {
|
|
2034
2320
|
// note type
|
2035
2321
|
out.push(
|
2036
2322
|
...getNodeType(scope, decl.alternate),
|
2037
|
-
|
2323
|
+
setLastType(scope)
|
2038
2324
|
);
|
2039
2325
|
|
2040
2326
|
out.push([ Opcodes.end ]);
|
@@ -2055,8 +2341,10 @@ const generateFor = (scope, decl) => {
|
|
2055
2341
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2056
2342
|
depth.push('for');
|
2057
2343
|
|
2058
|
-
out.push(...generate(scope, decl.test));
|
2059
|
-
|
2344
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2345
|
+
else out.push(...number(1, Valtype.i32));
|
2346
|
+
|
2347
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2060
2348
|
depth.push('if');
|
2061
2349
|
|
2062
2350
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2064,8 +2352,7 @@ const generateFor = (scope, decl) => {
|
|
2064
2352
|
out.push(...generate(scope, decl.body));
|
2065
2353
|
out.push([ Opcodes.end ]);
|
2066
2354
|
|
2067
|
-
out.push(...generate(scope, decl.update));
|
2068
|
-
depth.pop();
|
2355
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2069
2356
|
|
2070
2357
|
out.push([ Opcodes.br, 1 ]);
|
2071
2358
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2122,7 +2409,13 @@ const generateForOf = (scope, decl) => {
|
|
2122
2409
|
// setup local for left
|
2123
2410
|
generate(scope, decl.left);
|
2124
2411
|
|
2125
|
-
|
2412
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2413
|
+
if (!leftName && decl.left.name) {
|
2414
|
+
leftName = decl.left.name;
|
2415
|
+
|
2416
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2417
|
+
}
|
2418
|
+
|
2126
2419
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2127
2420
|
|
2128
2421
|
depth.push('block');
|
@@ -2131,13 +2424,14 @@ const generateForOf = (scope, decl) => {
|
|
2131
2424
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
2425
|
// hack: this is naughty and will break things!
|
2133
2426
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
-
if (pages.
|
2427
|
+
if (pages.hasAnyString) {
|
2135
2428
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
2429
|
rawElements: new Array(1)
|
2137
2430
|
}, isGlobal, leftName, true, 'i16');
|
2138
2431
|
}
|
2139
2432
|
|
2140
2433
|
// set type for local
|
2434
|
+
// todo: optimize away counter and use end pointer
|
2141
2435
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
2436
|
[TYPES._array]: [
|
2143
2437
|
...setType(scope, leftName, TYPES.number),
|
@@ -2262,7 +2556,7 @@ const generateThrow = (scope, decl) => {
|
|
2262
2556
|
// hack: throw new X("...") -> throw "..."
|
2263
2557
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2264
2558
|
constructor = decl.argument.callee.name;
|
2265
|
-
message = decl.argument.arguments[0]
|
2559
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2266
2560
|
}
|
2267
2561
|
|
2268
2562
|
if (tags.length === 0) tags.push({
|
@@ -2274,6 +2568,9 @@ const generateThrow = (scope, decl) => {
|
|
2274
2568
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2275
2569
|
let tagIdx = tags[0].idx;
|
2276
2570
|
|
2571
|
+
scope.exceptions ??= [];
|
2572
|
+
scope.exceptions.push(exceptId);
|
2573
|
+
|
2277
2574
|
// todo: write a description of how this works lol
|
2278
2575
|
|
2279
2576
|
return [
|
@@ -2318,25 +2615,31 @@ const generateAssignPat = (scope, decl) => {
|
|
2318
2615
|
};
|
2319
2616
|
|
2320
2617
|
let pages = new Map();
|
2321
|
-
const allocPage = (reason, type) => {
|
2618
|
+
const allocPage = (scope, reason, type) => {
|
2322
2619
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2323
2620
|
|
2324
2621
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
2622
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2623
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2624
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2326
2625
|
|
2327
2626
|
const ind = pages.size;
|
2328
2627
|
pages.set(reason, { ind, type });
|
2329
2628
|
|
2330
|
-
|
2629
|
+
scope.pages ??= new Map();
|
2630
|
+
scope.pages.set(reason, { ind, type });
|
2631
|
+
|
2632
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2331
2633
|
|
2332
2634
|
return ind;
|
2333
2635
|
};
|
2334
2636
|
|
2637
|
+
// todo: add scope.pages
|
2335
2638
|
const freePage = reason => {
|
2336
2639
|
const { ind } = pages.get(reason);
|
2337
2640
|
pages.delete(reason);
|
2338
2641
|
|
2339
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2642
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2340
2643
|
|
2341
2644
|
return ind;
|
2342
2645
|
};
|
@@ -2356,25 +2659,34 @@ const StoreOps = {
|
|
2356
2659
|
f64: Opcodes.f64_store,
|
2357
2660
|
|
2358
2661
|
// expects i32 input!
|
2359
|
-
|
2662
|
+
i8: Opcodes.i32_store8,
|
2663
|
+
i16: Opcodes.i32_store16,
|
2360
2664
|
};
|
2361
2665
|
|
2362
2666
|
let data = [];
|
2363
2667
|
|
2364
|
-
const compileBytes = (val, itemType
|
2668
|
+
const compileBytes = (val, itemType) => {
|
2365
2669
|
// todo: this is a mess and needs confirming / ????
|
2366
2670
|
switch (itemType) {
|
2367
2671
|
case 'i8': return [ val % 256 ];
|
2368
|
-
case 'i16': return [ val % 256,
|
2369
|
-
|
2370
|
-
case 'i32':
|
2371
|
-
|
2372
|
-
return enforceFourBytes(signedLEB128(val));
|
2672
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2673
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2674
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2675
|
+
// todo: i64
|
2373
2676
|
|
2374
2677
|
case 'f64': return ieee754_binary64(val);
|
2375
2678
|
}
|
2376
2679
|
};
|
2377
2680
|
|
2681
|
+
const getAllocType = itemType => {
|
2682
|
+
switch (itemType) {
|
2683
|
+
case 'i8': return 'bytestring';
|
2684
|
+
case 'i16': return 'string';
|
2685
|
+
|
2686
|
+
default: return 'array';
|
2687
|
+
}
|
2688
|
+
};
|
2689
|
+
|
2378
2690
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2379
2691
|
const out = [];
|
2380
2692
|
|
@@ -2384,7 +2696,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2384
2696
|
|
2385
2697
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2386
2698
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2387
|
-
arrays.set(name, allocPage(`${itemType
|
2699
|
+
arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2388
2700
|
}
|
2389
2701
|
|
2390
2702
|
const pointer = arrays.get(name);
|
@@ -2404,10 +2716,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2404
2716
|
bytes.push(...compileBytes(elements[i], itemType));
|
2405
2717
|
}
|
2406
2718
|
|
2407
|
-
data.push({
|
2719
|
+
const ind = data.push({
|
2408
2720
|
offset: pointer,
|
2409
2721
|
bytes
|
2410
|
-
});
|
2722
|
+
}) - 1;
|
2723
|
+
|
2724
|
+
scope.data ??= [];
|
2725
|
+
scope.data.push(ind);
|
2411
2726
|
|
2412
2727
|
// local value as pointer
|
2413
2728
|
out.push(...number(pointer));
|
@@ -2430,7 +2745,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2430
2745
|
out.push(
|
2431
2746
|
...number(0, Valtype.i32),
|
2432
2747
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2433
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2748
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2434
2749
|
);
|
2435
2750
|
}
|
2436
2751
|
|
@@ -2440,15 +2755,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2440
2755
|
return [ out, pointer ];
|
2441
2756
|
};
|
2442
2757
|
|
2443
|
-
const
|
2758
|
+
const byteStringable = str => {
|
2759
|
+
if (!Prefs.bytestring) return false;
|
2760
|
+
|
2761
|
+
for (let i = 0; i < str.length; i++) {
|
2762
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2763
|
+
}
|
2764
|
+
|
2765
|
+
return true;
|
2766
|
+
};
|
2767
|
+
|
2768
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2444
2769
|
const rawElements = new Array(str.length);
|
2770
|
+
let byteStringable = Prefs.bytestring;
|
2445
2771
|
for (let i = 0; i < str.length; i++) {
|
2446
|
-
|
2772
|
+
const c = str.charCodeAt(i);
|
2773
|
+
rawElements[i] = c;
|
2774
|
+
|
2775
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2447
2776
|
}
|
2448
2777
|
|
2778
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2779
|
+
|
2449
2780
|
return makeArray(scope, {
|
2450
2781
|
rawElements
|
2451
|
-
}, global, name, false, 'i16')[0];
|
2782
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2452
2783
|
};
|
2453
2784
|
|
2454
2785
|
let arrays = new Map();
|
@@ -2476,10 +2807,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2476
2807
|
];
|
2477
2808
|
}
|
2478
2809
|
|
2810
|
+
const object = generate(scope, decl.object);
|
2811
|
+
const property = generate(scope, decl.property);
|
2812
|
+
|
2479
2813
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
2814
|
// hack: this is naughty and will break things!
|
2481
|
-
let newOut = number(0,
|
2482
|
-
if (pages.
|
2815
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2816
|
+
if (pages.hasAnyString) {
|
2483
2817
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
2818
|
rawElements: new Array(1)
|
2485
2819
|
}, _global, _name, true, 'i16');
|
@@ -2488,7 +2822,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2488
2822
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
2823
|
[TYPES._array]: [
|
2490
2824
|
// get index as valtype
|
2491
|
-
...
|
2825
|
+
...property,
|
2492
2826
|
|
2493
2827
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2494
2828
|
Opcodes.i32_to_u,
|
@@ -2496,7 +2830,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2496
2830
|
[ Opcodes.i32_mul ],
|
2497
2831
|
|
2498
2832
|
...(aotPointer ? [] : [
|
2499
|
-
...
|
2833
|
+
...object,
|
2500
2834
|
Opcodes.i32_to_u,
|
2501
2835
|
[ Opcodes.i32_add ]
|
2502
2836
|
]),
|
@@ -2505,7 +2839,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2505
2839
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2506
2840
|
|
2507
2841
|
...number(TYPES.number, Valtype.i32),
|
2508
|
-
|
2842
|
+
setLastType(scope)
|
2509
2843
|
],
|
2510
2844
|
|
2511
2845
|
[TYPES.string]: [
|
@@ -2515,14 +2849,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2515
2849
|
|
2516
2850
|
...number(0, Valtype.i32), // base 0 for store later
|
2517
2851
|
|
2518
|
-
...
|
2519
|
-
|
2852
|
+
...property,
|
2520
2853
|
Opcodes.i32_to_u,
|
2854
|
+
|
2521
2855
|
...number(ValtypeSize.i16, Valtype.i32),
|
2522
2856
|
[ Opcodes.i32_mul ],
|
2523
2857
|
|
2524
2858
|
...(aotPointer ? [] : [
|
2525
|
-
...
|
2859
|
+
...object,
|
2526
2860
|
Opcodes.i32_to_u,
|
2527
2861
|
[ Opcodes.i32_add ]
|
2528
2862
|
]),
|
@@ -2537,10 +2871,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2537
2871
|
...number(newPointer),
|
2538
2872
|
|
2539
2873
|
...number(TYPES.string, Valtype.i32),
|
2540
|
-
|
2874
|
+
setLastType(scope)
|
2875
|
+
],
|
2876
|
+
[TYPES._bytestring]: [
|
2877
|
+
// setup new/out array
|
2878
|
+
...newOut,
|
2879
|
+
[ Opcodes.drop ],
|
2880
|
+
|
2881
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2882
|
+
|
2883
|
+
...property,
|
2884
|
+
Opcodes.i32_to_u,
|
2885
|
+
|
2886
|
+
...(aotPointer ? [] : [
|
2887
|
+
...object,
|
2888
|
+
Opcodes.i32_to_u,
|
2889
|
+
[ Opcodes.i32_add ]
|
2890
|
+
]),
|
2891
|
+
|
2892
|
+
// load current string ind {arg}
|
2893
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2894
|
+
|
2895
|
+
// store to new string ind 0
|
2896
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2897
|
+
|
2898
|
+
// return new string (page)
|
2899
|
+
...number(newPointer),
|
2900
|
+
|
2901
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2902
|
+
setLastType(scope)
|
2541
2903
|
],
|
2542
2904
|
|
2543
|
-
default:
|
2905
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2544
2906
|
});
|
2545
2907
|
};
|
2546
2908
|
|
@@ -2557,13 +2919,16 @@ const objectHack = node => {
|
|
2557
2919
|
// if object is not identifier or another member exp, give up
|
2558
2920
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2559
2921
|
|
2560
|
-
if (!objectName) objectName = objectHack(node.object)
|
2922
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2561
2923
|
|
2562
2924
|
// if .length, give up (hack within a hack!)
|
2563
2925
|
if (node.property.name === 'length') return node;
|
2564
2926
|
|
2927
|
+
// no object name, give up
|
2928
|
+
if (!objectName) return node;
|
2929
|
+
|
2565
2930
|
const name = '__' + objectName + '_' + node.property.name;
|
2566
|
-
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2931
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2567
2932
|
|
2568
2933
|
return {
|
2569
2934
|
type: 'Identifier',
|
@@ -2586,7 +2951,7 @@ const generateFunc = (scope, decl) => {
|
|
2586
2951
|
if (decl.generator) return todo('generator functions are not supported');
|
2587
2952
|
|
2588
2953
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2589
|
-
const params = decl.params
|
2954
|
+
const params = decl.params ?? [];
|
2590
2955
|
|
2591
2956
|
// const innerScope = { ...scope };
|
2592
2957
|
// TODO: share scope/locals between !!!
|
@@ -2600,7 +2965,11 @@ const generateFunc = (scope, decl) => {
|
|
2600
2965
|
};
|
2601
2966
|
|
2602
2967
|
for (let i = 0; i < params.length; i++) {
|
2603
|
-
allocVar(innerScope, params[i], false);
|
2968
|
+
allocVar(innerScope, params[i].name, false);
|
2969
|
+
|
2970
|
+
if (typedInput && params[i].typeAnnotation) {
|
2971
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2972
|
+
}
|
2604
2973
|
}
|
2605
2974
|
|
2606
2975
|
let body = objectHack(decl.body);
|
@@ -2616,10 +2985,8 @@ const generateFunc = (scope, decl) => {
|
|
2616
2985
|
const func = {
|
2617
2986
|
name,
|
2618
2987
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2619
|
-
|
2620
|
-
|
2621
|
-
throws: innerScope.throws,
|
2622
|
-
index: currentFuncIndex++
|
2988
|
+
index: currentFuncIndex++,
|
2989
|
+
...innerScope
|
2623
2990
|
};
|
2624
2991
|
funcIndex[name] = func.index;
|
2625
2992
|
|
@@ -2639,117 +3006,6 @@ const generateFunc = (scope, decl) => {
|
|
2639
3006
|
);
|
2640
3007
|
}
|
2641
3008
|
|
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
3009
|
func.wasm = wasm;
|
2754
3010
|
|
2755
3011
|
funcs.push(func);
|
@@ -2768,6 +3024,16 @@ const generateCode = (scope, decl) => {
|
|
2768
3024
|
};
|
2769
3025
|
|
2770
3026
|
const internalConstrs = {
|
3027
|
+
Boolean: {
|
3028
|
+
generate: (scope, decl) => {
|
3029
|
+
if (decl.arguments.length === 0) return number(0);
|
3030
|
+
|
3031
|
+
// should generate/run all args
|
3032
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3033
|
+
},
|
3034
|
+
type: TYPES.boolean
|
3035
|
+
},
|
3036
|
+
|
2771
3037
|
Array: {
|
2772
3038
|
generate: (scope, decl, global, name) => {
|
2773
3039
|
// new Array(i0, i1, ...)
|
@@ -2889,7 +3155,7 @@ export default program => {
|
|
2889
3155
|
body: program.body
|
2890
3156
|
};
|
2891
3157
|
|
2892
|
-
if (
|
3158
|
+
if (Prefs.astLog) console.log(program.body.body);
|
2893
3159
|
|
2894
3160
|
generateFunc(scope, program);
|
2895
3161
|
|