porffor 0.17.0-f43ba190c → 0.18.3

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.
@@ -123,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
123
123
  case 'EmptyStatement':
124
124
  return generateEmpty(scope, decl);
125
125
 
126
+ case 'MetaProperty':
127
+ return generateMeta(scope, decl);
128
+
126
129
  case 'ConditionalExpression':
127
130
  return generateConditional(scope, decl);
128
131
 
@@ -295,10 +298,10 @@ const generateIdent = (scope, decl) => {
295
298
  }
296
299
 
297
300
  // todo: enable this by default in future
298
- // if (!Object.hasOwn(funcIndex, name) && Object.hasOwn(builtinFuncs, name)) {
299
- // includeBuiltin(scope, name);
300
- // return number(funcIndex[name] - importedFuncs.length);
301
- // }
301
+ if (!Object.hasOwn(funcIndex, name) && Object.hasOwn(builtinFuncs, name)) {
302
+ includeBuiltin(scope, name);
303
+ return number(funcIndex[name] - importedFuncs.length);
304
+ }
302
305
 
303
306
  if (isExistingProtoFunc(name) || Object.hasOwn(internalConstrs, name) || Object.hasOwn(builtinFuncs, name)) {
304
307
  // todo: return an actual something
@@ -1026,12 +1029,13 @@ const asmFuncToAsm = (func, scope) => {
1026
1029
  idx = funcIndex[n];
1027
1030
  }
1028
1031
 
1032
+ if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
1029
1033
  return idx;
1030
1034
  }
1031
1035
  });
1032
1036
  };
1033
1037
 
1034
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits = [], returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1038
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits = [], returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false, constr = false }) => {
1035
1039
  const existing = funcs.find(x => x.name === name);
1036
1040
  if (existing) return existing;
1037
1041
 
@@ -1058,13 +1062,17 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1058
1062
  returnType,
1059
1063
  internal: true,
1060
1064
  index: currentFuncIndex++,
1061
- table
1065
+ table,
1066
+ constr
1062
1067
  };
1063
1068
 
1064
1069
  funcs.push(func);
1065
1070
  funcIndex[name] = func.index;
1066
1071
 
1067
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1072
+ if (typeof wasm === 'function') {
1073
+ if (globalThis.precompile) wasm = [];
1074
+ else wasm = asmFuncToAsm(wasm, func);
1075
+ }
1068
1076
 
1069
1077
  let baseGlobalIdx, i = 0;
1070
1078
  for (const type of globalTypes) {
@@ -1085,15 +1093,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1085
1093
 
1086
1094
  if (table) {
1087
1095
  for (const inst of wasm) {
1088
- if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1096
+ if (inst.at(-1) === 'read func lut') {
1089
1097
  inst.splice(2, 99);
1090
- inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1098
+ inst.push(...unsignedLEB128(allocPage({}, 'func lut') * pageSize));
1091
1099
  }
1092
1100
  }
1093
1101
 
1094
1102
  funcs.table = true;
1095
1103
  }
1096
1104
 
1105
+ if (constr) {
1106
+ func.params = [...func.params];
1107
+ func.params.unshift(Valtype.i32);
1108
+
1109
+ // move all locals +1 idx (sigh)
1110
+ func.localInd++;
1111
+ const locals = func.locals;
1112
+ for (const x in locals) {
1113
+ locals[x].idx++;
1114
+ }
1115
+
1116
+ locals['#newtarget'] = { idx: 0, type: Valtype.i32 };
1117
+
1118
+ for (const inst of wasm) {
1119
+ if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) {
1120
+ inst[1]++;
1121
+ }
1122
+ }
1123
+ }
1124
+
1097
1125
  func.wasm = wasm;
1098
1126
 
1099
1127
  return func;
@@ -1220,20 +1248,7 @@ const getNodeType = (scope, node) => {
1220
1248
  return TYPES.number;
1221
1249
  }
1222
1250
 
1223
- if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1224
- if (builtinFuncs[name + '$constructor'].typedReturns) {
1225
- if (scope.locals['#last_type']) return getLastType(scope);
1226
-
1227
- // presume
1228
- // todo: warn here?
1229
- return TYPES.number;
1230
- }
1231
-
1232
- return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1233
- }
1234
-
1235
1251
  const func = funcs.find(x => x.name === name);
1236
-
1237
1252
  if (func) {
1238
1253
  if (func.returnType != null) return func.returnType;
1239
1254
  }
