porffor 0.14.0-eca486960 → 0.16.0-594397507

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.
@@ -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: returnType ?? TYPES.number,
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
  };
@@ -1249,7 +1269,17 @@ 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) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1253
1283
  }
1254
1284
 
1255
1285
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1435,16 +1465,11 @@ const countLeftover = wasm => {
1435
1465
  else if (inst[0] === Opcodes.return) count = 0;
1436
1466
  else if (inst[0] === Opcodes.call) {
1437
1467
  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;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1443
1471
  } else {
1444
- if (func) {
1445
- count -= func.params.length;
1446
- } else count--;
1447
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1448
1473
  }
1449
1474
  } else if (inst[0] === Opcodes.call_indirect) {
1450
1475
  count--; // funcidx
@@ -1467,7 +1492,7 @@ const disposeLeftover = wasm => {
1467
1492
  const generateExp = (scope, decl) => {
1468
1493
  const expression = decl.expression;
1469
1494
 
1470
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1471
1496
  disposeLeftover(out);
1472
1497
 
1473
1498
  return out;
@@ -1597,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1597
1622
 
1598
1623
  if (!funcIndex[rhemynName]) {
1599
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1600
1626
 
1601
1627
  funcIndex[func.name] = func.index;
1602
1628
  funcs.push(func);
@@ -1704,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1704
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1705
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1706
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1707
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1708
1734
  return makeArray(scope, {
1709
1735
  rawElements: new Array(length)
1710
1736
  }, _global, _name, true, itemType);
@@ -1718,7 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1718
1744
  protoBC[x] = [
1719
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1720
1746
  ...protoOut,
1721
- ...setLastType(scope, protoFunc.returnType ?? TYPES.number),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1722
1748
  [ Opcodes.end ]
1723
1749
  ];
1724
1750
  }
@@ -1768,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1768
1794
 
1769
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1770
1796
 
1771
- if (idx === undefined && name === scope.name) {
1772
- // hack: calling self, func generator will fix later
1773
- idx = -1;
1774
- }
1775
-
1776
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1777
1798
  const wasmOps = {
1778
1799
  // pointer, align, offset
@@ -1824,36 +1845,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1824
1845
  const [ local, global ] = lookupName(scope, name);
1825
1846
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1826
1847
 
1827
- // todo: only works when:
1828
- // 1. arg count matches arg count of function
1829
- // 2. function uses typedParams and typedReturns
1848
+ // todo: only works when function uses typedParams and typedReturns
1849
+
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1830
1857
 
1831
1858
  funcs.table = true;
1859
+ scope.table = true;
1832
1860
 
1833
1861
  let args = decl.arguments;
1834
- let argWasm = [];
1862
+ let out = [];
1863
+
1864
+ let locals = [];
1865
+
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1868
+
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1835
1873
 
1836
1874
  for (let i = 0; i < args.length; i++) {
1837
1875
  const arg = args[i];
1838
- argWasm = argWasm.concat(generate(scope, arg));
1876
+ out = out.concat(generate(scope, arg));
1839
1877
 
1840
1878
  if (valtypeBinary !== Valtype.i32 && (
1841
1879
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1842
1880
  (importedFuncs[name] && name.startsWith('profile'))
1843
1881
  )) {
1844
- argWasm.push(Opcodes.i32_to);
1882
+ out.push(Opcodes.i32_to);
1883
+ }
1884
+
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1845
1897
  }
1898
+ }
1846
1899
 
1847
- argWasm = argWasm.concat(getNodeType(scope, arg));
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1848
1911
  }
1849
1912
 
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1950
+ }
1951
+
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1850
1954
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1851
1955
  [TYPES.function]: [
1852
- ...argWasm,
1956
+ ...out,
1957
+
1853
1958
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1854
1959
  Opcodes.i32_to_u,
1855
- [ Opcodes.call_indirect, args.length, 0 ],
1856
- ...setLastType(scope)
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1857
1970
  ],
1858
1971
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1859
1972
  });
@@ -1862,11 +1975,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1862
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1863
1976
  }
1864
1977
 
1865
- const func = funcs.find(x => x.index === idx);
1866
-
1867
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
1868
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1869
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1870
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1871
1983
 
1872
1984
  let args = decl.arguments;
@@ -2011,16 +2123,17 @@ const brTable = (input, bc, returns) => {
2011
2123
  }
2012
2124
 
2013
2125
  for (let i = 0; i < count; i++) {
2014
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2015
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
2016
2129
  }
2017
2130
 
2018
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
2019
2132
  const offset = Math.min(...nums);
2020
2133
  const max = Math.max(...nums);
2021
2134
 
2022
2135
  const table = [];
2023
- let br = 1;
2136
+ let br = 0;
2024
2137
 
