porffor 0.2.0-c7b7423 → 0.2.0-cb647c8
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 +68 -0
- package/compiler/builtins/porffor.d.ts +2 -0
- package/compiler/builtins.js +177 -60
- package/compiler/codeGen.js +461 -227
- package/compiler/decompile.js +3 -3
- package/compiler/encoding.js +2 -116
- package/compiler/index.js +15 -9
- package/compiler/opt.js +24 -2
- package/compiler/parse.js +11 -12
- package/compiler/prototype.js +171 -16
- package/compiler/sections.js +1 -1
- 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/package.json +1 -1
- package/porf +2 -0
- package/runner/index.js +20 -3
- package/runner/repl.js +2 -2
- package/tmp.c +1231 -52
- 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";
|
@@ -55,7 +55,7 @@ const todo = msg => {
|
|
55
55
|
};
|
56
56
|
|
57
57
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
58
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
59
|
switch (decl.type) {
|
60
60
|
case 'BinaryExpression':
|
61
61
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
68
|
|
69
69
|
case 'ArrowFunctionExpression':
|
70
70
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
71
|
+
const func = generateFunc(scope, decl);
|
72
|
+
|
73
|
+
if (decl.type.endsWith('Expression')) {
|
74
|
+
return number(func.index);
|
75
|
+
}
|
76
|
+
|
72
77
|
return [];
|
73
78
|
|
74
79
|
case 'BlockStatement':
|
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
86
|
return generateExp(scope, decl);
|
82
87
|
|
83
88
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
90
|
|
86
91
|
case 'NewExpression':
|
87
92
|
return generateNew(scope, decl, global, name);
|
@@ -155,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
155
160
|
|
156
161
|
case 'TaggedTemplateExpression': {
|
157
162
|
const funcs = {
|
158
|
-
|
163
|
+
__Porffor_asm: str => {
|
159
164
|
let out = [];
|
160
165
|
|
161
166
|
for (const line of str.split('\n')) {
|
@@ -191,19 +196,19 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
191
196
|
return out;
|
192
197
|
},
|
193
198
|
|
194
|
-
|
195
|
-
|
199
|
+
__Porffor_bs: str => [
|
200
|
+
...makeString(scope, str, undefined, undefined, true),
|
196
201
|
|
197
|
-
|
198
|
-
|
199
|
-
|
202
|
+
...number(TYPES._bytestring, Valtype.i32),
|
203
|
+
setLastType(scope)
|
204
|
+
],
|
205
|
+
__Porffor_s: str => [
|
206
|
+
...makeString(scope, str, undefined, undefined, false),
|
200
207
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
}
|
208
|
+
...number(TYPES.string, Valtype.i32),
|
209
|
+
setLastType(scope)
|
210
|
+
],
|
211
|
+
};
|
207
212
|
|
208
213
|
const name = decl.tag.name;
|
209
214
|
// hack for inline asm
|
@@ -214,6 +219,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
214
219
|
}
|
215
220
|
|
216
221
|
default:
|
222
|
+
if (decl.type.startsWith('TS')) {
|
223
|
+
// ignore typescript nodes
|
224
|
+
return [];
|
225
|
+
}
|
226
|
+
|
217
227
|
return todo(`no generation for ${decl.type}!`);
|
218
228
|
}
|
219
229
|
};
|
@@ -264,25 +274,25 @@ const generateIdent = (scope, decl) => {
|
|
264
274
|
const name = mapName(rawName);
|
265
275
|
let local = scope.locals[rawName];
|
266
276
|
|
267
|
-
if (builtinVars
|
277
|
+
if (Object.hasOwn(builtinVars, name)) {
|
268
278
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
269
279
|
return builtinVars[name];
|
270
280
|
}
|
271
281
|
|
272
|
-
if (builtinFuncs
|
282
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
273
283
|
// todo: return an actual something
|
274
284
|
return number(1);
|
275
285
|
}
|
276
286
|
|
277
|
-
if (local === undefined) {
|
287
|
+
if (local?.idx === undefined) {
|
278
288
|
// no local var with name
|
279
|
-
if (
|
280
|
-
if (funcIndex
|
289
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
290
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
281
291
|
|
282
|
-
if (globals
|
292
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
283
293
|
}
|
284
294
|
|
285
|
-
if (local === undefined && rawName.startsWith('__')) {
|
295
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
286
296
|
// return undefined if unknown key in already known var
|
287
297
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
288
298
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -291,7 +301,7 @@ const generateIdent = (scope, decl) => {
|
|
291
301
|
if (!parentLookup[1]) return number(UNDEFINED);
|
292
302
|
}
|
293
303
|
|
294
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
304
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
295
305
|
|
296
306
|
return [ [ Opcodes.local_get, local.idx ] ];
|
297
307
|
};
|
@@ -360,12 +370,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
370
|
...right,
|
361
371
|
// note type
|
362
372
|
...rightType,
|
363
|
-
|
373
|
+
setLastType(scope),
|
364
374
|
[ Opcodes.else ],
|
365
375
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
376
|
// note type
|
367
377
|
...leftType,
|
368
|
-
|
378
|
+
setLastType(scope),
|
369
379
|
[ Opcodes.end ],
|
370
380
|
Opcodes.i32_from
|
371
381
|
];
|
@@ -379,12 +389,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
389
|
...right,
|
380
390
|
// note type
|
381
391
|
...rightType,
|
382
|
-
|
392
|
+
setLastType(scope),
|
383
393
|
[ Opcodes.else ],
|
384
394
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
395
|
// note type
|
386
396
|
...leftType,
|
387
|
-
|
397
|
+
setLastType(scope),
|
388
398
|
[ Opcodes.end ]
|
389
399
|
];
|
390
400
|
};
|
@@ -675,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
675
685
|
[ Opcodes.i32_eqz ], */
|
676
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
687
|
],
|
688
|
+
[TYPES._bytestring]: [ // duplicate of string
|
689
|
+
[ Opcodes.local_get, tmp ],
|
690
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
|
+
|
692
|
+
// get length
|
693
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
694
|
+
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
678
697
|
default: def
|
679
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
699
|
];
|
@@ -702,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
702
721
|
[ Opcodes.i32_eqz ],
|
703
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
704
723
|
],
|
724
|
+
[TYPES._bytestring]: [ // duplicate of string
|
725
|
+
[ Opcodes.local_get, tmp ],
|
726
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
|
+
|
728
|
+
// get length
|
729
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
730
|
+
|
731
|
+
// if length == 0
|
732
|
+
[ Opcodes.i32_eqz ],
|
733
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
734
|
+
],
|
705
735
|
default: [
|
706
736
|
// if value == 0
|
707
737
|
[ Opcodes.local_get, tmp ],
|
@@ -852,7 +882,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
852
882
|
|
853
883
|
let tmpLeft, tmpRight;
|
854
884
|
// if equal op, check if strings for compareStrings
|
855
|
-
if (op === '===' || op === '==' || op === '!==' || op === '!=') {
|
885
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
886
|
+
const knownLeft = knownType(scope, leftType);
|
887
|
+
const knownRight = knownType(scope, rightType);
|
888
|
+
|
889
|
+
// todo: intelligent partial skip later
|
890
|
+
// if neither known are string, stop this madness
|
891
|
+
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
892
|
+
return;
|
893
|
+
}
|
894
|
+
|
856
895
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
896
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
897
|
|
@@ -886,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
886
925
|
[ Opcodes.i32_or ],
|
887
926
|
[ Opcodes.if, Blocktype.void ],
|
888
927
|
...number(0, Valtype.i32),
|
889
|
-
[ Opcodes.br,
|
928
|
+
[ Opcodes.br, 2 ],
|
890
929
|
[ Opcodes.end ],
|
891
930
|
|
892
931
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -902,7 +941,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
902
941
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
903
942
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
904
943
|
// }
|
905
|
-
}
|
944
|
+
})();
|
906
945
|
|
907
946
|
return finalise([
|
908
947
|
...left,
|
@@ -933,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
972
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
973
|
}
|
935
974
|
|
975
|
+
if (typeof wasm === 'function') {
|
976
|
+
const scope = {
|
977
|
+
name,
|
978
|
+
params,
|
979
|
+
locals,
|
980
|
+
returns,
|
981
|
+
localInd: allLocals.length,
|
982
|
+
};
|
983
|
+
|
984
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
985
|
+
}
|
986
|
+
|
936
987
|
let baseGlobalIdx, i = 0;
|
937
988
|
for (const type of globalTypes) {
|
938
989
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1013,7 +1064,8 @@ const TYPES = {
|
|
1013
1064
|
|
1014
1065
|
// these are not "typeof" types but tracked internally
|
1015
1066
|
_array: 0x10,
|
1016
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1017
1069
|
};
|
1018
1070
|
|
1019
1071
|
const TYPE_NAMES = {
|
@@ -1027,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1027
1079
|
[TYPES.bigint]: 'BigInt',
|
1028
1080
|
|
1029
1081
|
[TYPES._array]: 'Array',
|
1030
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1031
1084
|
};
|
1032
1085
|
|
1033
1086
|
const getType = (scope, _name) => {
|
@@ -1051,11 +1104,13 @@ const setType = (scope, _name, type) => {
|
|
1051
1104
|
|
1052
1105
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1053
1106
|
|
1107
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1054
1108
|
if (scope.locals[name]) return [
|
1055
1109
|
...out,
|
1056
1110
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
1111
|
];
|
1058
1112
|
|
1113
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1059
1114
|
if (globals[name]) return [
|
1060
1115
|
...out,
|
1061
1116
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
@@ -1064,11 +1119,22 @@ const setType = (scope, _name, type) => {
|
|
1064
1119
|
// throw new Error('could not find var');
|
1065
1120
|
};
|
1066
1121
|
|
1122
|
+
const getLastType = scope => {
|
1123
|
+
scope.gotLastType = true;
|
1124
|
+
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1125
|
+
};
|
1126
|
+
|
1127
|
+
const setLastType = scope => {
|
1128
|
+
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1129
|
+
};
|
1130
|
+
|
1067
1131
|
const getNodeType = (scope, node) => {
|
1068
1132
|
const inner = () => {
|
1069
1133
|
if (node.type === 'Literal') {
|
1070
1134
|
if (node.regex) return TYPES._regexp;
|
1071
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1072
1138
|
return TYPES[typeof node.value];
|
1073
1139
|
}
|
1074
1140
|
|
@@ -1082,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1082
1148
|
|
1083
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
1150
|
const name = node.callee.name;
|
1151
|
+
if (!name) {
|
1152
|
+
// iife
|
1153
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1154
|
+
|
1155
|
+
// presume
|
1156
|
+
// todo: warn here?
|
1157
|
+
return TYPES.number;
|
1158
|
+
}
|
1159
|
+
|
1085
1160
|
const func = funcs.find(x => x.name === name);
|
1086
1161
|
|
1087
1162
|
if (func) {
|
@@ -1092,7 +1167,19 @@ const getNodeType = (scope, node) => {
|
|
1092
1167
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1093
1168
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
1169
|
|
1095
|
-
|
1170
|
+
// check if this is a prototype function
|
1171
|
+
// if so and there is only one impl (eg charCodeAt)
|
1172
|
+
// use that return type as that is the only possibility
|
1173
|
+
// (if non-matching type it would error out)
|
1174
|
+
if (name.startsWith('__')) {
|
1175
|
+
const spl = name.slice(2).split('_');
|
1176
|
+
|
1177
|
+
const func = spl[spl.length - 1];
|
1178
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1179
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
|
+
}
|
1181
|
+
|
1182
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1096
1183
|
|
1097
1184
|
// presume
|
1098
1185
|
// todo: warn here?
|
@@ -1164,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1165
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1166
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1168
1255
|
|
1169
1256
|
return TYPES.number;
|
1170
1257
|
}
|
@@ -1173,11 +1260,17 @@ const getNodeType = (scope, node) => {
|
|
1173
1260
|
// hack: if something.length, number type
|
1174
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1175
1262
|
|
1176
|
-
//
|
1263
|
+
// ts hack
|
1264
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1265
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1266
|
+
|
1267
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1268
|
+
|
1269
|
+
// presume
|
1177
1270
|
return TYPES.number;
|
1178
1271
|
}
|
1179
1272
|
|
1180
|
-
if (scope.locals['#last_type']) return [
|
1273
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1181
1274
|
|
1182
1275
|
// presume
|
1183
1276
|
// todo: warn here?
|
@@ -1193,8 +1286,8 @@ const getNodeType = (scope, node) => {
|
|
1193
1286
|
const generateLiteral = (scope, decl, global, name) => {
|
1194
1287
|
if (decl.value === null) return number(NULL);
|
1195
1288
|
|
1289
|
+
// hack: just return 1 for regex literals
|
1196
1290
|
if (decl.regex) {
|
1197
|
-
scope.regex[name] = decl.regex;
|
1198
1291
|
return number(1);
|
1199
1292
|
}
|
1200
1293
|
|
@@ -1207,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1207
1300
|
return number(decl.value ? 1 : 0);
|
1208
1301
|
|
1209
1302
|
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];
|
1303
|
+
return makeString(scope, decl.value, global, name);
|
1220
1304
|
|
1221
1305
|
default:
|
1222
1306
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1237,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1237
1321
|
|
1238
1322
|
if (depth === 0)
|
1239
1323
|
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)) {}
|
1324
|
+
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
1325
|
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;
|
1326
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1243
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1245
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1265,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1265
1349
|
const generateExp = (scope, decl) => {
|
1266
1350
|
const expression = decl.expression;
|
1267
1351
|
|
1268
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1269
1353
|
disposeLeftover(out);
|
1270
1354
|
|
1271
1355
|
return out;
|
@@ -1323,7 +1407,7 @@ const RTArrayUtil = {
|
|
1323
1407
|
]
|
1324
1408
|
};
|
1325
1409
|
|
1326
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1327
1411
|
/* const callee = decl.callee;
|
1328
1412
|
const args = decl.arguments;
|
1329
1413
|
|
@@ -1356,13 +1440,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1356
1440
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
1441
|
out.push(
|
1358
1442
|
...getNodeType(scope, finalStatement),
|
1359
|
-
|
1443
|
+
setLastType(scope)
|
1360
1444
|
);
|
1361
1445
|
} else if (countLeftover(out) === 0) {
|
1362
1446
|
out.push(...number(UNDEFINED));
|
1363
1447
|
out.push(
|
1364
1448
|
...number(TYPES.undefined, Valtype.i32),
|
1365
|
-
|
1449
|
+
setLastType(scope)
|
1366
1450
|
);
|
1367
1451
|
}
|
1368
1452
|
|
@@ -1380,8 +1464,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1380
1464
|
if (name && name.startsWith('__')) {
|
1381
1465
|
const spl = name.slice(2).split('_');
|
1382
1466
|
|
1383
|
-
|
1384
|
-
protoName = func;
|
1467
|
+
protoName = spl[spl.length - 1];
|
1385
1468
|
|
1386
1469
|
target = { ...decl.callee };
|
1387
1470
|
target.name = spl.slice(0, -1).join('_');
|
@@ -1390,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1390
1473
|
// literal.func()
|
1391
1474
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1392
1475
|
// megahack for /regex/.func()
|
1393
|
-
|
1394
|
-
|
1476
|
+
const funcName = decl.callee.property.name;
|
1477
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1395
1478
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1396
1479
|
|
1397
1480
|
funcIndex[func.name] = func.index;
|
@@ -1407,12 +1490,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1407
1490
|
Opcodes.i32_from_u,
|
1408
1491
|
|
1409
1492
|
...number(TYPES.boolean, Valtype.i32),
|
1410
|
-
|
1493
|
+
setLastType(scope)
|
1411
1494
|
];
|
1412
1495
|
}
|
1413
1496
|
|
1414
|
-
|
1415
|
-
protoName = func;
|
1497
|
+
protoName = decl.callee.property.name;
|
1416
1498
|
|
1417
1499
|
target = decl.callee.object;
|
1418
1500
|
}
|
@@ -1434,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1434
1516
|
|
1435
1517
|
if (protoName) {
|
1436
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
-
|
1438
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1439
1520
|
return acc;
|
1440
1521
|
}, {});
|
1441
1522
|
|
@@ -1444,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1444
1525
|
// use local for cached i32 length as commonly used
|
1445
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1448
1528
|
|
1449
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
1530
|
|
1531
|
+
const rawPointer = [
|
1532
|
+
...generate(scope, target),
|
1533
|
+
Opcodes.i32_to_u
|
1534
|
+
];
|
1535
|
+
|
1536
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1537
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1538
|
+
|
1539
|
+
let allOptUnused = true;
|
1451
1540
|
let lengthI32CacheUsed = false;
|
1452
1541
|
const protoBC = {};
|
1453
1542
|
for (const x in protoCands) {
|
@@ -1457,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1457
1546
|
...RTArrayUtil.getLength(getPointer),
|
1458
1547
|
|
1459
1548
|
...number(TYPES.number, Valtype.i32),
|
1460
|
-
|
1549
|
+
setLastType(scope)
|
1461
1550
|
];
|
1462
1551
|
continue;
|
1463
1552
|
}
|
@@ -1467,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1467
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
1558
|
|
1559
|
+
let optUnused = false;
|
1470
1560
|
const protoOut = protoFunc(getPointer, {
|
1471
1561
|
getCachedI32: () => {
|
1472
1562
|
lengthI32CacheUsed = true;
|
@@ -1481,23 +1571,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1481
1571
|
return makeArray(scope, {
|
1482
1572
|
rawElements: new Array(length)
|
1483
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1484
1577
|
});
|
1485
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1486
1581
|
protoBC[x] = [
|
1487
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1488
1583
|
...protoOut,
|
1489
1584
|
|
1490
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
-
|
1586
|
+
setLastType(scope),
|
1492
1587
|
[ Opcodes.end ]
|
1493
1588
|
];
|
1494
1589
|
}
|
1495
1590
|
|
1496
|
-
|
1497
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1498
1592
|
|
1499
|
-
|
1500
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1501
1598
|
|
1502
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1503
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1509,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1606
|
|
1510
1607
|
// TODO: error better
|
1511
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1513
1610
|
];
|
1514
1611
|
}
|
1515
1612
|
}
|
@@ -1556,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1556
1653
|
const func = funcs.find(x => x.index === idx);
|
1557
1654
|
|
1558
1655
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
-
const
|
1656
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1658
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1560
1659
|
|
1561
1660
|
let args = decl.arguments;
|
1562
1661
|
if (func && args.length < paramCount) {
|
@@ -1574,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1574
1673
|
let out = [];
|
1575
1674
|
for (const arg of args) {
|
1576
1675
|
out = out.concat(generate(scope, arg));
|
1577
|
-
if (
|
1676
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1578
1677
|
}
|
1579
1678
|
|
1580
1679
|
out.push([ Opcodes.call, idx ]);
|
1581
1680
|
|
1582
|
-
if (!
|
1681
|
+
if (!typedReturn) {
|
1583
1682
|
// let type;
|
1584
1683
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
1684
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1589,7 +1688,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1589
1688
|
// ...number(type, Valtype.i32),
|
1590
1689
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
1690
|
// );
|
1592
|
-
} else out.push(
|
1691
|
+
} else out.push(setLastType(scope));
|
1593
1692
|
|
1594
1693
|
return out;
|
1595
1694
|
};
|
@@ -1614,9 +1713,118 @@ const unhackName = name => {
|
|
1614
1713
|
return name;
|
1615
1714
|
};
|
1616
1715
|
|
1716
|
+
const knownType = (scope, type) => {
|
1717
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1718
|
+
return type[0][1];
|
1719
|
+
}
|
1720
|
+
|
1721
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1722
|
+
const idx = type[0][1];
|
1723
|
+
|
1724
|
+
// type idx = var idx + 1
|
1725
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1726
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1727
|
+
}
|
1728
|
+
|
1729
|
+
return null;
|
1730
|
+
};
|
1731
|
+
|
1732
|
+
const brTable = (input, bc, returns) => {
|
1733
|
+
const out = [];
|
1734
|
+
const keys = Object.keys(bc);
|
1735
|
+
const count = keys.length;
|
1736
|
+
|
1737
|
+
if (count === 1) {
|
1738
|
+
// return [
|
1739
|
+
// ...input,
|
1740
|
+
// ...bc[keys[0]]
|
1741
|
+
// ];
|
1742
|
+
return bc[keys[0]];
|
1743
|
+
}
|
1744
|
+
|
1745
|
+
if (count === 2) {
|
1746
|
+
// just use if else
|
1747
|
+
const other = keys.find(x => x !== 'default');
|
1748
|
+
return [
|
1749
|
+
...input,
|
1750
|
+
...number(other, Valtype.i32),
|
1751
|
+
[ Opcodes.i32_eq ],
|
1752
|
+
[ Opcodes.if, returns ],
|
1753
|
+
...bc[other],
|
1754
|
+
[ Opcodes.else ],
|
1755
|
+
...bc.default,
|
1756
|
+
[ Opcodes.end ]
|
1757
|
+
];
|
1758
|
+
}
|
1759
|
+
|
1760
|
+
for (let i = 0; i < count; i++) {
|
1761
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1762
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
const nums = keys.filter(x => +x);
|
1766
|
+
const offset = Math.min(...nums);
|
1767
|
+
const max = Math.max(...nums);
|
1768
|
+
|
1769
|
+
const table = [];
|
1770
|
+
let br = 1;
|
1771
|
+
|
1772
|
+
for (let i = offset; i <= max; i++) {
|
1773
|
+
// if branch for this num, go to that block
|
1774
|
+
if (bc[i]) {
|
1775
|
+
table.push(br);
|
1776
|
+
br++;
|
1777
|
+
continue;
|
1778
|
+
}
|
1779
|
+
|
1780
|
+
// else default
|
1781
|
+
table.push(0);
|
1782
|
+
}
|
1783
|
+
|
1784
|
+
out.push(
|
1785
|
+
[ Opcodes.block, Blocktype.void ],
|
1786
|
+
...input,
|
1787
|
+
...(offset > 0 ? [
|
1788
|
+
...number(offset, Valtype.i32),
|
1789
|
+
[ Opcodes.i32_sub ]
|
1790
|
+
] : []),
|
1791
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1792
|
+
);
|
1793
|
+
|
1794
|
+
// if you can guess why we sort the wrong way and then reverse
|
1795
|
+
// (instead of just sorting the correct way)
|
1796
|
+
// dm me and if you are correct and the first person
|
1797
|
+
// I will somehow shout you out or something
|
1798
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1799
|
+
|
1800
|
+
br = count - 1;
|
1801
|
+
for (const x of orderedBc) {
|
1802
|
+
out.push(
|
1803
|
+
[ Opcodes.end ],
|
1804
|
+
...bc[x],
|
1805
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1806
|
+
);
|
1807
|
+
br--;
|
1808
|
+
}
|
1809
|
+
|
1810
|
+
return [
|
1811
|
+
...out,
|
1812
|
+
[ Opcodes.end, 'br table end' ]
|
1813
|
+
];
|
1814
|
+
};
|
1815
|
+
|
1617
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1618
|
-
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1818
|
+
|
1819
|
+
const known = knownType(scope, type);
|
1820
|
+
if (known != null) {
|
1821
|
+
return bc[known] ?? bc.default;
|
1822
|
+
}
|
1619
1823
|
|
1824
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1825
|
+
return brTable(type, bc, returns);
|
1826
|
+
|
1827
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1620
1828
|
const out = [
|
1621
1829
|
...type,
|
1622
1830
|
[ Opcodes.local_set, tmp ],
|
@@ -1668,6 +1876,51 @@ const allocVar = (scope, name, global = false) => {
|
|
1668
1876
|
return idx;
|
1669
1877
|
};
|
1670
1878
|
|
1879
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
1880
|
+
const target = global ? globals : scope.locals;
|
1881
|
+
|
1882
|
+
target[name].metadata ??= {};
|
1883
|
+
for (const x in metadata) {
|
1884
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
1885
|
+
}
|
1886
|
+
};
|
1887
|
+
|
1888
|
+
const typeAnnoToPorfType = x => {
|
1889
|
+
if (TYPES[x]) return TYPES[x];
|
1890
|
+
if (TYPES['_' + x]) return TYPES['_' + x];
|
1891
|
+
|
1892
|
+
switch (x) {
|
1893
|
+
case 'i32':
|
1894
|
+
return TYPES.number;
|
1895
|
+
}
|
1896
|
+
|
1897
|
+
return null;
|
1898
|
+
};
|
1899
|
+
|
1900
|
+
const extractTypeAnnotation = decl => {
|
1901
|
+
let a = decl;
|
1902
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
1903
|
+
|
1904
|
+
let type, elementType;
|
1905
|
+
if (a.typeName) {
|
1906
|
+
type = a.typeName.name;
|
1907
|
+
} else if (a.type.endsWith('Keyword')) {
|
1908
|
+
type = a.type.slice(2, -7).toLowerCase();
|
1909
|
+
} else if (a.type === 'TSArrayType') {
|
1910
|
+
type = 'array';
|
1911
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
1912
|
+
}
|
1913
|
+
|
1914
|
+
const typeName = type;
|
1915
|
+
type = typeAnnoToPorfType(type);
|
1916
|
+
|
1917
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1918
|
+
|
1919
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
1920
|
+
|
1921
|
+
return { type, typeName, elementType };
|
1922
|
+
};
|
1923
|
+
|
1671
1924
|
const generateVar = (scope, decl) => {
|
1672
1925
|
let out = [];
|
1673
1926
|
|
@@ -1679,6 +1932,8 @@ const generateVar = (scope, decl) => {
|
|
1679
1932
|
for (const x of decl.declarations) {
|
1680
1933
|
const name = mapName(x.id.name);
|
1681
1934
|
|
1935
|
+
if (!name) return todo('destructuring is not supported yet');
|
1936
|
+
|
1682
1937
|
if (x.init && isFuncType(x.init.type)) {
|
1683
1938
|
// hack for let a = function () { ... }
|
1684
1939
|
x.init.id = { name };
|
@@ -1695,6 +1950,11 @@ const generateVar = (scope, decl) => {
|
|
1695
1950
|
}
|
1696
1951
|
|
1697
1952
|
let idx = allocVar(scope, name, global);
|
1953
|
+
|
1954
|
+
if (typedInput && x.id.typeAnnotation) {
|
1955
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
|
+
}
|
1957
|
+
|
1698
1958
|
if (x.init) {
|
1699
1959
|
out = out.concat(generate(scope, x.init, global, name));
|
1700
1960
|
|
@@ -1812,6 +2072,8 @@ const generateAssign = (scope, decl) => {
|
|
1812
2072
|
];
|
1813
2073
|
}
|
1814
2074
|
|
2075
|
+
if (!name) return todo('destructuring is not supported yet');
|
2076
|
+
|
1815
2077
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1816
2078
|
|
1817
2079
|
if (local === undefined) {
|
@@ -1858,7 +2120,7 @@ const generateAssign = (scope, decl) => {
|
|
1858
2120
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
2121
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
2122
|
|
1861
|
-
|
2123
|
+
getLastType(scope),
|
1862
2124
|
// hack: type is idx+1
|
1863
2125
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1864
2126
|
];
|
@@ -1950,6 +2212,8 @@ const generateUnary = (scope, decl) => {
|
|
1950
2212
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
2213
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1952
2214
|
|
2215
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
|
+
|
1953
2217
|
// object and internal types
|
1954
2218
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
2219
|
});
|
@@ -2025,7 +2289,7 @@ const generateConditional = (scope, decl) => {
|
|
2025
2289
|
// note type
|
2026
2290
|
out.push(
|
2027
2291
|
...getNodeType(scope, decl.consequent),
|
2028
|
-
|
2292
|
+
setLastType(scope)
|
2029
2293
|
);
|
2030
2294
|
|
2031
2295
|
out.push([ Opcodes.else ]);
|
@@ -2034,7 +2298,7 @@ const generateConditional = (scope, decl) => {
|
|
2034
2298
|
// note type
|
2035
2299
|
out.push(
|
2036
2300
|
...getNodeType(scope, decl.alternate),
|
2037
|
-
|
2301
|
+
setLastType(scope)
|
2038
2302
|
);
|
2039
2303
|
|
2040
2304
|
out.push([ Opcodes.end ]);
|
@@ -2055,8 +2319,10 @@ const generateFor = (scope, decl) => {
|
|
2055
2319
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2056
2320
|
depth.push('for');
|
2057
2321
|
|
2058
|
-
out.push(...generate(scope, decl.test));
|
2059
|
-
|
2322
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2323
|
+
else out.push(...number(1, Valtype.i32));
|
2324
|
+
|
2325
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2060
2326
|
depth.push('if');
|
2061
2327
|
|
2062
2328
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2064,8 +2330,7 @@ const generateFor = (scope, decl) => {
|
|
2064
2330
|
out.push(...generate(scope, decl.body));
|
2065
2331
|
out.push([ Opcodes.end ]);
|
2066
2332
|
|
2067
|
-
out.push(...generate(scope, decl.update));
|
2068
|
-
depth.pop();
|
2333
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2069
2334
|
|
2070
2335
|
out.push([ Opcodes.br, 1 ]);
|
2071
2336
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2122,7 +2387,13 @@ const generateForOf = (scope, decl) => {
|
|
2122
2387
|
// setup local for left
|
2123
2388
|
generate(scope, decl.left);
|
2124
2389
|
|
2125
|
-
|
2390
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2391
|
+
if (!leftName && decl.left.name) {
|
2392
|
+
leftName = decl.left.name;
|
2393
|
+
|
2394
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2395
|
+
}
|
2396
|
+
|
2126
2397
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2127
2398
|
|
2128
2399
|
depth.push('block');
|
@@ -2131,13 +2402,14 @@ const generateForOf = (scope, decl) => {
|
|
2131
2402
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
2403
|
// hack: this is naughty and will break things!
|
2133
2404
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
-
if (pages.
|
2405
|
+
if (pages.hasAnyString) {
|
2135
2406
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
2407
|
rawElements: new Array(1)
|
2137
2408
|
}, isGlobal, leftName, true, 'i16');
|
2138
2409
|
}
|
2139
2410
|
|
2140
2411
|
// set type for local
|
2412
|
+
// todo: optimize away counter and use end pointer
|
2141
2413
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
2414
|
[TYPES._array]: [
|
2143
2415
|
...setType(scope, leftName, TYPES.number),
|
@@ -2262,7 +2534,7 @@ const generateThrow = (scope, decl) => {
|
|
2262
2534
|
// hack: throw new X("...") -> throw "..."
|
2263
2535
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2264
2536
|
constructor = decl.argument.callee.name;
|
2265
|
-
message = decl.argument.arguments[0]
|
2537
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2266
2538
|
}
|
2267
2539
|
|
2268
2540
|
if (tags.length === 0) tags.push({
|
@@ -2323,6 +2595,8 @@ const allocPage = (reason, type) => {
|
|
2323
2595
|
|
2324
2596
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
2597
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2598
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2599
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2326
2600
|
|
2327
2601
|
const ind = pages.size;
|
2328
2602
|
pages.set(reason, { ind, type });
|
@@ -2356,25 +2630,34 @@ const StoreOps = {
|
|
2356
2630
|
f64: Opcodes.f64_store,
|
2357
2631
|
|
2358
2632
|
// expects i32 input!
|
2359
|
-
|
2633
|
+
i8: Opcodes.i32_store8,
|
2634
|
+
i16: Opcodes.i32_store16,
|
2360
2635
|
};
|
2361
2636
|
|
2362
2637
|
let data = [];
|
2363
2638
|
|
2364
|
-
const compileBytes = (val, itemType
|
2639
|
+
const compileBytes = (val, itemType) => {
|
2365
2640
|
// todo: this is a mess and needs confirming / ????
|
2366
2641
|
switch (itemType) {
|
2367
2642
|
case 'i8': return [ val % 256 ];
|
2368
|
-
case 'i16': return [ val % 256,
|
2369
|
-
|
2370
|
-
case 'i32':
|
2371
|
-
|
2372
|
-
return enforceFourBytes(signedLEB128(val));
|
2643
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2644
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2645
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2646
|
+
// todo: i64
|
2373
2647
|
|
2374
2648
|
case 'f64': return ieee754_binary64(val);
|
2375
2649
|
}
|
2376
2650
|
};
|
2377
2651
|
|
2652
|
+
const getAllocType = itemType => {
|
2653
|
+
switch (itemType) {
|
2654
|
+
case 'i8': return 'bytestring';
|
2655
|
+
case 'i16': return 'string';
|
2656
|
+
|
2657
|
+
default: return 'array';
|
2658
|
+
}
|
2659
|
+
};
|
2660
|
+
|
2378
2661
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2379
2662
|
const out = [];
|
2380
2663
|
|
@@ -2384,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2384
2667
|
|
2385
2668
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2386
2669
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2387
|
-
arrays.set(name, allocPage(`${itemType
|
2670
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2388
2671
|
}
|
2389
2672
|
|
2390
2673
|
const pointer = arrays.get(name);
|
@@ -2430,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2430
2713
|
out.push(
|
2431
2714
|
...number(0, Valtype.i32),
|
2432
2715
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2433
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2716
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2434
2717
|
);
|
2435
2718
|
}
|
2436
2719
|
|
@@ -2440,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2440
2723
|
return [ out, pointer ];
|
2441
2724
|
};
|
2442
2725
|
|
2443
|
-
const
|
2726
|
+
const byteStringable = str => {
|
2727
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2728
|
+
|
2729
|
+
for (let i = 0; i < str.length; i++) {
|
2730
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2731
|
+
}
|
2732
|
+
|
2733
|
+
return true;
|
2734
|
+
};
|
2735
|
+
|
2736
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2444
2737
|
const rawElements = new Array(str.length);
|
2738
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2445
2739
|
for (let i = 0; i < str.length; i++) {
|
2446
|
-
|
2740
|
+
const c = str.charCodeAt(i);
|
2741
|
+
rawElements[i] = c;
|
2742
|
+
|
2743
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2447
2744
|
}
|
2448
2745
|
|
2746
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2747
|
+
|
2449
2748
|
return makeArray(scope, {
|
2450
2749
|
rawElements
|
2451
|
-
}, global, name, false, 'i16')[0];
|
2750
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2452
2751
|
};
|
2453
2752
|
|
2454
2753
|
let arrays = new Map();
|
@@ -2476,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2476
2775
|
];
|
2477
2776
|
}
|
2478
2777
|
|
2778
|
+
const object = generate(scope, decl.object);
|
2779
|
+
const property = generate(scope, decl.property);
|
2780
|
+
|
2479
2781
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
2782
|
// hack: this is naughty and will break things!
|
2481
|
-
let newOut = number(0,
|
2482
|
-
if (pages.
|
2783
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2784
|
+
if (pages.hasAnyString) {
|
2483
2785
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
2786
|
rawElements: new Array(1)
|
2485
2787
|
}, _global, _name, true, 'i16');
|
@@ -2488,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2488
2790
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
2791
|
[TYPES._array]: [
|
2490
2792
|
// get index as valtype
|
2491
|
-
...
|
2793
|
+
...property,
|
2492
2794
|
|
2493
2795
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2494
2796
|
Opcodes.i32_to_u,
|
@@ -2496,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2496
2798
|
[ Opcodes.i32_mul ],
|
2497
2799
|
|
2498
2800
|
...(aotPointer ? [] : [
|
2499
|
-
...
|
2801
|
+
...object,
|
2500
2802
|
Opcodes.i32_to_u,
|
2501
2803
|
[ Opcodes.i32_add ]
|
2502
2804
|
]),
|
@@ -2505,7 +2807,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2505
2807
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2506
2808
|
|
2507
2809
|
...number(TYPES.number, Valtype.i32),
|
2508
|
-
|
2810
|
+
setLastType(scope)
|
2509
2811
|
],
|
2510
2812
|
|
2511
2813
|
[TYPES.string]: [
|
@@ -2515,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2515
2817
|
|
2516
2818
|
...number(0, Valtype.i32), // base 0 for store later
|
2517
2819
|
|
2518
|
-
...
|
2519
|
-
|
2820
|
+
...property,
|
2520
2821
|
Opcodes.i32_to_u,
|
2822
|
+
|
2521
2823
|
...number(ValtypeSize.i16, Valtype.i32),
|
2522
2824
|
[ Opcodes.i32_mul ],
|
2523
2825
|
|
2524
2826
|
...(aotPointer ? [] : [
|
2525
|
-
...
|
2827
|
+
...object,
|
2526
2828
|
Opcodes.i32_to_u,
|
2527
2829
|
[ Opcodes.i32_add ]
|
2528
2830
|
]),
|
@@ -2537,10 +2839,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2537
2839
|
...number(newPointer),
|
2538
2840
|
|
2539
2841
|
...number(TYPES.string, Valtype.i32),
|
2540
|
-
|
2842
|
+
setLastType(scope)
|
2843
|
+
],
|
2844
|
+
[TYPES._bytestring]: [
|
2845
|
+
// setup new/out array
|
2846
|
+
...newOut,
|
2847
|
+
[ Opcodes.drop ],
|
2848
|
+
|
2849
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2850
|
+
|
2851
|
+
...property,
|
2852
|
+
Opcodes.i32_to_u,
|
2853
|
+
|
2854
|
+
...(aotPointer ? [] : [
|
2855
|
+
...object,
|
2856
|
+
Opcodes.i32_to_u,
|
2857
|
+
[ Opcodes.i32_add ]
|
2858
|
+
]),
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
|
+
|
2863
|
+
// store to new string ind 0
|
2864
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// return new string (page)
|
2867
|
+
...number(newPointer),
|
2868
|
+
|
2869
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
+
setLastType(scope)
|
2541
2871
|
],
|
2542
2872
|
|
2543
|
-
default:
|
2873
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2544
2874
|
});
|
2545
2875
|
};
|
2546
2876
|
|
@@ -2557,11 +2887,14 @@ const objectHack = node => {
|
|
2557
2887
|
// if object is not identifier or another member exp, give up
|
2558
2888
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2559
2889
|
|
2560
|
-
if (!objectName) objectName = objectHack(node.object)
|
2890
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2561
2891
|
|
2562
2892
|
// if .length, give up (hack within a hack!)
|
2563
2893
|
if (node.property.name === 'length') return node;
|
2564
2894
|
|
2895
|
+
// no object name, give up
|
2896
|
+
if (!objectName) return node;
|
2897
|
+
|
2565
2898
|
const name = '__' + objectName + '_' + node.property.name;
|
2566
2899
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2567
2900
|
|
@@ -2586,7 +2919,7 @@ const generateFunc = (scope, decl) => {
|
|
2586
2919
|
if (decl.generator) return todo('generator functions are not supported');
|
2587
2920
|
|
2588
2921
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2589
|
-
const params = decl.params
|
2922
|
+
const params = decl.params ?? [];
|
2590
2923
|
|
2591
2924
|
// const innerScope = { ...scope };
|
2592
2925
|
// TODO: share scope/locals between !!!
|
@@ -2600,7 +2933,11 @@ const generateFunc = (scope, decl) => {
|
|
2600
2933
|
};
|
2601
2934
|
|
2602
2935
|
for (let i = 0; i < params.length; i++) {
|
2603
|
-
allocVar(innerScope, params[i], false);
|
2936
|
+
allocVar(innerScope, params[i].name, false);
|
2937
|
+
|
2938
|
+
if (typedInput && params[i].typeAnnotation) {
|
2939
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2940
|
+
}
|
2604
2941
|
}
|
2605
2942
|
|
2606
2943
|
let body = objectHack(decl.body);
|
@@ -2616,10 +2953,8 @@ const generateFunc = (scope, decl) => {
|
|
2616
2953
|
const func = {
|
2617
2954
|
name,
|
2618
2955
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2619
|
-
|
2620
|
-
|
2621
|
-
throws: innerScope.throws,
|
2622
|
-
index: currentFuncIndex++
|
2956
|
+
index: currentFuncIndex++,
|
2957
|
+
...innerScope
|
2623
2958
|
};
|
2624
2959
|
funcIndex[name] = func.index;
|
2625
2960
|
|
@@ -2639,117 +2974,6 @@ const generateFunc = (scope, decl) => {
|
|
2639
2974
|
);
|
2640
2975
|
}
|
2641
2976
|
|
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
2977
|
func.wasm = wasm;
|
2754
2978
|
|
2755
2979
|
funcs.push(func);
|
@@ -2768,6 +2992,16 @@ const generateCode = (scope, decl) => {
|
|
2768
2992
|
};
|
2769
2993
|
|
2770
2994
|
const internalConstrs = {
|
2995
|
+
Boolean: {
|
2996
|
+
generate: (scope, decl) => {
|
2997
|
+
if (decl.arguments.length === 0) return number(0);
|
2998
|
+
|
2999
|
+
// should generate/run all args
|
3000
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3001
|
+
},
|
3002
|
+
type: TYPES.boolean
|
3003
|
+
},
|
3004
|
+
|
2771
3005
|
Array: {
|
2772
3006
|
generate: (scope, decl, global, name) => {
|
2773
3007
|
// new Array(i0, i1, ...)
|