porffor 0.14.0-eca486960 → 0.16.0-053a03e10

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.
Files changed (43) hide show
  1. package/CONTRIBUTING.md +15 -9
  2. package/README.md +9 -13
  3. package/asur/index.js +1 -1
  4. package/compiler/2c.js +104 -51
  5. package/compiler/assemble.js +18 -3
  6. package/compiler/builtins/annexb_string.ts +1 -0
  7. package/compiler/builtins/array.ts +84 -4
  8. package/compiler/builtins/base64.ts +1 -0
  9. package/compiler/builtins/boolean.ts +2 -0
  10. package/compiler/builtins/console.ts +6 -0
  11. package/compiler/builtins/crypto.ts +1 -0
  12. package/compiler/builtins/date.ts +2 -0
  13. package/compiler/builtins/error.js +22 -0
  14. package/compiler/builtins/escape.ts +1 -2
  15. package/compiler/builtins/function.ts +2 -0
  16. package/compiler/builtins/int.ts +2 -0
  17. package/compiler/builtins/math.ts +410 -0
  18. package/compiler/builtins/number.ts +2 -0
  19. package/compiler/builtins/object.ts +2 -0
  20. package/compiler/builtins/porffor.d.ts +11 -0
  21. package/compiler/builtins/set.ts +15 -1
  22. package/compiler/builtins/string.ts +1 -0
  23. package/compiler/builtins/symbol.ts +8 -7
  24. package/compiler/builtins.js +46 -10
  25. package/compiler/codegen.js +384 -192
  26. package/compiler/cyclone.js +535 -0
  27. package/compiler/decompile.js +6 -0
  28. package/compiler/generated_builtins.js +503 -53
  29. package/compiler/havoc.js +93 -0
  30. package/compiler/index.js +78 -7
  31. package/compiler/opt.js +3 -39
  32. package/compiler/parse.js +2 -2
  33. package/compiler/pgo.js +206 -0
  34. package/compiler/precompile.js +5 -4
  35. package/compiler/prefs.js +7 -2
  36. package/compiler/prototype.js +180 -157
  37. package/compiler/wrap.js +71 -16
  38. package/no_pgo.txt +923 -0
  39. package/package.json +1 -1
  40. package/pgo.txt +916 -0
  41. package/runner/index.js +18 -12
  42. package/runner/repl.js +18 -2
  43. /package/runner/{profiler.js → profile.js} +0 -0
@@ -40,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
40
40
  }
41
41
  };
42
42
 
43
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
- const hasFuncWithName = name => {
45
- const func = funcs.find(x => x.name === name);
46
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
- };
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
48
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
49
49
  switch (decl.type) {
50
50
  case 'BinaryExpression':
@@ -147,14 +147,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
147
147
  return generateMember(scope, decl, global, name);
148
148
 
149
149
  case 'ExportNamedDeclaration':
150
- // hack to flag new func for export
151
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
152
151
  generate(scope, decl.declaration);
153
152
 
154
- if (funcsBefore !== funcs.length) {
155
- // new func added
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
156
+
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
158
160
  }
159
161
 
160
162
  return [];
@@ -361,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
361
363
  };
362
364
 
363
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
364
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
365
367
 
366
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
367
369
  const checks = {
@@ -378,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
378
380
 
379
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
380
382
  // (like if we are in an if condition - very common)
381
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
382
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
383
385
 
384
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
385
387
 
386
388
  if (canInt) {
387
389
  // remove int -> float conversions from left and right
@@ -444,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
444
446
  ...number(0, Valtype.i32), // base 0 for store later
445
447
 
446
448
  ...number(pointer, Valtype.i32),
447
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
448
450
  [ Opcodes.local_tee, leftLength ],
449
451
 
450
452
  [ Opcodes.local_get, rightPointer ],
451
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
452
454
  [ Opcodes.local_tee, rightLength ],
453
455
 
454
456
  [ Opcodes.i32_add ],
@@ -504,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
504
506
  ...number(0, Valtype.i32), // base 0 for store later
505
507
 
506
508
  [ Opcodes.local_get, leftPointer ],
507
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
508
510
  [ Opcodes.local_tee, leftLength ],
509
511
 
510
512
  [ Opcodes.local_get, rightPointer ],
511
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
512
514
  [ Opcodes.local_tee, rightLength ],
513
515
 
514
516
  [ Opcodes.i32_add ],
@@ -586,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
586
588
 
587
589
  // get lengths
588
590
  [ Opcodes.local_get, leftPointer ],
589
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
590
592
  [ Opcodes.local_tee, leftLength ],
591
593
 
592
594
  [ Opcodes.local_get, rightPointer ],
593
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
594
596
 
595
597
  // fast path: check leftLength != rightLength
596
598
  [ Opcodes.i32_ne ],
@@ -646,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
646
648
  [ Opcodes.i32_add ],
647
649
  [ Opcodes.local_tee, index ],
648
650
 
649
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
650
652
  [ Opcodes.local_get, indexEnd ],
651
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
652
654
  [ Opcodes.br_if, 0 ],
653
655
  [ Opcodes.end ],
654
656
 
@@ -666,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
666
668
  ];
667
669
  };
