porffor 0.17.0-a1f691e30 → 0.17.0-a23029b3a

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,
1673
+
1674
+ ...setLastType(scope, Rhemyn.types[protoName])
1675
+ ];
1676
+ }
1639
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,7 +2072,16 @@ 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);
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);
1954
2085
 
1955
2086
  let args = decl.arguments;
1956
2087
  if (func && args.length < paramCount) {
@@ -1965,7 +2096,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1965
2096
 
1966
2097
  if (func && func.throws) scope.throws = true;
1967
2098
 
1968
- let out = [];
1969
2099
  for (let i = 0; i < args.length; i++) {
1970
2100
  const arg = args[i];
1971
2101
  out = out.concat(generate(scope, arg));
@@ -1978,13 +2108,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1978
2108
  }
1979
2109
 
1980
2110
  if (valtypeBinary !== Valtype.i32 &&
1981
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2111
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.i32)
1982
2112
  ) {
1983
2113
  out.push(Opcodes.i32_to);
1984
2114
  }
1985
2115
 
1986
2116
  if (valtypeBinary === Valtype.i32 &&
1987
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
2117
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.f64)
1988
2118
  ) {
1989
2119
  out.push([ Opcodes.f64_convert_i32_s ]);
1990
2120
  }
@@ -2017,32 +2147,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2017
2147
  return out;
2018
2148
  };
2019
2149
 
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
- };
2150
+ const generateNew = (scope, decl, _global, _name) => generateCall(scope, {
2151
+ ...decl,
2152
+ _new: true
2153
+ }, _global, _name);
2046
2154
 
2047
2155
  // bad hack for undefined and null working without additional logic
