porffor 0.2.0-9ca9aed → 0.2.0-9f8ffb2
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 +36 -14
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +413 -207
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- 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 +101 -8
- package/package.json +1 -1
- package/runner/repl.js +2 -2
- package/tmp.c +0 -71
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);
|
@@ -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
|
};
|
@@ -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
|
|
@@ -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, typeSwitch, makeArray });
|
985
|
+
}
|
986
|
+
|
936
987
|
let baseGlobalIdx, i = 0;
|
937
988
|
for (const type of globalTypes) {
|
938
989
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1014,17 +1065,7 @@ const TYPES = {
|
|
1014
1065
|
// these are not "typeof" types but tracked internally
|
1015
1066
|
_array: 0x10,
|
1016
1067
|
_regexp: 0x11,
|
1017
|
-
|
1018
|
-
// typed arrays
|
1019
|
-
_int8array: 0x20,
|
1020
|
-
_uint8array: 0x21,
|
1021
|
-
_uint8clampedarray: 0x22,
|
1022
|
-
_int16array: 0x23,
|
1023
|
-
_uint16array: 0x24,
|
1024
|
-
_int32array: 0x25,
|
1025
|
-
_uint32array: 0x26,
|
1026
|
-
_float32array: 0x27,
|
1027
|
-
_float64array: 0x28,
|
1068
|
+
_bytestring: 0x12
|
1028
1069
|
};
|
1029
1070
|
|
1030
1071
|
const TYPE_NAMES = {
|
@@ -1038,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1038
1079
|
[TYPES.bigint]: 'BigInt',
|
1039
1080
|
|
1040
1081
|
[TYPES._array]: 'Array',
|
1041
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1042
1084
|
};
|
1043
1085
|
|
1044
1086
|
const getType = (scope, _name) => {
|
@@ -1062,11 +1104,13 @@ const setType = (scope, _name, type) => {
|
|
1062
1104
|
|
1063
1105
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1064
1106
|
|
1107
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1065
1108
|
if (scope.locals[name]) return [
|
1066
1109
|
...out,
|
1067
1110
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1068
1111
|
];
|
1069
1112
|
|
1113
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1070
1114
|
if (globals[name]) return [
|
1071
1115
|
...out,
|
1072
1116
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
@@ -1075,11 +1119,22 @@ const setType = (scope, _name, type) => {
|
|
1075
1119
|
// throw new Error('could not find var');
|
1076
1120
|
};
|
1077
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
|
+
|
1078
1131
|
const getNodeType = (scope, node) => {
|
1079
1132
|
const inner = () => {
|
1080
1133
|
if (node.type === 'Literal') {
|
1081
1134
|
if (node.regex) return TYPES._regexp;
|
1082
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1083
1138
|
return TYPES[typeof node.value];
|
1084
1139
|
}
|
1085
1140
|
|
@@ -1093,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1093
1148
|
|
1094
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1095
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
|
+
|
1096
1160
|
const func = funcs.find(x => x.name === name);
|
1097
1161
|
|
1098
1162
|
if (func) {
|
@@ -1103,7 +1167,19 @@ const getNodeType = (scope, node) => {
|
|
1103
1167
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1104
1168
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1105
1169
|
|
1106
|
-
|
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) ];
|
1107
1183
|
|
1108
1184
|
// presume
|
1109
1185
|
// todo: warn here?
|
@@ -1175,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1175
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1176
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1177
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1178
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1179
1255
|
|
1180
1256
|
return TYPES.number;
|
1181
1257
|
}
|
@@ -1184,11 +1260,17 @@ const getNodeType = (scope, node) => {
|
|
1184
1260
|
// hack: if something.length, number type
|
1185
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1186
1262
|
|
1187
|
-
//
|
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
|
1188
1270
|
return TYPES.number;
|
1189
1271
|
}
|
1190
1272
|
|
1191
|
-
if (scope.locals['#last_type']) return [
|
1273
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1192
1274
|
|
1193
1275
|
// presume
|
1194
1276
|
// todo: warn here?
|
@@ -1204,8 +1286,8 @@ const getNodeType = (scope, node) => {
|
|
1204
1286
|
const generateLiteral = (scope, decl, global, name) => {
|
1205
1287
|
if (decl.value === null) return number(NULL);
|
1206
1288
|
|
1289
|
+
// hack: just return 1 for regex literals
|
1207
1290
|
if (decl.regex) {
|
1208
|
-
scope.regex[name] = decl.regex;
|
1209
1291
|
return number(1);
|
1210
1292
|
}
|
1211
1293
|
|
@@ -1218,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1218
1300
|
return number(decl.value ? 1 : 0);
|
1219
1301
|
|
1220
1302
|
case 'string':
|
1221
|
-
|
1222
|
-
const rawElements = new Array(str.length);
|
1223
|
-
let j = 0;
|
1224
|
-
for (let i = 0; i < str.length; i++) {
|
1225
|
-
rawElements[i] = str.charCodeAt(i);
|
1226
|
-
}
|
1227
|
-
|
1228
|
-
return makeArray(scope, {
|
1229
|
-
rawElements
|
1230
|
-
}, global, name, false, 'i16')[0];
|
1303
|
+
return makeString(scope, decl.value, global, name);
|
1231
1304
|
|
1232
1305
|
default:
|
1233
1306
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1248,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1248
1321
|
|
1249
1322
|
if (depth === 0)
|
1250
1323
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1251
|
-
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)) {}
|
1252
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++;
|
1253
|
-
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;
|
1254
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1255
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1256
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1276,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1276
1349
|
const generateExp = (scope, decl) => {
|
1277
1350
|
const expression = decl.expression;
|
1278
1351
|
|
1279
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1280
1353
|
disposeLeftover(out);
|
1281
1354
|
|
1282
1355
|
return out;
|
@@ -1334,7 +1407,7 @@ const RTArrayUtil = {
|
|
1334
1407
|
]
|
1335
1408
|
};
|
1336
1409
|
|
1337
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1338
1411
|
/* const callee = decl.callee;
|
1339
1412
|
const args = decl.arguments;
|
1340
1413
|
|
@@ -1367,13 +1440,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1367
1440
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1368
1441
|
out.push(
|
1369
1442
|
...getNodeType(scope, finalStatement),
|
1370
|
-
|
1443
|
+
setLastType(scope)
|
1371
1444
|
);
|
1372
1445
|
} else if (countLeftover(out) === 0) {
|
1373
1446
|
out.push(...number(UNDEFINED));
|
1374
1447
|
out.push(
|
1375
1448
|
...number(TYPES.undefined, Valtype.i32),
|
1376
|
-
|
1449
|
+
setLastType(scope)
|
1377
1450
|
);
|
1378
1451
|
}
|
1379
1452
|
|
@@ -1391,8 +1464,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1391
1464
|
if (name && name.startsWith('__')) {
|
1392
1465
|
const spl = name.slice(2).split('_');
|
1393
1466
|
|
1394
|
-
|
1395
|
-
protoName = func;
|
1467
|
+
protoName = spl[spl.length - 1];
|
1396
1468
|
|
1397
1469
|
target = { ...decl.callee };
|
1398
1470
|
target.name = spl.slice(0, -1).join('_');
|
@@ -1401,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1401
1473
|
// literal.func()
|
1402
1474
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1403
1475
|
// megahack for /regex/.func()
|
1404
|
-
|
1405
|
-
|
1476
|
+
const funcName = decl.callee.property.name;
|
1477
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1406
1478
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1407
1479
|
|
1408
1480
|
funcIndex[func.name] = func.index;
|
@@ -1418,12 +1490,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1418
1490
|
Opcodes.i32_from_u,
|
1419
1491
|
|
1420
1492
|
...number(TYPES.boolean, Valtype.i32),
|
1421
|
-
|
1493
|
+
setLastType(scope)
|
1422
1494
|
];
|
1423
1495
|
}
|
1424
1496
|
|
1425
|
-
|
1426
|
-
protoName = func;
|
1497
|
+
protoName = decl.callee.property.name;
|
1427
1498
|
|
1428
1499
|
target = decl.callee.object;
|
1429
1500
|
}
|
@@ -1445,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1445
1516
|
|
1446
1517
|
if (protoName) {
|
1447
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1448
|
-
|
1449
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1450
1520
|
return acc;
|
1451
1521
|
}, {});
|
1452
1522
|
|
@@ -1455,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1455
1525
|
// use local for cached i32 length as commonly used
|
1456
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1457
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1458
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1459
1528
|
|
1460
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1461
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;
|
1462
1540
|
let lengthI32CacheUsed = false;
|
1463
1541
|
const protoBC = {};
|
1464
1542
|
for (const x in protoCands) {
|
@@ -1468,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1468
1546
|
...RTArrayUtil.getLength(getPointer),
|
1469
1547
|
|
1470
1548
|
...number(TYPES.number, Valtype.i32),
|
1471
|
-
|
1549
|
+
setLastType(scope)
|
1472
1550
|
];
|
1473
1551
|
continue;
|
1474
1552
|
}
|
@@ -1478,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1478
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1479
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1480
1558
|
|
1559
|
+
let optUnused = false;
|
1481
1560
|
const protoOut = protoFunc(getPointer, {
|
1482
1561
|
getCachedI32: () => {
|
1483
1562
|
lengthI32CacheUsed = true;
|
@@ -1492,23 +1571,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1492
1571
|
return makeArray(scope, {
|
1493
1572
|
rawElements: new Array(length)
|
1494
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1495
1577
|
});
|
1496
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1497
1581
|
protoBC[x] = [
|
1498
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1499
1583
|
...protoOut,
|
1500
1584
|
|
1501
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1502
|
-
|
1586
|
+
setLastType(scope),
|
1503
1587
|
[ Opcodes.end ]
|
1504
1588
|
];
|
1505
1589
|
}
|
1506
1590
|
|
1507
|
-
|
1508
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1509
1592
|
|
1510
|
-
|
1511
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1512
1598
|
|
1513
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1514
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1520,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1520
1606
|
|
1521
1607
|
// TODO: error better
|
1522
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1523
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1524
1610
|
];
|
1525
1611
|
}
|
1526
1612
|
}
|
@@ -1567,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1567
1653
|
const func = funcs.find(x => x.index === idx);
|
1568
1654
|
|
1569
1655
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1570
|
-
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);
|
1571
1659
|
|
1572
1660
|
let args = decl.arguments;
|
1573
1661
|
if (func && args.length < paramCount) {
|
@@ -1585,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1585
1673
|
let out = [];
|
1586
1674
|
for (const arg of args) {
|
1587
1675
|
out = out.concat(generate(scope, arg));
|
1588
|
-
if (
|
1676
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1589
1677
|
}
|
1590
1678
|
|
1591
1679
|
out.push([ Opcodes.call, idx ]);
|
1592
1680
|
|
1593
|
-
if (!
|
1681
|
+
if (!typedReturn) {
|
1594
1682
|
// let type;
|
1595
1683
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1596
1684
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1600,7 +1688,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1688
|
// ...number(type, Valtype.i32),
|
1601
1689
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1602
1690
|
// );
|
1603
|
-
} else out.push(
|
1691
|
+
} else out.push(setLastType(scope));
|
1604
1692
|
|
1605
1693
|
return out;
|
1606
1694
|
};
|
@@ -1625,9 +1713,118 @@ const unhackName = name => {
|
|
1625
1713
|
return name;
|
1626
1714
|
};
|
1627
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
|
+
|
1628
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1629
|
-
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1630
1818
|
|
1819
|
+
const known = knownType(scope, type);
|
1820
|
+
if (known != null) {
|
1821
|
+
return bc[known] ?? bc.default;
|
1822
|
+
}
|
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);
|
1631
1828
|
const out = [
|
1632
1829
|
...type,
|
1633
1830
|
[ Opcodes.local_set, tmp ],
|
@@ -1679,6 +1876,51 @@ const allocVar = (scope, name, global = false) => {
|
|
1679
1876
|
return idx;
|
1680
1877
|
};
|
1681
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
|
+
|
1682
1924
|
const generateVar = (scope, decl) => {
|
1683
1925
|
let out = [];
|
1684
1926
|
|
@@ -1690,6 +1932,8 @@ const generateVar = (scope, decl) => {
|
|
1690
1932
|
for (const x of decl.declarations) {
|
1691
1933
|
const name = mapName(x.id.name);
|
1692
1934
|
|
1935
|
+
if (!name) return todo('destructuring is not supported yet');
|
1936
|
+
|
1693
1937
|
if (x.init && isFuncType(x.init.type)) {
|
1694
1938
|
// hack for let a = function () { ... }
|
1695
1939
|
x.init.id = { name };
|
@@ -1706,6 +1950,11 @@ const generateVar = (scope, decl) => {
|
|
1706
1950
|
}
|
1707
1951
|
|
1708
1952
|
let idx = allocVar(scope, name, global);
|
1953
|
+
|
1954
|
+
if (typedInput && x.id.typeAnnotation) {
|
1955
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
|
+
}
|
1957
|
+
|
1709
1958
|
if (x.init) {
|
1710
1959
|
out = out.concat(generate(scope, x.init, global, name));
|
1711
1960
|
|
@@ -1823,6 +2072,8 @@ const generateAssign = (scope, decl) => {
|
|
1823
2072
|
];
|
1824
2073
|
}
|
1825
2074
|
|
2075
|
+
if (!name) return todo('destructuring is not supported yet');
|
2076
|
+
|
1826
2077
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1827
2078
|
|
1828
2079
|
if (local === undefined) {
|
@@ -1869,7 +2120,7 @@ const generateAssign = (scope, decl) => {
|
|
1869
2120
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1870
2121
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
2122
|
|
1872
|
-
|
2123
|
+
getLastType(scope),
|
1873
2124
|
// hack: type is idx+1
|
1874
2125
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1875
2126
|
];
|
@@ -1961,6 +2212,8 @@ const generateUnary = (scope, decl) => {
|
|
1961
2212
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1962
2213
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1963
2214
|
|
2215
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
|
+
|
1964
2217
|
// object and internal types
|
1965
2218
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1966
2219
|
});
|
@@ -2036,7 +2289,7 @@ const generateConditional = (scope, decl) => {
|
|
2036
2289
|
// note type
|
2037
2290
|
out.push(
|
2038
2291
|
...getNodeType(scope, decl.consequent),
|
2039
|
-
|
2292
|
+
setLastType(scope)
|
2040
2293
|
);
|
2041
2294
|
|
2042
2295
|
out.push([ Opcodes.else ]);
|
@@ -2045,7 +2298,7 @@ const generateConditional = (scope, decl) => {
|
|
2045
2298
|
// note type
|
2046
2299
|
out.push(
|
2047
2300
|
...getNodeType(scope, decl.alternate),
|
2048
|
-
|
2301
|
+
setLastType(scope)
|
2049
2302
|
);
|
2050
2303
|
|
2051
2304
|
out.push([ Opcodes.end ]);
|
@@ -2066,8 +2319,10 @@ const generateFor = (scope, decl) => {
|
|
2066
2319
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2067
2320
|
depth.push('for');
|
2068
2321
|
|
2069
|
-
out.push(...generate(scope, decl.test));
|
2070
|
-
|
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 ]);
|
2071
2326
|
depth.push('if');
|
2072
2327
|
|
2073
2328
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2075,8 +2330,7 @@ const generateFor = (scope, decl) => {
|
|
2075
2330
|
out.push(...generate(scope, decl.body));
|
2076
2331
|
out.push([ Opcodes.end ]);
|
2077
2332
|
|
2078
|
-
out.push(...generate(scope, decl.update));
|
2079
|
-
depth.pop();
|
2333
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2080
2334
|
|
2081
2335
|
out.push([ Opcodes.br, 1 ]);
|
2082
2336
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2142,13 +2396,14 @@ const generateForOf = (scope, decl) => {
|
|
2142
2396
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2143
2397
|
// hack: this is naughty and will break things!
|
2144
2398
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2145
|
-
if (pages.
|
2399
|
+
if (pages.hasAnyString) {
|
2146
2400
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2147
2401
|
rawElements: new Array(1)
|
2148
2402
|
}, isGlobal, leftName, true, 'i16');
|
2149
2403
|
}
|
2150
2404
|
|
2151
2405
|
// set type for local
|
2406
|
+
// todo: optimize away counter and use end pointer
|
2152
2407
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2153
2408
|
[TYPES._array]: [
|
2154
2409
|
...setType(scope, leftName, TYPES.number),
|
@@ -2273,7 +2528,7 @@ const generateThrow = (scope, decl) => {
|
|
2273
2528
|
// hack: throw new X("...") -> throw "..."
|
2274
2529
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2275
2530
|
constructor = decl.argument.callee.name;
|
2276
|
-
message = decl.argument.arguments[0]
|
2531
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2277
2532
|
}
|
2278
2533
|
|
2279
2534
|
if (tags.length === 0) tags.push({
|
@@ -2334,6 +2589,8 @@ const allocPage = (reason, type) => {
|
|
2334
2589
|
|
2335
2590
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2336
2591
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2592
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2593
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2337
2594
|
|
2338
2595
|
const ind = pages.size;
|
2339
2596
|
pages.set(reason, { ind, type });
|
@@ -2367,7 +2624,8 @@ const StoreOps = {
|
|
2367
2624
|
f64: Opcodes.f64_store,
|
2368
2625
|
|
2369
2626
|
// expects i32 input!
|
2370
|
-
|
2627
|
+
i8: Opcodes.i32_store8,
|
2628
|
+
i16: Opcodes.i32_store16,
|
2371
2629
|
};
|
2372
2630
|
|
2373
2631
|
let data = [];
|
@@ -2386,6 +2644,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2386
2644
|
}
|
2387
2645
|
};
|
2388
2646
|
|
2647
|
+
const getAllocType = itemType => {
|
2648
|
+
switch (itemType) {
|
2649
|
+
case 'i8': return 'bytestring';
|
2650
|
+
case 'i16': return 'string';
|
2651
|
+
|
2652
|
+
default: return 'array';
|
2653
|
+
}
|
2654
|
+
};
|
2655
|
+
|
2389
2656
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2390
2657
|
const out = [];
|
2391
2658
|
|
@@ -2395,7 +2662,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2395
2662
|
|
2396
2663
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2397
2664
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2398
|
-
arrays.set(name, allocPage(`${itemType
|
2665
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2399
2666
|
}
|
2400
2667
|
|
2401
2668
|
const pointer = arrays.get(name);
|
@@ -2441,7 +2708,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2441
2708
|
out.push(
|
2442
2709
|
...number(0, Valtype.i32),
|
2443
2710
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2444
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2711
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2445
2712
|
);
|
2446
2713
|
}
|
2447
2714
|
|
@@ -2451,15 +2718,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2451
2718
|
return [ out, pointer ];
|
2452
2719
|
};
|
2453
2720
|
|
2721
|
+
const byteStringable = str => {
|
2722
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2723
|
+
|
2724
|
+
for (let i = 0; i < str.length; i++) {
|
2725
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2726
|
+
}
|
2727
|
+
|
2728
|
+
return true;
|
2729
|
+
};
|
2730
|
+
|
2454
2731
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2455
2732
|
const rawElements = new Array(str.length);
|
2733
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2456
2734
|
for (let i = 0; i < str.length; i++) {
|
2457
|
-
|
2735
|
+
const c = str.charCodeAt(i);
|
2736
|
+
rawElements[i] = c;
|
2737
|
+
|
2738
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2458
2739
|
}
|
2459
2740
|
|
2460
2741
|
return makeArray(scope, {
|
2461
2742
|
rawElements
|
2462
|
-
}, global, name, false, 'i16')[0];
|
2743
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2463
2744
|
};
|
2464
2745
|
|
2465
2746
|
let arrays = new Map();
|
@@ -2487,10 +2768,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2487
2768
|
];
|
2488
2769
|
}
|
2489
2770
|
|
2771
|
+
const object = generate(scope, decl.object);
|
2772
|
+
const property = generate(scope, decl.property);
|
2773
|
+
|
2490
2774
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2491
2775
|
// hack: this is naughty and will break things!
|
2492
|
-
let newOut = number(0,
|
2493
|
-
if (pages.
|
2776
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2777
|
+
if (pages.hasAnyString) {
|
2494
2778
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2495
2779
|
rawElements: new Array(1)
|
2496
2780
|
}, _global, _name, true, 'i16');
|
@@ -2499,7 +2783,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2499
2783
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2500
2784
|
[TYPES._array]: [
|
2501
2785
|
// get index as valtype
|
2502
|
-
...
|
2786
|
+
...property,
|
2503
2787
|
|
2504
2788
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2505
2789
|
Opcodes.i32_to_u,
|
@@ -2507,7 +2791,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2507
2791
|
[ Opcodes.i32_mul ],
|
2508
2792
|
|
2509
2793
|
...(aotPointer ? [] : [
|
2510
|
-
...
|
2794
|
+
...object,
|
2511
2795
|
Opcodes.i32_to_u,
|
2512
2796
|
[ Opcodes.i32_add ]
|
2513
2797
|
]),
|
@@ -2516,7 +2800,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2516
2800
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2517
2801
|
|
2518
2802
|
...number(TYPES.number, Valtype.i32),
|
2519
|
-
|
2803
|
+
setLastType(scope)
|
2520
2804
|
],
|
2521
2805
|
|
2522
2806
|
[TYPES.string]: [
|
@@ -2526,14 +2810,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2526
2810
|
|
2527
2811
|
...number(0, Valtype.i32), // base 0 for store later
|
2528
2812
|
|
2529
|
-
...
|
2530
|
-
|
2813
|
+
...property,
|
2531
2814
|
Opcodes.i32_to_u,
|
2815
|
+
|
2532
2816
|
...number(ValtypeSize.i16, Valtype.i32),
|
2533
2817
|
[ Opcodes.i32_mul ],
|
2534
2818
|
|
2535
2819
|
...(aotPointer ? [] : [
|
2536
|
-
...
|
2820
|
+
...object,
|
2537
2821
|
Opcodes.i32_to_u,
|
2538
2822
|
[ Opcodes.i32_add ]
|
2539
2823
|
]),
|
@@ -2548,7 +2832,35 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2548
2832
|
...number(newPointer),
|
2549
2833
|
|
2550
2834
|
...number(TYPES.string, Valtype.i32),
|
2551
|
-
|
2835
|
+
setLastType(scope)
|
2836
|
+
],
|
2837
|
+
[TYPES._bytestring]: [
|
2838
|
+
// setup new/out array
|
2839
|
+
...newOut,
|
2840
|
+
[ Opcodes.drop ],
|
2841
|
+
|
2842
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2843
|
+
|
2844
|
+
...property,
|
2845
|
+
Opcodes.i32_to_u,
|
2846
|
+
|
2847
|
+
...(aotPointer ? [] : [
|
2848
|
+
...object,
|
2849
|
+
Opcodes.i32_to_u,
|
2850
|
+
[ Opcodes.i32_add ]
|
2851
|
+
]),
|
2852
|
+
|
2853
|
+
// load current string ind {arg}
|
2854
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2855
|
+
|
2856
|
+
// store to new string ind 0
|
2857
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2858
|
+
|
2859
|
+
// return new string (page)
|
2860
|
+
...number(newPointer),
|
2861
|
+
|
2862
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2863
|
+
setLastType(scope)
|
2552
2864
|
],
|
2553
2865
|
|
2554
2866
|
default: [ [ Opcodes.unreachable ] ]
|
@@ -2568,11 +2880,14 @@ const objectHack = node => {
|
|
2568
2880
|
// if object is not identifier or another member exp, give up
|
2569
2881
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2570
2882
|
|
2571
|
-
if (!objectName) objectName = objectHack(node.object)
|
2883
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2572
2884
|
|
2573
2885
|
// if .length, give up (hack within a hack!)
|
2574
2886
|
if (node.property.name === 'length') return node;
|
2575
2887
|
|
2888
|
+
// no object name, give up
|
2889
|
+
if (!objectName) return node;
|
2890
|
+
|
2576
2891
|
const name = '__' + objectName + '_' + node.property.name;
|
2577
2892
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2578
2893
|
|
@@ -2597,7 +2912,7 @@ const generateFunc = (scope, decl) => {
|
|
2597
2912
|
if (decl.generator) return todo('generator functions are not supported');
|
2598
2913
|
|
2599
2914
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2600
|
-
const params = decl.params
|
2915
|
+
const params = decl.params ?? [];
|
2601
2916
|
|
2602
2917
|
// const innerScope = { ...scope };
|
2603
2918
|
// TODO: share scope/locals between !!!
|
@@ -2611,7 +2926,11 @@ const generateFunc = (scope, decl) => {
|
|
2611
2926
|
};
|
2612
2927
|
|
2613
2928
|
for (let i = 0; i < params.length; i++) {
|
2614
|
-
allocVar(innerScope, params[i], false);
|
2929
|
+
allocVar(innerScope, params[i].name, false);
|
2930
|
+
|
2931
|
+
if (typedInput && params[i].typeAnnotation) {
|
2932
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2933
|
+
}
|
2615
2934
|
}
|
2616
2935
|
|
2617
2936
|
let body = objectHack(decl.body);
|
@@ -2627,10 +2946,8 @@ const generateFunc = (scope, decl) => {
|
|
2627
2946
|
const func = {
|
2628
2947
|
name,
|
2629
2948
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2630
|
-
|
2631
|
-
|
2632
|
-
throws: innerScope.throws,
|
2633
|
-
index: currentFuncIndex++
|
2949
|
+
index: currentFuncIndex++,
|
2950
|
+
...innerScope
|
2634
2951
|
};
|
2635
2952
|
funcIndex[name] = func.index;
|
2636
2953
|
|
@@ -2650,117 +2967,6 @@ const generateFunc = (scope, decl) => {
|
|
2650
2967
|
);
|
2651
2968
|
}
|
2652
2969
|
|
2653
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2654
|
-
let offset = 0, vecParams = 0;
|
2655
|
-
for (let i = 0; i < params.length; i++) {
|
2656
|
-
const name = params[i];
|
2657
|
-
const local = func.locals[name];
|
2658
|
-
if (local.type === Valtype.v128) {
|
2659
|
-
vecParams++;
|
2660
|
-
|
2661
|
-
/* wasm.unshift( // add v128 load for param
|
2662
|
-
[ Opcodes.i32_const, 0 ],
|
2663
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2664
|
-
[ Opcodes.local_set, local.idx ]
|
2665
|
-
); */
|
2666
|
-
|
2667
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2668
|
-
|
2669
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2670
|
-
const { vecType } = local;
|
2671
|
-
let [ type, lanes ] = vecType.split('x');
|
2672
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2673
|
-
|
2674
|
-
lanes = parseInt(lanes);
|
2675
|
-
type = Valtype[type];
|
2676
|
-
|
2677
|
-
const name = params[i]; // get original param name
|
2678
|
-
|
2679
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2680
|
-
|
2681
|
-
// update index of original local
|
2682
|
-
// delete func.locals[name];
|
2683
|
-
|
2684
|
-
// add new locals for params
|
2685
|
-
for (let j = 0; j < lanes; j++) {
|
2686
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2687
|
-
}
|
2688
|
-
|
2689
|
-
// prepend wasm to generate expected v128 locals
|
2690
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2691
|
-
...i32x4(0, 0, 0, 0),
|
2692
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2693
|
-
[ Opcodes.local_get, offset + j ],
|
2694
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2695
|
-
]),
|
2696
|
-
[ Opcodes.local_set, i ]
|
2697
|
-
);
|
2698
|
-
|
2699
|
-
offset += lanes;
|
2700
|
-
|
2701
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2702
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2703
|
-
|
2704
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2705
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2706
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2707
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2708
|
-
}
|
2709
|
-
}
|
2710
|
-
|
2711
|
-
if (offset !== 0) {
|
2712
|
-
// bump local indexes for all other locals after
|
2713
|
-
for (const x in func.locals) {
|
2714
|
-
const local = func.locals[x];
|
2715
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2716
|
-
}
|
2717
|
-
|
2718
|
-
// bump local indexes in wasm local.get/set
|
2719
|
-
for (let j = 0; j < wasm.length; j++) {
|
2720
|
-
const inst = wasm[j];
|
2721
|
-
if (j < offset * 2 + vecParams * 2) {
|
2722
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2723
|
-
continue;
|
2724
|
-
}
|
2725
|
-
|
2726
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2727
|
-
}
|
2728
|
-
}
|
2729
|
-
|
2730
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2731
|
-
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]);
|
2732
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2733
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2734
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2735
|
-
const { vecType } = lastReturnLocal;
|
2736
|
-
let [ type, lanes ] = vecType.split('x');
|
2737
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2738
|
-
|
2739
|
-
lanes = parseInt(lanes);
|
2740
|
-
type = Valtype[type];
|
2741
|
-
|
2742
|
-
const vecIdx = lastReturnLocal.idx;
|
2743
|
-
|
2744
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2745
|
-
const tmpIdx = [];
|
2746
|
-
for (let i = 0; i < lanes; i++) {
|
2747
|
-
const idx = lastIdx + i + 1;
|
2748
|
-
tmpIdx.push(idx);
|
2749
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2750
|
-
}
|
2751
|
-
|
2752
|
-
wasm.splice(wasm.length - 1, 1,
|
2753
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2754
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2755
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2756
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2757
|
-
].filter(x => x !== null)),
|
2758
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2759
|
-
);
|
2760
|
-
|
2761
|
-
func.returns = new Array(lanes).fill(type);
|
2762
|
-
}
|
2763
|
-
|
2764
2970
|
func.wasm = wasm;
|
2765
2971
|
|
2766
2972
|
funcs.push(func);
|