porffor 0.2.0-3fad637 → 0.2.0-4035760
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 +28 -14
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +403 -200
- 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/r.js +45 -0
- 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?
|
@@ -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('_');
|
@@ -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];
|
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);
|
1630
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
|
|
@@ -1706,6 +1948,11 @@ const generateVar = (scope, decl) => {
|
|
1706
1948
|
}
|
1707
1949
|
|
1708
1950
|
let idx = allocVar(scope, name, global);
|
1951
|
+
|
1952
|
+
if (typedInput && x.id.typeAnnotation) {
|
1953
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1954
|
+
}
|
1955
|
+
|
1709
1956
|
if (x.init) {
|
1710
1957
|
out = out.concat(generate(scope, x.init, global, name));
|
1711
1958
|
|
@@ -1869,7 +2116,7 @@ const generateAssign = (scope, decl) => {
|
|
1869
2116
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1870
2117
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
2118
|
|
1872
|
-
|
2119
|
+
getLastType(scope),
|
1873
2120
|
// hack: type is idx+1
|
1874
2121
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1875
2122
|
];
|
@@ -1961,6 +2208,8 @@ const generateUnary = (scope, decl) => {
|
|
1961
2208
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1962
2209
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1963
2210
|
|
2211
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2212
|
+
|
1964
2213
|
// object and internal types
|
1965
2214
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1966
2215
|
});
|
@@ -2036,7 +2285,7 @@ const generateConditional = (scope, decl) => {
|
|
2036
2285
|
// note type
|
2037
2286
|
out.push(
|
2038
2287
|
...getNodeType(scope, decl.consequent),
|
2039
|
-
|
2288
|
+
setLastType(scope)
|
2040
2289
|
);
|
2041
2290
|
|
2042
2291
|
out.push([ Opcodes.else ]);
|
@@ -2045,7 +2294,7 @@ const generateConditional = (scope, decl) => {
|
|
2045
2294
|
// note type
|
2046
2295
|
out.push(
|
2047
2296
|
...getNodeType(scope, decl.alternate),
|
2048
|
-
|
2297
|
+
setLastType(scope)
|
2049
2298
|
);
|
2050
2299
|
|
2051
2300
|
out.push([ Opcodes.end ]);
|
@@ -2142,13 +2391,14 @@ const generateForOf = (scope, decl) => {
|
|
2142
2391
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2143
2392
|
// hack: this is naughty and will break things!
|
2144
2393
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2145
|
-
if (pages.
|
2394
|
+
if (pages.hasAnyString) {
|
2146
2395
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2147
2396
|
rawElements: new Array(1)
|
2148
2397
|
}, isGlobal, leftName, true, 'i16');
|
2149
2398
|
}
|
2150
2399
|
|
2151
2400
|
// set type for local
|
2401
|
+
// todo: optimize away counter and use end pointer
|
2152
2402
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2153
2403
|
[TYPES._array]: [
|
2154
2404
|
...setType(scope, leftName, TYPES.number),
|
@@ -2273,7 +2523,7 @@ const generateThrow = (scope, decl) => {
|
|
2273
2523
|
// hack: throw new X("...") -> throw "..."
|
2274
2524
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2275
2525
|
constructor = decl.argument.callee.name;
|
2276
|
-
message = decl.argument.arguments[0]
|
2526
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2277
2527
|
}
|
2278
2528
|
|
2279
2529
|
if (tags.length === 0) tags.push({
|
@@ -2334,6 +2584,8 @@ const allocPage = (reason, type) => {
|
|
2334
2584
|
|
2335
2585
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2336
2586
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2587
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2588
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2337
2589
|
|
2338
2590
|
const ind = pages.size;
|
2339
2591
|
pages.set(reason, { ind, type });
|
@@ -2367,7 +2619,8 @@ const StoreOps = {
|
|
2367
2619
|
f64: Opcodes.f64_store,
|
2368
2620
|
|
2369
2621
|
// expects i32 input!
|
2370
|
-
|
2622
|
+
i8: Opcodes.i32_store8,
|
2623
|
+
i16: Opcodes.i32_store16,
|
2371
2624
|
};
|
2372
2625
|
|
2373
2626
|
let data = [];
|
@@ -2386,6 +2639,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2386
2639
|
}
|
2387
2640
|
};
|
2388
2641
|
|
2642
|
+
const getAllocType = itemType => {
|
2643
|
+
switch (itemType) {
|
2644
|
+
case 'i8': return 'bytestring';
|
2645
|
+
case 'i16': return 'string';
|
2646
|
+
|
2647
|
+
default: return 'array';
|
2648
|
+
}
|
2649
|
+
};
|
2650
|
+
|
2389
2651
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2390
2652
|
const out = [];
|
2391
2653
|
|
@@ -2395,7 +2657,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2395
2657
|
|
2396
2658
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2397
2659
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2398
|
-
arrays.set(name, allocPage(`${itemType
|
2660
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2399
2661
|
}
|
2400
2662
|
|
2401
2663
|
const pointer = arrays.get(name);
|
@@ -2441,7 +2703,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2441
2703
|
out.push(
|
2442
2704
|
...number(0, Valtype.i32),
|
2443
2705
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2444
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2706
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2445
2707
|
);
|
2446
2708
|
}
|
2447
2709
|
|
@@ -2451,15 +2713,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2451
2713
|
return [ out, pointer ];
|
2452
2714
|
};
|
2453
2715
|
|
2716
|
+
const byteStringable = str => {
|
2717
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2718
|
+
|
2719
|
+
for (let i = 0; i < str.length; i++) {
|
2720
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2721
|
+
}
|
2722
|
+
|
2723
|
+
return true;
|
2724
|
+
};
|
2725
|
+
|
2454
2726
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2455
2727
|
const rawElements = new Array(str.length);
|
2728
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2456
2729
|
for (let i = 0; i < str.length; i++) {
|
2457
|
-
|
2730
|
+
const c = str.charCodeAt(i);
|
2731
|
+
rawElements[i] = c;
|
2732
|
+
|
2733
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2458
2734
|
}
|
2459
2735
|
|
2460
2736
|
return makeArray(scope, {
|
2461
2737
|
rawElements
|
2462
|
-
}, global, name, false, 'i16')[0];
|
2738
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2463
2739
|
};
|
2464
2740
|
|
2465
2741
|
let arrays = new Map();
|
@@ -2487,10 +2763,15 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2487
2763
|
];
|
2488
2764
|
}
|
2489
2765
|
|
2766
|
+
const object = generate(scope, decl.object);
|
2767
|
+
const property = generate(scope, decl.property);
|
2768
|
+
|
2769
|
+
console.log(decl.property, property);
|
2770
|
+
|
2490
2771
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2491
2772
|
// hack: this is naughty and will break things!
|
2492
|
-
let newOut = number(0,
|
2493
|
-
if (pages.
|
2773
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2774
|
+
if (pages.hasAnyString) {
|
2494
2775
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2495
2776
|
rawElements: new Array(1)
|
2496
2777
|
}, _global, _name, true, 'i16');
|
@@ -2499,7 +2780,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2499
2780
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2500
2781
|
[TYPES._array]: [
|
2501
2782
|
// get index as valtype
|
2502
|
-
...
|
2783
|
+
...property,
|
2503
2784
|
|
2504
2785
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2505
2786
|
Opcodes.i32_to_u,
|
@@ -2507,7 +2788,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2507
2788
|
[ Opcodes.i32_mul ],
|
2508
2789
|
|
2509
2790
|
...(aotPointer ? [] : [
|
2510
|
-
...
|
2791
|
+
...object,
|
2511
2792
|
Opcodes.i32_to_u,
|
2512
2793
|
[ Opcodes.i32_add ]
|
2513
2794
|
]),
|
@@ -2516,7 +2797,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2516
2797
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2517
2798
|
|
2518
2799
|
...number(TYPES.number, Valtype.i32),
|
2519
|
-
|
2800
|
+
setLastType(scope)
|
2520
2801
|
],
|
2521
2802
|
|
2522
2803
|
[TYPES.string]: [
|
@@ -2526,14 +2807,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2526
2807
|
|
2527
2808
|
...number(0, Valtype.i32), // base 0 for store later
|
2528
2809
|
|
2529
|
-
...
|
2530
|
-
|
2810
|
+
...property,
|
2531
2811
|
Opcodes.i32_to_u,
|
2812
|
+
|
2532
2813
|
...number(ValtypeSize.i16, Valtype.i32),
|
2533
2814
|
[ Opcodes.i32_mul ],
|
2534
2815
|
|
2535
2816
|
...(aotPointer ? [] : [
|
2536
|
-
...
|
2817
|
+
...object,
|
2537
2818
|
Opcodes.i32_to_u,
|
2538
2819
|
[ Opcodes.i32_add ]
|
2539
2820
|
]),
|
@@ -2548,7 +2829,35 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2548
2829
|
...number(newPointer),
|
2549
2830
|
|
2550
2831
|
...number(TYPES.string, Valtype.i32),
|
2551
|
-
|
2832
|
+
setLastType(scope)
|
2833
|
+
],
|
2834
|
+
[TYPES._bytestring]: [
|
2835
|
+
// setup new/out array
|
2836
|
+
...newOut,
|
2837
|
+
[ Opcodes.drop ],
|
2838
|
+
|
2839
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2840
|
+
|
2841
|
+
...property,
|
2842
|
+
Opcodes.i32_to_u,
|
2843
|
+
|
2844
|
+
...(aotPointer ? [] : [
|
2845
|
+
...object,
|
2846
|
+
Opcodes.i32_to_u,
|
2847
|
+
[ Opcodes.i32_add ]
|
2848
|
+
]),
|
2849
|
+
|
2850
|
+
// load current string ind {arg}
|
2851
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2852
|
+
|
2853
|
+
// store to new string ind 0
|
2854
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2855
|
+
|
2856
|
+
// return new string (page)
|
2857
|
+
...number(newPointer),
|
2858
|
+
|
2859
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2860
|
+
setLastType(scope)
|
2552
2861
|
],
|
2553
2862
|
|
2554
2863
|
default: [ [ Opcodes.unreachable ] ]
|
@@ -2568,11 +2877,14 @@ const objectHack = node => {
|
|
2568
2877
|
// if object is not identifier or another member exp, give up
|
2569
2878
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2570
2879
|
|
2571
|
-
if (!objectName) objectName = objectHack(node.object)
|
2880
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2572
2881
|
|
2573
2882
|
// if .length, give up (hack within a hack!)
|
2574
2883
|
if (node.property.name === 'length') return node;
|
2575
2884
|
|
2885
|
+
// no object name, give up
|
2886
|
+
if (!objectName) return node;
|
2887
|
+
|
2576
2888
|
const name = '__' + objectName + '_' + node.property.name;
|
2577
2889
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2578
2890
|
|
@@ -2597,7 +2909,7 @@ const generateFunc = (scope, decl) => {
|
|
2597
2909
|
if (decl.generator) return todo('generator functions are not supported');
|
2598
2910
|
|
2599
2911
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2600
|
-
const params = decl.params
|
2912
|
+
const params = decl.params ?? [];
|
2601
2913
|
|
2602
2914
|
// const innerScope = { ...scope };
|
2603
2915
|
// TODO: share scope/locals between !!!
|
@@ -2611,7 +2923,11 @@ const generateFunc = (scope, decl) => {
|
|
2611
2923
|
};
|
2612
2924
|
|
2613
2925
|
for (let i = 0; i < params.length; i++) {
|
2614
|
-
allocVar(innerScope, params[i], false);
|
2926
|
+
allocVar(innerScope, params[i].name, false);
|
2927
|
+
|
2928
|
+
if (typedInput && params[i].typeAnnotation) {
|
2929
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2930
|
+
}
|
2615
2931
|
}
|
2616
2932
|
|
2617
2933
|
let body = objectHack(decl.body);
|
@@ -2627,10 +2943,8 @@ const generateFunc = (scope, decl) => {
|
|
2627
2943
|
const func = {
|
2628
2944
|
name,
|
2629
2945
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2630
|
-
|
2631
|
-
|
2632
|
-
throws: innerScope.throws,
|
2633
|
-
index: currentFuncIndex++
|
2946
|
+
index: currentFuncIndex++,
|
2947
|
+
...innerScope
|
2634
2948
|
};
|
2635
2949
|
funcIndex[name] = func.index;
|
2636
2950
|
|
@@ -2650,117 +2964,6 @@ const generateFunc = (scope, decl) => {
|
|
2650
2964
|
);
|
2651
2965
|
}
|
2652
2966
|
|
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
2967
|
func.wasm = wasm;
|
2765
2968
|
|
2766
2969
|
funcs.push(func);
|