porffor 0.17.0-cab4904b8 → 0.17.0-cbb73d209

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.
@@ -4,7 +4,7 @@ import { operatorOpcode } from './expression.js';
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
5
  import { PrototypeFuncs } from './prototype.js';
6
6
  import { number } from './embedding.js';
7
- import { TYPES, TYPE_NAMES } from './types.js';
7
+ import { TYPES, TYPE_FLAGS, TYPE_NAMES, typeHasFlag } from './types.js';
8
8
  import * as Rhemyn from '../rhemyn/compile.js';
9
9
  import parse from './parse.js';
10
10
  import { log } from './log.js';
@@ -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
@@ -619,11 +622,14 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
619
622
  };
620
623
 
621
624
  const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
622
- // if (isIntToFloatOp(wasm[wasm.length - 1])) return [
623
- // ...wasm,
624
- // ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
625
- // ];
626
- // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
625
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
626
+ ...wasm,
627
+ ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
628
+ ];
629
+ if (isIntOp(wasm[wasm.length - 1])) return [
630
+ ...wasm,
631
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
632
+ ];
627
633
 
628
634
  // todo/perf: use knownType and custom bytecode here instead of typeSwitch
629
635
 
@@ -1023,12 +1029,13 @@ const asmFuncToAsm = (func, scope) => {
1023
1029
  idx = funcIndex[n];
1024
1030
  }
1025
1031
 
1032
+ if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
1026
1033
  return idx;
1027
1034
  }
1028
1035
  });
1029
1036
  };
1030
1037
 
1031
- 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 }) => {
1032
1039
  const existing = funcs.find(x => x.name === name);
1033
1040
  if (existing) return existing;
1034
1041
 
@@ -1055,13 +1062,17 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1055
1062
  returnType,
1056
1063
  internal: true,
1057
1064
  index: currentFuncIndex++,
1058
- table
1065
+ table,
1066
+ constr
1059
1067
  };
1060
1068
 
1061
1069
  funcs.push(func);
1062
1070
  funcIndex[name] = func.index;
1063
1071
 
1064
- 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
+ }
1065
1076
 
1066
1077
  let baseGlobalIdx, i = 0;
1067
1078
  for (const type of globalTypes) {
@@ -1082,15 +1093,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1082
1093
 
1083
1094
  if (table) {
1084
1095
  for (const inst of wasm) {
1085
- if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1096
+ if (inst.at(-1) === 'read func lut') {
1086
1097
  inst.splice(2, 99);
1087
- inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1098
+ inst.push(...unsignedLEB128(allocPage({}, 'func lut') * pageSize));
1088
1099
  }
1089
1100
  }
1090
1101
 
1091
1102
  funcs.table = true;
1092
1103
  }
1093
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
+
1094
1125
  func.wasm = wasm;
1095
1126
 
1096
1127
  return func;
@@ -1217,20 +1248,7 @@ const getNodeType = (scope, node) => {
1217
1248
  return TYPES.number;
1218
1249
  }
1219
1250
 
1220
- if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1221
- if (builtinFuncs[name + '$constructor'].typedReturns) {
1222
- if (scope.locals['#last_type']) return getLastType(scope);
1223
-
1224
- // presume
1225
- // todo: warn here?
1226
- return TYPES.number;
1227
- }
1228
-
1229
- return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1230
- }
1231
-
1232
1251
  const func = funcs.find(x => x.name === name);
1233
-
1234
1252
  if (func) {
1235
1253
  if (func.returnType != null) return func.returnType;
1236
1254
  }