668
670
 
669
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
670
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
671
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
671
673
  ...wasm,
672
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
673
675
  ];
674
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
675
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
676
680
  const useTmp = knownType(scope, type) == null;
677
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
678
682
 
679
- const def = [
680
- // if value != 0
681
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
682
688
 
683
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
684
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
685
691
 
686
- /* Opcodes.eqz,
687
- [ Opcodes.i32_eqz ],
688
- Opcodes.i32_from */
689
- ];
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
690
709
 
691
710
  return [
692
711
  ...wasm,
693
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
694
713
 
695
714
  ...typeSwitch(scope, type, {
696
- // [TYPES.number]: def,
697
- [TYPES.array]: [
698
- // arrays are always truthy
699
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
700
- ],
701
715
  [TYPES.string]: [
702
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
703
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -733,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
733
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
734
748
 
735
749
  ...typeSwitch(scope, type, {
736
- [TYPES.array]: [
737
- // arrays are always truthy
738
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
739
- ],
740
750
  [TYPES.string]: [
741
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
742
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -980,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
980
990
  // if both are true
981
991
  [ Opcodes.i32_and ],
982
992
  [ Opcodes.if, Blocktype.void ],
983
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
984
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
985
995
  [ Opcodes.br, 1 ],
986
996
  [ Opcodes.end ],
@@ -1029,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1029
1039
  return out;
1030
1040
  };
1031
1041
 
1032
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1033
- return func({ name, params, locals, returns, localInd }, {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1034
1044
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1035
- builtin: name => {
1036
- let idx = funcIndex[name] ?? importedFuncs[name];
1037
- if (idx === undefined && builtinFuncs[name]) {
1038
- includeBuiltin(null, name);
1039
- idx = funcIndex[name];
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1040
1050
  }
1041
1051
 
1042
1052
  return idx;
@@ -1044,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1044
1054
  });
1045
1055
  };
1046
1056
 
1047
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1048
1058
  const existing = funcs.find(x => x.name === name);
1049
1059
  if (existing) return existing;
1050
1060
 
@@ -1062,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1062
1072
  data.push(copy);
1063
1073
  }
1064
1074
 
1065
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1066
1091
 
1067
1092
  let baseGlobalIdx, i = 0;
1068
1093
  for (const type of globalTypes) {
@@ -1081,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1081
1106
  }
1082
1107
  }
1083
1108
 
1084
- const func = {
1085
- name,
1086
- params,
1087
- locals,
1088
- returns,
1089
- returnType: returnType ?? TYPES.number,
1090
- wasm,
1091
- internal: true,
1092
- index: currentFuncIndex++
1093
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1094
1115
 
1095
- funcs.push(func);
1096
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1097
1117
 
1098
1118
  return func;
1099
1119
  };
@@ -1234,7 +1254,7 @@ const getNodeType = (scope, node) => {
1234
1254
  const func = funcs.find(x => x.name === name);
1235
1255
 
1236
1256
  if (func) {
1237
- if (func.returnType) return func.returnType;
1257
+ if (func.returnType != null) return func.returnType;
1238
1258
  }
1239
1259
 
1240
1260
  if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
@@ -1249,7 +1269,9 @@ const getNodeType = (scope, node) => {
1249
1269
 
1250
1270
  const func = spl[spl.length - 1];
1251
1271
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1252
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType != null) return protoFuncs[0].returnType;
1274
+ }
1253
1275
  }
1254
1276
 
1255
1277
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1435,16 +1457,11 @@ const countLeftover = wasm => {
1435
1457
  else if (inst[0] === Opcodes.return) count = 0;
1436
1458
  else if (inst[0] === Opcodes.call) {
1437
1459
  let func = funcs.find(x => x.index === inst[1]);
1438
- if (inst[1] === -1) {
1439
- // todo: count for calling self
1440
- } else if (!func && inst[1] < importedFuncs.length) {
1441
- count -= importedFuncs[inst[1]].params;
1442
- count += importedFuncs[inst[1]].returns;
1460
+ if (inst[1] < importedFuncs.length) {
1461
+ func = importedFuncs[inst[1]];
1462
+ count = count - func.params + func.returns;
1443
1463
  } else {
1444
- if (func) {
1445
- count -= func.params.length;
1446
- } else count--;
1447
- if (func) count += func.returns.length;
1464
+ count = count - func.params.length + func.returns.length;
1448
1465
  }
1449
1466
  } else if (inst[0] === Opcodes.call_indirect) {
1450
1467
  count--; // funcidx
@@ -1467,7 +1484,7 @@ const disposeLeftover = wasm => {
1467
1484
  const generateExp = (scope, decl) => {
1468
1485
  const expression = decl.expression;
1469
1486
 
1470
- const out = generate(scope, expression, undefined, undefined, true);
1487
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1471
1488
  disposeLeftover(out);
1472
1489
 
1473
1490
  return out;
@@ -1597,6 +1614,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1597
1614
 
1598
1615
  if (!funcIndex[rhemynName]) {
1599
1616
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1617
+ func.internal = true;
1600
1618
 
1601
1619
  funcIndex[func.name] = func.index;
1602
1620
  funcs.push(func);
@@ -1704,7 +1722,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1704
1722
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1705
1723
  set: value => RTArrayUtil.setLength(getPointer, value),
1706
1724
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1707
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1725
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1708
1726
  return makeArray(scope, {
1709
1727
  rawElements: new Array(length)
1710
1728
  }, _global, _name, true, itemType);
@@ -1718,7 +1736,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1718
1736
  protoBC[x] = [
1719
1737
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1720
1738
  ...protoOut,
1721
- ...setLastType(scope, protoFunc.returnType ?? TYPES.number),
1739
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1722
1740
  [ Opcodes.end ]
1723
1741
  ];
1724
1742
  }
@@ -1768,11 +1786,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1768
1786
 
1769
1787
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1770
1788
 
1771
- if (idx === undefined && name === scope.name) {
1772
- // hack: calling self, func generator will fix later
1773
- idx = -1;
1774
- }
1775
-
1776
1789
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1777
1790
  const wasmOps = {
1778
1791
  // pointer, align, offset
@@ -1794,7 +1807,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1794
1807
  f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1795
1808
 
1796
1809
  // value
1797
- i32_const: { imms: 1, args: [], returns: 1 },
1810
+ i32_const: { imms: 1, args: [], returns: 0 },
1798
1811
  };
1799
1812
 
1800
1813
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1824,36 +1837,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1824
1837
  const [ local, global ] = lookupName(scope, name);
1825
1838
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1826
1839
 
1827
- // todo: only works when:
1828
- // 1. arg count matches arg count of function
1829
- // 2. function uses typedParams and typedReturns
1840
+ // todo: only works when function uses typedParams and typedReturns
1841
+
1842
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1843
+ // options: vararg, strict
1844
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1845
+ // ONLY works when arg count of call == arg count of function being called
1846
+ // - vararg: large size usage, cursed.
1847
+ // works when arg count of call != arg count of function being called*
1848
+ // * most of the time, some edgecases
1830
1849
 
1831
1850
  funcs.table = true;
1851
+ scope.table = true;
1832
1852
 
1833
1853
  let args = decl.arguments;
1834
- let argWasm = [];
1854
+ let out = [];
1855
+
1856
+ let locals = [];
1857
+
1858
+ if (indirectMode === 'vararg') {
1859
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1860
+
1861
+ if (args.length < minArgc) {
1862
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1863
+ }
1864
+ }
1835
1865
 
1836
1866
  for (let i = 0; i < args.length; i++) {
1837
1867
  const arg = args[i];
1838
- argWasm = argWasm.concat(generate(scope, arg));
1868
+ out = out.concat(generate(scope, arg));
1839
1869
 
1840
1870
  if (valtypeBinary !== Valtype.i32 && (
1841
1871
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1842
1872
  (importedFuncs[name] && name.startsWith('profile'))
1843
1873
  )) {
1844
- argWasm.push(Opcodes.i32_to);
1874
+ out.push(Opcodes.i32_to);
1875
+ }
1876
+
1877
+ out = out.concat(getNodeType(scope, arg));
1878
+
1879
+ if (indirectMode === 'vararg') {
1880
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1881
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1882
+
1883
+ locals.push([valLocal, typeLocal]);
1884
+
1885
+ out.push(
1886
+ [ Opcodes.local_set, typeLocal ],
1887
+ [ Opcodes.local_set, valLocal ]
1888
+ );
1845
1889
  }
1890
+ }
1891
+
1892
+ if (indirectMode === 'strict') {
1893
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1894
+ [TYPES.function]: [
1895
+ ...argWasm,
1896
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1897
+ Opcodes.i32_to_u,
1898
+ [ Opcodes.call_indirect, args.length, 0 ],
1899
+ ...setLastType(scope)
1900
+ ],
1901
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1902
+ });
1903
+ }
1904
+
1905
+ // hi, I will now explain how vararg mode works:
1906
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1907
+ // since we have varargs (variable argument count), we do not know it.
1908
+ // we could just store args in memory and not use wasm func args,
1909
+ // but that is slow (probably) and breaks js exports.
1910
+ // instead, we generate every* possibility of argc and use different indirect_call
1911
+ // ops for each one, with type depending on argc for that branch.
1912
+ // then we load the argc for the wanted function from a memory lut,
1913
+ // and call the branch with the matching argc we require.
1914
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1915
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1916
+ // *for argc 0-3, in future (todo:) the max number should be
1917
+ // dynamically changed to the max argc of any func in the js file.
1918
+
1919
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1920
+
1921
+ const gen = argc => {
1922
+ const out = [];
1923
+ for (let i = 0; i < argc; i++) {
1924
+ out.push(
1925
+ [ Opcodes.local_get, locals[i][0] ],
1926
+ [ Opcodes.local_get, locals[i][1] ]
1927
+ );
1928
+ }
1929
+
1930
+ out.push(
1931
+ [ Opcodes.local_get, funcLocal ],
1932
+ [ Opcodes.call_indirect, argc, 0 ],
1933
+ ...setLastType(scope)
1934
+ )
1935
+
1936
+ return out;
1937
+ };
1846
1938
 
1847
- argWasm = argWasm.concat(getNodeType(scope, arg));
1939
+ const tableBc = {};
1940
+ for (let i = 0; i <= args.length; i++) {
1941
+ tableBc[i] = gen(i);
1848
1942
  }
1849
1943
 
1944
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1945
+
1850
1946
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1851
1947
  [TYPES.function]: [
1852
- ...argWasm,
1948
+ ...out,
1949
+
1853
1950
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1854
1951
  Opcodes.i32_to_u,
1855
- [ Opcodes.call_indirect, args.length, 0 ],
1856
- ...setLastType(scope)
1952
+ [ Opcodes.local_set, funcLocal ],
1953
+
1954
+ ...brTable([
1955
+ // get argc of func we are calling
1956
+ [ Opcodes.local_get, funcLocal ],
1957
+ ...number(ValtypeSize.i16, Valtype.i32),
1958
+ [ Opcodes.i32_mul ],
1959
+
1960
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1961
+ ], tableBc, valtypeBinary)
1857
1962
  ],
1858
1963
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1859
1964
  });
@@ -1862,11 +1967,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1862
1967
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1863
1968
  }
1864
1969
 
1865
- const func = funcs.find(x => x.index === idx);
1866
-
1867
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1970
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1971
+ const userFunc = func && !func.internal;
1868
1972
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1869
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1973
+ const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1870
1974
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1871
1975
 
1872
1976
  let args = decl.arguments;
@@ -1895,8 +1999,8 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1895
1999
  }
1896
2000
 
1897
2001
  if (valtypeBinary !== Valtype.i32 && (
1898
- (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1899
- (importedFuncs[name] && name.startsWith('profile'))
2002
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2003
+ // (importedFuncs[name] && name.startsWith('profile'))
1900
2004
  )) {
1901
2005
  out.push(Opcodes.i32_to);
1902
2006
  }
@@ -2011,16 +2115,17 @@ const brTable = (input, bc, returns) => {
2011
2115
  }
2012
2116
 
2013
2117
  for (let i = 0; i < count; i++) {
2014
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2118
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2119
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2015
2120
  else out.push([ Opcodes.block, Blocktype.void ]);
2016
2121
  }
2017
2122
 
2018
- const nums = keys.filter(x => +x);
2123
+ const nums = keys.filter(x => +x >= 0);
2019
2124
  const offset = Math.min(...nums);
2020
2125
  const max = Math.max(...nums);
2021
2126
 
2022
2127
  const table = [];
2023
- let br = 1;
2128
+ let br = 0;
2024
2129
 
2025
2130
  for (let i = offset; i <= max; i++) {
2026
2131
  // if branch for this num, go to that block
@@ -2060,10 +2165,9 @@ const brTable = (input, bc, returns) => {
2060
2165
  br--;
2061
2166
  }
2062
2167
 
2063
- return [
2064
- ...out,
2065
- [ Opcodes.end, 'br table end' ]
2066
- ];
2168
+ out.push([ Opcodes.end ]);
2169
+
2170
+ return out;
2067
2171
  };
2068
2172
 
2069
2173
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2077,7 +2181,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2077
2181
  if (Prefs.typeswitchUseBrtable)
2078
2182
  return brTable(type, bc, returns);
2079
2183
 
2080
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2184
+ const tmp = localTmp(scope, '#typeswitch_tmp' + (Prefs.typeswitchUniqueTmp ? randId() : ''), Valtype.i32);
2081
2185
  const out = [
2082
2186
  ...type,
2083
2187
  [ Opcodes.local_set, tmp ],
@@ -2347,18 +2451,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2347
2451
  Opcodes.i32_to_u,
2348
2452
 
2349
2453
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2350
- ...number(ValtypeSize[valtype], Valtype.i32),
2454
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2351
2455
  [ Opcodes.i32_mul ],
2352
2456
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2353
2457
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2354
2458
 
2355
2459
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2356
2460
  [ Opcodes.local_get, pointerTmp ],
2357
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2358
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2461
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2462
+ ], generate(scope, decl.right), [
2463
+ [ Opcodes.local_get, pointerTmp ],
2464
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2465
+ ], getNodeType(scope, decl.right), false, name, true)),
2359
2466
  [ Opcodes.local_tee, newValueTmp ],
2360
2467
 
2361
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2468
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2362
2469
  ],
