porffor 0.14.0-b481bfc5f → 0.14.0-bb0b06c17

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 [];
@@ -1048,14 +1050,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1048
1050
  return out;
1049
1051
  };
1050
1052
 
1051
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1052
- return func({ name, params, locals, returns, localInd }, {
1053
+ const asmFuncToAsm = (func, scope) => {
1054
+ return func(scope, {
1053
1055
  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];
1056
+ builtin: n => {
1057
+ let idx = funcIndex[n] ?? importedFuncs[n];
1058
+ if (idx == null && builtinFuncs[n]) {
1059
+ includeBuiltin(null, n);
1060
+ idx = funcIndex[n];
1059
1061
  }
1060
1062
 
1061
1063
  return idx;
@@ -1063,7 +1065,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1063
1065
  });
1064
1066
  };
1065
1067
 
1066
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], callsSelf = false }) => {
1068
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1067
1069
  const existing = funcs.find(x => x.name === name);
1068
1070
  if (existing) return existing;
1069
1071
 
@@ -1081,7 +1083,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1081
1083
  data.push(copy);
1082
1084
  }
1083
1085
 
1084
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1086
+ const func = {
1087
+ name,
1088
+ params,
1089
+ locals,
1090
+ localInd: allLocals.length,
1091
+ returns,
1092
+ returnType: returnType ?? TYPES.number,
1093
+ internal: true,
1094
+ index: currentFuncIndex++,
1095
+ table
1096
+ };
1097
+
1098
+ funcs.push(func);
1099
+ funcIndex[name] = func.index;
1100
+
1101
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1085
1102
 
1086
1103
  let baseGlobalIdx, i = 0;
1087
1104
  for (const type of globalTypes) {
@@ -1100,25 +1117,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1100
1117
  }
1101
1118
  }
1102
1119
 
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;
1120
+ if (table) for (const inst of wasm) {
1121
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1122
+ inst.splice(2, 99);
1123
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1117
1124
  }
1118
1125
  }
1119
1126
 
1120
- funcs.push(func);
1121
- funcIndex[name] = func.index;
1127
+ func.wasm = wasm;
1122
1128
 
1123
1129
  return func;
1124
1130
  };