@@ -1420,9 +1435,9 @@ const countLeftover = wasm => {
1420
1435
 
1421
1436
  if (depth === 0)
1422
1437
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1423
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x04)) {}
1438
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.f32_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x04)) {}
1424
1439
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1425
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1440
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.f32_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1426
1441
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1427
1442
  else if (inst[0] === Opcodes.return) count = 0;
1428
1443
  else if (inst[0] === Opcodes.call) {
@@ -1531,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1531
1546
  name = func.name;
1532
1547
  }
1533
1548
 
1534
- if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1549
+ if (!decl._new && name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1535
1550
  // literal eval hack
1536
1551
  const code = decl.arguments[0]?.value ?? '';
1537
1552
 
@@ -1574,7 +1589,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1574
1589
 
1575
1590
  let protoName, target;
1576
1591
  // ident.func()
1577
- if (name && name.startsWith('__')) {
1592
+ if (!decl._new && name && name.startsWith('__')) {
1578
1593
  const spl = name.slice(2).split('_');
1579
1594
 
1580
1595
  protoName = spl[spl.length - 1];
@@ -1587,12 +1602,12 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1587
1602
  }
1588
1603
 
1589
1604
  // literal.func()
1590
- if (!name && decl.callee.type === 'MemberExpression') {
1605
+ if (!decl._new && !name && decl.callee.type === 'MemberExpression') {
1591
1606
  // megahack for /regex/.func()
1592
1607
  const funcName = decl.callee.property.name;
1593
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1608
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1594
1609
  const regex = decl.callee.object.regex.pattern;
1595
- const rhemynName = `regex_${funcName}_${regex}`;
1610
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1596
1611
 
1597
1612
  if (!funcIndex[rhemynName]) {
1598
1613
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1613,7 +1628,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1613
1628
  [ Opcodes.call, idx ],
1614
1629
  Opcodes.i32_from_u,
1615
1630
 
1616
- ...setLastType(scope, TYPES.boolean)
1631
+ ...setLastType(scope, Rhemyn.types[funcName])
1617
1632
  ];
1618
1633
  }
1619
1634
 
@@ -1622,27 +1637,56 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1637
  target = decl.callee.object;
1623
1638
  }
1624
1639
 
1625
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1626
- // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1640
+ let out = [];
1641
+ if (protoName) {
1642
+ if (['search'].includes(protoName)) {
1643
+ const regex = decl.arguments[0]?.regex?.pattern;
1644
+ if (!regex) return [
1645
+ // no/bad regex arg, return -1/0 for now
1646
+ ...generate(scope, target),
1647
+ [ Opcodes.drop ],
1627
1648
 
1628
- // funcIndex[func.name] = func.index;
1629
- // funcs.push(func);
1649
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1650
+ ...setLastType(scope, Rhemyn.types[protoName])
1651
+ ];
1630
1652
 
1631
- // return [
1632
- // generate(scope, decl.callee.object)
1653
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1633
1654
 
1634
- // // call regex func
1635
- // [ Opcodes.call, func.index ],
1636
- // Opcodes.i32_from_u
1637
- // ];
1638
- // }
1655
+ if (!funcIndex[rhemynName]) {
1656
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1657
+ func.internal = true;
1639
1658
 
1640
- if (protoName) {
1641
- const protoBC = {};
1659
+ funcIndex[func.name] = func.index;
1660
+ funcs.push(func);
1661
+ }
1662
+
1663
+ const idx = funcIndex[rhemynName];
1664
+ return [
1665
+ // make string arg
1666
+ ...generate(scope, target),
1667
+ Opcodes.i32_to_u,
1668
+ ...getNodeType(scope, target),
1669
+
1670
+ // call regex func
1671
+ [ Opcodes.call, idx ],
1672
+ Opcodes.i32_from,
1673
+
1674
+ ...setLastType(scope, Rhemyn.types[protoName])
1675
+ ];
1676
+ }
1642
1677
 
1678
+ const protoBC = {};
1643
1679
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1644
1680
 
1645
1681
  if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1682
+ out.push(
1683
+ ...generate(scope, target),
1684
+ [ Opcodes.local_set, localTmp(scope, '#proto_target') ],
1685
+
1686
+ ...getNodeType(scope, target),
1687
+ [ Opcodes.local_set, localTmp(scope, '#proto_target#type', Valtype.i32) ],
1688
+ );
1689
+
1646
1690
  for (const x of builtinProtoCands) {
1647
1691
  const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1648
1692
  if (type == null) continue;
@@ -1652,7 +1696,14 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1652
1696
  type: 'Identifier',
1653
1697
  name: x
1654
1698
  },
1655
- arguments: [ target, ...decl.arguments ],
1699
+ arguments: [
1700
+ {
1701
+ type: 'Identifier',
1702
+ name: '#proto_target'
1703
+ },
1704
+
1705
+ ...decl.arguments
1706
+ ],
1656
1707
  _protoInternalCall: true
1657
1708
  });
1658
1709
  }
@@ -1746,29 +1797,37 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1746
1797
  }
