porffor 0.17.0-bf4206d7b → 0.17.0-c2dd8f88f

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';
@@ -72,6 +72,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
72
72
  case 'ExpressionStatement':
73
73
  return generateExp(scope, decl);
74
74
 
75
+ case 'SequenceExpression':
76
+ return generateSequence(scope, decl);
77
+
75
78
  case 'CallExpression':
76
79
  return generateCall(scope, decl, global, name, valueUnused);
77
80
 
@@ -120,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
120
123
  case 'EmptyStatement':
121
124
  return generateEmpty(scope, decl);
122
125
 
126
+ case 'MetaProperty':
127
+ return generateMeta(scope, decl);
128
+
123
129
  case 'ConditionalExpression':
124
130
  return generateConditional(scope, decl);
125
131
 
@@ -264,10 +270,12 @@ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysV
264
270
  argument: {
265
271
  type: 'NewExpression',
266
272
  callee: {
273
+ type: 'Identifier',
267
274
  name: constructor
268
275
  },
269
276
  arguments: [
270
277
  {
278
+ type: 'Literal',
271
279
  value: message
272
280
  }
273
281
  ]
@@ -289,12 +297,13 @@ const generateIdent = (scope, decl) => {
289
297
  return wasm.slice();
290
298
  }
291
299
 
292
- if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
293
- // todo: return an actual something
294
- return number(1);
300
+ // todo: enable this by default in future
301
+ if (!Object.hasOwn(funcIndex, name) && Object.hasOwn(builtinFuncs, name)) {
302
+ includeBuiltin(scope, name);
303
+ return number(funcIndex[name] - importedFuncs.length);
295
304
  }
296
305
 
297
- if (isExistingProtoFunc(name)) {
306
+ if (isExistingProtoFunc(name) || Object.hasOwn(internalConstrs, name) || Object.hasOwn(builtinFuncs, name)) {
298
307
  // todo: return an actual something
299
308
  return number(1);
300
309
  }
@@ -613,11 +622,14 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
613
622
  };
614
623
 
615
624
  const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
616
- // if (isIntToFloatOp(wasm[wasm.length - 1])) return [
617
- // ...wasm,
618
- // ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
619
- // ];
620
- // 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
+ ];
621
633
 
622
634
  // todo/perf: use knownType and custom bytecode here instead of typeSwitch
623
635
 
@@ -973,6 +985,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
973
985
  };
974
986
 
975
987
  const generateBinaryExp = (scope, decl, _global, _name) => {
988
+ if (decl.operator === 'instanceof') {
989
+ // very hacky basic instanceof
990
+ // todo: support dynamic right-hand side
991
+
992
+ const out = generate(scope, decl.left);
993
+ disposeLeftover(out);
994
+
995
+ const rightName = decl.right.name;
996
+ if (!rightName) return todo(scope, 'instanceof dynamic right-hand side is not supported yet', true);
997
+
998
+ const checkType = TYPES[rightName.toLowerCase()];
999
+ if (checkType == null || rightName !== TYPE_NAMES[checkType] || checkType === TYPES.undefined) return todo(scope, 'instanceof right-hand side type unsupported', true);
1000
+
1001
+ if ([TYPES.number, TYPES.boolean, TYPES.string, TYPES.symbol, TYPES.object].includes(checkType)) {
1002
+ out.push(...number(0));
1003
+ } else {
1004
+ out.push(
1005
+ ...getNodeType(scope, decl.left),
1006
+ ...number(checkType, Valtype.i32),
1007
+ [ Opcodes.i32_eq ],
1008
+ Opcodes.i32_from_u
1009
+ );
1010
+ }
1011
+
1012
+ return out;
1013
+ }
1014
+
976
1015
  const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
977
1016
 
978
1017
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
@@ -990,12 +1029,13 @@ const asmFuncToAsm = (func, scope) => {
990
1029
  idx = funcIndex[n];
991
1030
  }
992
1031
 
1032
+ if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
993
1033
  return idx;
994
1034
  }
995
1035
  });
996
1036
  };
997
1037
 
998
- 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 }) => {
999
1039
  const existing = funcs.find(x => x.name === name);
1000
1040
  if (existing) return existing;
1001
1041
 
@@ -1022,13 +1062,17 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1022
1062
  returnType,
1023
1063
  internal: true,
1024
1064
  index: currentFuncIndex++,
1025
- table
1065
+ table,
1066
+ constr
1026
1067
  };
1027
1068
 
1028
1069
  funcs.push(func);
1029
1070
  funcIndex[name] = func.index;
1030
1071
 
1031
- 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
+ }
1032
1076
 
1033
1077
  let baseGlobalIdx, i = 0;
1034
1078
  for (const type of globalTypes) {
@@ -1049,15 +1093,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1049
1093
 
1050
1094
  if (table) {
1051
1095
  for (const inst of wasm) {
1052
- if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1096
+ if (inst.at(-1) === 'read func lut') {
1053
1097
  inst.splice(2, 99);
1054
- inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1098
+ inst.push(...unsignedLEB128(allocPage({}, 'func lut') * pageSize));
1055
1099
  }
1056
1100
  }
1057
1101
 
1058
1102
  funcs.table = true;
1059
1103
  }
1060
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
+
1061
1125
  func.wasm = wasm;
1062
1126
 
1063
1127
  return func;
@@ -1184,20 +1248,7 @@ const getNodeType = (scope, node) => {
1184
1248
  return TYPES.number;
1185
1249
  }
1186
1250
 
1187
- if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1188
- if (builtinFuncs[name + '$constructor'].typedReturns) {
1189
- if (scope.locals['#last_type']) return getLastType(scope);
1190
-
1191
- // presume
1192
- // todo: warn here?
1193
- return TYPES.number;
1194
- }
1195
-
1196
- return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1197
- }
1198
-
1199
1251
  const func = funcs.find(x => x.name === name);