@@ -1470,16 +1476,11 @@ const countLeftover = wasm => {
1470
1476
  else if (inst[0] === Opcodes.return) count = 0;
1471
1477
  else if (inst[0] === Opcodes.call) {
1472
1478
  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;
1479
+ if (inst[1] < importedFuncs.length) {
1480
+ func = importedFuncs[inst[1]];
1481
+ count = count - func.params + func.returns;
1478
1482
  } else {
1479
- if (func) {
1480
- count -= func.params.length;
1481
- } else count--;
1482
- if (func) count += func.returns.length;
1483
+ count = count - func.params.length + func.returns.length;
1483
1484
  }
1484
1485
  } else if (inst[0] === Opcodes.call_indirect) {
1485
1486
  count--; // funcidx
@@ -1632,6 +1633,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1632
1633
 
1633
1634
  if (!funcIndex[rhemynName]) {
1634
1635
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1636
+ func.internal = true;
1635
1637
 
1636
1638
  funcIndex[func.name] = func.index;
1637
1639
  funcs.push(func);
@@ -1803,11 +1805,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1803
1805
 
1804
1806
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1805
1807
 
1806
- if (idx === undefined && name === scope.name) {
1807
- // hack: calling self, func generator will fix later
1808
- idx = -1;
1809
- }
1810
-
1811
1808
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1812
1809
  const wasmOps = {
1813
1810
  // pointer, align, offset
@@ -1859,36 +1856,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1859
1856
  const [ local, global ] = lookupName(scope, name);
1860
1857
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1861
1858
 
1862
- // todo: only works when:
1863
- // 1. arg count matches arg count of function
1864
- // 2. function uses typedParams and typedReturns
1859
+ // todo: only works when function uses typedParams and typedReturns
1860
+
1861
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1862
+ // options: vararg, strict
1863
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1864
+ // ONLY works when arg count of call == arg count of function being called
1865
+ // - vararg: large size usage, cursed.
1866
+ // works when arg count of call != arg count of function being called*
1867
+ // * most of the time, some edgecases
1865
1868
 
1866
1869
  funcs.table = true;
1870
+ scope.table = true;
1867
1871
 
1868
1872
  let args = decl.arguments;
1869
- let argWasm = [];
1873
+ let out = [];
1874
+
1875
+ let locals = [];
1876
+
1877
+ if (indirectMode === 'vararg') {
1878
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1879
+
1880
+ if (args.length < minArgc) {
1881
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1882
+ }
1883
+ }
1870
1884
 
1871
1885
  for (let i = 0; i < args.length; i++) {
1872
1886
  const arg = args[i];
1873
- argWasm = argWasm.concat(generate(scope, arg));
1887
+ out = out.concat(generate(scope, arg));
1874
1888
 
1875
1889
  if (valtypeBinary !== Valtype.i32 && (
1876
1890
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1877
1891
  (importedFuncs[name] && name.startsWith('profile'))
1878
1892
  )) {
1879
- argWasm.push(Opcodes.i32_to);
1893
+ out.push(Opcodes.i32_to);
1894
+ }
1895
+
1896
+ out = out.concat(getNodeType(scope, arg));
1897
+
1898
+ if (indirectMode === 'vararg') {
1899
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1900
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1901
+
1902
+ locals.push([valLocal, typeLocal]);
1903
+
1904
+ out.push(
1905
+ [ Opcodes.local_set, typeLocal ],
1906
+ [ Opcodes.local_set, valLocal ]
1907
+ );
1880
1908
  }
1909
+ }
1910
+
1911
+ if (indirectMode === 'strict') {
1912
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1913
+ [TYPES.function]: [
1914
+ ...argWasm,
1915
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1916
+ Opcodes.i32_to_u,
1917
+ [ Opcodes.call_indirect, args.length, 0 ],
1918
+ ...setLastType(scope)
1919
+ ],
1920
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1921
+ });
1922
+ }
1923
+
1924
+ // hi, I will now explain how vararg mode works:
1925
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1926
+ // since we have varargs (variable argument count), we do not know it.
1927
+ // we could just store args in memory and not use wasm func args,
1928
+ // but that is slow (probably) and breaks js exports.
1929
+ // instead, we generate every* possibility of argc and use different indirect_call
1930
+ // ops for each one, with type depending on argc for that branch.
1931
+ // then we load the argc for the wanted function from a memory lut,
1932
+ // and call the branch with the matching argc we require.
1933
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1934
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1935
+ // *for argc 0-3, in future (todo:) the max number should be
1936
+ // dynamically changed to the max argc of any func in the js file.
1937
+
1938
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1939
+
1940
+ const gen = argc => {
1941
+ const out = [];
1942
+ for (let i = 0; i < argc; i++) {
1943
+ out.push(
1944
+ [ Opcodes.local_get, locals[i][0] ],
1945
+ [ Opcodes.local_get, locals[i][1] ]
1946
+ );
1947
+ }
1948
+
1949
+ out.push(
1950
+ [ Opcodes.local_get, funcLocal ],
1951
+ [ Opcodes.call_indirect, argc, 0 ],
1952
+ ...setLastType(scope)
1953
+ )
1954
+
1955
+ return out;
1956
+ };
1881
1957
 
1882
- argWasm = argWasm.concat(getNodeType(scope, arg));
1958
+ const tableBc = {};
1959
+ for (let i = 0; i <= args.length; i++) {
1960
+ tableBc[i] = gen(i);
1883
1961
  }
1884
1962
 
1963
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1964
+
1885
1965
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1886
1966
  [TYPES.function]: [
1887
- ...argWasm,
1967
+ ...out,
1968
+
1888
1969
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1889
1970
  Opcodes.i32_to_u,
1890
- [ Opcodes.call_indirect, args.length, 0 ],
1891
- ...setLastType(scope)
1971
+ [ Opcodes.local_set, funcLocal ],
1972
+
1973
+ ...brTable([
1974
+ // get argc of func we are calling
1975
+ [ Opcodes.local_get, funcLocal ],
1976
+ ...number(ValtypeSize.i16, Valtype.i32),
1977
+ [ Opcodes.i32_mul ],
1978
+
1979
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1980
+ ], tableBc, valtypeBinary)
1892
1981
  ],
1893
1982
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1894
1983
  });
@@ -1897,11 +1986,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1897
1986
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1898
1987
  }
1899
1988
 
1900
- const func = funcs.find(x => x.index === idx);
1901
-
1902
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1989
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1990
+ const userFunc = func && !func.internal;
1903
1991
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1904
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1992
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1905
1993
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1906
1994
 
1907
1995
  let args = decl.arguments;
@@ -2046,16 +2134,17 @@ const brTable = (input, bc, returns) => {
2046
2134
  }
2047
2135
 
2048
2136
  for (let i = 0; i < count; i++) {
2049
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2137
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2138
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2050
2139
  else out.push([ Opcodes.block, Blocktype.void ]);
2051
2140
  }
2052
2141
 
2053
- const nums = keys.filter(x => +x);
2142
+ const nums = keys.filter(x => +x >= 0);
2054
2143
  const offset = Math.min(...nums);
2055
2144
  const max = Math.max(...nums);
2056
2145
 
2057
2146
  const table = [];
2058
- let br = 1;
2147
+ let br = 0;
2059
2148
 
2060
2149
  for (let i = offset; i <= max; i++) {
2061
2150
  // if branch for this num, go to that block
@@ -2095,10 +2184,9 @@ const brTable = (input, bc, returns) => {
2095
2184
  br--;
2096
2185
  }
2097
2186
 
2098
- return [
2099
- ...out,
2100
- [ Opcodes.end, 'br table end' ]
2101
- ];
2187
+ out.push([ Opcodes.end ]);
2188
+
2189
+ return out;
2102
2190
  };
