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.
@@ -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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
392
+ setLastType(scope),
383
393
  [ Opcodes.else ],
384
394
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
395
  // note type
386
396
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
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
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
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
- // we cannot guess
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 [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
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
- const str = decl.value;
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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
- const func = spl[spl.length - 1];
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
- if (decl.callee.object.regex) {
1405
- const funcName = decl.callee.property.name;
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1493
+ setLastType(scope)
1422
1494
  ];
1423
1495
  }
1424
1496
 
1425
- const func = decl.callee.property.name;
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
- const f = prototypeFuncs[x][protoName];
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1586
+ setLastType(scope),
1503
1587
  [ Opcodes.end ]
1504
1588
  ];
1505
1589
  }
1506
1590
 
1507
- return [
1508
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1509
1592
 
1510
- Opcodes.i32_to_u,
1511
- [ Opcodes.local_set, pointerLocal ],
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 paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
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 (userFunc) out = out.concat(getNodeType(scope, arg));
1676
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1589
1677
  }
1590
1678
 
1591
1679
  out.push([ Opcodes.call, idx ]);
1592
1680
 
1593
- if (!userFunc) {
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([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
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
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
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
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
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.hasString) {
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].value;
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
- i16: Opcodes.i32_store16
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 === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
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
- rawElements[i] = str.charCodeAt(i);
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, Valtype.f64), newPointer = -1;
2493
- if (pages.hasString) {
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
- ...generate(scope, decl.property),
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
- ...generate(scope, decl.object),
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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
- ...generate(scope, decl.property),
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
- ...generate(scope, decl.object),
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
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
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).name.slice(2);
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?.map(x => x.name) ?? [];
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
- returns: innerScope.returns,
2631
- locals: innerScope.locals,
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);