2025
2138
  for (let i = offset; i <= max; i++) {
2026
2139
  // if branch for this num, go to that block
@@ -2060,10 +2173,9 @@ const brTable = (input, bc, returns) => {
2060
2173
  br--;
2061
2174
  }
2062
2175
 
2063
- return [
2064
- ...out,
2065
- [ Opcodes.end, 'br table end' ]
2066
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
2067
2179
  };
2068
2180
 
2069
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2347,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2347
2459
  Opcodes.i32_to_u,
2348
2460
 
2349
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2350
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2351
2463
  [ Opcodes.i32_mul ],
2352
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2353
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2354
2466
 
2355
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2356
2468
  [ 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)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2359
2474
  [ Opcodes.local_tee, newValueTmp ],
2360
2475
 
2361
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2362
2477
  ],
2363
2478
 
2364
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2466,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2466
2581
  ];
2467
2582
 
2468
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === 'UnaryExpression' && arg.operator === '!') {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2469
2589
  // !=
2470
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2471
2591
 
@@ -2769,12 +2889,15 @@ const generateForOf = (scope, decl) => {
2769
2889
  // todo: optimize away counter and use end pointer
2770
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2771
2891
  [TYPES.array]: [
2772
- ...setType(scope, leftName, TYPES.number),
2773
-
2774
2892
  [ Opcodes.loop, Blocktype.void ],
2775
2893
 
2776
2894
  [ Opcodes.local_get, pointer ],
2777
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2778
2901
 
2779
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2780
2903
 
@@ -2783,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2783
2906
  ...generate(scope, decl.body),
2784
2907
  [ Opcodes.end ],
2785
2908
 
2786
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2787
2910
  [ Opcodes.local_get, pointer ],
2788
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2789
2912
  [ Opcodes.i32_add ],
2790
2913
  [ Opcodes.local_set, pointer ],
2791
2914
 
@@ -2901,6 +3024,44 @@ const generateForOf = (scope, decl) => {
2901
3024
  [ Opcodes.end ],
2902
3025
  [ Opcodes.end ]
2903
3026
  ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
2904
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2905
3066
  }, Blocktype.void));
2906
3067
 
@@ -3002,14 +3163,18 @@ const generateThrow = (scope, decl) => {
3002
3163
  };
3003
3164
 
3004
3165
  const generateTry = (scope, decl) => {
3005
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3006
3168
 
3007
3169
  const out = [];
3008
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
3009
3173
  out.push([ Opcodes.try, Blocktype.void ]);
3010
3174
  depth.push('try');
3011
3175
 
3012
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
3013
3178
 
3014
3179
  if (decl.handler) {
3015
3180
  depth.pop();
@@ -3017,6 +3182,7 @@ const generateTry = (scope, decl) => {
3017
3182
 
3018
3183
  out.push([ Opcodes.catch_all ]);
3019
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
3020
3186
  }
3021
3187
 
3022
3188
  out.push([ Opcodes.end ]);
@@ -3102,7 +3268,7 @@ const getAllocType = itemType => {
3102
3268
  }
3103
3269
  };
3104
3270
 
3105
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3106
3272
  const out = [];
3107
3273
 
3108
3274
  scope.arrays ??= new Map();
@@ -3114,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3114
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3115
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3116
3282
 
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);
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
3119
3290
  }
3120
3291
 
3121
3292
  const pointer = scope.arrays.get(name);
@@ -3165,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3165
3336
 
3166
3337
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3167
3338
 
3168
- // store length as 0th array
3339
+ // store length
3169
3340
  out.push(
3170
3341
  ...pointerWasm,
3171
3342
  ...number(length, Valtype.i32),
@@ -3173,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3173
3344
  );
3174
3345
 
3175
3346
  const storeOp = StoreOps[itemType];
3176
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3177
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
3178
3349
  if (elements[i] == null) continue;
3179
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3180
3352
  out.push(
3181
3353
  ...pointerWasm,
3182
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3183
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
3184
3361
  );
3185
3362
  }
3186
3363
 
@@ -3190,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3190
3367
  return [ out, pointer ];
3191
3368
  };
3192
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
3193
3429
  const byteStringable = str => {
3194
3430
  if (!Prefs.bytestring) return false;
3195
3431
 
@@ -3218,7 +3454,7 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3218
3454
  };
3219
3455
 
3220
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3221
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3222
3458
  };
3223
3459
 