2103
2191
 
2104
2192
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -3043,14 +3131,18 @@ const generateThrow = (scope, decl) => {
3043
3131
  };
3044
3132
 
3045
3133
  const generateTry = (scope, decl) => {
3046
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3134
+ // todo: handle control-flow pre-exit for finally
3135
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3047
3136
 
3048
3137
  const out = [];
3049
3138
 
3139
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3140
+
3050
3141
  out.push([ Opcodes.try, Blocktype.void ]);
3051
3142
  depth.push('try');
3052
3143
 
3053
3144
  out.push(...generate(scope, decl.block));
3145
+ out.push(...finalizer);
3054
3146
 
3055
3147
  if (decl.handler) {
3056
3148
  depth.pop();
@@ -3058,6 +3150,7 @@ const generateTry = (scope, decl) => {
3058
3150
 
3059
3151
  out.push([ Opcodes.catch_all ]);
3060
3152
  out.push(...generate(scope, decl.handler.body));
3153
+ out.push(...finalizer);
3061
3154
  }
3062
3155
 
3063
3156
  out.push([ Opcodes.end ]);
@@ -3365,8 +3458,7 @@ const generateMember = (scope, decl, _global, _name) => {
3365
3458
  if (decl.property.name === 'length') {
3366
3459
  const func = funcs.find(x => x.name === name);
3367
3460
  if (func) {
3368
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3369
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3461
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3370
3462
  return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3371
3463
  }
3372
3464
 
@@ -3592,33 +3684,39 @@ const generateFunc = (scope, decl) => {
3592
3684
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3593
3685
  const params = decl.params ?? [];
3594
3686
 
3595
- // const innerScope = { ...scope };
3596
3687
  // TODO: share scope/locals between !!!
3597
- const innerScope = {
3688
+ const func = {
3598
3689
  locals: {},
3599
3690
  localInd: 0,
3600
3691
  // value, type
3601
3692
  returns: [ valtypeBinary, Valtype.i32 ],
3602
3693
  throws: false,
3603
- name
3694
+ name,
3695
+ index: currentFuncIndex++
3604
3696
  };
3605
3697
 
3606
3698
  if (typedInput && decl.returnType) {
3607
3699
  const { type } = extractTypeAnnotation(decl.returnType);
3608
- if (type != null && !Prefs.indirectCalls) {
3609
- innerScope.returnType = type;
3610
- innerScope.returns = [ valtypeBinary ];
3700
+ // if (type != null && !Prefs.indirectCalls) {
3701
+ if (type != null) {
3702
+ func.returnType = type;
3703
+ func.returns = [ valtypeBinary ];
3611
3704
  }
3612
3705
  }
3613
3706
 
3614
3707
  for (let i = 0; i < params.length; i++) {
3615
- allocVar(innerScope, params[i].name, false);
3708
+ const name = params[i].name;
3709
+ // if (name == null) return todo('non-identifier args are not supported');
3710
+
3711
+ allocVar(func, name, false);
3616
3712
 
3617
3713
  if (typedInput && params[i].typeAnnotation) {
3618
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3714
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3619
3715
  }
3620
3716
  }
3621
3717
 
3718
+ func.params = Object.values(func.locals).map(x => x.type);
3719
+
3622
3720
  let body = objectHack(decl.body);
3623
3721
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3624
3722
  // hack: () => 0 -> () => return 0
@@ -3628,37 +3726,23 @@ const generateFunc = (scope, decl) => {
3628
3726
  };
3629
3727
  }
3630
3728
 
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
3729
  funcIndex[name] = func.index;
3730
+ funcs.push(func);
3639
3731
 
3640
- if (name === 'main') func.gotLastType = true;
3732
+ const wasm = generate(func, body);
3733
+ func.wasm = wasm;
3641
3734
 
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
- }
3735
+ if (name === 'main') func.gotLastType = true;
3648
3736
 
3649
3737
  // add end return if not found
3650
3738
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3651
3739
  wasm.push(
3652
3740
  ...number(0),
3653
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3741
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3654
3742
  [ Opcodes.return ]
3655
3743
  );
3656
3744
  }
3657
3745
 
3658
- func.wasm = wasm;
3659
-
3660
- funcs.push(func);
3661
-
3662
3746
  return func;
3663
3747
  };
3664
3748
 
@@ -3907,9 +3991,8 @@ export default program => {
3907
3991
 
3908
3992
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3909
3993
 
3910
- generateFunc(scope, program);
3994
+ const main = generateFunc(scope, program);
3911
3995
 
3912
- const main = funcs[funcs.length - 1];
3913
3996
  main.export = true;
3914
3997
  main.returns = [ valtypeBinary, Valtype.i32 ];
3915
3998
 
@@ -3936,7 +4019,7 @@ export default program => {
3936
4019
  }
3937
4020
 
3938
4021
  // 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);
4022
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3940
4023
 
3941
4024
  return { funcs, globals, tags, exceptions, pages, data };
3942
4025
  };