2363
2470
 
2364
2471
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2456,7 +2563,7 @@ const generateUnary = (scope, decl) => {
2456
2563
  // * -1
2457
2564
 
2458
2565
  if (decl.prefix && decl.argument.type === 'Literal' && typeof decl.argument.value === 'number') {
2459
- // if -<N>, just return that
2566
+ // if -n, just return that as a const
2460
2567
  return number(-1 * decl.argument.value);
2461
2568
  }
2462
2569
 
@@ -2466,11 +2573,16 @@ const generateUnary = (scope, decl) => {
2466
2573
  ];
2467
2574
 
2468
2575
  case '!':
2576
+ const arg = decl.argument;
2577
+ if (arg.type === 'UnaryExpression' && arg.operator === '!') {
2578
+ // opt: !!x -> is x truthy
2579
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2580
+ }
2581
+
2469
2582
  // !=
2470
- return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2583
+ return falsy(scope, generate(scope, arg), getNodeType(scope, arg), false, false);
2471
2584
 
2472
2585
  case '~':
2473
- // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
2474
2586
  return [
2475
2587
  ...generate(scope, decl.argument),
2476
2588
  Opcodes.i32_to,
@@ -2769,12 +2881,15 @@ const generateForOf = (scope, decl) => {
2769
2881
  // todo: optimize away counter and use end pointer
2770
2882
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2771
2883
  [TYPES.array]: [
2772
- ...setType(scope, leftName, TYPES.number),
2773
-
2774
2884
  [ Opcodes.loop, Blocktype.void ],
2775
2885
 
2776
2886
  [ Opcodes.local_get, pointer ],
2777
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2887
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2888
+
2889
+ ...setType(scope, leftName, [
2890
+ [ Opcodes.local_get, pointer ],
2891
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2892
+ ]),
2778
2893
 
2779
2894
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2780
2895
 
@@ -2783,9 +2898,9 @@ const generateForOf = (scope, decl) => {
2783
2898
  ...generate(scope, decl.body),
2784
2899
  [ Opcodes.end ],
2785
2900
 
2786
- // increment iter pointer by valtype size
2901
+ // increment iter pointer by valtype size + 1
2787
2902
  [ Opcodes.local_get, pointer ],
2788
- ...number(ValtypeSize[valtype], Valtype.i32),
2903
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2789
2904
  [ Opcodes.i32_add ],
2790
2905
  [ Opcodes.local_set, pointer ],
2791
2906
 
@@ -2901,6 +3016,44 @@ const generateForOf = (scope, decl) => {
2901
3016
  [ Opcodes.end ],
2902
3017
  [ Opcodes.end ]
2903
3018
  ],
3019
+ [TYPES.set]: [
3020
+ [ Opcodes.loop, Blocktype.void ],
3021
+
3022
+ [ Opcodes.local_get, pointer ],
3023
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3024
+
3025
+ ...setType(scope, leftName, [
3026
+ [ Opcodes.local_get, pointer ],
3027
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3028
+ ]),
3029
+
3030
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3031
+
3032
+ [ Opcodes.block, Blocktype.void ],
3033
+ [ Opcodes.block, Blocktype.void ],
3034
+ ...generate(scope, decl.body),
3035
+ [ Opcodes.end ],
3036
+
3037
+ // increment iter pointer by valtype size + 1
3038
+ [ Opcodes.local_get, pointer ],
3039
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3040
+ [ Opcodes.i32_add ],
3041
+ [ Opcodes.local_set, pointer ],
3042
+
3043
+ // increment counter by 1
3044
+ [ Opcodes.local_get, counter ],
3045
+ ...number(1, Valtype.i32),
3046
+ [ Opcodes.i32_add ],
3047
+ [ Opcodes.local_tee, counter ],
3048
+
3049
+ // loop if counter != length
3050
+ [ Opcodes.local_get, length ],
3051
+ [ Opcodes.i32_ne ],
3052
+ [ Opcodes.br_if, 1 ],
3053
+
3054
+ [ Opcodes.end ],
3055
+ [ Opcodes.end ]
3056
+ ],
2904
3057
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2905
3058
  }, Blocktype.void));
2906
3059
 
@@ -3002,14 +3155,18 @@ const generateThrow = (scope, decl) => {
3002
3155
  };
3003
3156
 
3004
3157
  const generateTry = (scope, decl) => {
3005
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3158
+ // todo: handle control-flow pre-exit for finally
3159
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3006
3160
 
3007
3161
  const out = [];
3008
3162
 
3163
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3164
+
3009
3165
  out.push([ Opcodes.try, Blocktype.void ]);
3010
3166
  depth.push('try');
3011
3167
 
3012
3168
  out.push(...generate(scope, decl.block));
3169
+ out.push(...finalizer);
3013
3170
 
3014
3171
  if (decl.handler) {
3015
3172
  depth.pop();
@@ -3017,6 +3174,7 @@ const generateTry = (scope, decl) => {
3017
3174
 
3018
3175
  out.push([ Opcodes.catch_all ]);
3019
3176
  out.push(...generate(scope, decl.handler.body));
3177
+ out.push(...finalizer);
3020
3178
  }
3021
3179
 
3022
3180
  out.push([ Opcodes.end ]);
@@ -3102,7 +3260,7 @@ const getAllocType = itemType => {
3102
3260
  }
3103
3261
  };
3104
3262
 
3105
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3263
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3106
3264
  const out = [];
3107
3265
 
3108
3266
  scope.arrays ??= new Map();
@@ -3114,8 +3272,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3114
3272
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3115
3273
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3116
3274
 
3117
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3118
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3275
+ let page;
3276
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3277
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3278
+
3279
+ // hack: use 1 for page 0 pointer for fast truthiness
3280
+ const ptr = page === 0 ? 1 : (page * pageSize);
3281
+ scope.arrays.set(name, ptr);
3119
3282
  }
3120
3283
 
3121
3284
  const pointer = scope.arrays.get(name);
@@ -3165,7 +3328,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3165
3328
 
3166
3329
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3167
3330
 
3168
- // store length as 0th array
3331
+ // store length
3169
3332
  out.push(
3170
3333
  ...pointerWasm,
3171
3334
  ...number(length, Valtype.i32),
@@ -3173,14 +3336,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3173
3336
  );
3174
3337
 
3175
3338
  const storeOp = StoreOps[itemType];
3176
-
3339
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3177
3340
  if (!initEmpty) for (let i = 0; i < length; i++) {
3178
3341
  if (elements[i] == null) continue;
3179
3342
 
3343
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3180
3344
  out.push(
3181
3345
  ...pointerWasm,
3182
3346
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3183
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3347
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3348
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3349
+ ...pointerWasm,
3350
+ ...getNodeType(scope, elements[i]),
3351
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3352
+ ])
3184
3353
  );
3185
3354
  }