@@ -1345,7 +1363,7 @@ const getNodeType = (scope, node) => {
1345
1363
  const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1346
1364
  if (objectKnownType != null) {
1347
1365
  if (name === 'length') {
1348
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1366
+ if (typeHasFlag(objectKnownType, TYPE_FLAGS.length)) return TYPES.number;
1349
1367
  else return TYPES.undefined;
1350
1368
  }
1351
1369
 
@@ -1417,9 +1435,9 @@ const countLeftover = wasm => {
1417
1435
 
1418
1436
  if (depth === 0)
1419
1437
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1420
- 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)) {}
1421
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++;
1422
- 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;
1423
1441
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1424
1442
  else if (inst[0] === Opcodes.return) count = 0;
1425
1443
  else if (inst[0] === Opcodes.call) {
@@ -1528,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1528
1546
  name = func.name;
1529
1547
  }
1530
1548
 
1531
- if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1549
+ if (!decl._new && name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1532
1550
  // literal eval hack
1533
1551
  const code = decl.arguments[0]?.value ?? '';
1534
1552
 
@@ -1571,7 +1589,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1571
1589
 
1572
1590
  let protoName, target;
1573
1591
  // ident.func()
1574
- if (name && name.startsWith('__')) {
1592
+ if (!decl._new && name && name.startsWith('__')) {
1575
1593
  const spl = name.slice(2).split('_');
1576
1594
 
1577
1595
  protoName = spl[spl.length - 1];
@@ -1584,12 +1602,12 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1584
1602
  }
1585
1603
 
1586
1604
  // literal.func()
1587
- if (!name && decl.callee.type === 'MemberExpression') {
1605
+ if (!decl._new && !name && decl.callee.type === 'MemberExpression') {
1588
1606
  // megahack for /regex/.func()
1589
1607
  const funcName = decl.callee.property.name;
1590
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1608
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1591
1609
  const regex = decl.callee.object.regex.pattern;
1592
- const rhemynName = `regex_${funcName}_${regex}`;
1610
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1593
1611
 
1594
1612
  if (!funcIndex[rhemynName]) {
1595
1613
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1610,7 +1628,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1610
1628
  [ Opcodes.call, idx ],
1611
1629
  Opcodes.i32_from_u,
1612
1630
 
1613
- ...setLastType(scope, TYPES.boolean)
1631
+ ...setLastType(scope, Rhemyn.types[funcName])
1614
1632
  ];
1615
1633
  }
1616
1634
 
@@ -1619,27 +1637,56 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1619
1637
  target = decl.callee.object;
1620
1638
  }
1621
1639
 
1622
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1623
- // 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 ],
1624
1648
 
1625
- // funcIndex[func.name] = func.index;
1626
- // funcs.push(func);
1649
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1650
+ ...setLastType(scope, Rhemyn.types[protoName])
1651
+ ];
1627
1652
 
1628
- // return [
1629
- // generate(scope, decl.callee.object)
1653
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1630
1654
 
1631
- // // call regex func
1632
- // [ Opcodes.call, func.index ],
1633
- // Opcodes.i32_from_u
1634
- // ];
1635
- // }
1655
+ if (!funcIndex[rhemynName]) {
1656
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1657
+ func.internal = true;
1636
1658
 
1637
- if (protoName) {
1638
- 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,
1639
1673
 
1674
+ ...setLastType(scope, Rhemyn.types[protoName])
1675
+ ];
1676
+ }
1677
+
1678
+ const protoBC = {};
1640
1679
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1641
1680
 
1642
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
+
1643
1690
  for (const x of builtinProtoCands) {
1644
1691
  const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1645
1692
  if (type == null) continue;
@@ -1649,7 +1696,14 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1649
1696
  type: 'Identifier',
1650
1697
  name: x
1651
1698
  },
1652
- arguments: [ target, ...decl.arguments ],
1699
+ arguments: [
1700
+ {
1701
+ type: 'Identifier',
1702
+ name: '#proto_target'
1703
+ },
1704
+
1705
+ ...decl.arguments
1706
+ ],
1653
1707
  _protoInternalCall: true
1654
1708
  });
1655
1709
  }
@@ -1743,29 +1797,37 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1743
1797
  }
1744
1798
 
1745
1799
  if (Object.keys(protoBC).length > 0) {
1746
- return typeSwitch(scope, getNodeType(scope, target), {
1747
- ...protoBC,
1800
+ return [
1801
+ ...out,
1748
1802
 
1749
- // TODO: error better
1750
- default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1751
- }, valtypeBinary);
1803
+ ...typeSwitch(scope, builtinProtoCands.length > 0 ? [ [ Opcodes.local_get, localTmp(scope, '#proto_target#type', Valtype.i32) ] ] : getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
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
+ ];
1752
1810
  }
1753
1811
  }
1754
1812
 