2048
2156
  const DEFAULT_VALUE = {
@@ -2050,6 +2158,16 @@ const DEFAULT_VALUE = {
2050
2158
  name: 'undefined'
2051
2159
  };
2052
2160
 
2161
+ const codeToSanitizedStr = code => {
2162
+ let out = '';
2163
+ while (code > 0) {
2164
+ out += String.fromCharCode(97 + code % 26);
2165
+ code -= 26;
2166
+ }
2167
+ return out;
2168
+ };
2169
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2170
+
2053
2171
  const unhackName = name => {
2054
2172
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2055
2173
  return name;
@@ -2057,7 +2175,7 @@ const unhackName = name => {
2057
2175
 
2058
2176
  const knownType = (scope, type) => {
2059
2177
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2060
- return type[0][1];
2178
+ return read_signedLEB128(type[0].slice(1));
2061
2179
  }
2062
2180
 
2063
2181
  if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
@@ -2285,9 +2403,120 @@ const generateVar = (scope, decl) => {
2285
2403
  const global = topLevel || decl._bare;
2286
2404
 
2287
2405
  for (const x of decl.declarations) {
2288
- const name = mapName(x.id.name);
2406
+ if (x.id.type === 'ArrayPattern') {
2407
+ const decls = [];
2408
+ const tmpName = '#destructure' + randId();
2409
+
2410
+ let i = 0;
2411
+ const elements = [...x.id.elements];
2412
+ for (const e of elements) {
2413
+ switch (e?.type) {
2414
+ case 'RestElement': { // let [ ...foo ] = []
2415
+ if (e.argument.type === 'ArrayPattern') {
2416
+ // let [ ...[a, b, c] ] = []
2417
+ elements.push(...e.argument.elements);
2418
+ } else {
2419
+ decls.push({
2420
+ type: 'VariableDeclarator',
2421
+ id: { type: 'Identifier', name: e.argument.name },
2422
+ init: {
2423
+ type: 'CallExpression',
2424
+ callee: {
2425
+ type: 'Identifier',
2426
+ name: '__Array_prototype_slice'
2427
+ },
2428
+ arguments: [
2429
+ { type: 'Identifier', name: tmpName },
2430
+ { type: 'Literal', value: i },
2431
+ {
2432
+ type: 'MemberExpression',
2433
+ object: { type: 'Identifier', name: tmpName, },
2434
+ property: { type: 'Identifier', name: 'length', }
2435
+ }
2436
+ ]
2437
+ }
2438
+ });
2439
+ }
2440
+
2441
+ continue; // skip i++
2442
+ }
2289
2443
 
2290
- if (!name) return todo(scope, 'destructuring is not supported yet');
2444
+ case 'Identifier': { // let [ foo ] = []
2445
+ decls.push({
2446
+ type: 'VariableDeclarator',
2447
+ id: e,
2448
+ init: {
2449
+ type: 'MemberExpression',
2450
+ object: { type: 'Identifier', name: tmpName },
2451
+ property: { type: 'Literal', value: i }
2452
+ }
2453
+ });
2454
+
2455
+ break;
2456
+ }
2457
+
2458
+ case 'AssignmentPattern': { // let [ foo = defaultValue ] = []
2459
+ decls.push({
2460
+ type: 'VariableDeclarator',
2461
+ id: e.left,
2462
+ init: {
2463
+ type: 'LogicalExpression',
2464
+ operator: '??',
2465
+ left: {
2466
+ type: 'MemberExpression',
2467
+ object: { type: 'Identifier', name: tmpName },
2468
+ property: { type: 'Literal', value: i }
2469
+ },
2470
+ right: e.right
2471
+ }
2472
+ });
2473
+
2474
+ break;
2475
+ }
2476
+
2477
+ case 'ArrayPattern': { // let [ [ foo, bar ] ] = []
2478
+ decls.push({
2479
+ type: 'VariableDeclarator',
2480
+ id: e,
2481
+ init: {
2482
+ type: 'MemberExpression',
2483
+ object: { type: 'Identifier', name: tmpName },
2484
+ property: { type: 'Literal', value: i }
2485
+ }
2486
+ });
2487
+
2488
+ break;
2489
+ }
2490
+
2491
+ case 'ObjectPattern':
2492
+ return todo(scope, 'object destructuring is not supported yet')
2493
+ }
2494
+
2495
+ i++;
2496
+ }
2497
+
2498
+ out = out.concat([
2499
+ ...generateVar(scope, {
2500
+ type: 'VariableDeclaration',
2501
+ declarations: [{
2502
+ type: 'VariableDeclarator',
2503
+ id: { type: 'Identifier', name: tmpName },
2504
+ init: x.init
2505
+ }],
2506
+ kind: decl.kind
2507
+ }),
2508
+ ...generateVar(scope, {
2509
+ type: 'VariableDeclaration',
2510
+ declarations: decls,
2511
+ kind: decl.kind
2512
+ })
2513
+ ]);
2514
+
2515
+ continue;
2516
+ }
2517
+
2518
+ const name = mapName(x.id.name);
2519
+ if (!name) return todo(scope, 'object destructuring is not supported yet')
2291
2520
 
2292
2521
  if (x.init && isFuncType(x.init.type)) {
2293
2522
  // hack for let a = function () { ... }
@@ -2322,7 +2551,9 @@ const generateVar = (scope, decl) => {
2322
2551
  // hack to set local as pointer before
2323
2552
  out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2324
2553
  if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2325
- generated.pop();
2554
+ // generated.pop();
2555
+ generated.push([ Opcodes.drop ]);
2556
+
2326
2557
  out = out.concat(generated);
2327
2558
  } else {
2328
2559
  out = out.concat(generated);
@@ -2389,8 +2620,8 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2389
2620
 
2390
2621
  // arr[i]
2391
2622
  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);
2623
+ const newValueTmp = localTmp(scope, '#member_setter_val_tmp');
2624
+ const pointerTmp = localTmp(scope, '#member_setter_ptr_tmp', Valtype.i32);
2394
2625
 
2395
2626
  return [
2396
2627
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
@@ -2402,11 +2633,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2402
2633
  ...generate(scope, decl.left.property),
2403
2634
  Opcodes.i32_to_u,
2404
2635
 
2405
- // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2636
+ // turn into byte offset by * valtypeSize + 1
2406
2637
  ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2407
2638
  [ Opcodes.i32_mul ],
2408
2639
  [ Opcodes.i32_add ],
2409
- ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2640
+ [ Opcodes.local_tee, pointerTmp ],
2410
2641
 
2411
2642
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2412
2643
  [ Opcodes.local_get, pointerTmp ],
@@ -2416,10 +2647,164 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2416
2647
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2417
2648
  ], getNodeType(scope, decl.right), false, name, true)),
2418
2649
  [ Opcodes.local_tee, newValueTmp ],
2650
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
2419
2651
 
2420
- [ Opcodes.store, 0, ValtypeSize.i32 ]
2652
+ [ Opcodes.local_get, pointerTmp ],
2653
+ ...getNodeType(scope, decl),
2654
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
2421
2655
  ],
2422
2656
 
2657
+ ...wrapBC({
2658
+ [TYPES.uint8array]: [
2659
+ [ Opcodes.i32_add ],
2660
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2661
+
2662
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2663
+ [ Opcodes.local_get, pointerTmp ],
2664
+ [ Opcodes.i32_load8_u, 0, 4 ],
2665
+ Opcodes.i32_from_u
2666
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2667
+ [ Opcodes.local_tee, newValueTmp ],
2668
+
2669
+ Opcodes.i32_to_u,
2670
+ [ Opcodes.i32_store8, 0, 4 ]
2671
+ ],
2672
+ [TYPES.uint8clampedarray]: [
2673
+ [ Opcodes.i32_add ],
2674
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2675
+
2676
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2677
+ [ Opcodes.local_get, pointerTmp ],
2678
+ [ Opcodes.i32_load8_u, 0, 4 ],
2679
+ Opcodes.i32_from_u
2680
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2681
+ [ Opcodes.local_tee, newValueTmp ],
2682
+
2683
+ ...number(0),
2684
+ [ Opcodes.f64_max ],
2685
+ ...number(255),
2686
+ [ Opcodes.f64_min ],
2687
+ Opcodes.i32_to_u,
2688
+ [ Opcodes.i32_store8, 0, 4 ]
2689
+ ],
2690
+ [TYPES.int8array]: [
2691
+ [ Opcodes.i32_add ],
2692
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2693
+
2694
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2695
+ [ Opcodes.local_get, pointerTmp ],
2696
+ [ Opcodes.i32_load8_s, 0, 4 ],
2697
+ Opcodes.i32_from
2698
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2699
+ [ Opcodes.local_tee, newValueTmp ],
2700
+
2701
+ Opcodes.i32_to,
2702
+ [ Opcodes.i32_store8, 0, 4 ]
2703
+ ],
2704
+ [TYPES.uint16array]: [
2705
+ ...number(2, Valtype.i32),
2706
+ [ Opcodes.i32_mul ],
2707
+ [ Opcodes.i32_add ],
2708
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2709
+
2710
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2711
+ [ Opcodes.local_get, pointerTmp ],
2712
+ [ Opcodes.i32_load16_u, 0, 4 ],
2713
+ Opcodes.i32_from_u
2714
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2715
+ [ Opcodes.local_tee, newValueTmp ],
2716
+
2717
+ Opcodes.i32_to_u,
2718
+ [ Opcodes.i32_store16, 0, 4 ]
2719
+ ],
2720
+ [TYPES.int16array]: [
2721
+ ...number(2, Valtype.i32),
2722
+ [ Opcodes.i32_mul ],
2723
+ [ Opcodes.i32_add ],
2724
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2725
+
2726
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2727
+ [ Opcodes.local_get, pointerTmp ],
2728
+ [ Opcodes.i32_load16_s, 0, 4 ],
2729
+ Opcodes.i32_from
2730
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2731
+ [ Opcodes.local_tee, newValueTmp ],
2732
+
2733
+ Opcodes.i32_to,
2734
+ [ Opcodes.i32_store16, 0, 4 ]
2735
+ ],
2736
+ [TYPES.uint32array]: [
2737
+ ...number(4, Valtype.i32),
2738
+ [ Opcodes.i32_mul ],
2739
+ [ Opcodes.i32_add ],
2740
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2741
+
2742
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2743
+ [ Opcodes.local_get, pointerTmp ],
2744
+ [ Opcodes.i32_load, 0, 4 ],
2745
+ Opcodes.i32_from_u
2746
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2747
+ [ Opcodes.local_tee, newValueTmp ],
2748
+
2749
+ Opcodes.i32_to_u,
2750
+ [ Opcodes.i32_store, 0, 4 ]
2751
+ ],
2752
+ [TYPES.int32array]: [
2753
+ ...number(4, Valtype.i32),
2754
+ [ Opcodes.i32_mul ],
2755
+ [ Opcodes.i32_add ],
2756
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2757
+
2758
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2759
+ [ Opcodes.local_get, pointerTmp ],
2760
+ [ Opcodes.i32_load, 0, 4 ],
2761
+ Opcodes.i32_from
2762
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2763
+ [ Opcodes.local_tee, newValueTmp ],
2764
+
2765
+ Opcodes.i32_to,
2766
+ [ Opcodes.i32_store, 0, 4 ]
2767
+ ],
2768
+ [TYPES.float32array]: [
2769
+ ...number(4, Valtype.i32),
2770
+ [ Opcodes.i32_mul ],
2771
+ [ Opcodes.i32_add ],
2772
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2773
+
2774
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2775
+ [ Opcodes.local_get, pointerTmp ],
2776
+ [ Opcodes.f32_load, 0, 4 ],
2777
+ [ Opcodes.f64_promote_f32 ]
2778
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2779
+ [ Opcodes.local_tee, newValueTmp ],
2780
+
2781
+ [ Opcodes.f32_demote_f64 ],
2782
+ [ Opcodes.f32_store, 0, 4 ]
2783
+ ],
2784
+ [TYPES.float64array]: [
2785
+ ...number(8, Valtype.i32),
2786
+ [ Opcodes.i32_mul ],
2787
+ [ Opcodes.i32_add ],
2788
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2789
+
2790
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2791
+ [ Opcodes.local_get, pointerTmp ],
2792
+ [ Opcodes.f64_load, 0, 4 ]
2793
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2794
+ [ Opcodes.local_tee, newValueTmp ],
2795
+
2796
+ [ Opcodes.f64_store, 0, 4 ]
2797
+ ],
2798
+ }, {
2799
+ prelude: [
2800
+ ...generate(scope, decl.left.object),
2801
+ Opcodes.i32_to_u,
2802
+ ...generate(scope, decl.left.property),
2803
+ Opcodes.i32_to_u,
2804
+ ],
2805
+ postlude: setLastType(scope, TYPES.number)
2806
+ }),
2807
+
2423
2808
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2424
2809
  }, Blocktype.void),
2425
2810
 
@@ -2803,7 +3188,9 @@ const generateForOf = (scope, decl) => {
2803
3188
  // // todo: we should only do this for strings but we don't know at compile-time :(
2804
3189
  // hack: this is naughty and will break things!
2805
3190
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2806
- if (pages.hasAnyString) {
3191
+
3192
+ const known = knownType(scope, getNodeType(scope, decl.right));
3193
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
2807
3194
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2808
3195
  0, [ newOut, newPointer ] = makeArray(scope, {
2809
3196
  rawElements: new Array(0)
@@ -2851,6 +3238,7 @@ const generateForOf = (scope, decl) => {
2851
3238
  [ Opcodes.end ],
2852
3239
  [ Opcodes.end ]
2853
3240
  ],
3241
+
2854
3242
  [TYPES.string]: [
2855
3243
  ...setType(scope, leftName, TYPES.string),
2856
3244
 
@@ -2959,6 +3347,7 @@ const generateForOf = (scope, decl) => {
2959
3347
  [ Opcodes.end ],
2960
3348
  [ Opcodes.end ]
2961
3349
  ],
3350
+
2962
3351
  [TYPES.set]: [
2963
3352
  [ Opcodes.loop, Blocktype.void ],
2964
3353
 
@@ -2997,6 +3386,106 @@ const generateForOf = (scope, decl) => {
2997
3386
  [ Opcodes.end ],
2998
3387
  [ Opcodes.end ]
2999
3388
  ],
3389
+
3390
+ ...wrapBC({
3391
+ [TYPES.uint8array]: [
3392
+ [ Opcodes.i32_add ],
3393
+
3394
+ [ Opcodes.i32_load8_u, 0, 4 ],
3395
+ Opcodes.i32_from_u
3396
+ ],
3397
+ [TYPES.uint8clampedarray]: [
3398
+ [ Opcodes.i32_add ],
3399
+
3400
+ [ Opcodes.i32_load8_u, 0, 4 ],
3401
+ Opcodes.i32_from_u
3402
+ ],
3403
+ [TYPES.int8array]: [
3404
+ [ Opcodes.i32_add ],
3405
+
3406
+ [ Opcodes.i32_load8_s, 0, 4 ],
3407
+ Opcodes.i32_from
3408
+ ],
3409
+ [TYPES.uint16array]: [
3410
+ ...number(2, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ [ Opcodes.i32_add ],
3413
+
3414
+ [ Opcodes.i32_load16_u, 0, 4 ],
3415
+ Opcodes.i32_from_u
3416
+ ],
3417
+ [TYPES.int16array]: [
3418
+ ...number(2, Valtype.i32),
3419
+ [ Opcodes.i32_mul ],
3420
+ [ Opcodes.i32_add ],
3421
+
3422
+ [ Opcodes.i32_load16_s, 0, 4 ],
3423
+ Opcodes.i32_from
3424
+ ],
3425
+ [TYPES.uint32array]: [
3426
+ ...number(4, Valtype.i32),
3427
+ [ Opcodes.i32_mul ],
3428
+ [ Opcodes.i32_add ],
3429
+
3430
+ [ Opcodes.i32_load, 0, 4 ],
3431
+ Opcodes.i32_from_u
3432
+ ],
3433
+ [TYPES.int32array]: [
3434
+ ...number(4, Valtype.i32),
3435
+ [ Opcodes.i32_mul ],
3436
+ [ Opcodes.i32_add ],
3437
+
3438
+ [ Opcodes.i32_load, 0, 4 ],
3439
+ Opcodes.i32_from
3440
+ ],
3441
+ [TYPES.float32array]: [
3442
+ ...number(4, Valtype.i32),
3443
+ [ Opcodes.i32_mul ],
3444
+ [ Opcodes.i32_add ],
3445
+
3446
+ [ Opcodes.f32_load, 0, 4 ],
3447
+ [ Opcodes.f64_promote_f32 ]
3448
+ ],
3449
+ [TYPES.float64array]: [
3450
+ ...number(8, Valtype.i32),
3451
+ [ Opcodes.i32_mul ],
3452
+ [ Opcodes.i32_add ],
3453
+
3454
+ [ Opcodes.f64_load, 0, 4 ]
3455
+ ],
3456
+ }, {
3457
+ prelude: [
3458
+ ...setType(scope, leftName, TYPES.number),
3459
+
3460
+ [ Opcodes.loop, Blocktype.void ],
3461
+
3462
+ [ Opcodes.local_get, pointer ],
3463
+ [ Opcodes.local_get, counter ]
3464
+ ],
3465
+ postlude: [
3466
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3467
+
3468
+ [ Opcodes.block, Blocktype.void ],
3469
+ [ Opcodes.block, Blocktype.void ],
3470
+ ...generate(scope, decl.body),
3471
+ [ Opcodes.end ],
3472
+
3473
+ // increment counter by 1
3474
+ [ Opcodes.local_get, counter ],
3475
+ ...number(1, Valtype.i32),
3476
+ [ Opcodes.i32_add ],
3477
+ [ Opcodes.local_tee, counter ],
3478
+
3479
+ // loop if counter != length
3480
+ [ Opcodes.local_get, length ],
3481
+ [ Opcodes.i32_ne ],
3482
+ [ Opcodes.br_if, 1 ],
3483
+
3484
+ [ Opcodes.end ],
3485
+ [ Opcodes.end ]
3486
+ ]
3487
+ }),
3488
+
3000
3489
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
3001
3490
  }, Blocktype.void));
3002
3491
 
@@ -3085,13 +3574,14 @@ const generateThrow = (scope, decl) => {
3085
3574
  idx: tags.length
3086
3575
  });
3087
3576
 
3088
- let exceptId = exceptions.push({ constructor, message }) - 1;
3577
+ let exceptId = exceptions.findIndex(x => x.constructor === constructor && x.message === message);
3578
+ if (exceptId === -1) exceptId = exceptions.push({ constructor, message }) - 1;
3089
3579
 
3090
3580
  scope.exceptions ??= [];
3091
3581
  scope.exceptions.push(exceptId);
3092
3582
 
3093
3583
  return [
3094
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3584
+ ...number(exceptId, Valtype.i32),
3095
3585
  [ Opcodes.throw, tags[0].idx ]
3096
3586
  ];
3097
3587
  }
@@ -3158,7 +3648,7 @@ const generateThrow = (scope, decl) => {
3158
3648
  scope.exceptions.push(exceptId);
3159
3649
 
3160
3650
  return [
3161
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3651
+ ...number(exceptId, Valtype.i32),
3162
3652
  ...generate(scope, message),
3163
3653
  ...getNodeType(scope, message),
3164
3654
  [ Opcodes.throw, tags[0].idx ]
@@ -3199,6 +3689,22 @@ const generateEmpty = (scope, decl) => {
3199
3689
  return [];
3200
3690
  };
3201
3691
 
3692
+ const generateMeta = (scope, decl) => {
3693
+ if (decl.meta.name !== 'new') return todo(scope, `meta property object ${decl.meta.name} is not supported yet`, true);
3694
+
3695
+ switch (`${decl.meta.name}.${decl.property.name}`) {
3696
+ case 'new.target': {
3697
+ scope.constr = true;
3698
+
3699
+ return [
3700
+ [ Opcodes.local_get, -1 ],
3701
+ Opcodes.i32_from_u,
3702
+ ...setLastType(scope, TYPES.boolean)
3703
+ ];
3704
+ }
3705
+ }
3706
+ };
3707
+
3202
3708
  let pages = new Map();
3203
3709
  const allocPage = (scope, reason, type) => {
3204
3710
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3516,6 +4022,19 @@ const withType = (scope, wasm, type) => [
3516
4022
  ...setLastType(scope, type)
3517
4023
  ];
3518
4024
 
4025
+ const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
4026
+ const out = {};
4027
+ for (const x in bc) {
4028
+ out[x] = [
4029
+ ...prelude,
4030
+ ...bc[x],
4031
+ ...postlude
4032
+ ];
4033
+ }
4034
+
4035
+ return out;
4036
+ };
4037
+
3519
4038
  const generateMember = (scope, decl, _global, _name) => {
3520
4039
  const name = decl.object.name;
3521
4040
 
@@ -3538,20 +4057,10 @@ const generateMember = (scope, decl, _global, _name) => {
3538
4057
  const func = funcs.find(x => x.name === name);
3539
4058
  if (func) {
3540
4059
  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);
4060
+ return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
3552
4061
  }
3553
4062
 
3554
- if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
4063
+ 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
4064
  if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3556
4065
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3557
4066
 
@@ -3569,7 +4078,7 @@ const generateMember = (scope, decl, _global, _name) => {
3569
4078
  const type = getNodeType(scope, decl.object);
3570
4079
  const known = knownType(scope, type);
3571
4080
  if (known != null) {
3572
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
4081
+ if (typeHasFlag(known, TYPE_FLAGS.length)) return [
3573
4082
  ...generate(scope, decl.object),
3574
4083
  Opcodes.i32_to_u,
3575
4084
 
@@ -3581,7 +4090,9 @@ const generateMember = (scope, decl, _global, _name) => {
3581
4090
  }
3582
4091
 
3583
4092
  return [
3584
- ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
4093
+ ...getNodeType(scope, decl.object),
4094
+ ...number(TYPE_FLAGS.length, Valtype.i32),
4095
+ [ Opcodes.i32_and ],
3585
4096
  [ Opcodes.if, valtypeBinary ],
3586
4097
  ...generate(scope, decl.object),
3587
4098
  Opcodes.i32_to_u,
@@ -3598,7 +4109,7 @@ const generateMember = (scope, decl, _global, _name) => {
3598
4109
  }
3599
4110
 
3600
4111
  // todo: generate this array procedurally during builtinFuncs creation
3601
- if (['size', 'description'].includes(decl.property.name)) {
4112
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3602
4113
  const bc = {};
3603
4114
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3604
4115
 
@@ -3630,7 +4141,9 @@ const generateMember = (scope, decl, _global, _name) => {
3630
4141
  // // todo: we should only do this for strings but we don't know at compile-time :(
3631
4142
  // hack: this is naughty and will break things!
3632
4143
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3633
- if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
4144
+
4145
+ const known = knownType(scope, getNodeType(scope, decl.object));
4146
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
3634
4147
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3635
4148
  0, [ newOut, newPointer ] = makeArray(scope, {
3636
4149
  rawElements: new Array(0)
@@ -3704,6 +4217,82 @@ const generateMember = (scope, decl, _global, _name) => {
3704
4217
  ...setLastType(scope, TYPES.bytestring)
3705
4218
  ],
3706
4219
 
4220
+ ...wrapBC({
4221
+ [TYPES.uint8array]: [
4222
+ [ Opcodes.i32_add ],
4223
+
4224
+ [ Opcodes.i32_load8_u, 0, 4 ],
4225
+ Opcodes.i32_from_u
4226
+ ],
4227
+ [TYPES.uint8clampedarray]: [
4228
+ [ Opcodes.i32_add ],
4229
+
4230
+ [ Opcodes.i32_load8_u, 0, 4 ],
4231
+ Opcodes.i32_from_u
4232
+ ],
4233
+ [TYPES.int8array]: [
4234
+ [ Opcodes.i32_add ],
4235
+
4236
+ [ Opcodes.i32_load8_s, 0, 4 ],
4237
+ Opcodes.i32_from
4238
+ ],
4239
+ [TYPES.uint16array]: [
4240
+ ...number(2, Valtype.i32),
4241
+ [ Opcodes.i32_mul ],
4242
+ [ Opcodes.i32_add ],
4243
+
4244
+ [ Opcodes.i32_load16_u, 0, 4 ],
4245
+ Opcodes.i32_from_u
4246
+ ],
4247
+ [TYPES.int16array]: [
4248
+ ...number(2, Valtype.i32),
4249
+ [ Opcodes.i32_mul ],
4250
+ [ Opcodes.i32_add ],
4251
+
4252
+ [ Opcodes.i32_load16_s, 0, 4 ],
4253
+ Opcodes.i32_from
4254
+ ],
4255
+ [TYPES.uint32array]: [
4256
+ ...number(4, Valtype.i32),
4257
+ [ Opcodes.i32_mul ],
4258
+ [ Opcodes.i32_add ],
4259
+
4260
+ [ Opcodes.i32_load, 0, 4 ],
4261
+ Opcodes.i32_from_u
4262
+ ],
4263
+ [TYPES.int32array]: [
4264
+ ...number(4, Valtype.i32),
4265
+ [ Opcodes.i32_mul ],
4266
+ [ Opcodes.i32_add ],
4267
+
4268
+ [ Opcodes.i32_load, 0, 4 ],
4269
+ Opcodes.i32_from
4270
+ ],
4271
+ [TYPES.float32array]: [
4272
+ ...number(4, Valtype.i32),
4273
+ [ Opcodes.i32_mul ],
4274
+ [ Opcodes.i32_add ],
4275
+
4276
+ [ Opcodes.f32_load, 0, 4 ],
4277
+ [ Opcodes.f64_promote_f32 ]
4278
+ ],
4279
+ [TYPES.float64array]: [
4280
+ ...number(8, Valtype.i32),
4281
+ [ Opcodes.i32_mul ],
4282
+ [ Opcodes.i32_add ],
4283
+
4284
+ [ Opcodes.f64_load, 0, 4 ]
4285
+ ],
4286
+ }, {
4287
+ prelude: [
4288
+ ...object,
4289
+ Opcodes.i32_to_u,
4290
+ ...property,
4291
+ Opcodes.i32_to_u
4292
+ ],
4293
+ postlude: setLastType(scope, TYPES.number)
4294
+ }),
4295
+
3707
4296
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3708
4297
  });
3709
4298
  };
@@ -3726,7 +4315,7 @@ const objectHack = node => {
3726
4315
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3727
4316
 
3728
4317
  // if .name or .length, give up (hack within a hack!)
3729
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4318
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3730
4319
  node.object = objectHack(node.object);
3731
4320
  return;
3732
4321
  }
@@ -3779,8 +4368,8 @@ const generateFunc = (scope, decl) => {
3779
4368
 
3780
4369
  if (typedInput && decl.returnType) {
3781
4370
  const { type } = extractTypeAnnotation(decl.returnType);
3782
- // if (type != null && !Prefs.indirectCalls) {
3783
- if (type != null) {
4371
+ if (type != null && !Prefs.indirectCalls) {
4372
+ // if (type != null) {
3784
4373
  func.returnType = type;
3785
4374
  func.returns = [ valtypeBinary ];
3786
4375
  }