3186
3355
 
@@ -3190,6 +3359,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3190
3359
  return [ out, pointer ];
3191
3360
  };
3192
3361
 
3362
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3363
+ if (!Array.isArray(element)) element = generate(scope, element);
3364
+ if (typeof index === 'number') index = number(index);
3365
+
3366
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3367
+
3368
+ return [
3369
+ // calculate offset
3370
+ ...index,
3371
+ Opcodes.i32_to_u,
3372
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3373
+ [ Opcodes.i32_mul ],
3374
+ ...(aotPointer ? [] : [
3375
+ ...array,
3376
+ Opcodes.i32_to_u,
3377
+ [ Opcodes.i32_add ],
3378
+ ]),
3379
+ [ Opcodes.local_set, offset ],
3380
+
3381
+ // store value
3382
+ [ Opcodes.local_get, offset ],
3383
+ ...generate(scope, element),
3384
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3385
+
3386
+ // store type
3387
+ [ Opcodes.local_get, offset ],
3388
+ ...getNodeType(scope, element),
3389
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3390
+ ];
3391
+ };
3392
+
3393
+ const loadArray = (scope, array, index, aotPointer = null) => {
3394
+ if (typeof index === 'number') index = number(index);
3395
+
3396
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3397
+
3398
+ return [
3399
+ // calculate offset
3400
+ ...index,
3401
+ Opcodes.i32_to_u,
3402
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3403
+ [ Opcodes.i32_mul ],
3404
+ ...(aotPointer ? [] : [
3405
+ ...array,
3406
+ Opcodes.i32_to_u,
3407
+ [ Opcodes.i32_add ],
3408
+ ]),
3409
+ [ Opcodes.local_set, offset ],
3410
+
3411
+ // load value
3412
+ [ Opcodes.local_get, offset ],
3413
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3414
+
3415
+ // load type
3416
+ [ Opcodes.local_get, offset ],
3417
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3418
+ ];
3419
+ };
3420
+
3193
3421
  const byteStringable = str => {
3194
3422
  if (!Prefs.bytestring) return false;
3195
3423
 
@@ -3218,7 +3446,7 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3218
3446
  };