3224
3460
  const generateObject = (scope, decl, global = false, name = '$undeclared') => {
@@ -3239,7 +3475,7 @@ const generateMember = (scope, decl, _global, _name) => {
3239
3475
  const name = decl.object.name;
3240
3476
  const pointer = scope.arrays?.get(name);
3241
3477
 
3242
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3478
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3243
3479
 
3244
3480
  // hack: .name
3245
3481
  if (decl.property.name === 'name') {
@@ -3259,8 +3495,7 @@ const generateMember = (scope, decl, _global, _name) => {
3259
3495
  if (decl.property.name === 'length') {
3260
3496
  const func = funcs.find(x => x.name === name);
3261
3497
  if (func) {
3262
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3263
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3498
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3264
3499
  return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3265
3500
  }
3266
3501
 
@@ -3367,23 +3602,8 @@ const generateMember = (scope, decl, _global, _name) => {
3367
3602
 
3368
3603
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3369
3604
  [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)
3605
+ ...loadArray(scope, object, property, aotPointer),
3606
+ ...setLastType(scope)
3387
3607
  ],
3388
3608
 
3389
3609
  [TYPES.string]: [
@@ -3501,33 +3721,39 @@ const generateFunc = (scope, decl) => {
3501
3721
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3502
3722
  const params = decl.params ?? [];
3503
3723
 
3504
- // const innerScope = { ...scope };
3505
3724
  // TODO: share scope/locals between !!!
3506
- const innerScope = {
3725
+ const func = {
3507
3726
  locals: {},
3508
3727
  localInd: 0,
3509
3728
  // value, type
3510
3729
  returns: [ valtypeBinary, Valtype.i32 ],
3511
3730
  throws: false,
3512
- name
3731
+ name,
3732
+ index: currentFuncIndex++
3513
3733
  };
3514
3734
 
3515
3735
  if (typedInput && decl.returnType) {
3516
3736
  const { type } = extractTypeAnnotation(decl.returnType);
3517
- if (type != null && !Prefs.indirectCalls) {
3518
- innerScope.returnType = type;
3519
- innerScope.returns = [ valtypeBinary ];
3737
+ // if (type != null && !Prefs.indirectCalls) {
3738
+ if (type != null) {
3739
+ func.returnType = type;
3740
+ func.returns = [ valtypeBinary ];
3520
3741
  }
3521
3742
  }
3522
3743
 
3523
3744
  for (let i = 0; i < params.length; i++) {
3524
- allocVar(innerScope, params[i].name, false);
3745
+ const name = params[i].name;
3746
+ // if (name == null) return todo('non-identifier args are not supported');
3747
+
3748
+ allocVar(func, name, false);
3525
3749
 
3526
3750
  if (typedInput && params[i].typeAnnotation) {
3527
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3751
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3528
3752
  }
3529
3753
  }
3530
3754
 
3755
+ func.params = Object.values(func.locals).map(x => x.type);
3756
+
3531
3757
  let body = objectHack(decl.body);
3532
3758
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3533
3759
  // hack: () => 0 -> () => return 0
@@ -3537,37 +3763,23 @@ const generateFunc = (scope, decl) => {
3537
3763
  };
3538
3764
  }
3539
3765
 
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
3766
  funcIndex[name] = func.index;
3767
+ funcs.push(func);
3548
3768
 
3549
- if (name === 'main') func.gotLastType = true;
3769
+ const wasm = generate(func, body);
3770
+ func.wasm = wasm;
3550
3771
 
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
- }
3772
+ if (name === 'main') func.gotLastType = true;
3557
3773
 
3558
3774
  // add end return if not found
3559
3775
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3560
3776
  wasm.push(
3561
3777
  ...number(0),
3562
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3778
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3563
3779
  [ Opcodes.return ]
3564
3780
  );
3565
3781
  }
3566
3782
 
3567
- func.wasm = wasm;
3568
-
3569
- funcs.push(func);
3570
-
3571
3783
  return func;
3572
3784
  };
3573
3785
 
@@ -3671,7 +3883,7 @@ const internalConstrs = {
3671
3883
  generate: (scope, decl) => {
3672
3884
  // todo: boolean object when used as constructor
3673
3885
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3674
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3886
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3675
3887
  },
3676
3888
  type: TYPES.boolean,
3677
3889
  length: 1
@@ -3816,9 +4028,8 @@ export default program => {
3816
4028
 
3817
4029
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3818
4030
 
3819
- generateFunc(scope, program);
4031
+ const main = generateFunc(scope, program);
3820
4032
 
3821
- const main = funcs[funcs.length - 1];
3822
4033
  main.export = true;
3823
4034
  main.returns = [ valtypeBinary, Valtype.i32 ];
3824
4035
 
@@ -3845,7 +4056,7 @@ export default program => {
3845
4056
  }
3846
4057
 
3847
4058
  // 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);
4059
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3849
4060
 
3850
4061
  return { funcs, globals, tags, exceptions, pages, data };
3851
4062
  };