1755
- // TODO: only allows callee as literal
1756
- 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})`);
1757
1815
 
1758
1816
  let idx = funcIndex[name] ?? importedFuncs[name];
1759
1817
  if (idx === undefined && builtinFuncs[name]) {
1760
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);
1761
1820
 
1762
1821
  includeBuiltin(scope, name);
1763
1822
  idx = funcIndex[name];
1764
1823
  }
1765
1824
 
1766
- 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
+ }
1767
1829
 
1768
- if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1830
+ if (idx === undefined && !decl._new && name.startsWith('__Porffor_wasm_')) {
1769
1831
  const wasmOps = {
1770
1832
  // pointer, align, offset
1771
1833
  i32_load: { imms: 2, args: [ true ], returns: 1 },
@@ -1820,7 +1882,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1820
1882
 
1821
1883
  const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1822
1884
  // options: vararg, strict
1823
- // - strict: simpler, smaller size usage, no func argc lut needed.
1885
+ // - strict: simpler, smaller size usage, no func lut needed.
1824
1886
  // ONLY works when arg count of call == arg count of function being called
1825
1887
  // - vararg: large size usage, cursed.
1826
1888
  // works when arg count of call != arg count of function being called*
@@ -1830,8 +1892,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1830
1892
  scope.table = true;
1831
1893
 
1832
1894
  let args = decl.arguments;
1833
- let out = [];
1834
-
1835
1895
  let locals = [];
1836
1896
 
1837
1897
  if (indirectMode === 'vararg') {
@@ -1895,24 +1955,67 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1895
1955
  // *for argc 0-3, in future (todo:) the max number should be
1896
1956
  // dynamically changed to the max argc of any func in the js file.
1897
1957
 
1898
- 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);
1899
1960
 
1900
1961
  const gen = argc => {
1901
- const out = [];
1962
+ const argsOut = [];
1902
1963
  for (let i = 0; i < argc; i++) {
1903
- out.push(
1964
+ argsOut.push(
1904
1965
  [ Opcodes.local_get, locals[i][0] ],
1905
1966
  [ Opcodes.local_get, locals[i][1] ]
1906
1967
  );
1907
1968
  }
1908
1969
 
1909
- out.push(
1910
- [ Opcodes.local_get, funcLocal ],
1911
- [ Opcodes.call_indirect, argc, 0 ],
1912
- ...setLastType(scope)
1913
- )
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
+ ];
1914
1980
 
1915
- 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
+ );
1916
2019
  };
1917
2020
 
1918
2021
  const tableBc = {};
@@ -1930,13 +2033,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1930
2033
  Opcodes.i32_to_u,
1931
2034
  [ Opcodes.local_set, funcLocal ],
1932
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
+
1933
2056
  ...brTable([
1934
2057
  // get argc of func we are calling
1935
2058
  [ Opcodes.local_get, funcLocal ],
1936
- ...number(ValtypeSize.i16, Valtype.i32),
2059
+ ...number(3, Valtype.i32),
1937
2060
  [ Opcodes.i32_mul ],
1938
-
1939
- [ 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' ]
1940
2062
  ], tableBc, valtypeBinary)
1941
2063
  ],
1942
2064
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
@@ -1950,14 +2072,35 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1950
2072
  const userFunc = func && !func.internal;
1951
2073
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1952
2074
  const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1953
- const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1954
-
1955
- let args = decl.arguments;
1956
- 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) {
1957
2088
  // too little args, push undefineds
1958
2089
  args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1959
2090
  }
1960
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
+
1961
2104
  if (func && args.length > paramCount) {
1962
2105
  // too many args, slice extras off
1963
2106
  args = args.slice(0, paramCount);
@@ -1965,7 +2108,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1965
2108
 
1966
2109
  if (func && func.throws) scope.throws = true;
1967
2110
 
1968
- let out = [];
1969
2111
  for (let i = 0; i < args.length; i++) {
1970
2112
  const arg = args[i];
1971
2113
  out = out.concat(generate(scope, arg));
@@ -1978,13 +2120,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1978
2120
  }
1979
2121
 
1980
2122
  if (valtypeBinary !== Valtype.i32 &&
1981
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2123
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.i32)
1982
2124
  ) {
1983
2125
  out.push(Opcodes.i32_to);
1984
2126
  }
1985
2127
 
1986
2128
  if (valtypeBinary === Valtype.i32 &&
1987
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
2129
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.f64)
1988
2130
  ) {
1989
2131
  out.push([ Opcodes.f64_convert_i32_s ]);
1990
2132
  }
@@ -2017,32 +2159,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2017
2159
  return out;
2018
2160
  };
2019
2161
 
2020
- const generateNew = (scope, decl, _global, _name) => {
2021
- // hack: basically treat this as a normal call for builtins for now
2022
- const name = mapName(decl.callee.name);
2023
-
2024
- if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
2025
-
2026
- if (builtinFuncs[name + '$constructor']) {
2027
- // custom ...$constructor override builtin func
2028
- return generateCall(scope, {
2029
- ...decl,
2030
- callee: {
2031
- type: 'Identifier',
2032
- name: name + '$constructor'
2033
- }
2034
- }, _global, _name);
2035
- }
2036
-
2037
- if (
2038
- (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2039
- (internalConstrs[name] && builtinFuncs[name].notConstr)
2040
- ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`, true);
2041
-
2042
- 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)})`);
2043
-
2044
- return generateCall(scope, decl, _global, _name);
2045
- };
2162
+ const generateNew = (scope, decl, _global, _name) => generateCall(scope, {
2163
+ ...decl,
2164
+ _new: true
2165
+ }, _global, _name);
2046
2166
 
2047
2167
  // bad hack for undefined and null working without additional logic
2048
2168
  const DEFAULT_VALUE = {
@@ -2050,6 +2170,16 @@ const DEFAULT_VALUE = {
2050
2170
  name: 'undefined'
2051
2171
  };
2052
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
+
2053
2183
  const unhackName = name => {
2054
2184
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2055
2185
  return name;
@@ -2057,7 +2187,7 @@ const unhackName = name => {
2057
2187
 
2058
2188
  const knownType = (scope, type) => {
2059
2189
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2060
- return type[0][1];
2190
+ return read_signedLEB128(type[0].slice(1));
2061
2191
  }
2062
2192
 
2063
2193
  if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
@@ -2285,9 +2415,120 @@ const generateVar = (scope, decl) => {
2285
2415
  const global = topLevel || decl._bare;
2286
2416
 
2287
2417
  for (const x of decl.declarations) {
2288
- 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
+ }
2289
2455
 
2290
- if (!name) return todo(scope, 'destructuring is not supported yet');
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
+ }
2506
+
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')
2291
2532
 
2292
2533
  if (x.init && isFuncType(x.init.type)) {
2293
2534
  // hack for let a = function () { ... }
@@ -2322,7 +2563,9 @@ const generateVar = (scope, decl) => {
2322
2563
  // hack to set local as pointer before
2323
2564
  out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2324
2565
  if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2325
- generated.pop();
2566
+ // generated.pop();
2567
+ generated.push([ Opcodes.drop ]);
2568
+
2326
2569
  out = out.concat(generated);
2327
2570
  } else {
2328
2571
  out = out.concat(generated);
@@ -2389,8 +2632,8 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2389
2632
 
2390
2633
  // arr[i]
2391
2634
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2392
- const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2393
- 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);
2394
2637
 
2395
2638
  return [
2396
2639
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
@@ -2402,11 +2645,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2402
2645
  ...generate(scope, decl.left.property),
2403
2646
  Opcodes.i32_to_u,
2404
2647
 
2405
- // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2648
+ // turn into byte offset by * valtypeSize + 1
2406
2649
  ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2407
2650
  [ Opcodes.i32_mul ],
2408
2651
  [ Opcodes.i32_add ],
2409
- ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2652
+ [ Opcodes.local_tee, pointerTmp ],
2410
2653
 
2411
2654
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2412
2655
  [ Opcodes.local_get, pointerTmp ],
@@ -2416,10 +2659,164 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2416
2659
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2417
2660
  ], getNodeType(scope, decl.right), false, name, true)),
2418
2661
  [ Opcodes.local_tee, newValueTmp ],
2662
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
2419
2663
 
2420
- [ Opcodes.store, 0, ValtypeSize.i32 ]
2664
+ [ Opcodes.local_get, pointerTmp ],
2665
+ ...getNodeType(scope, decl),
2666
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
2421
2667
  ],
2422
2668
 
2669
+ ...wrapBC({
2670
+ [TYPES.uint8array]: [
2671
+ [ Opcodes.i32_add ],
2672
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2673
+
2674
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2675
+ [ Opcodes.local_get, pointerTmp ],
2676
+ [ Opcodes.i32_load8_u, 0, 4 ],
2677
+ Opcodes.i32_from_u
2678
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2679
+ [ Opcodes.local_tee, newValueTmp ],
2680
+
2681
+ Opcodes.i32_to_u,
2682
+ [ Opcodes.i32_store8, 0, 4 ]
2683
+ ],
2684
+ [TYPES.uint8clampedarray]: [
2685
+ [ Opcodes.i32_add ],
2686
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2687
+
2688
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2689
+ [ Opcodes.local_get, pointerTmp ],
2690
+ [ Opcodes.i32_load8_u, 0, 4 ],
2691
+ Opcodes.i32_from_u
2692
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2693
+ [ Opcodes.local_tee, newValueTmp ],
2694
+
2695
+ ...number(0),
2696
+ [ Opcodes.f64_max ],
2697
+ ...number(255),
2698
+ [ Opcodes.f64_min ],
2699
+ Opcodes.i32_to_u,
2700
+ [ Opcodes.i32_store8, 0, 4 ]
2701
+ ],
2702
+ [TYPES.int8array]: [
2703
+ [ Opcodes.i32_add ],
2704
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2705
+
2706
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2707
+ [ Opcodes.local_get, pointerTmp ],
2708
+ [ Opcodes.i32_load8_s, 0, 4 ],
2709
+ Opcodes.i32_from
2710
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2711
+ [ Opcodes.local_tee, newValueTmp ],
2712
+
2713
+ Opcodes.i32_to,
2714
+ [ Opcodes.i32_store8, 0, 4 ]
2715
+ ],
2716
+ [TYPES.uint16array]: [
2717
+ ...number(2, Valtype.i32),
2718
+ [ Opcodes.i32_mul ],
2719
+ [ Opcodes.i32_add ],
2720
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2721
+
2722
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2723
+ [ Opcodes.local_get, pointerTmp ],
2724
+ [ Opcodes.i32_load16_u, 0, 4 ],
2725
+ Opcodes.i32_from_u
2726
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2727
+ [ Opcodes.local_tee, newValueTmp ],
2728
+
2729
+ Opcodes.i32_to_u,
2730
+ [ Opcodes.i32_store16, 0, 4 ]
2731
+ ],
2732
+ [TYPES.int16array]: [
2733
+ ...number(2, Valtype.i32),
2734
+ [ Opcodes.i32_mul ],
2735
+ [ Opcodes.i32_add ],
2736
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2737
+
2738
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2739
+ [ Opcodes.local_get, pointerTmp ],
2740
+ [ Opcodes.i32_load16_s, 0, 4 ],
2741
+ Opcodes.i32_from
2742
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2743
+ [ Opcodes.local_tee, newValueTmp ],
2744
+
2745
+ Opcodes.i32_to,
2746
+ [ Opcodes.i32_store16, 0, 4 ]
2747
+ ],
2748
+ [TYPES.uint32array]: [
2749
+ ...number(4, Valtype.i32),
2750
+ [ Opcodes.i32_mul ],
2751
+ [ Opcodes.i32_add ],
2752
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2753
+
2754
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2755
+ [ Opcodes.local_get, pointerTmp ],
2756
+ [ Opcodes.i32_load, 0, 4 ],
2757
+ Opcodes.i32_from_u
2758
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2759
+ [ Opcodes.local_tee, newValueTmp ],
2760
+
2761
+ Opcodes.i32_to_u,
2762
+ [ Opcodes.i32_store, 0, 4 ]
2763
+ ],
2764
+ [TYPES.int32array]: [
2765
+ ...number(4, Valtype.i32),
2766
+ [ Opcodes.i32_mul ],
2767
+ [ Opcodes.i32_add ],
2768
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2769
+
2770
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2771
+ [ Opcodes.local_get, pointerTmp ],
2772
+ [ Opcodes.i32_load, 0, 4 ],
2773
+ Opcodes.i32_from
2774
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2775
+ [ Opcodes.local_tee, newValueTmp ],
2776
+
2777
+ Opcodes.i32_to,
2778
+ [ Opcodes.i32_store, 0, 4 ]
2779
+ ],
2780
+ [TYPES.float32array]: [
2781
+ ...number(4, Valtype.i32),
2782
+ [ Opcodes.i32_mul ],
2783
+ [ Opcodes.i32_add ],
2784
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2785
+
2786
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2787
+ [ Opcodes.local_get, pointerTmp ],
2788
+ [ Opcodes.f32_load, 0, 4 ],
2789
+ [ Opcodes.f64_promote_f32 ]
2790
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2791
+ [ Opcodes.local_tee, newValueTmp ],
2792
+
2793
+ [ Opcodes.f32_demote_f64 ],
2794
+ [ Opcodes.f32_store, 0, 4 ]
2795
+ ],
2796
+ [TYPES.float64array]: [
2797
+ ...number(8, Valtype.i32),
2798
+ [ Opcodes.i32_mul ],
2799
+ [ Opcodes.i32_add ],
2800
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2801
+
2802
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2803
+ [ Opcodes.local_get, pointerTmp ],
2804
+ [ Opcodes.f64_load, 0, 4 ]
2805
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2806
+ [ Opcodes.local_tee, newValueTmp ],
2807
+
2808
+ [ Opcodes.f64_store, 0, 4 ]
2809
+ ],
2810
+ }, {
2811
+ prelude: [
2812
+ ...generate(scope, decl.left.object),
2813
+ Opcodes.i32_to_u,
2814
+ ...generate(scope, decl.left.property),
2815
+ Opcodes.i32_to_u,
2816
+ ],
2817
+ postlude: setLastType(scope, TYPES.number)
2818
+ }),
2819
+
2423
2820
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2424
2821
  }, Blocktype.void),
2425
2822
 
@@ -2803,7 +3200,9 @@ const generateForOf = (scope, decl) => {
2803
3200
  // // todo: we should only do this for strings but we don't know at compile-time :(
2804
3201
  // hack: this is naughty and will break things!
2805
3202
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2806
- 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)) {
2807
3206
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2808
3207
  0, [ newOut, newPointer ] = makeArray(scope, {
2809
3208
  rawElements: new Array(0)
@@ -2851,6 +3250,7 @@ const generateForOf = (scope, decl) => {
2851
3250
  [ Opcodes.end ],
2852
3251
  [ Opcodes.end ]
2853
3252
  ],
3253
+
2854
3254
  [TYPES.string]: [
2855
3255
  ...setType(scope, leftName, TYPES.string),
2856
3256
 
@@ -2959,6 +3359,7 @@ const generateForOf = (scope, decl) => {
2959
3359
  [ Opcodes.end ],
2960
3360
  [ Opcodes.end ]
2961
3361
  ],
3362
+
2962
3363
  [TYPES.set]: [
2963
3364
  [ Opcodes.loop, Blocktype.void ],
2964
3365
 
@@ -2997,6 +3398,106 @@ const generateForOf = (scope, decl) => {
2997
3398
  [ Opcodes.end ],
2998
3399
  [ Opcodes.end ]
2999
3400
  ],
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
+
3000
3501
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
3001
3502
  }, Blocktype.void));
3002
3503
 
@@ -3085,13 +3586,14 @@ const generateThrow = (scope, decl) => {
3085
3586
  idx: tags.length
3086
3587
  });
3087
3588
 
3088
- let exceptId = exceptions.push({ constructor, message }) - 1;
3589
+ let exceptId = exceptions.findIndex(x => x.constructor === constructor && x.message === message);
3590
+ if (exceptId === -1) exceptId = exceptions.push({ constructor, message }) - 1;
3089
3591
 
3090
3592
  scope.exceptions ??= [];
3091
3593
  scope.exceptions.push(exceptId);
3092
3594
 
3093
3595
  return [
3094
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3596
+ ...number(exceptId, Valtype.i32),
3095
3597
  [ Opcodes.throw, tags[0].idx ]
3096
3598
  ];
3097
3599
  }
@@ -3158,7 +3660,7 @@ const generateThrow = (scope, decl) => {
3158
3660
  scope.exceptions.push(exceptId);
3159
3661
 
3160
3662
  return [
3161
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3663
+ ...number(exceptId, Valtype.i32),
3162
3664
  ...generate(scope, message),
3163
3665
  ...getNodeType(scope, message),
3164
3666
  [ Opcodes.throw, tags[0].idx ]
@@ -3199,6 +3701,22 @@ const generateEmpty = (scope, decl) => {
3199
3701
  return [];
3200
3702
  };
3201
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
+
3202
3720
  let pages = new Map();
3203
3721
  const allocPage = (scope, reason, type) => {
3204
3722
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3516,6 +4034,19 @@ const withType = (scope, wasm, type) => [
3516
4034
  ...setLastType(scope, type)
3517
4035
  ];
3518
4036
 
4037
+ const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
4038
+ const out = {};
4039
+ for (const x in bc) {
4040
+ out[x] = [
4041
+ ...prelude,
4042
+ ...bc[x],
4043
+ ...postlude
4044
+ ];
4045
+ }
4046
+
4047
+ return out;
4048
+ };
4049
+
3519
4050
  const generateMember = (scope, decl, _global, _name) => {
3520
4051
  const name = decl.object.name;
3521
4052
 
@@ -3538,20 +4069,10 @@ const generateMember = (scope, decl, _global, _name) => {
3538
4069
  const func = funcs.find(x => x.name === name);
3539
4070
  if (func) {
3540
4071
  const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3541
- return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3542
- }
3543
-
3544
- if (builtinFuncs[name + '$constructor']) {
3545
- const regularFunc = builtinFuncs[name];
3546
- const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3547
-
3548
- const constructorFunc = builtinFuncs[name + '$constructor'];
3549
- const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3550
-
3551
- return withType(scope, number(Math.max(regularParams, constructorParams)), 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);
3552
4073
  }
3553
4074
 
3554
- 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);
3555
4076
  if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3556
4077
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3557
4078
 
@@ -3569,7 +4090,7 @@ const generateMember = (scope, decl, _global, _name) => {
3569
4090
  const type = getNodeType(scope, decl.object);
3570
4091
  const known = knownType(scope, type);
3571
4092
  if (known != null) {
3572
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
4093
+ if (typeHasFlag(known, TYPE_FLAGS.length)) return [
3573
4094
  ...generate(scope, decl.object),
3574
4095
  Opcodes.i32_to_u,
3575
4096
 
@@ -3581,7 +4102,9 @@ const generateMember = (scope, decl, _global, _name) => {
3581
4102
  }
3582
4103
 
3583
4104
  return [
3584
- ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
4105
+ ...getNodeType(scope, decl.object),
4106
+ ...number(TYPE_FLAGS.length, Valtype.i32),
4107
+ [ Opcodes.i32_and ],
3585
4108
  [ Opcodes.if, valtypeBinary ],
3586
4109
  ...generate(scope, decl.object),
3587
4110
  Opcodes.i32_to_u,
@@ -3598,7 +4121,7 @@ const generateMember = (scope, decl, _global, _name) => {
3598
4121
  }
3599
4122
 
3600
4123
  // todo: generate this array procedurally during builtinFuncs creation
3601
- if (['size', 'description'].includes(decl.property.name)) {
4124
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3602
4125
  const bc = {};
3603
4126
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3604
4127
 
@@ -3630,7 +4153,9 @@ const generateMember = (scope, decl, _global, _name) => {
3630
4153
  // // todo: we should only do this for strings but we don't know at compile-time :(
3631
4154
  // hack: this is naughty and will break things!
3632
4155
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3633
- if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
4156
+
4157
+ const known = knownType(scope, getNodeType(scope, decl.object));
4158
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
3634
4159
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3635
4160
  0, [ newOut, newPointer ] = makeArray(scope, {
3636
4161
  rawElements: new Array(0)
@@ -3704,6 +4229,82 @@ const generateMember = (scope, decl, _global, _name) => {
3704
4229
  ...setLastType(scope, TYPES.bytestring)
3705
4230
  ],
3706
4231
 
4232
+ ...wrapBC({
4233
+ [TYPES.uint8array]: [
4234
+ [ Opcodes.i32_add ],
4235
+
4236
+ [ Opcodes.i32_load8_u, 0, 4 ],
4237
+ Opcodes.i32_from_u
4238
+ ],
4239
+ [TYPES.uint8clampedarray]: [
4240
+ [ Opcodes.i32_add ],
4241
+
4242
+ [ Opcodes.i32_load8_u, 0, 4 ],
4243
+ Opcodes.i32_from_u
4244
+ ],
4245
+ [TYPES.int8array]: [
4246
+ [ Opcodes.i32_add ],
4247
+
4248
+ [ Opcodes.i32_load8_s, 0, 4 ],
4249
+ Opcodes.i32_from
4250
+ ],
4251
+ [TYPES.uint16array]: [
4252
+ ...number(2, Valtype.i32),
4253
+ [ Opcodes.i32_mul ],
4254
+ [ Opcodes.i32_add ],
4255
+
4256
+ [ Opcodes.i32_load16_u, 0, 4 ],
4257
+ Opcodes.i32_from_u
4258
+ ],
4259
+ [TYPES.int16array]: [
4260
+ ...number(2, Valtype.i32),
4261
+ [ Opcodes.i32_mul ],
4262
+ [ Opcodes.i32_add ],
4263
+
4264
+ [ Opcodes.i32_load16_s, 0, 4 ],
4265
+ Opcodes.i32_from
4266
+ ],
4267
+ [TYPES.uint32array]: [
4268
+ ...number(4, Valtype.i32),
4269
+ [ Opcodes.i32_mul ],
4270
+ [ Opcodes.i32_add ],
4271
+
4272
+ [ Opcodes.i32_load, 0, 4 ],
4273
+ Opcodes.i32_from_u
4274
+ ],
4275
+ [TYPES.int32array]: [
4276
+ ...number(4, Valtype.i32),
4277
+ [ Opcodes.i32_mul ],
4278
+ [ Opcodes.i32_add ],
4279
+
4280
+ [ Opcodes.i32_load, 0, 4 ],
4281
+ Opcodes.i32_from
4282
+ ],
4283
+ [TYPES.float32array]: [
4284
+ ...number(4, Valtype.i32),
4285
+ [ Opcodes.i32_mul ],
4286
+ [ Opcodes.i32_add ],
4287
+
4288
+ [ Opcodes.f32_load, 0, 4 ],
4289
+ [ Opcodes.f64_promote_f32 ]
4290
+ ],
4291
+ [TYPES.float64array]: [
4292
+ ...number(8, Valtype.i32),
4293
+ [ Opcodes.i32_mul ],
4294
+ [ Opcodes.i32_add ],
4295
+
4296
+ [ Opcodes.f64_load, 0, 4 ]
4297
+ ],
4298
+ }, {
4299
+ prelude: [
4300
+ ...object,
4301
+ Opcodes.i32_to_u,
4302
+ ...property,
4303
+ Opcodes.i32_to_u
4304
+ ],
4305
+ postlude: setLastType(scope, TYPES.number)
4306
+ }),
4307
+
3707
4308
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3708
4309
  });
3709
4310
  };
@@ -3726,7 +4327,7 @@ const objectHack = node => {
3726
4327
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3727
4328
 
3728
4329
  // if .name or .length, give up (hack within a hack!)
3729
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4330
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3730
4331
  node.object = objectHack(node.object);
3731
4332
  return;
3732
4333
  }
@@ -3779,8 +4380,8 @@ const generateFunc = (scope, decl) => {
3779
4380
 
3780
4381
  if (typedInput && decl.returnType) {
3781
4382
  const { type } = extractTypeAnnotation(decl.returnType);
3782
- // if (type != null && !Prefs.indirectCalls) {
3783
- if (type != null) {
4383
+ if (type != null && !Prefs.indirectCalls) {
4384
+ // if (type != null) {
3784
4385
  func.returnType = type;
3785
4386
  func.returns = [ valtypeBinary ];
3786
4387
  }
@@ -3801,6 +4402,12 @@ const generateFunc = (scope, decl) => {
3801
4402
  defaultValues[name] = x.right;
3802
4403
  break;
3803
4404
  }
4405
+
4406
+ case 'RestElement': {
4407
+ name = x.argument.name;
4408
+ func.hasRestArgument = true;
4409
+ break;
4410
+ }
3804
4411
  }
3805
4412
 
3806
4413
  // if (name == null) return todo('non-identifier args are not supported');