porffor 0.14.0-b481bfc5f → 0.14.0-b880d42f1

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
@@ -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,8 +668,8 @@ 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
  ];
@@ -676,47 +678,38 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
676
678
  const useTmp = knownType(scope, type) == null;
677
679
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
678
680
 
679
- const def = [
680
- // if value != 0
681
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
681
+ const def = (truthyMode => {
682
+ if (truthyMode === 'full') return [
683
+ // if value != 0 or NaN
684
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
685
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
682
686
 
683
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
684
- // [ Opcodes.i32_eqz ],
687
+ [ Opcodes.i32_eqz ],
688
+ [ Opcodes.i32_eqz ],
685
689
 
686
- // ...(intIn ? [
687
- // ...number(0, Valtype.i32),
688
- // [ Opcodes.i32_gt_s ]
689
- // ] : [
690
- // ...number(0),
691
- // [ Opcodes.f64_gt ]
692
- // ]),
693
-
694
- // ...(intOut ? [] : [ Opcodes.i32_from ]),
695
-
696
- // ...(intIn ? [] : [ Opcodes.i32_to ]),
697
- // [ Opcodes.if, intOut ? Valtype.i32 : valtypeBinary ],
698
- // ...number(1, intOut ? Valtype.i32 : valtypeBinary),
699
- // [ Opcodes.else ],
700
- // ...number(0, intOut ? Valtype.i32 : valtypeBinary),
701
- // [ Opcodes.end ],
702
-
703
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
704
-
705
- /* Opcodes.eqz,
706
- [ Opcodes.i32_eqz ],
707
- Opcodes.i32_from */
708
- ];
690
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
691
+ ];
692
+
693
+ if (truthyMode === 'no_negative') return [
694
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
695
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
696
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
697
+ ...(intOut ? [] : [ Opcodes.i32_from ])
698
+ ];
699
+
700
+ if (truthyMode === 'no_nan_negative') return [
701
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
702
+ // plus non-binary output
703
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
704
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
705
+ ];
706
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
709
707
 
710
708
  return [
711
709
  ...wasm,
712
710
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
713
711
 
714
712
  ...typeSwitch(scope, type, {
715
- // [TYPES.number]: def,
716
- [TYPES.array]: [
717
- // arrays are always truthy
718
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
719
- ],
720
713
  [TYPES.string]: [
721
714
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
722
715
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -752,10 +745,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
752
745
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
753
746
 
754
747
  ...typeSwitch(scope, type, {
755
- [TYPES.array]: [
756
- // arrays are always truthy
757
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
758
- ],
759
748
  [TYPES.string]: [
760
749
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
761
750
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -999,7 +988,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
999
988
  // if both are true
1000
989
  [ Opcodes.i32_and ],
1001
990
  [ Opcodes.if, Blocktype.void ],
1002
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
991
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
1003
992
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1004
993
  [ Opcodes.br, 1 ],
1005
994
  [ Opcodes.end ],
@@ -1048,14 +1037,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1048
1037
  return out;
1049
1038
  };
1050
1039
 
1051
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1052
- return func({ name, params, locals, returns, localInd }, {
1040
+ const asmFuncToAsm = (func, scope) => {
1041
+ return func(scope, {
1053
1042
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1054
- builtin: name => {
1055
- let idx = funcIndex[name] ?? importedFuncs[name];
1056
- if (idx === undefined && builtinFuncs[name]) {
1057
- includeBuiltin(null, name);
1058
- idx = funcIndex[name];
1043
+ builtin: n => {
1044
+ let idx = funcIndex[n] ?? importedFuncs[n];
1045
+ if (idx == null && builtinFuncs[n]) {
1046
+ includeBuiltin(null, n);
1047
+ idx = funcIndex[n];
1059
1048
  }
1060
1049
 
1061
1050
  return idx;
@@ -1063,7 +1052,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1063
1052
  });
1064
1053
  };
1065
1054
 
1066
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], callsSelf = false }) => {
1055
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1067
1056
  const existing = funcs.find(x => x.name === name);
1068
1057
  if (existing) return existing;
1069
1058
 
@@ -1081,7 +1070,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1081
1070
  data.push(copy);
1082
1071
  }
1083
1072
 
1084
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1073
+ const func = {
1074
+ name,
1075
+ params,
1076
+ locals,
1077
+ localInd: allLocals.length,
1078
+ returns,
1079
+ returnType: returnType ?? TYPES.number,
1080
+ internal: true,
1081
+ index: currentFuncIndex++,
1082
+ table
1083
+ };
1084
+
1085
+ funcs.push(func);
1086
+ funcIndex[name] = func.index;
1087
+
1088
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1085
1089
 
1086
1090
  let baseGlobalIdx, i = 0;
1087
1091
  for (const type of globalTypes) {
@@ -1100,25 +1104,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1100
1104
  }
1101
1105
  }
1102
1106
 
1103
- const func = {
1104
- name,
1105
- params,
1106
- locals,
1107
- returns,
1108
- returnType: returnType ?? TYPES.number,
1109
- wasm,
1110
- internal: true,
1111
- index: currentFuncIndex++
1112
- };
1113
-
1114
- if (callsSelf) for (const inst of wasm) {
1115
- if (inst[0] === Opcodes.call && inst[1] === -1) {
1116
- inst[1] = func.index;
1107
+ if (table) for (const inst of wasm) {
1108
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1109
+ inst.splice(2, 99);
1110
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1117
1111
  }
1118
1112
  }
1119
1113
 
1120
- funcs.push(func);
1121
- funcIndex[name] = func.index;
1114
+ func.wasm = wasm;
1122
1115
 
1123
1116
  return func;
1124
1117
  };
@@ -1470,16 +1463,11 @@ const countLeftover = wasm => {
1470
1463
  else if (inst[0] === Opcodes.return) count = 0;
1471
1464
  else if (inst[0] === Opcodes.call) {
1472
1465
  let func = funcs.find(x => x.index === inst[1]);
1473
- if (inst[1] === -1) {
1474
- // todo: count for calling self
1475
- } else if (!func && inst[1] < importedFuncs.length) {
1476
- count -= importedFuncs[inst[1]].params;
1477
- count += importedFuncs[inst[1]].returns;
1466
+ if (inst[1] < importedFuncs.length) {
1467
+ func = importedFuncs[inst[1]];
1468
+ count = count - func.params + func.returns;
1478
1469
  } else {
1479
- if (func) {
1480
- count -= func.params.length;
1481
- } else count--;
1482
- if (func) count += func.returns.length;
1470
+ count = count - func.params.length + func.returns.length;
1483
1471
  }
1484
1472
  } else if (inst[0] === Opcodes.call_indirect) {
1485
1473
  count--; // funcidx
@@ -1632,6 +1620,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1632
1620
 
1633
1621
  if (!funcIndex[rhemynName]) {
1634
1622
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1623
+ func.internal = true;
1635
1624
 
1636
1625
  funcIndex[func.name] = func.index;
1637
1626
  funcs.push(func);
@@ -1803,11 +1792,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1803
1792
 
1804
1793
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1805
1794
 
1806
- if (idx === undefined && name === scope.name) {
1807
- // hack: calling self, func generator will fix later
1808
- idx = -1;
1809
- }
1810
-
1811
1795
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1812
1796
  const wasmOps = {
1813
1797
  // pointer, align, offset
@@ -1859,36 +1843,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1859
1843
  const [ local, global ] = lookupName(scope, name);
1860
1844
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1861
1845
 
1862
- // todo: only works when:
1863
- // 1. arg count matches arg count of function
1864
- // 2. function uses typedParams and typedReturns
1846
+ // todo: only works when function uses typedParams and typedReturns
1847
+
1848
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1849
+ // options: vararg, strict
1850
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1851
+ // ONLY works when arg count of call == arg count of function being called
1852
+ // - vararg: large size usage, cursed.
1853
+ // works when arg count of call != arg count of function being called*
1854
+ // * most of the time, some edgecases
1865
1855
 
1866
1856
  funcs.table = true;
1857
+ scope.table = true;
1867
1858
 
1868
1859
  let args = decl.arguments;
1869
- let argWasm = [];
1860
+ let out = [];
1861
+
1862
+ let locals = [];
1863
+
1864
+ if (indirectMode === 'vararg') {
1865
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1866
+
1867
+ if (args.length < minArgc) {
1868
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1869
+ }
1870
+ }
1870
1871
 
1871
1872
  for (let i = 0; i < args.length; i++) {
1872
1873
  const arg = args[i];
1873
- argWasm = argWasm.concat(generate(scope, arg));
1874
+ out = out.concat(generate(scope, arg));
1874
1875
 
1875
1876
  if (valtypeBinary !== Valtype.i32 && (
1876
1877
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1877
1878
  (importedFuncs[name] && name.startsWith('profile'))
1878
1879
  )) {
1879
- argWasm.push(Opcodes.i32_to);
1880
+ out.push(Opcodes.i32_to);
1880
1881
  }
1881
1882
 
1882
- argWasm = argWasm.concat(getNodeType(scope, arg));
1883
+ out = out.concat(getNodeType(scope, arg));
1884
+
1885
+ if (indirectMode === 'vararg') {
1886
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1887
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1888
+
1889
+ locals.push([valLocal, typeLocal]);
1890
+
1891
+ out.push(
1892
+ [ Opcodes.local_set, typeLocal ],
1893
+ [ Opcodes.local_set, valLocal ]
1894
+ );
1895
+ }
1896
+ }
1897
+
1898
+ if (indirectMode === 'strict') {
1899
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1900
+ [TYPES.function]: [
1901
+ ...argWasm,
1902
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1903
+ Opcodes.i32_to_u,
1904
+ [ Opcodes.call_indirect, args.length, 0 ],
1905
+ ...setLastType(scope)
1906
+ ],
1907
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1908
+ });
1909
+ }
1910
+
1911
+ // hi, I will now explain how vararg mode works:
1912
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1913
+ // since we have varargs (variable argument count), we do not know it.
1914
+ // we could just store args in memory and not use wasm func args,
1915
+ // but that is slow (probably) and breaks js exports.
1916
+ // instead, we generate every* possibility of argc and use different indirect_call
1917
+ // ops for each one, with type depending on argc for that branch.
1918
+ // then we load the argc for the wanted function from a memory lut,
1919
+ // and call the branch with the matching argc we require.
1920
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1921
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1922
+ // *for argc 0-3, in future (todo:) the max number should be
1923
+ // dynamically changed to the max argc of any func in the js file.
1924
+
1925
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1926
+
1927
+ const gen = argc => {
1928
+ const out = [];
1929
+ for (let i = 0; i < argc; i++) {
1930
+ out.push(
1931
+ [ Opcodes.local_get, locals[i][0] ],
1932
+ [ Opcodes.local_get, locals[i][1] ]
1933
+ );
1934
+ }
1935
+
1936
+ out.push(
1937
+ [ Opcodes.local_get, funcLocal ],
1938
+ [ Opcodes.call_indirect, argc, 0 ],
1939
+ ...setLastType(scope)
1940
+ )
1941
+
1942
+ return out;
1943
+ };
1944
+
1945
+ const tableBc = {};
1946
+ for (let i = 0; i <= args.length; i++) {
1947
+ tableBc[i] = gen(i);
1883
1948
  }
1884
1949
 
1950
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1951
+
1885
1952
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1886
1953
  [TYPES.function]: [
1887
- ...argWasm,
1954
+ ...out,
1955
+
1888
1956
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1889
1957
  Opcodes.i32_to_u,
1890
- [ Opcodes.call_indirect, args.length, 0 ],
1891
- ...setLastType(scope)
1958
+ [ Opcodes.local_set, funcLocal ],
1959
+
1960
+ ...brTable([
1961
+ // get argc of func we are calling
1962
+ [ Opcodes.local_get, funcLocal ],
1963
+ ...number(ValtypeSize.i16, Valtype.i32),
1964
+ [ Opcodes.i32_mul ],
1965
+
1966
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1967
+ ], tableBc, valtypeBinary)
1892
1968
  ],
1893
1969
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1894
1970
  });
@@ -1897,11 +1973,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1897
1973
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1898
1974
  }
1899
1975
 
1900
- const func = funcs.find(x => x.index === idx);
1901
-
1902
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1976
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1977
+ const userFunc = func && !func.internal;
1903
1978
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1904
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1979
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1905
1980
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1906
1981
 
1907
1982
  let args = decl.arguments;
@@ -2046,16 +2121,17 @@ const brTable = (input, bc, returns) => {
2046
2121
  }
2047
2122
 
2048
2123
  for (let i = 0; i < count; i++) {
2049
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2124
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2125
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2050
2126
  else out.push([ Opcodes.block, Blocktype.void ]);
2051
2127
  }
2052
2128
 
2053
- const nums = keys.filter(x => +x);
2129
+ const nums = keys.filter(x => +x >= 0);
2054
2130
  const offset = Math.min(...nums);
2055
2131
  const max = Math.max(...nums);
2056
2132
 
2057
2133
  const table = [];
2058
- let br = 1;
2134
+ let br = 0;
2059
2135
 
2060
2136
  for (let i = offset; i <= max; i++) {
2061
2137
  // if branch for this num, go to that block
@@ -2095,10 +2171,9 @@ const brTable = (input, bc, returns) => {
2095
2171
  br--;
2096
2172
  }
2097
2173
 
2098
- return [
2099
- ...out,
2100
- [ Opcodes.end, 'br table end' ]
2101
- ];
2174
+ out.push([ Opcodes.end ]);
2175
+
2176
+ return out;
2102
2177
  };
2103
2178
 
2104
2179
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2504,6 +2579,7 @@ const generateUnary = (scope, decl) => {
2504
2579
  ];
2505
2580
 
2506
2581
  case '!':
2582
+ // todo/perf: optimize !!
2507
2583
  // !=
2508
2584
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2509
2585
 
@@ -3043,14 +3119,18 @@ const generateThrow = (scope, decl) => {
3043
3119
  };
3044
3120
 
3045
3121
  const generateTry = (scope, decl) => {
3046
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3122
+ // todo: handle control-flow pre-exit for finally
3123
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3047
3124
 
3048
3125
  const out = [];
3049
3126
 
3127
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3128
+
3050
3129
  out.push([ Opcodes.try, Blocktype.void ]);
3051
3130
  depth.push('try');
3052
3131
 
3053
3132
  out.push(...generate(scope, decl.block));
3133
+ out.push(...finalizer);
3054
3134
 
3055
3135
  if (decl.handler) {
3056
3136
  depth.pop();
@@ -3058,6 +3138,7 @@ const generateTry = (scope, decl) => {
3058
3138
 
3059
3139
  out.push([ Opcodes.catch_all ]);
3060
3140
  out.push(...generate(scope, decl.handler.body));
3141
+ out.push(...finalizer);
3061
3142
  }
3062
3143
 
3063
3144
  out.push([ Opcodes.end ]);
@@ -3155,8 +3236,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3155
3236
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3156
3237
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3157
3238
 
3158
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3159
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3239
+ let page;
3240
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3241
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3242
+
3243
+ // hack: use 1 for page 0 pointer for fast truthiness
3244
+ const ptr = page === 0 ? 1 : (page * pageSize);
3245
+ scope.arrays.set(name, ptr);
3160
3246
  }
3161
3247
 
3162
3248
  const pointer = scope.arrays.get(name);
@@ -3343,8 +3429,19 @@ const withType = (scope, wasm, type) => [
3343
3429
 
3344
3430
  const generateMember = (scope, decl, _global, _name) => {
3345
3431
  const name = decl.object.name;
3346
- const pointer = scope.arrays?.get(name);
3347
3432
 
3433
+ // hack: process.argv[n]
3434
+ if (name === '__process_argv') {
3435
+ const setPointer = scope.arrays?.get(_name);
3436
+
3437
+ return [
3438
+ ...number(decl.property.value - 1),
3439
+ ...(setPointer ? number(setPointer) : allocPage(scope, `__process_argv out (${randId()})`)),
3440
+ [ Opcodes.call, importedFuncs.__Porffor_readArgv ]
3441
+ ];
3442
+ }
3443
+
3444
+ const pointer = scope.arrays?.get(name);
3348
3445
  const aotPointer = Prefs.aotPointerOpt && pointer;
3349
3446
 
3350
3447
  // hack: .name
@@ -3365,8 +3462,7 @@ const generateMember = (scope, decl, _global, _name) => {
3365
3462
  if (decl.property.name === 'length') {
3366
3463
  const func = funcs.find(x => x.name === name);
3367
3464
  if (func) {
3368
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3369
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3465
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3370
3466
  return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3371
3467
  }
3372
3468
 
@@ -3592,33 +3688,39 @@ const generateFunc = (scope, decl) => {
3592
3688
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3593
3689
  const params = decl.params ?? [];
3594
3690
 
3595
- // const innerScope = { ...scope };
3596
3691
  // TODO: share scope/locals between !!!
3597
- const innerScope = {
3692
+ const func = {
3598
3693
  locals: {},
3599
3694
  localInd: 0,
3600
3695
  // value, type
3601
3696
  returns: [ valtypeBinary, Valtype.i32 ],
3602
3697
  throws: false,
3603
- name
3698
+ name,
3699
+ index: currentFuncIndex++
3604
3700
  };
3605
3701
 
3606
3702
  if (typedInput && decl.returnType) {
3607
3703
  const { type } = extractTypeAnnotation(decl.returnType);
3608
- if (type != null && !Prefs.indirectCalls) {
3609
- innerScope.returnType = type;
3610
- innerScope.returns = [ valtypeBinary ];
3704
+ // if (type != null && !Prefs.indirectCalls) {
3705
+ if (type != null) {
3706
+ func.returnType = type;
3707
+ func.returns = [ valtypeBinary ];
3611
3708
  }
3612
3709
  }
3613
3710
 
3614
3711
  for (let i = 0; i < params.length; i++) {
3615
- allocVar(innerScope, params[i].name, false);
3712
+ const name = params[i].name;
3713
+ // if (name == null) return todo('non-identifier args are not supported');
3714
+
3715
+ allocVar(func, name, false);
3616
3716
 
3617
3717
  if (typedInput && params[i].typeAnnotation) {
3618
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3718
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3619
3719
  }
3620
3720
  }
3621
3721
 
3722
+ func.params = Object.values(func.locals).map(x => x.type);
3723
+
3622
3724
  let body = objectHack(decl.body);
3623
3725
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3624
3726
  // hack: () => 0 -> () => return 0
@@ -3628,37 +3730,23 @@ const generateFunc = (scope, decl) => {
3628
3730
  };
3629
3731
  }
3630
3732
 
3631
- const wasm = generate(innerScope, body);
3632
- const func = {
3633
- name,
3634
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3635
- index: currentFuncIndex++,
3636
- ...innerScope
3637
- };
3638
3733
  funcIndex[name] = func.index;
3734
+ funcs.push(func);
3639
3735
 
3640
- if (name === 'main') func.gotLastType = true;
3736
+ const wasm = generate(func, body);
3737
+ func.wasm = wasm;
3641
3738
 
3642
- // quick hack fixes
3643
- for (const inst of wasm) {
3644
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3645
- inst[1] = func.index;
3646
- }
3647
- }
3739
+ if (name === 'main') func.gotLastType = true;
3648
3740
 
3649
3741
  // add end return if not found
3650
3742
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3651
3743
  wasm.push(
3652
3744
  ...number(0),
3653
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3745
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3654
3746
  [ Opcodes.return ]
3655
3747
  );
3656
3748
  }
3657
3749
 
3658
- func.wasm = wasm;
3659
-
3660
- funcs.push(func);
3661
-
3662
3750
  return func;
3663
3751
  };
3664
3752
 
@@ -3762,7 +3850,7 @@ const internalConstrs = {
3762
3850
  generate: (scope, decl) => {
3763
3851
  // todo: boolean object when used as constructor
3764
3852
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3765
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3853
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3766
3854
  },
3767
3855
  type: TYPES.boolean,
3768
3856
  length: 1
@@ -3907,9 +3995,8 @@ export default program => {
3907
3995
 
3908
3996
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3909
3997
 
3910
- generateFunc(scope, program);
3998
+ const main = generateFunc(scope, program);
3911
3999
 
3912
- const main = funcs[funcs.length - 1];
3913
4000
  main.export = true;
3914
4001
  main.returns = [ valtypeBinary, Valtype.i32 ];
3915
4002
 
@@ -3936,7 +4023,7 @@ export default program => {
3936
4023
  }
3937
4024
 
3938
4025
  // if blank main func and other exports, remove it
3939
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4026
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3940
4027
 
3941
4028
  return { funcs, globals, tags, exceptions, pages, data };
3942
4029
  };