1200
-
1201
1252
  if (func) {
1202
1253
  if (func.returnType != null) return func.returnType;
1203
1254
  }
@@ -1279,7 +1330,7 @@ const getNodeType = (scope, node) => {
1279
1330
  }
1280
1331
 
1281
1332
  if (node.type === 'BinaryExpression') {
1282
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1333
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<=', 'instanceof'].includes(node.operator)) return TYPES.boolean;
1283
1334
  if (node.operator !== '+') return TYPES.number;
1284
1335
 
1285
1336
  const knownLeft = knownType(scope, getNodeType(scope, node.left));
@@ -1312,7 +1363,7 @@ const getNodeType = (scope, node) => {
1312
1363
  const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1313
1364
  if (objectKnownType != null) {
1314
1365
  if (name === 'length') {
1315
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1366
+ if (typeHasFlag(objectKnownType, TYPE_FLAGS.length)) return TYPES.number;
1316
1367
  else return TYPES.undefined;
1317
1368
  }
1318
1369
 
@@ -1384,9 +1435,9 @@ const countLeftover = wasm => {
1384
1435
 
1385
1436
  if (depth === 0)
1386
1437
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1387
- 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)) {}
1388
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++;
1389
- 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;
1390
1441
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1391
1442
  else if (inst[0] === Opcodes.return) count = 0;
1392
1443
  else if (inst[0] === Opcodes.call) {
@@ -1424,6 +1475,18 @@ const generateExp = (scope, decl) => {
1424
1475
  return out;
1425
1476
  };
1426
1477
 
1478
+ const generateSequence = (scope, decl) => {
1479
+ let out = [];
1480
+
1481
+ const exprs = decl.expressions;
1482
+ for (let i = 0; i < exprs.length; i++) {
1483
+ if (i > 0) disposeLeftover(out);
1484
+ out.push(...generate(scope, exprs[i]));
1485
+ }
1486
+
1487
+ return out;
1488
+ };
1489
+
1427
1490
  const CTArrayUtil = {
1428
1491
  getLengthI32: pointer => [
1429
1492
  ...number(0, Valtype.i32),
@@ -1483,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1483
1546
  name = func.name;
1484
1547
  }
1485
1548
 
1486
- if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1549
+ if (!decl._new && name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1487
1550
  // literal eval hack
1488
1551
  const code = decl.arguments[0]?.value ?? '';
1489
1552
 
@@ -1526,7 +1589,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1526
1589
 
1527
1590
  let protoName, target;
1528
1591
  // ident.func()
1529
- if (name && name.startsWith('__')) {
1592
+ if (!decl._new && name && name.startsWith('__')) {
1530
1593
  const spl = name.slice(2).split('_');
1531
1594
 
1532
1595
  protoName = spl[spl.length - 1];
@@ -1539,12 +1602,12 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1539
1602
  }
1540
1603
 
1541
1604
  // literal.func()
1542
- if (!name && decl.callee.type === 'MemberExpression') {
1605
+ if (!decl._new && !name && decl.callee.type === 'MemberExpression') {
1543
1606
  // megahack for /regex/.func()
1544
1607
  const funcName = decl.callee.property.name;
1545
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1608
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1546
1609
  const regex = decl.callee.object.regex.pattern;
1547
- const rhemynName = `regex_${funcName}_${regex}`;
1610
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1548
1611
 
1549
1612
  if (!funcIndex[rhemynName]) {
1550
1613
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1565,7 +1628,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1565
1628
  [ Opcodes.call, idx ],
1566
1629
  Opcodes.i32_from_u,
1567
1630
 
1568
- ...setLastType(scope, TYPES.boolean)
1631
+ ...setLastType(scope, Rhemyn.types[funcName])
1569
1632
  ];
1570
1633
  }
1571
1634
 
@@ -1574,27 +1637,56 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1574
1637
  target = decl.callee.object;
1575
1638
  }
1576
1639
 
1577
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1578
- // 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 ],
1579
1648
 
1580
- // funcIndex[func.name] = func.index;
1581
- // funcs.push(func);
1649
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1650
+ ...setLastType(scope, Rhemyn.types[protoName])
1651
+ ];
1582
1652
 
1583
- // return [
1584
- // generate(scope, decl.callee.object)
1653
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1585
1654
 
1586
- // // call regex func
1587
- // [ Opcodes.call, func.index ],
1588
- // Opcodes.i32_from_u
1589
- // ];
1590
- // }
1655
+ if (!funcIndex[rhemynName]) {
1656
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1657
+ func.internal = true;
1591
1658
 
1592
- if (protoName) {
1593
- 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
+ }
1594
1677
 
1678
+ const protoBC = {};
1595
1679
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1596
1680
 
1597
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
+
1598
1690
  for (const x of builtinProtoCands) {
1599
1691
  const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1600
1692
  if (type == null) continue;
@@ -1604,7 +1696,14 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1604
1696
  type: 'Identifier',
1605
1697
  name: x
1606
1698
  },
1607
- arguments: [ target, ...decl.arguments ],
1699
+ arguments: [
1700
+ {
1701
+ type: 'Identifier',
1702
+ name: '#proto_target'
1703
+ },
1704
+
1705
+ ...decl.arguments
1706
+ ],
1608
1707
  _protoInternalCall: true
1609
1708
  });
1610
1709
  }
@@ -1698,29 +1797,37 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1698
1797
  }
1699
1798
 
1700
1799
  if (Object.keys(protoBC).length > 0) {
1701
- return typeSwitch(scope, getNodeType(scope, target), {
1702
- ...protoBC,
1800
+ return [
1801
+ ...out,
1802
+
1803
+ ...typeSwitch(scope, builtinProtoCands.length > 0 ? [ [ Opcodes.local_get, localTmp(scope, '#proto_target#type', Valtype.i32) ] ] : getNodeType(scope, target), {
1804
+ ...protoBC,
1703
1805
 
1704
- // TODO: error better
1705
- default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1706
- }, valtypeBinary);
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary)
1809
+ ];
1707
1810
  }
1708
1811
  }
1709
1812
 
1710
- // TODO: only allows callee as literal
1711
- 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})`);
1712
1815
 
1713
1816
  let idx = funcIndex[name] ?? importedFuncs[name];
1714
1817
  if (idx === undefined && builtinFuncs[name]) {
1715
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);
1716
1820
 
1717
1821
  includeBuiltin(scope, name);
1718
1822
  idx = funcIndex[name];
1719
1823
  }
1720
1824
 
1721
- 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
+ }
1722
1829
 
1723
- if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1830
+ if (idx === undefined && !decl._new && name.startsWith('__Porffor_wasm_')) {
1724
1831
  const wasmOps = {
1725
1832
  // pointer, align, offset
1726
1833
  i32_load: { imms: 2, args: [ true ], returns: 1 },
@@ -1775,7 +1882,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1775
1882
 
1776
1883
  const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1777
1884
  // options: vararg, strict
1778
- // - strict: simpler, smaller size usage, no func argc lut needed.
1885
+ // - strict: simpler, smaller size usage, no func lut needed.
1779
1886
  // ONLY works when arg count of call == arg count of function being called
1780
1887
  // - vararg: large size usage, cursed.
1781
1888
  // works when arg count of call != arg count of function being called*
@@ -1785,8 +1892,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1785
1892
  scope.table = true;
1786
1893
 
1787
1894
  let args = decl.arguments;
1788
- let out = [];
1789
-
1790
1895
  let locals = [];
1791
1896
 
1792
1897
  if (indirectMode === 'vararg') {
@@ -1850,24 +1955,67 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1850
1955
  // *for argc 0-3, in future (todo:) the max number should be
1851
1956
  // dynamically changed to the max argc of any func in the js file.
1852
1957
 
1853
- 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);
1854
1960
 
1855
1961
  const gen = argc => {
1856
- const out = [];
1962
+ const argsOut = [];
1857
1963
  for (let i = 0; i < argc; i++) {
1858
- out.push(
1964
+ argsOut.push(
1859
1965
  [ Opcodes.local_get, locals[i][0] ],
1860
1966
  [ Opcodes.local_get, locals[i][1] ]
1861
1967
  );
1862
1968
  }
1863
1969
 
1864
- out.push(
1865
- [ Opcodes.local_get, funcLocal ],
1866
- [ Opcodes.call_indirect, argc, 0 ],
1867
- ...setLastType(scope)
1868
- )
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
+ ];
1869
1980
 
1870
- 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
+ );
1871
2019
  };
1872
2020
 
1873
2021
  const tableBc = {};
@@ -1885,13 +2033,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1885
2033
  Opcodes.i32_to_u,
1886
2034
  [ Opcodes.local_set, funcLocal ],
1887
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
+
1888
2056
  ...brTable([
1889
2057
  // get argc of func we are calling
1890
2058
  [ Opcodes.local_get, funcLocal ],
1891
- ...number(ValtypeSize.i16, Valtype.i32),
2059
+ ...number(3, Valtype.i32),
1892
2060
  [ Opcodes.i32_mul ],
1893
-
1894
- [ 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' ]
1895
2062
  ], tableBc, valtypeBinary)
1896
2063
  ],
1897
2064
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
@@ -1905,7 +2072,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1905
2072
  const userFunc = func && !func.internal;
1906
2073
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1907
2074
  const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1908
- 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);
1909
2085
 
1910
2086
  let args = decl.arguments;
1911
2087
  if (func && args.length < paramCount) {
@@ -1920,7 +2096,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1920
2096
 
1921
2097
  if (func && func.throws) scope.throws = true;
1922
2098
 
1923
- let out = [];
1924
2099
  for (let i = 0; i < args.length; i++) {
1925
2100
  const arg = args[i];
1926
2101
  out = out.concat(generate(scope, arg));
@@ -1933,13 +2108,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1933
2108
  }
1934
2109
 
1935
2110
  if (valtypeBinary !== Valtype.i32 &&
1936
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
2111
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.i32)
1937
2112
  ) {
1938
2113
  out.push(Opcodes.i32_to);
1939
2114
  }
1940
2115
 
1941
2116
  if (valtypeBinary === Valtype.i32 &&
1942
- (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
2117
+ (func && func.params[paramOffset + i * (typedParams ? 2 : 1)] === Valtype.f64)
1943
2118
  ) {
1944
2119
  out.push([ Opcodes.f64_convert_i32_s ]);
1945
2120
  }
@@ -1972,32 +2147,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1972
2147
  return out;
1973
2148
  };
1974
2149
 
1975
- const generateNew = (scope, decl, _global, _name) => {
1976
- // hack: basically treat this as a normal call for builtins for now
1977
- const name = mapName(decl.callee.name);
1978
-
1979
- if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1980
-
1981
- if (builtinFuncs[name + '$constructor']) {
1982
- // custom ...$constructor override builtin func
1983
- return generateCall(scope, {
1984
- ...decl,
1985
- callee: {
1986
- type: 'Identifier',
1987
- name: name + '$constructor'
1988
- }
1989
- }, _global, _name);
1990
- }
1991
-
1992
- if (
1993
- (builtinFuncs[name] && !builtinFuncs[name].constr) ||
1994
- (internalConstrs[name] && builtinFuncs[name].notConstr)
1995
- ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`, true);
1996
-
1997
- 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)})`);
1998
-
1999
- return generateCall(scope, decl, _global, _name);
2000
- };
2150
+ const generateNew = (scope, decl, _global, _name) => generateCall(scope, {
2151
+ ...decl,
2152
+ _new: true
2153
+ }, _global, _name);
2001
2154
 
2002
2155
  // bad hack for undefined and null working without additional logic
2003
2156
  const DEFAULT_VALUE = {
@@ -2005,6 +2158,16 @@ const DEFAULT_VALUE = {
2005
2158
  name: 'undefined'
2006
2159
  };
2007
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
+
2008
2171
  const unhackName = name => {
2009
2172
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2010
2173
  return name;
@@ -2012,7 +2175,7 @@ const unhackName = name => {
2012
2175
 
2013
2176
  const knownType = (scope, type) => {
2014
2177
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2015
- return type[0][1];
2178
+ return read_signedLEB128(type[0].slice(1));
2016
2179
  }
2017
2180
 
2018
2181
  if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
@@ -2299,11 +2462,6 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2299
2462
  const { type, name } = decl.left;
2300
2463
  const [ local, isGlobal ] = lookupName(scope, name);
2301
2464
 
2302
- if (type === 'ObjectPattern') {
2303
- // hack: ignore object parts of `var a = {} = 2`
2304
- return generate(scope, decl.right);
2305
- }
2306
-
2307
2465
  if (isFuncType(decl.right.type)) {
2308
2466
  // hack for a = function () { ... }
2309
2467
  decl.right.id = { name };
@@ -2349,8 +2507,8 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2349
2507
 
2350
2508
  // arr[i]
2351
2509
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2352
- const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2353
- const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2510
+ const newValueTmp = localTmp(scope, '#member_setter_val_tmp');
2511
+ const pointerTmp = localTmp(scope, '#member_setter_ptr_tmp', Valtype.i32);
2354
2512
 
2355
2513
  return [
2356
2514
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
@@ -2362,11 +2520,11 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2362
2520
  ...generate(scope, decl.left.property),
2363
2521
  Opcodes.i32_to_u,
2364
2522
 
2365
- // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2523
+ // turn into byte offset by * valtypeSize + 1
2366
2524
  ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2367
2525
  [ Opcodes.i32_mul ],
2368
2526
  [ Opcodes.i32_add ],
2369
- ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2527
+ [ Opcodes.local_tee, pointerTmp ],
2370
2528
 
2371
2529
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2372
2530
  [ Opcodes.local_get, pointerTmp ],
@@ -2376,10 +2534,164 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2376
2534
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2377
2535
  ], getNodeType(scope, decl.right), false, name, true)),
2378
2536
  [ Opcodes.local_tee, newValueTmp ],
2537
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
2379
2538
 
2380
- [ Opcodes.store, 0, ValtypeSize.i32 ]
2539
+ [ Opcodes.local_get, pointerTmp ],
2540
+ ...getNodeType(scope, decl),
2541
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
2381
2542
  ],
2382
2543
 
2544
+ ...wrapBC({
2545
+ [TYPES.uint8array]: [
2546
+ [ Opcodes.i32_add ],
2547
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2548
+
2549
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2550
+ [ Opcodes.local_get, pointerTmp ],
2551
+ [ Opcodes.i32_load8_u, 0, 4 ],
2552
+ Opcodes.i32_from_u
2553
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2554
+ [ Opcodes.local_tee, newValueTmp ],
2555
+
2556
+ Opcodes.i32_to_u,
2557
+ [ Opcodes.i32_store8, 0, 4 ]
2558
+ ],
2559
+ [TYPES.uint8clampedarray]: [
2560
+ [ Opcodes.i32_add ],
2561
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2562
+
2563
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2564
+ [ Opcodes.local_get, pointerTmp ],
2565
+ [ Opcodes.i32_load8_u, 0, 4 ],
2566
+ Opcodes.i32_from_u
2567
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2568
+ [ Opcodes.local_tee, newValueTmp ],
2569
+
2570
+ ...number(0),
2571
+ [ Opcodes.f64_max ],
2572
+ ...number(255),
2573
+ [ Opcodes.f64_min ],
2574
+ Opcodes.i32_to_u,
2575
+ [ Opcodes.i32_store8, 0, 4 ]
2576
+ ],
2577
+ [TYPES.int8array]: [
2578
+ [ Opcodes.i32_add ],
2579
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2580
+
2581
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2582
+ [ Opcodes.local_get, pointerTmp ],
2583
+ [ Opcodes.i32_load8_s, 0, 4 ],
2584
+ Opcodes.i32_from
2585
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2586
+ [ Opcodes.local_tee, newValueTmp ],
2587
+
2588
+ Opcodes.i32_to,
2589
+ [ Opcodes.i32_store8, 0, 4 ]
2590
+ ],
2591
+ [TYPES.uint16array]: [
2592
+ ...number(2, Valtype.i32),
2593
+ [ Opcodes.i32_mul ],
2594
+ [ Opcodes.i32_add ],
2595
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2596
+
2597
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2598
+ [ Opcodes.local_get, pointerTmp ],
2599
+ [ Opcodes.i32_load16_u, 0, 4 ],
2600
+ Opcodes.i32_from_u
2601
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2602
+ [ Opcodes.local_tee, newValueTmp ],
2603
+
2604
+ Opcodes.i32_to_u,
2605
+ [ Opcodes.i32_store16, 0, 4 ]
2606
+ ],
2607
+ [TYPES.int16array]: [
2608
+ ...number(2, Valtype.i32),
2609
+ [ Opcodes.i32_mul ],
2610
+ [ Opcodes.i32_add ],
2611
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2612
+
2613
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2614
+ [ Opcodes.local_get, pointerTmp ],
2615
+ [ Opcodes.i32_load16_s, 0, 4 ],
2616
+ Opcodes.i32_from
2617
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2618
+ [ Opcodes.local_tee, newValueTmp ],
2619
+
2620
+ Opcodes.i32_to,
2621
+ [ Opcodes.i32_store16, 0, 4 ]
2622
+ ],
2623
+ [TYPES.uint32array]: [
2624
+ ...number(4, Valtype.i32),
2625
+ [ Opcodes.i32_mul ],
2626
+ [ Opcodes.i32_add ],
2627
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2628
+
2629
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2630
+ [ Opcodes.local_get, pointerTmp ],
2631
+ [ Opcodes.i32_load, 0, 4 ],
2632
+ Opcodes.i32_from_u
2633
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2634
+ [ Opcodes.local_tee, newValueTmp ],
2635
+
2636
+ Opcodes.i32_to_u,
2637
+ [ Opcodes.i32_store, 0, 4 ]
2638
+ ],
2639
+ [TYPES.int32array]: [
2640
+ ...number(4, Valtype.i32),
2641
+ [ Opcodes.i32_mul ],
2642
+ [ Opcodes.i32_add ],
2643
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2644
+
2645
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2646
+ [ Opcodes.local_get, pointerTmp ],
2647
+ [ Opcodes.i32_load, 0, 4 ],
2648
+ Opcodes.i32_from
2649
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2650
+ [ Opcodes.local_tee, newValueTmp ],
2651
+
2652
+ Opcodes.i32_to,
2653
+ [ Opcodes.i32_store, 0, 4 ]
2654
+ ],
2655
+ [TYPES.float32array]: [
2656
+ ...number(4, Valtype.i32),
2657
+ [ Opcodes.i32_mul ],
2658
+ [ Opcodes.i32_add ],
2659
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2660
+
2661
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2662
+ [ Opcodes.local_get, pointerTmp ],
2663
+ [ Opcodes.f32_load, 0, 4 ],
2664
+ [ Opcodes.f64_promote_f32 ]
2665
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2666
+ [ Opcodes.local_tee, newValueTmp ],
2667
+
2668
+ [ Opcodes.f32_demote_f64 ],
2669
+ [ Opcodes.f32_store, 0, 4 ]
2670
+ ],
2671
+ [TYPES.float64array]: [
2672
+ ...number(8, Valtype.i32),
2673
+ [ Opcodes.i32_mul ],
2674
+ [ Opcodes.i32_add ],
2675
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2676
+
2677
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2678
+ [ Opcodes.local_get, pointerTmp ],
2679
+ [ Opcodes.f64_load, 0, 4 ]
2680
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2681
+ [ Opcodes.local_tee, newValueTmp ],
2682
+
2683
+ [ Opcodes.f64_store, 0, 4 ]
2684
+ ],
2685
+ }, {
2686
+ prelude: [
2687
+ ...generate(scope, decl.left.object),
2688
+ Opcodes.i32_to_u,
2689
+ ...generate(scope, decl.left.property),
2690
+ Opcodes.i32_to_u,
2691
+ ],
2692
+ postlude: setLastType(scope, TYPES.number)
2693
+ }),
2694
+
2383
2695
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2384
2696
  }, Blocktype.void),
2385
2697
 
@@ -2763,7 +3075,9 @@ const generateForOf = (scope, decl) => {
2763
3075
  // // todo: we should only do this for strings but we don't know at compile-time :(
2764
3076
  // hack: this is naughty and will break things!
2765
3077
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2766
- if (pages.hasAnyString) {
3078
+
3079
+ const known = knownType(scope, getNodeType(scope, decl.right));
3080
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
2767
3081
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2768
3082
  0, [ newOut, newPointer ] = makeArray(scope, {
2769
3083
  rawElements: new Array(0)
@@ -2811,6 +3125,7 @@ const generateForOf = (scope, decl) => {
2811
3125
  [ Opcodes.end ],
2812
3126
  [ Opcodes.end ]
2813
3127
  ],
3128
+
2814
3129
  [TYPES.string]: [
2815
3130
  ...setType(scope, leftName, TYPES.string),
2816
3131
 
@@ -2919,6 +3234,7 @@ const generateForOf = (scope, decl) => {
2919
3234
  [ Opcodes.end ],
2920
3235
  [ Opcodes.end ]
2921
3236
  ],
3237
+
2922
3238
  [TYPES.set]: [
2923
3239
  [ Opcodes.loop, Blocktype.void ],
2924
3240
 
@@ -2957,6 +3273,106 @@ const generateForOf = (scope, decl) => {
2957
3273
  [ Opcodes.end ],
2958
3274
  [ Opcodes.end ]
2959
3275
  ],
3276
+
3277
+ ...wrapBC({
3278
+ [TYPES.uint8array]: [
3279
+ [ Opcodes.i32_add ],
3280
+
3281
+ [ Opcodes.i32_load8_u, 0, 4 ],
3282
+ Opcodes.i32_from_u
3283
+ ],
3284
+ [TYPES.uint8clampedarray]: [
3285
+ [ Opcodes.i32_add ],
3286
+
3287
+ [ Opcodes.i32_load8_u, 0, 4 ],
3288
+ Opcodes.i32_from_u
3289
+ ],
3290
+ [TYPES.int8array]: [
3291
+ [ Opcodes.i32_add ],
3292
+
3293
+ [ Opcodes.i32_load8_s, 0, 4 ],
3294
+ Opcodes.i32_from
3295
+ ],
3296
+ [TYPES.uint16array]: [
3297
+ ...number(2, Valtype.i32),
3298
+ [ Opcodes.i32_mul ],
3299
+ [ Opcodes.i32_add ],
3300
+
3301
+ [ Opcodes.i32_load16_u, 0, 4 ],
3302
+ Opcodes.i32_from_u
3303
+ ],
3304
+ [TYPES.int16array]: [
3305
+ ...number(2, Valtype.i32),
3306
+ [ Opcodes.i32_mul ],
3307
+ [ Opcodes.i32_add ],
3308
+
3309
+ [ Opcodes.i32_load16_s, 0, 4 ],
3310
+ Opcodes.i32_from
3311
+ ],
3312
+ [TYPES.uint32array]: [
3313
+ ...number(4, Valtype.i32),
3314
+ [ Opcodes.i32_mul ],
3315
+ [ Opcodes.i32_add ],
3316
+
3317
+ [ Opcodes.i32_load, 0, 4 ],
3318
+ Opcodes.i32_from_u
3319
+ ],
3320
+ [TYPES.int32array]: [
3321
+ ...number(4, Valtype.i32),
3322
+ [ Opcodes.i32_mul ],
3323
+ [ Opcodes.i32_add ],
3324
+
3325
+ [ Opcodes.i32_load, 0, 4 ],
3326
+ Opcodes.i32_from
3327
+ ],
3328
+ [TYPES.float32array]: [
3329
+ ...number(4, Valtype.i32),
3330
+ [ Opcodes.i32_mul ],
3331
+ [ Opcodes.i32_add ],
3332
+
3333
+ [ Opcodes.f32_load, 0, 4 ],
3334
+ [ Opcodes.f64_promote_f32 ]
3335
+ ],
3336
+ [TYPES.float64array]: [
3337
+ ...number(8, Valtype.i32),
3338
+ [ Opcodes.i32_mul ],
3339
+ [ Opcodes.i32_add ],
3340
+
3341
+ [ Opcodes.f64_load, 0, 4 ]
3342
+ ],
3343
+ }, {
3344
+ prelude: [
3345
+ ...setType(scope, leftName, TYPES.number),
3346
+
3347
+ [ Opcodes.loop, Blocktype.void ],
3348
+
3349
+ [ Opcodes.local_get, pointer ],
3350
+ [ Opcodes.local_get, counter ]
3351
+ ],
3352
+ postlude: [
3353
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3354
+
3355
+ [ Opcodes.block, Blocktype.void ],
3356
+ [ Opcodes.block, Blocktype.void ],
3357
+ ...generate(scope, decl.body),
3358
+ [ Opcodes.end ],
3359
+
3360
+ // increment counter by 1
3361
+ [ Opcodes.local_get, counter ],
3362
+ ...number(1, Valtype.i32),
3363
+ [ Opcodes.i32_add ],
3364
+ [ Opcodes.local_tee, counter ],
3365
+
3366
+ // loop if counter != length
3367
+ [ Opcodes.local_get, length ],
3368
+ [ Opcodes.i32_ne ],
3369
+ [ Opcodes.br_if, 1 ],
3370
+
3371
+ [ Opcodes.end ],
3372
+ [ Opcodes.end ]
3373
+ ]
3374
+ }),
3375
+
2960
3376
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2961
3377
  }, Blocktype.void));
2962
3378
 
@@ -3029,32 +3445,102 @@ const generateLabel = (scope, decl) => {
3029
3445
  const generateThrow = (scope, decl) => {
3030
3446
  scope.throws = true;
3031
3447
 
3032
- let message = decl.argument.value, constructor = null;
3448
+ const exceptionMode = Prefs.exceptionMode ?? 'lut';
3449
+ if (exceptionMode === 'lut') {
3450
+ let message = decl.argument.value, constructor = null;
3033
3451
 
3034
- // hack: throw new X("...") -> throw "..."
3035
- if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
3036
- constructor = decl.argument.callee.name;
3037
- message = decl.argument.arguments[0]?.value ?? '';
3452
+ // support `throw (new)? Error(...)`
3453
+ if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
3454
+ constructor = decl.argument.callee.name;
3455
+ message = decl.argument.arguments[0]?.value ?? '';
3456
+ }
3457
+
3458
+ if (tags.length === 0) tags.push({
3459
+ params: [ Valtype.i32 ],
3460
+ results: [],
3461
+ idx: tags.length
3462
+ });
3463
+
3464
+ let exceptId = exceptions.findIndex(x => x.constructor === constructor && x.message === message);
3465
+ if (exceptId === -1) exceptId = exceptions.push({ constructor, message }) - 1;
3466
+
3467
+ scope.exceptions ??= [];
3468
+ scope.exceptions.push(exceptId);
3469
+
3470
+ return [
3471
+ ...number(exceptId, Valtype.i32),
3472
+ [ Opcodes.throw, tags[0].idx ]
3473
+ ];
3038
3474
  }
3039
3475
 
3040
- if (tags.length === 0) tags.push({
3041
- params: [ Valtype.i32 ],
3042
- results: [],
3043
- idx: tags.length
3044
- });
3476
+ if (exceptionMode === 'stack') {
3477
+ if (tags.length === 0) tags.push({
3478
+ params: [ valtypeBinary, Valtype.i32 ],
3479
+ results: [],
3480
+ idx: tags.length
3481
+ });
3482
+
3483
+ return [
3484
+ ...generate(scope, decl.argument),
3485
+ ...getNodeType(scope, decl.argument),
3486
+ [ Opcodes.throw, tags[0].idx ]
3487
+ ];
3488
+ }
3045
3489
 
3046
- let exceptId = exceptions.push({ constructor, message }) - 1;
3047
- let tagIdx = tags[0].idx;
3490
+ if (exceptionMode === 'stackest') {
3491
+ let message = decl.argument, constructor = null;
3048
3492
 
3049
- scope.exceptions ??= [];
3050
- scope.exceptions.push(exceptId);
3493
+ // support `throw (new)? Error(...)`
3494
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3495
+ constructor = decl.argument.callee;
3496
+ message = decl.argument.arguments[0];
3497
+ }
3051
3498
 
3052
- // todo: write a description of how this works lol
3499
+ message ??= DEFAULT_VALUE;
3053
3500
 
3054
- return [
3055
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3056
- [ Opcodes.throw, tagIdx ]
3057
- ];
3501
+ if (tags.length === 0) tags.push({
3502
+ params: [ valtypeBinary, valtypeBinary, Valtype.i32 ],
3503
+ results: [],
3504
+ idx: tags.length
3505
+ });
3506
+
3507
+ return [
3508
+ ...(constructor == null ? number(-1) : generate(scope, constructor)),
3509
+ ...generate(scope, message),
3510
+ ...getNodeType(scope, message),
3511
+ [ Opcodes.throw, tags[0].idx ]
3512
+ ];
3513
+ }
3514
+
3515
+ if (exceptionMode === 'partial') {
3516
+ let message = decl.argument, constructor = null;
3517
+
3518
+ // support `throw (new)? Error(...)`
3519
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3520
+ constructor = decl.argument.callee.name;
3521
+ message = decl.argument.arguments[0];
3522
+ }
3523
+
3524
+ message ??= DEFAULT_VALUE;
3525
+
3526
+ if (tags.length === 0) tags.push({
3527
+ params: [ Valtype.i32, valtypeBinary, Valtype.i32 ],
3528
+ results: [],
3529
+ idx: tags.length
3530
+ });
3531
+
3532
+ let exceptId = exceptions.push({ constructor }) - 1;
3533
+
3534
+ scope.exceptions ??= [];
3535
+ scope.exceptions.push(exceptId);
3536
+
3537
+ return [
3538
+ ...number(exceptId, Valtype.i32),
3539
+ ...generate(scope, message),
3540
+ ...getNodeType(scope, message),
3541
+ [ Opcodes.throw, tags[0].idx ]
3542
+ ];
3543
+ }
3058
3544
  };
3059
3545
 
3060
3546
  const generateTry = (scope, decl) => {
@@ -3090,6 +3576,22 @@ const generateEmpty = (scope, decl) => {
3090
3576
  return [];
3091
3577
  };
3092
3578
 
3579
+ const generateMeta = (scope, decl) => {
3580
+ if (decl.meta.name !== 'new') return todo(scope, `meta property object ${decl.meta.name} is not supported yet`, true);
3581
+
3582
+ switch (`${decl.meta.name}.${decl.property.name}`) {
3583
+ case 'new.target': {
3584
+ scope.constr = true;
3585
+
3586
+ return [
3587
+ [ Opcodes.local_get, -1 ],
3588
+ Opcodes.i32_from_u,
3589
+ ...setLastType(scope, TYPES.boolean)
3590
+ ];
3591
+ }
3592
+ }
3593
+ };
3594
+
3093
3595
  let pages = new Map();
3094
3596
  const allocPage = (scope, reason, type) => {
3095
3597
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3407,6 +3909,19 @@ const withType = (scope, wasm, type) => [
3407
3909
  ...setLastType(scope, type)
3408
3910
  ];
3409
3911
 
3912
+ const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
3913
+ const out = {};
3914
+ for (const x in bc) {
3915
+ out[x] = [
3916
+ ...prelude,
3917
+ ...bc[x],
3918
+ ...postlude
3919
+ ];
3920
+ }
3921
+
3922
+ return out;
3923
+ };
3924
+
3410
3925
  const generateMember = (scope, decl, _global, _name) => {
3411
3926
  const name = decl.object.name;
3412
3927
 
@@ -3429,20 +3944,10 @@ const generateMember = (scope, decl, _global, _name) => {
3429
3944
  const func = funcs.find(x => x.name === name);
3430
3945
  if (func) {
3431
3946
  const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3432
- return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3947
+ return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
3433
3948
  }
3434
3949
 
3435
- if (builtinFuncs[name + '$constructor']) {
3436
- const regularFunc = builtinFuncs[name];
3437
- const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3438
-
3439
- const constructorFunc = builtinFuncs[name + '$constructor'];
3440
- const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3441
-
3442
- return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3443
- }
3444
-
3445
- if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3950
+ 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);
3446
3951
  if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3447
3952
  if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3448
3953
 
@@ -3460,7 +3965,7 @@ const generateMember = (scope, decl, _global, _name) => {
3460
3965
  const type = getNodeType(scope, decl.object);
3461
3966
  const known = knownType(scope, type);
3462
3967
  if (known != null) {
3463
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3968
+ if (typeHasFlag(known, TYPE_FLAGS.length)) return [
3464
3969
  ...generate(scope, decl.object),
3465
3970
  Opcodes.i32_to_u,
3466
3971
 
@@ -3472,7 +3977,9 @@ const generateMember = (scope, decl, _global, _name) => {
3472
3977
  }
3473
3978
 
3474
3979
  return [
3475
- ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3980
+ ...getNodeType(scope, decl.object),
3981
+ ...number(TYPE_FLAGS.length, Valtype.i32),
3982
+ [ Opcodes.i32_and ],
3476
3983
  [ Opcodes.if, valtypeBinary ],
3477
3984
  ...generate(scope, decl.object),
3478
3985
  Opcodes.i32_to_u,
@@ -3489,7 +3996,7 @@ const generateMember = (scope, decl, _global, _name) => {
3489
3996
  }
3490
3997
 
3491
3998
  // todo: generate this array procedurally during builtinFuncs creation
3492
- if (['size', 'description'].includes(decl.property.name)) {
3999
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3493
4000
  const bc = {};
3494
4001
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3495
4002
 
@@ -3521,7 +4028,9 @@ const generateMember = (scope, decl, _global, _name) => {
3521
4028
  // // todo: we should only do this for strings but we don't know at compile-time :(
3522
4029
  // hack: this is naughty and will break things!
3523
4030
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3524
- if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
4031
+
4032
+ const known = knownType(scope, getNodeType(scope, decl.object));
4033
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
3525
4034
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3526
4035
  0, [ newOut, newPointer ] = makeArray(scope, {
3527
4036
  rawElements: new Array(0)
@@ -3595,6 +4104,82 @@ const generateMember = (scope, decl, _global, _name) => {
3595
4104
  ...setLastType(scope, TYPES.bytestring)
3596
4105
  ],
3597
4106
 
4107
+ ...wrapBC({
4108
+ [TYPES.uint8array]: [
4109
+ [ Opcodes.i32_add ],
4110
+
4111
+ [ Opcodes.i32_load8_u, 0, 4 ],
4112
+ Opcodes.i32_from_u
4113
+ ],
4114
+ [TYPES.uint8clampedarray]: [
4115
+ [ Opcodes.i32_add ],
4116
+
4117
+ [ Opcodes.i32_load8_u, 0, 4 ],
4118
+ Opcodes.i32_from_u
4119
+ ],
4120
+ [TYPES.int8array]: [
4121
+ [ Opcodes.i32_add ],
4122
+
4123
+ [ Opcodes.i32_load8_s, 0, 4 ],
4124
+ Opcodes.i32_from
4125
+ ],
4126
+ [TYPES.uint16array]: [
4127
+ ...number(2, Valtype.i32),
4128
+ [ Opcodes.i32_mul ],
4129
+ [ Opcodes.i32_add ],
4130
+
4131
+ [ Opcodes.i32_load16_u, 0, 4 ],
4132
+ Opcodes.i32_from_u
4133
+ ],
4134
+ [TYPES.int16array]: [
4135
+ ...number(2, Valtype.i32),
4136
+ [ Opcodes.i32_mul ],
4137
+ [ Opcodes.i32_add ],
4138
+
4139
+ [ Opcodes.i32_load16_s, 0, 4 ],
4140
+ Opcodes.i32_from
4141
+ ],
4142
+ [TYPES.uint32array]: [
4143
+ ...number(4, Valtype.i32),
4144
+ [ Opcodes.i32_mul ],
4145
+ [ Opcodes.i32_add ],
4146
+
4147
+ [ Opcodes.i32_load, 0, 4 ],
4148
+ Opcodes.i32_from_u
4149
+ ],
4150
+ [TYPES.int32array]: [
4151
+ ...number(4, Valtype.i32),
4152
+ [ Opcodes.i32_mul ],
4153
+ [ Opcodes.i32_add ],
4154
+
4155
+ [ Opcodes.i32_load, 0, 4 ],
4156
+ Opcodes.i32_from
4157
+ ],
4158
+ [TYPES.float32array]: [
4159
+ ...number(4, Valtype.i32),
4160
+ [ Opcodes.i32_mul ],
4161
+ [ Opcodes.i32_add ],
4162
+
4163
+ [ Opcodes.f32_load, 0, 4 ],
4164
+ [ Opcodes.f64_promote_f32 ]
4165
+ ],
4166
+ [TYPES.float64array]: [
4167
+ ...number(8, Valtype.i32),
4168
+ [ Opcodes.i32_mul ],
4169
+ [ Opcodes.i32_add ],
4170
+
4171
+ [ Opcodes.f64_load, 0, 4 ]
4172
+ ],
4173
+ }, {
4174
+ prelude: [
4175
+ ...object,
4176
+ Opcodes.i32_to_u,
4177
+ ...property,
4178
+ Opcodes.i32_to_u
4179
+ ],
4180
+ postlude: setLastType(scope, TYPES.number)
4181
+ }),
4182
+
3598
4183
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3599
4184
  });
3600
4185
  };
@@ -3617,7 +4202,7 @@ const objectHack = node => {
3617
4202
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3618
4203
 
3619
4204
  // if .name or .length, give up (hack within a hack!)
3620
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4205
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3621
4206
  node.object = objectHack(node.object);
3622
4207
  return;
3623
4208
  }
@@ -3670,8 +4255,8 @@ const generateFunc = (scope, decl) => {
3670
4255
 
3671
4256
  if (typedInput && decl.returnType) {
3672
4257
  const { type } = extractTypeAnnotation(decl.returnType);
3673
- // if (type != null && !Prefs.indirectCalls) {
3674
- if (type != null) {
4258
+ if (type != null && !Prefs.indirectCalls) {
4259
+ // if (type != null) {
3675
4260
  func.returnType = type;
3676
4261
  func.returns = [ valtypeBinary ];
3677
4262
  }
@@ -3772,7 +4357,7 @@ const internalConstrs = {
3772
4357
 
3773
4358
  // todo: check in wasm instead of here
3774
4359
  const literalValue = arg.value ?? 0;
3775
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
4360
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeError', 'Invalid array length', true);
3776
4361
 
3777
4362
  return [
3778
4363
  ...out,