1747
1798
 
1748
1799
  if (Object.keys(protoBC).length > 0) {
1749
- return typeSwitch(scope, getNodeType(scope, target), {
1750
- ...protoBC,
1800
+ return [
1801
+ ...out,
1802
+
1803
+ ...typeSwitch(scope, builtinProtoCands.length > 0 ? [ [ Opcodes.local_get, localTmp(scope, '#proto_target#type', Valtype.i32) ] ] : getNodeType(scope, target), {
1804
+ ...protoBC,
1751
1805
 
1752
- // TODO: error better
1753
- default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1754
- }, valtypeBinary);
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary)
1809
+ ];
1755
1810
  }
1756
1811
  }
1757
1812
 
1758
- // TODO: only allows callee as literal
1759
- if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1813
+ // TODO: only allows callee as identifier
1814
+ if (!name) return todo(scope, `only identifier callees (got ${decl.callee.type})`);
1760
1815
 
1761
1816
  let idx = funcIndex[name] ?? importedFuncs[name];
1762
1817
  if (idx === undefined && builtinFuncs[name]) {
1763
1818
  if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
1819
+ if (decl._new && !builtinFuncs[name].constr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
1764
1820
 
1765
1821
  includeBuiltin(scope, name);
1766
1822
  idx = funcIndex[name];
1767
1823
  }
1768
1824
 
1769
- if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1825
+ if (idx === undefined && internalConstrs[name]) {
1826
+ if (decl._new && internalConstrs[name].notConstr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
1827
+ return internalConstrs[name].generate(scope, decl, _global, _name);
1828
+ }
1770
1829
 
1771
- if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1830
+ if (idx === undefined && !decl._new && name.startsWith('__Porffor_wasm_')) {
1772
1831
  const wasmOps = {
1773
1832
  // pointer, align, offset
1774
1833
  i32_load: { imms: 2, args: [ true ], returns: 1 },
@@ -1823,7 +1882,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1823
1882
 
1824
1883
  const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1825
1884
  // options: vararg, strict
1826
- // - strict: simpler, smaller size usage, no func argc lut needed.
1885
+ // - strict: simpler, smaller size usage, no func lut needed.
1827
1886
  // ONLY works when arg count of call == arg count of function being called
1828
1887
  // - vararg: large size usage, cursed.
1829
1888
  // works when arg count of call != arg count of function being called*
@@ -1833,8 +1892,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1892
  scope.table = true;
1834
1893
 
1835
1894
  let args = decl.arguments;
1836
- let out = [];
1837
-
1838
1895
  let locals = [];
1839
1896
 
1840
1897
  if (indirectMode === 'vararg') {
@@ -1898,24 +1955,67 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1898
1955
  // *for argc 0-3, in future (todo:) the max number should be
1899
1956
  // dynamically changed to the max argc of any func in the js file.
1900
1957
 
1901
- const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1958
+ const funcLocal = localTmp(scope, '#indirect_func', Valtype.i32);
1959
+ const flags = localTmp(scope, '#indirect_flags', Valtype.i32);
1902
1960
 
1903
1961
  const gen = argc => {
1904
- const out = [];
1962
+ const argsOut = [];
1905
1963
  for (let i = 0; i < argc; i++) {
1906
- out.push(
1964
+ argsOut.push(
1907
1965
  [ Opcodes.local_get, locals[i][0] ],
1908
1966
  [ Opcodes.local_get, locals[i][1] ]
1909
1967
  );
1910
1968
  }
1911
1969
 
1912
- out.push(
1913
- [ Opcodes.local_get, funcLocal ],
1914
- [ Opcodes.call_indirect, argc, 0 ],
1915
- ...setLastType(scope)
1916
- )
1970
+ const checkFlag = (flag, pass, fail) => [
1971
+ [ Opcodes.local_get, flags ],
1972
+ ...number(flag, Valtype.i32),
1973
+ [ Opcodes.i32_and ],
1974
+ [ Opcodes.if, valtypeBinary ],
1975
+ ...pass,
1976
+ [ Opcodes.else ],
1977
+ ...fail,
1978
+ [ Opcodes.end ]
1979
+ ];
1917
1980
 
1918
- return out;
1981
+ // pain.
1982
+ // return checkFlag(0b10, [ // constr
1983
+ // [ Opcodes.i32_const, decl._new ? 1 : 0 ],
1984
+ // ...argsOut,
1985
+ // [ Opcodes.local_get, funcLocal ],
1986
+ // [ Opcodes.call_indirect, argc, 0, 'constr' ],
1987
+ // ...setLastType(scope),
1988
+ // ], [
1989
+ // ...argsOut,
1990
+ // [ Opcodes.local_get, funcLocal ],
1991
+ // [ Opcodes.call_indirect, argc, 0 ],
1992
+ // ...setLastType(scope),
1993
+ // ]);
1994
+
1995
+ return checkFlag(0b1, // no type return
1996
+ checkFlag(0b10, [ // no type return & constr
1997
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
1998
+ ...argsOut,
1999
+ [ Opcodes.local_get, funcLocal ],
2000
+ [ Opcodes.call_indirect, argc, 0, 'no_type_return', 'constr' ],
2001
+ ], [
2002
+ ...argsOut,
2003
+ [ Opcodes.local_get, funcLocal ],
2004
+ [ Opcodes.call_indirect, argc, 0, 'no_type_return' ]
2005
+ ]),
2006
+ checkFlag(0b10, [ // type return & constr
2007
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
2008
+ ...argsOut,
2009
+ [ Opcodes.local_get, funcLocal ],
2010
+ [ Opcodes.call_indirect, argc, 0, 'constr' ],
2011
+ ...setLastType(scope),
2012
+ ], [
2013
+ ...argsOut,
2014
+ [ Opcodes.local_get, funcLocal ],
2015
+ [ Opcodes.call_indirect, argc, 0 ],
2016
+ ...setLastType(scope),
2017
+ ]),
2018
+ );
1919
2019
  };
1920
2020
 
1921
2021
  const tableBc = {};
@@ -1933,13 +2033,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1933
2033
  Opcodes.i32_to_u,
1934
2034
  [ Opcodes.local_set, funcLocal ],
1935
2035
 
2036
+ // get if func we are calling is a constructor or not
2037
+ [ Opcodes.local_get, funcLocal ],
2038
+ ...number(3, Valtype.i32),
2039
+ [ Opcodes.i32_mul ],
2040
+ ...number(2, Valtype.i32),
2041
+ [ Opcodes.i32_add ],
2042
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ],
2043
+ [ Opcodes.local_set, flags ],
2044
+
2045
+ // check if non-constructor was called with new, if so throw
2046
+ [ Opcodes.local_get, flags ],
2047
+ ...number(0b10, Valtype.i32),
2048
+ [ Opcodes.i32_and ],
2049
+ [ Opcodes.i32_eqz ],
2050
+ [ Opcodes.i32_const, decl._new ? 1 : 0 ],
2051
+ [ Opcodes.i32_and ],
2052
+ [ Opcodes.if, Blocktype.void ],
2053
+ ...internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`),
2054
+ [ Opcodes.end ],
2055
+
1936
2056
  ...brTable([
1937
2057
  // get argc of func we are calling
1938
2058
  [ Opcodes.local_get, funcLocal ],
1939
- ...number(ValtypeSize.i16, Valtype.i32),
2059
+ ...number(3, Valtype.i32),
1940
2060
  [ Opcodes.i32_mul ],
1941
-
1942
- [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
2061
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ]
1943
2062
  ], tableBc, valtypeBinary)
1944
2063
  ],
1945
2064
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
@@ -1953,14 +2072,35 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1953
2072
  const userFunc = func && !func.internal;
1954
2073
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1955
2074
  const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1956
- const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1957
-
1958
- let args = decl.arguments;
1959
- if (func && args.length < paramCount) {
2075
+ let paramCount = func && (typedParams ? Math.floor(func.params.length / 2) : func.params.length);
2076
+
2077
+ let paramOffset = 0;
2078
+ if (func && func.constr) {
2079
+ // new.target arg
2080
+ if (func.internal) paramOffset = 1;
2081
+ if (!typedParams) paramCount--;
2082
+ out.push([ Opcodes.i32_const, decl._new ? 1 : 0 ]);
2083
+ } else if (decl._new)
2084
+ return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
2085
+
2086
+ let args = [...decl.arguments];
2087
+ if (func && !func.hasRestArgument && args.length < paramCount) {
1960
2088
  // too little args, push undefineds
1961
2089
  args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1962
2090
  }
1963
2091
 
2092
+ if (func && func.hasRestArgument) {
2093
+ if (args.length < paramCount) {
2094
+ args = args.concat(new Array(paramCount - 1 - args.length).fill(DEFAULT_VALUE));
2095
+ }
2096
+ const restArgs = args.slice(paramCount - 1);
2097
+ args = args.slice(0, paramCount - 1);
2098
+ args.push({
2099
+ type: 'ArrayExpression',
2100
+ elements: restArgs
2101
+ })
2102
+ }
2103
+
1964
2104
  if (func && args.length > paramCount) {
1965
2105
  // too many args, slice extras off
1966
2106
  args = args.slice(0, paramCount);
@@ -1968,7 +2108,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1968
2108
 
1969
2109
  if (func && func.throws) scope.throws = true;
1970
2110
 
1971
- let out = [];
1972
2111
  for (let i = 0; i < args.length; i++) {
1973
2112
  const arg = args[i];
1974
2113
  out = out.concat(generate(scope, arg));
@@ -1981,13 +2120,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1981
2120
  }
1982
2121
 
1983
2122
  if (valtypeBinary !== Valtype.i32 &&
1984
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2123
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.i32)
1985
2124
  ) {
1986
2125
  out.push(Opcodes.i32_to);
1987
2126
  }
1988
2127
 
1989
2128
  if (valtypeBinary === Valtype.i32 &&
1990
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
2129
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.f64)
1991
2130
  ) {
1992
2131
  out.push([ Opcodes.f64_convert_i32_s ]);
1993
2132
  }
@@ -2020,32 +2159,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2020
2159
  return out;
2021
2160
  };
2022
2161
 
2023
- const generateNew = (scope, decl, _global, _name) => {
2024
- // hack: basically treat this as a normal call for builtins for now
2025
- const name = mapName(decl.callee.name);
2026
-
2027
- if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
2028
-
2029
- if (builtinFuncs[name + '$constructor']) {
2030
- // custom ...$constructor override builtin func
2031
- return generateCall(scope, {
2032
- ...decl,
2033
- callee: {
2034
- type: 'Identifier',
2035
- name: name + '$constructor'
2036
- }
2037
- }, _global, _name);
2038
- }
2039
-
2040
- if (
2041
- (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2042
- (internalConstrs[name] && builtinFuncs[name].notConstr)
2043
- ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`, true);
2044
-
2045
- if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`, true); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
2046
-
2047
- return generateCall(scope, decl, _global, _name);
2048
- };
2162
+ const generateNew = (scope, decl, _global, _name) => generateCall(scope, {
2163
+ ...decl,
2164
+ _new: true
2165
+ }, _global, _name);
2049
2166
 
2050
2167
  // bad hack for undefined and null working without additional logic
2051
2168
  const DEFAULT_VALUE = {
@@ -2053,6 +2170,16 @@ const DEFAULT_VALUE = {
2053
2170
  name: 'undefined'
2054
2171
  };
2055
2172
 
2173
+ const codeToSanitizedStr = code => {
2174
+ let out = '';
2175
+ while (code > 0) {
2176
+ out += String.fromCharCode(97 + code % 26);
2177
+ code -= 26;
2178
+ }
2179
+ return out;
2180
+ };
2181
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2182
+
2056
2183
  const unhackName = name => {
2057
2184
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2058
2185
  return name;
@@ -2288,9 +2415,120 @@ const generateVar = (scope, decl) => {
2288
2415
  const global = topLevel || decl._bare;
2289
2416
 
2290
2417
  for (const x of decl.declarations) {
2291
- const name = mapName(x.id.name);
2418
+ if (x.id.type === 'ArrayPattern') {
2419
+ const decls = [];
2420
+ const tmpName = '#destructure' + randId();
2421
+
2422
+ let i = 0;
2423
+ const elements = [...x.id.elements];
2424
+ for (const e of elements) {
2425
+ switch (e?.type) {
2426
+ case 'RestElement': { // let [ ...foo ] = []
2427
+ if (e.argument.type === 'ArrayPattern') {
2428
+ // let [ ...[a, b, c] ] = []
2429
+ elements.push(...e.argument.elements);
2430
+ } else {
2431
+ decls.push({
2432
+ type: 'VariableDeclarator',
2433
+ id: { type: 'Identifier', name: e.argument.name },
2434
+ init: {
2435
+ type: 'CallExpression',
2436
+ callee: {
2437
+ type: 'Identifier',
2438
+ name: '__Array_prototype_slice'
2439
+ },
2440
+ arguments: [
2441
+ { type: 'Identifier', name: tmpName },
2442
+ { type: 'Literal', value: i },
2443
+ {
2444
+ type: 'MemberExpression',
2445
+ object: { type: 'Identifier', name: tmpName, },
2446
+ property: { type: 'Identifier', name: 'length', }
2447
+ }
2448
+ ]
2449
+ }
2450
+ });
2451
+ }
2452
+
2453
+ continue; // skip i++
2454
+ }
2455
+
2456
+ case 'Identifier': { // let [ foo ] = []
2457
+ decls.push({
2458
+ type: 'VariableDeclarator',
2459
+ id: e,
2460
+ init: {
2461
+ type: 'MemberExpression',
2462
+ object: { type: 'Identifier', name: tmpName },
2463
+ property: { type: 'Literal', value: i }
2464
+ }
2465
+ });
2466
+
2467
+ break;
2468
+ }
2469
+
2470
+ case 'AssignmentPattern': { // let [ foo = defaultValue ] = []
2471
+ decls.push({
2472
+ type: 'VariableDeclarator',
2473
+ id: e.left,
2474
+ init: {
2475
+ type: 'LogicalExpression',
2476
+ operator: '??',
2477
+ left: {
2478
+ type: 'MemberExpression',
2479
+ object: { type: 'Identifier', name: tmpName },
2480
+ property: { type: 'Literal', value: i }
2481
+ },
2482
+ right: e.right
2483
+ }
2484
+ });
2485
+
2486
+ break;
2487
+ }
2488
+
2489
+ case 'ArrayPattern': { // let [ [ foo, bar ] ] = []
2490
+ decls.push({
2491
+ type: 'VariableDeclarator',
2492
+ id: e,
2493
+ init: {
2494
+ type: 'MemberExpression',
2495
+ object: { type: 'Identifier', name: tmpName },
2496
+ property: { type: 'Literal', value: i }
2497
+ }
2498
+ });
2499
+
2500
+ break;
2501
+ }
2502
+
2503
+ case 'ObjectPattern':
2504
+ return todo(scope, 'object destructuring is not supported yet')
2505
+ }
2292
2506
 
2293
- if (!name) return todo(scope, 'destructuring is not supported yet');
2507
+ i++;
2508
+ }
2509
+
2510
+ out = out.concat([
2511
+ ...generateVar(scope, {
2512
+ type: 'VariableDeclaration',
2513
+ declarations: [{
2514
+ type: 'VariableDeclarator',
2515
+ id: { type: 'Identifier', name: tmpName },
2516
+ init: x.init
2517
+ }],
2518
+ kind: decl.kind
2519
+ }),
2520
+ ...generateVar(scope, {
2521
+ type: 'VariableDeclaration',
2522
+ declarations: decls,
2523
+ kind: decl.kind
2524
+ })
2525
+ ]);
2526
+
2527
+ continue;
2528
+ }
2529
+
2530
+ const name = mapName(x.id.name);
2531
+ if (!name) return todo(scope, 'object destructuring is not supported yet')
2294
2532
 
2295
2533
  if (x.init && isFuncType(x.init.type)) {
2296
2534
  // hack for let a = function () { ... }
@@ -2325,7 +2563,9 @@ const generateVar = (scope, decl) => {
2325
2563
  // hack to set local as pointer before
2326
2564
  out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2327
2565
  if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2328
- generated.pop();
2566
+ // generated.pop();
2567
+ generated.push([ Opcodes.drop ]);
2568
+
2329
2569
  out = out.concat(generated);
2330
2570
  } else {
2331
2571
  out = out.concat(generated);
@@ -2392,8 +2632,8 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2392
2632
 
2393
2633
  // arr[i]
2394
2634
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2395
- const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2396
- const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2635
+ const newValueTmp = localTmp(scope, '#member_setter_val_tmp');
2636
+ const pointerTmp = localTmp(scope, '#member_setter_ptr_tmp', Valtype.i32);
2397
2637
 
2398
2638
  return [
2399
2639
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
@@ -2405,11 +2645,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2405
2645
  ...generate(scope, decl.left.property),
2406
2646
  Opcodes.i32_to_u,
2407
2647
 
2408
- // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2648
+ // turn into byte offset by * valtypeSize + 1
2409
2649
  ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2410
2650
  [ Opcodes.i32_mul ],
2411
2651
  [ Opcodes.i32_add ],
2412
- ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2652
+ [ Opcodes.local_tee, pointerTmp ],
2413
2653
 
2414
2654
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2415
2655
  [ Opcodes.local_get, pointerTmp ],
@@ -2419,7 +2659,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2419
2659
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2420
2660
  ], getNodeType(scope, decl.right), false, name, true)),
2421
2661
  [ Opcodes.local_tee, newValueTmp ],
2422
- [ Opcodes.store, 0, ValtypeSize.i32 ]
2662
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
2663
+
2664
+ [ Opcodes.local_get, pointerTmp ],
2665
+ ...getNodeType(scope, decl),
2666
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
2423
2667
  ],
2424
2668
 
2425
2669
  ...wrapBC({
@@ -2956,7 +3200,9 @@ const generateForOf = (scope, decl) => {
2956
3200
  // // todo: we should only do this for strings but we don't know at compile-time :(
2957
3201
  // hack: this is naughty and will break things!
2958
3202
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2959
- if (pages.hasAnyString) {
3203
+
3204
+ const known = knownType(scope, getNodeType(scope, decl.right));
3205
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
2960
3206
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2961
3207
  0, [ newOut, newPointer ] = makeArray(scope, {
2962
3208
  rawElements: new Array(0)
@@ -3004,6 +3250,7 @@ const generateForOf = (scope, decl) => {
3004
3250
  [ Opcodes.end ],
3005
3251
  [ Opcodes.end ]
3006
3252
  ],
3253
+
3007
3254
  [TYPES.string]: [
3008
3255
  ...setType(scope, leftName, TYPES.string),
3009
3256
 
@@ -3112,6 +3359,7 @@ const generateForOf = (scope, decl) => {
3112
3359
  [ Opcodes.end ],
3113
3360
  [ Opcodes.end ]
3114
3361
  ],
3362
+
3115
3363
  [TYPES.set]: [
3116
3364
  [ Opcodes.loop, Blocktype.void ],
3117
3365
 
@@ -3150,7 +3398,106 @@ const generateForOf = (scope, decl) => {
3150
3398
  [ Opcodes.end ],
3151
3399
  [ Opcodes.end ]
3152
3400
  ],
3153
- // todo: typed arrays
3401
+
3402
+ ...wrapBC({
3403
+ [TYPES.uint8array]: [
3404
+ [ Opcodes.i32_add ],
3405
+
3406
+ [ Opcodes.i32_load8_u, 0, 4 ],
3407
+ Opcodes.i32_from_u
3408
+ ],
3409
+ [TYPES.uint8clampedarray]: [
3410
+ [ Opcodes.i32_add ],
3411
+
3412
+ [ Opcodes.i32_load8_u, 0, 4 ],
3413
+ Opcodes.i32_from_u
3414
+ ],
3415
+ [TYPES.int8array]: [
3416
+ [ Opcodes.i32_add ],
3417
+
3418
+ [ Opcodes.i32_load8_s, 0, 4 ],
3419
+ Opcodes.i32_from
3420
+ ],
3421
+ [TYPES.uint16array]: [
3422
+ ...number(2, Valtype.i32),
3423
+ [ Opcodes.i32_mul ],
3424
+ [ Opcodes.i32_add ],
3425
+
3426
+ [ Opcodes.i32_load16_u, 0, 4 ],
3427
+ Opcodes.i32_from_u
3428
+ ],
3429
+ [TYPES.int16array]: [
3430
+ ...number(2, Valtype.i32),
3431
+ [ Opcodes.i32_mul ],
3432
+ [ Opcodes.i32_add ],
3433
+
3434
+ [ Opcodes.i32_load16_s, 0, 4 ],
3435
+ Opcodes.i32_from
3436
+ ],
3437
+ [TYPES.uint32array]: [
3438
+ ...number(4, Valtype.i32),
3439
+ [ Opcodes.i32_mul ],
3440
+ [ Opcodes.i32_add ],
3441
+
3442
+ [ Opcodes.i32_load, 0, 4 ],
3443
+ Opcodes.i32_from_u
3444
+ ],
3445
+ [TYPES.int32array]: [
3446
+ ...number(4, Valtype.i32),
3447
+ [ Opcodes.i32_mul ],
3448
+ [ Opcodes.i32_add ],
3449
+
3450
+ [ Opcodes.i32_load, 0, 4 ],
3451
+ Opcodes.i32_from
3452
+ ],
3453
+ [TYPES.float32array]: [
3454
+ ...number(4, Valtype.i32),
3455
+ [ Opcodes.i32_mul ],
3456
+ [ Opcodes.i32_add ],
3457
+
3458
+ [ Opcodes.f32_load, 0, 4 ],
3459
+ [ Opcodes.f64_promote_f32 ]
3460
+ ],
3461
+ [TYPES.float64array]: [
3462
+ ...number(8, Valtype.i32),
3463
+ [ Opcodes.i32_mul ],
3464
+ [ Opcodes.i32_add ],
3465
+
3466
+ [ Opcodes.f64_load, 0, 4 ]
3467
+ ],
3468
+ }, {
3469
+ prelude: [
3470
+ ...setType(scope, leftName, TYPES.number),
3471
+
3472
+ [ Opcodes.loop, Blocktype.void ],
3473
+
3474
+ [ Opcodes.local_get, pointer ],
3475
+ [ Opcodes.local_get, counter ]
3476
+ ],
3477
+ postlude: [
3478
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3479
+
3480
+ [ Opcodes.block, Blocktype.void ],
3481
+ [ Opcodes.block, Blocktype.void ],
3482
+ ...generate(scope, decl.body),
3483
+ [ Opcodes.end ],
3484
+
3485
+ // increment counter by 1
3486
+ [ Opcodes.local_get, counter ],
3487
+ ...number(1, Valtype.i32),
3488
+ [ Opcodes.i32_add ],
3489
+ [ Opcodes.local_tee, counter ],
3490
+
3491
+ // loop if counter != length
3492
+ [ Opcodes.local_get, length ],
3493
+ [ Opcodes.i32_ne ],
3494
+ [ Opcodes.br_if, 1 ],
3495
+
3496
+ [ Opcodes.end ],
3497
+ [ Opcodes.end ]
3498
+ ]
3499
+ }),
3500
+
3154
3501
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
3155
3502
  }, Blocktype.void));
3156
3503
 
@@ -3354,6 +3701,22 @@ const generateEmpty = (scope, decl) => {
3354
3701
  return [];
3355
3702
  };
3356
3703
 
3704
+ const generateMeta = (scope, decl) => {
3705
+ if (decl.meta.name !== 'new') return todo(scope, `meta property object ${decl.meta.name} is not supported yet`, true);
3706
+
3707
+ switch (`${decl.meta.name}.${decl.property.name}`) {
3708
+ case 'new.target': {
3709
+ scope.constr = true;
3710
+
3711
+ return [
3712
+ [ Opcodes.local_get, -1 ],
3713
+ Opcodes.i32_from_u,
3714
+ ...setLastType(scope, TYPES.boolean)
3715
+ ];
3716
+ }
3717
+ }
3718
+ };
3719
+
3357
3720
  let pages = new Map();
3358
3721
  const allocPage = (scope, reason, type) => {
3359
3722
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3706,20 +4069,10 @@ const generateMember = (scope, decl, _global, _name) => {
3706
4069
  const func = funcs.find(x => x.name === name);
3707
4070
  if (func) {
3708
4071
  const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3709
- return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
4072
+ return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
3710
4073
  }
3711
4074
 
3712
- if (builtinFuncs[name + '$constructor']) {
3713
- const regularFunc = builtinFuncs[name];
3714
- const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3715
-
3716
- const constructorFunc = builtinFuncs[name + '$constructor'];
3717
- const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3718
-
3719
- return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3720
- }
3721
-
3722
- if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
4075
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? Math.floor(builtinFuncs[name].params.length / 2) : (builtinFuncs[name].constr ? (builtinFuncs[name].params.length - 1) : builtinFuncs[name].params.length)), TYPES.number);
3723
4076
  if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3724
4077
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3725
4078
 
@@ -3768,7 +4121,7 @@ const generateMember = (scope, decl, _global, _name) => {
3768
4121
  }
3769
4122
 
3770
4123
  // todo: generate this array procedurally during builtinFuncs creation
3771
- if (['size', 'description'].includes(decl.property.name)) {
4124
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3772
4125
  const bc = {};
3773
4126
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3774
4127
 
@@ -3974,7 +4327,7 @@ const objectHack = node => {
3974
4327
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3975
4328
 
3976
4329
  // if .name or .length, give up (hack within a hack!)
3977
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4330
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3978
4331
  node.object = objectHack(node.object);
3979
4332
  return;
3980
4333
  }
@@ -4027,8 +4380,8 @@ const generateFunc = (scope, decl) => {
4027
4380
 
4028
4381
  if (typedInput && decl.returnType) {
4029
4382
  const { type } = extractTypeAnnotation(decl.returnType);
4030
- // if (type != null && !Prefs.indirectCalls) {
4031
- if (type != null) {
4383
+ if (type != null && !Prefs.indirectCalls) {
4384
+ // if (type != null) {
4032
4385
  func.returnType = type;
4033
4386
  func.returns = [ valtypeBinary ];
4034
4387
  }
@@ -4049,6 +4402,12 @@ const generateFunc = (scope, decl) => {
4049
4402
  defaultValues[name] = x.right;
4050
4403
  break;
4051
4404
  }
4405
+
4406
+ case 'RestElement': {
4407
+ name = x.argument.name;
4408
+ func.hasRestArgument = true;
4409
+ break;
4410
+ }
4052
4411
  }
4053
4412
 
4054
4413
  // if (name == null) return todo('non-identifier args are not supported');