3219
3447
 
3220
3448
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3221
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3449
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3222
3450
  };
3223
3451
 
3224
3452
  const generateObject = (scope, decl, global = false, name = '$undeclared') => {
@@ -3239,7 +3467,7 @@ const generateMember = (scope, decl, _global, _name) => {
3239
3467
  const name = decl.object.name;
3240
3468
  const pointer = scope.arrays?.get(name);
3241
3469
 
3242
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3470
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3243
3471
 
3244
3472
  // hack: .name
3245
3473
  if (decl.property.name === 'name') {
@@ -3259,8 +3487,7 @@ const generateMember = (scope, decl, _global, _name) => {
3259
3487
  if (decl.property.name === 'length') {
3260
3488
  const func = funcs.find(x => x.name === name);
3261
3489
  if (func) {
3262
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3263
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3490
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3264
3491
  return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3265
3492
  }
3266
3493
 
@@ -3275,7 +3502,7 @@ const generateMember = (scope, decl, _global, _name) => {
3275
3502
  }
3276
3503
 
3277
3504
  if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3278
- if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3505
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3279
3506
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3280
3507
 
3281
3508
  if (Prefs.fastLength) {
@@ -3367,23 +3594,8 @@ const generateMember = (scope, decl, _global, _name) => {
3367
3594
 
3368
3595
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3369
3596
  [TYPES.array]: [
3370
- // get index as valtype
3371
- ...property,
3372
-
3373
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3374
- Opcodes.i32_to_u,
3375
- ...number(ValtypeSize[valtype], Valtype.i32),
3376
- [ Opcodes.i32_mul ],
3377
-
3378
- ...(aotPointer ? [] : [
3379
- ...object,
3380
- Opcodes.i32_to_u,
3381
- [ Opcodes.i32_add ]
3382
- ]),
3383
-
3384
- // read from memory
3385
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3386
- ...setLastType(scope, TYPES.number)
3597
+ ...loadArray(scope, object, property, aotPointer),
3598
+ ...setLastType(scope)
3387
3599
  ],
3388
3600
 
3389
3601
  [TYPES.string]: [
@@ -3501,33 +3713,39 @@ const generateFunc = (scope, decl) => {
3501
3713
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3502
3714
  const params = decl.params ?? [];
3503
3715
 
3504
- // const innerScope = { ...scope };
3505
3716
  // TODO: share scope/locals between !!!
3506
- const innerScope = {
3717
+ const func = {
3507
3718
  locals: {},
3508
3719
  localInd: 0,
3509
3720
  // value, type
3510
3721
  returns: [ valtypeBinary, Valtype.i32 ],
3511
3722
  throws: false,
3512
- name
3723
+ name,
3724
+ index: currentFuncIndex++
3513
3725
  };
3514
3726
 
3515
3727
  if (typedInput && decl.returnType) {
3516
3728
  const { type } = extractTypeAnnotation(decl.returnType);
3517
- if (type != null && !Prefs.indirectCalls) {
3518
- innerScope.returnType = type;
3519
- innerScope.returns = [ valtypeBinary ];
3729
+ // if (type != null && !Prefs.indirectCalls) {
3730
+ if (type != null) {
3731
+ func.returnType = type;
3732
+ func.returns = [ valtypeBinary ];
3520
3733
  }
3521
3734
  }
3522
3735
 
3523
3736
  for (let i = 0; i < params.length; i++) {
3524
- allocVar(innerScope, params[i].name, false);
3737
+ const name = params[i].name;
3738
+ // if (name == null) return todo('non-identifier args are not supported');
3739
+
3740
+ allocVar(func, name, false);
3525
3741
 
3526
3742
  if (typedInput && params[i].typeAnnotation) {
3527
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3743
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3528
3744
  }
3529
3745
  }
3530
3746
 
3747
+ func.params = Object.values(func.locals).map(x => x.type);
3748
+
3531
3749
  let body = objectHack(decl.body);
3532
3750
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3533
3751
  // hack: () => 0 -> () => return 0
@@ -3537,37 +3755,23 @@ const generateFunc = (scope, decl) => {
3537
3755
  };
3538
3756
  }
3539
3757
 
3540
- const wasm = generate(innerScope, body);
3541
- const func = {
3542
- name,
3543
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3544
- index: currentFuncIndex++,
3545
- ...innerScope
3546
- };
3547
3758
  funcIndex[name] = func.index;
3759
+ funcs.push(func);
3548
3760
 
3549
- if (name === 'main') func.gotLastType = true;
3761
+ const wasm = generate(func, body);
3762
+ func.wasm = wasm;
3550
3763
 
3551
- // quick hack fixes
3552
- for (const inst of wasm) {
3553
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3554
- inst[1] = func.index;
3555
- }
3556
- }
3764
+ if (name === 'main') func.gotLastType = true;
3557
3765
 
3558
3766
  // add end return if not found
3559
3767
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3560
3768
  wasm.push(
3561
3769
  ...number(0),
3562
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3770
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3563
3771
  [ Opcodes.return ]
3564
3772
  );
3565
3773
  }
3566
3774
 
3567
- func.wasm = wasm;
3568
-
3569
- funcs.push(func);
3570
-
3571
3775
  return func;
3572
3776
  };
3573
3777
 
@@ -3671,7 +3875,7 @@ const internalConstrs = {
3671
3875
  generate: (scope, decl) => {
3672
3876
  // todo: boolean object when used as constructor
3673
3877
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3674
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3878
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3675
3879
  },
3676
3880
  type: TYPES.boolean,
3677
3881
  length: 1
@@ -3767,19 +3971,8 @@ export default program => {
3767
3971
  data = [];
3768
3972
  currentFuncIndex = importedFuncs.length;
3769
3973
 
3770
- globalThis.valtype = 'f64';
3771
-
3772
- const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3773
- if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3774
-
3775
- globalThis.valtypeBinary = Valtype[valtype];
3776
-
3777
3974
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3778
3975
 
3779
- globalThis.pageSize = PageSize;
3780
- const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3781
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3782
-
3783
3976
  // set generic opcodes for current valtype
3784
3977
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3785
3978
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3816,9 +4009,8 @@ export default program => {
3816
4009
 
3817
4010
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3818
4011
 
3819
- generateFunc(scope, program);
4012
+ const main = generateFunc(scope, program);
3820
4013
 
3821
- const main = funcs[funcs.length - 1];
3822
4014
  main.export = true;
3823
4015
  main.returns = [ valtypeBinary, Valtype.i32 ];
3824
4016
 
@@ -3845,7 +4037,7 @@ export default program => {
3845
4037
  }
3846
4038
 
3847
4039
  // if blank main func and other exports, remove it
3848
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4040
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3849
4041
 
3850
4042
  return { funcs, globals, tags, exceptions, pages, data };
3851
4043
  };