porffor 0.17.0-b7ba5b83d → 0.17.0-b92c546cf

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
 
@@ -264,10 +267,12 @@ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysV
264
267
  argument: {
265
268
  type: 'NewExpression',
266
269
  callee: {
270
+ type: 'Identifier',
267
271
  name: constructor
268
272
  },
269
273
  arguments: [
270
274
  {
275
+ type: 'Literal',
271
276
  value: message
272
277
  }
273
278
  ]
@@ -289,12 +294,13 @@ const generateIdent = (scope, decl) => {
289
294
  return wasm.slice();
290
295
  }
291
296
 
292
- if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
293
- // todo: return an actual something
294
- return number(1);
295
- }
297
+ // 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
+ // }
296
302
 
297
- if (isExistingProtoFunc(name)) {
303
+ if (isExistingProtoFunc(name) || Object.hasOwn(internalConstrs, name) || Object.hasOwn(builtinFuncs, name)) {
298
304
  // todo: return an actual something
299
305
  return number(1);
300
306
  }
@@ -613,11 +619,14 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
613
619
  };
614
620
 
615
621
  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 ];
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 [
627
+ ...wasm,
628
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
629
+ ];
621
630
 
622
631
  // todo/perf: use knownType and custom bytecode here instead of typeSwitch
623
632
 
@@ -973,6 +982,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
973
982
  };
974
983
 
975
984
  const generateBinaryExp = (scope, decl, _global, _name) => {
985
+ if (decl.operator === 'instanceof') {
986
+ // very hacky basic instanceof
987
+ // todo: support dynamic right-hand side
988
+
989
+ const out = generate(scope, decl.left);
990
+ disposeLeftover(out);
991
+
992
+ const rightName = decl.right.name;
993
+ if (!rightName) return todo(scope, 'instanceof dynamic right-hand side is not supported yet', true);
994
+
995
+ const checkType = TYPES[rightName.toLowerCase()];
996
+ if (checkType == null || rightName !== TYPE_NAMES[checkType] || checkType === TYPES.undefined) return todo(scope, 'instanceof right-hand side type unsupported', true);
997
+
998
+ if ([TYPES.number, TYPES.boolean, TYPES.string, TYPES.symbol, TYPES.object].includes(checkType)) {
999
+ out.push(...number(0));
1000
+ } else {
1001
+ out.push(
1002
+ ...getNodeType(scope, decl.left),
1003
+ ...number(checkType, Valtype.i32),
1004
+ [ Opcodes.i32_eq ],
1005
+ Opcodes.i32_from_u
1006
+ );
1007
+ }
1008
+
1009
+ return out;
1010
+ }
1011
+
976
1012
  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
1013
 
978
1014
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
@@ -1279,7 +1315,7 @@ const getNodeType = (scope, node) => {
1279
1315
  }
1280
1316
 
1281
1317
  if (node.type === 'BinaryExpression') {
1282
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1318
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<=', 'instanceof'].includes(node.operator)) return TYPES.boolean;
1283
1319
  if (node.operator !== '+') return TYPES.number;
1284
1320
 
1285
1321
  const knownLeft = knownType(scope, getNodeType(scope, node.left));
@@ -1312,7 +1348,7 @@ const getNodeType = (scope, node) => {
1312
1348
  const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1313
1349
  if (objectKnownType != null) {
1314
1350
  if (name === 'length') {
1315
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1351
+ if (typeHasFlag(objectKnownType, TYPE_FLAGS.length)) return TYPES.number;
1316
1352
  else return TYPES.undefined;
1317
1353
  }
1318
1354
 
@@ -1384,9 +1420,9 @@ const countLeftover = wasm => {
1384
1420
 
1385
1421
  if (depth === 0)
1386
1422
  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)) {}
1423
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.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
1424
  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;
1425
+ 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
1426
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1391
1427
  else if (inst[0] === Opcodes.return) count = 0;
1392
1428
  else if (inst[0] === Opcodes.call) {
@@ -1424,6 +1460,18 @@ const generateExp = (scope, decl) => {
1424
1460
  return out;
1425
1461
  };
1426
1462
 
1463
+ const generateSequence = (scope, decl) => {
1464
+ let out = [];
1465
+
1466
+ const exprs = decl.expressions;
1467
+ for (let i = 0; i < exprs.length; i++) {
1468
+ if (i > 0) disposeLeftover(out);
1469
+ out.push(...generate(scope, exprs[i]));
1470
+ }
1471
+
1472
+ return out;
1473
+ };
1474
+
1427
1475
  const CTArrayUtil = {
1428
1476
  getLengthI32: pointer => [
1429
1477
  ...number(0, Valtype.i32),
@@ -1542,9 +1590,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1542
1590
  if (!name && decl.callee.type === 'MemberExpression') {
1543
1591
  // megahack for /regex/.func()
1544
1592
  const funcName = decl.callee.property.name;
1545
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1593
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1546
1594
  const regex = decl.callee.object.regex.pattern;
1547
- const rhemynName = `regex_${funcName}_${regex}`;
1595
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1548
1596
 
1549
1597
  if (!funcIndex[rhemynName]) {
1550
1598
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1565,7 +1613,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1565
1613
  [ Opcodes.call, idx ],
1566
1614
  Opcodes.i32_from_u,
1567
1615
 
1568
- ...setLastType(scope, TYPES.boolean)
1616
+ ...setLastType(scope, Rhemyn.types[funcName])
1569
1617
  ];
1570
1618
  }
1571
1619
 
@@ -1574,24 +1622,44 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1574
1622
  target = decl.callee.object;
1575
1623
  }
1576
1624
 
1577
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1578
- // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1625
+ if (protoName) {
1626
+ if (['search'].includes(protoName)) {
1627
+ const regex = decl.arguments[0]?.regex?.pattern;
1628
+ if (!regex) return [
1629
+ // no/bad regex arg, return -1/0 for now
1630
+ ...generate(scope, target),
1631
+ [ Opcodes.drop ],
1579
1632
 
1580
- // funcIndex[func.name] = func.index;
1581
- // funcs.push(func);
1633
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1634
+ ...setLastType(scope, Rhemyn.types[protoName])
1635
+ ];
1582
1636
 
1583
- // return [
1584
- // generate(scope, decl.callee.object)
1637
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1585
1638
 
1586
- // // call regex func
1587
- // [ Opcodes.call, func.index ],
1588
- // Opcodes.i32_from_u
1589
- // ];
1590
- // }
1639
+ if (!funcIndex[rhemynName]) {
1640
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1641
+ func.internal = true;
1591
1642
 
1592
- if (protoName) {
1593
- const protoBC = {};
1643
+ funcIndex[func.name] = func.index;
1644
+ funcs.push(func);
1645
+ }
1646
+
1647
+ const idx = funcIndex[rhemynName];
1648
+ return [
1649
+ // make string arg
1650
+ ...generate(scope, target),
1651
+ Opcodes.i32_to_u,
1652
+ ...getNodeType(scope, target),
1653
+
1654
+ // call regex func
1655
+ [ Opcodes.call, idx ],
1656
+ Opcodes.i32_from,
1657
+
1658
+ ...setLastType(scope, Rhemyn.types[protoName])
1659
+ ];
1660
+ }
1594
1661
 
1662
+ const protoBC = {};
1595
1663
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1596
1664
 
1597
1665
  if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
@@ -2005,6 +2073,16 @@ const DEFAULT_VALUE = {
2005
2073
  name: 'undefined'
2006
2074
  };
2007
2075
 
2076
+ const codeToSanitizedStr = code => {
2077
+ let out = '';
2078
+ while (code > 0) {
2079
+ out += String.fromCharCode(97 + code % 26);
2080
+ code -= 26;
2081
+ }
2082
+ return out;
2083
+ };
2084
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2085
+
2008
2086
  const unhackName = name => {
2009
2087
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2010
2088
  return name;
@@ -2012,7 +2090,7 @@ const unhackName = name => {
2012
2090
 
2013
2091
  const knownType = (scope, type) => {
2014
2092
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2015
- return type[0][1];
2093
+ return read_signedLEB128(type[0].slice(1));
2016
2094
  }
2017
2095
 
2018
2096
  if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
@@ -2299,11 +2377,6 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2299
2377
  const { type, name } = decl.left;
2300
2378
  const [ local, isGlobal ] = lookupName(scope, name);
2301
2379
 
2302
- if (type === 'ObjectPattern') {
2303
- // hack: ignore object parts of `var a = {} = 2`
2304
- return generate(scope, decl.right);
2305
- }
2306
-
2307
2380
  if (isFuncType(decl.right.type)) {
2308
2381
  // hack for a = function () { ... }
2309
2382
  decl.right.id = { name };
@@ -2376,10 +2449,160 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2376
2449
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2377
2450
  ], getNodeType(scope, decl.right), false, name, true)),
2378
2451
  [ Opcodes.local_tee, newValueTmp ],
2379
-
2380
2452
  [ Opcodes.store, 0, ValtypeSize.i32 ]
2381
2453
  ],
2382
2454
 
2455
+ ...wrapBC({
2456
+ [TYPES.uint8array]: [
2457
+ [ Opcodes.i32_add ],
2458
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2459
+
2460
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2461
+ [ Opcodes.local_get, pointerTmp ],
2462
+ [ Opcodes.i32_load8_u, 0, 4 ],
2463
+ Opcodes.i32_from_u
2464
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2465
+ [ Opcodes.local_tee, newValueTmp ],
2466
+
2467
+ Opcodes.i32_to_u,
2468
+ [ Opcodes.i32_store8, 0, 4 ]
2469
+ ],
2470
+ [TYPES.uint8clampedarray]: [
2471
+ [ Opcodes.i32_add ],
2472
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2473
+
2474
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2475
+ [ Opcodes.local_get, pointerTmp ],
2476
+ [ Opcodes.i32_load8_u, 0, 4 ],
2477
+ Opcodes.i32_from_u
2478
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2479
+ [ Opcodes.local_tee, newValueTmp ],
2480
+
2481
+ ...number(0),
2482
+ [ Opcodes.f64_max ],
2483
+ ...number(255),
2484
+ [ Opcodes.f64_min ],
2485
+ Opcodes.i32_to_u,
2486
+ [ Opcodes.i32_store8, 0, 4 ]
2487
+ ],
2488
+ [TYPES.int8array]: [
2489
+ [ Opcodes.i32_add ],
2490
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2491
+
2492
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2493
+ [ Opcodes.local_get, pointerTmp ],
2494
+ [ Opcodes.i32_load8_s, 0, 4 ],
2495
+ Opcodes.i32_from
2496
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2497
+ [ Opcodes.local_tee, newValueTmp ],
2498
+
2499
+ Opcodes.i32_to,
2500
+ [ Opcodes.i32_store8, 0, 4 ]
2501
+ ],
2502
+ [TYPES.uint16array]: [
2503
+ ...number(2, Valtype.i32),
2504
+ [ Opcodes.i32_mul ],
2505
+ [ Opcodes.i32_add ],
2506
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2507
+
2508
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2509
+ [ Opcodes.local_get, pointerTmp ],
2510
+ [ Opcodes.i32_load16_u, 0, 4 ],
2511
+ Opcodes.i32_from_u
2512
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2513
+ [ Opcodes.local_tee, newValueTmp ],
2514
+
2515
+ Opcodes.i32_to_u,
2516
+ [ Opcodes.i32_store16, 0, 4 ]
2517
+ ],
2518
+ [TYPES.int16array]: [
2519
+ ...number(2, Valtype.i32),
2520
+ [ Opcodes.i32_mul ],
2521
+ [ Opcodes.i32_add ],
2522
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2523
+
2524
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2525
+ [ Opcodes.local_get, pointerTmp ],
2526
+ [ Opcodes.i32_load16_s, 0, 4 ],
2527
+ Opcodes.i32_from
2528
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2529
+ [ Opcodes.local_tee, newValueTmp ],
2530
+
2531
+ Opcodes.i32_to,
2532
+ [ Opcodes.i32_store16, 0, 4 ]
2533
+ ],
2534
+ [TYPES.uint32array]: [
2535
+ ...number(4, Valtype.i32),
2536
+ [ Opcodes.i32_mul ],
2537
+ [ Opcodes.i32_add ],
2538
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2539
+
2540
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2541
+ [ Opcodes.local_get, pointerTmp ],
2542
+ [ Opcodes.i32_load, 0, 4 ],
2543
+ Opcodes.i32_from_u
2544
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2545
+ [ Opcodes.local_tee, newValueTmp ],
2546
+
2547
+ Opcodes.i32_to_u,
2548
+ [ Opcodes.i32_store, 0, 4 ]
2549
+ ],
2550
+ [TYPES.int32array]: [
2551
+ ...number(4, Valtype.i32),
2552
+ [ Opcodes.i32_mul ],
2553
+ [ Opcodes.i32_add ],
2554
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2555
+
2556
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2557
+ [ Opcodes.local_get, pointerTmp ],
2558
+ [ Opcodes.i32_load, 0, 4 ],
2559
+ Opcodes.i32_from
2560
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2561
+ [ Opcodes.local_tee, newValueTmp ],
2562
+
2563
+ Opcodes.i32_to,
2564
+ [ Opcodes.i32_store, 0, 4 ]
2565
+ ],
2566
+ [TYPES.float32array]: [
2567
+ ...number(4, Valtype.i32),
2568
+ [ Opcodes.i32_mul ],
2569
+ [ Opcodes.i32_add ],
2570
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2571
+
2572
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2573
+ [ Opcodes.local_get, pointerTmp ],
2574
+ [ Opcodes.f32_load, 0, 4 ],
2575
+ [ Opcodes.f64_promote_f32 ]
2576
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2577
+ [ Opcodes.local_tee, newValueTmp ],
2578
+
2579
+ [ Opcodes.f32_demote_f64 ],
2580
+ [ Opcodes.f32_store, 0, 4 ]
2581
+ ],
2582
+ [TYPES.float64array]: [
2583
+ ...number(8, Valtype.i32),
2584
+ [ Opcodes.i32_mul ],
2585
+ [ Opcodes.i32_add ],
2586
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2587
+
2588
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2589
+ [ Opcodes.local_get, pointerTmp ],
2590
+ [ Opcodes.f64_load, 0, 4 ]
2591
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2592
+ [ Opcodes.local_tee, newValueTmp ],
2593
+
2594
+ [ Opcodes.f64_store, 0, 4 ]
2595
+ ],
2596
+ }, {
2597
+ prelude: [
2598
+ ...generate(scope, decl.left.object),
2599
+ Opcodes.i32_to_u,
2600
+ ...generate(scope, decl.left.property),
2601
+ Opcodes.i32_to_u,
2602
+ ],
2603
+ postlude: setLastType(scope, TYPES.number)
2604
+ }),
2605
+
2383
2606
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2384
2607
  }, Blocktype.void),
2385
2608
 
@@ -2763,7 +2986,9 @@ const generateForOf = (scope, decl) => {
2763
2986
  // // todo: we should only do this for strings but we don't know at compile-time :(
2764
2987
  // hack: this is naughty and will break things!
2765
2988
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2766
- if (pages.hasAnyString) {
2989
+
2990
+ const known = knownType(scope, getNodeType(scope, decl.right));
2991
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
2767
2992
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2768
2993
  0, [ newOut, newPointer ] = makeArray(scope, {
2769
2994
  rawElements: new Array(0)
@@ -2811,6 +3036,7 @@ const generateForOf = (scope, decl) => {
2811
3036
  [ Opcodes.end ],
2812
3037
  [ Opcodes.end ]
2813
3038
  ],
3039
+
2814
3040
  [TYPES.string]: [
2815
3041
  ...setType(scope, leftName, TYPES.string),
2816
3042
 
@@ -2919,6 +3145,7 @@ const generateForOf = (scope, decl) => {
2919
3145
  [ Opcodes.end ],
2920
3146
  [ Opcodes.end ]
2921
3147
  ],
3148
+
2922
3149
  [TYPES.set]: [
2923
3150
  [ Opcodes.loop, Blocktype.void ],
2924
3151
 
@@ -2957,6 +3184,106 @@ const generateForOf = (scope, decl) => {
2957
3184
  [ Opcodes.end ],
2958
3185
  [ Opcodes.end ]
2959
3186
  ],
3187
+
3188
+ ...wrapBC({
3189
+ [TYPES.uint8array]: [
3190
+ [ Opcodes.i32_add ],
3191
+
3192
+ [ Opcodes.i32_load8_u, 0, 4 ],
3193
+ Opcodes.i32_from_u
3194
+ ],
3195
+ [TYPES.uint8clampedarray]: [
3196
+ [ Opcodes.i32_add ],
3197
+
3198
+ [ Opcodes.i32_load8_u, 0, 4 ],
3199
+ Opcodes.i32_from_u
3200
+ ],
3201
+ [TYPES.int8array]: [
3202
+ [ Opcodes.i32_add ],
3203
+
3204
+ [ Opcodes.i32_load8_s, 0, 4 ],
3205
+ Opcodes.i32_from
3206
+ ],
3207
+ [TYPES.uint16array]: [
3208
+ ...number(2, Valtype.i32),
3209
+ [ Opcodes.i32_mul ],
3210
+ [ Opcodes.i32_add ],
3211
+
3212
+ [ Opcodes.i32_load16_u, 0, 4 ],
3213
+ Opcodes.i32_from_u
3214
+ ],
3215
+ [TYPES.int16array]: [
3216
+ ...number(2, Valtype.i32),
3217
+ [ Opcodes.i32_mul ],
3218
+ [ Opcodes.i32_add ],
3219
+
3220
+ [ Opcodes.i32_load16_s, 0, 4 ],
3221
+ Opcodes.i32_from
3222
+ ],
3223
+ [TYPES.uint32array]: [
3224
+ ...number(4, Valtype.i32),
3225
+ [ Opcodes.i32_mul ],
3226
+ [ Opcodes.i32_add ],
3227
+
3228
+ [ Opcodes.i32_load, 0, 4 ],
3229
+ Opcodes.i32_from_u
3230
+ ],
3231
+ [TYPES.int32array]: [
3232
+ ...number(4, Valtype.i32),
3233
+ [ Opcodes.i32_mul ],
3234
+ [ Opcodes.i32_add ],
3235
+
3236
+ [ Opcodes.i32_load, 0, 4 ],
3237
+ Opcodes.i32_from
3238
+ ],
3239
+ [TYPES.float32array]: [
3240
+ ...number(4, Valtype.i32),
3241
+ [ Opcodes.i32_mul ],
3242
+ [ Opcodes.i32_add ],
3243
+
3244
+ [ Opcodes.f32_load, 0, 4 ],
3245
+ [ Opcodes.f64_promote_f32 ]
3246
+ ],
3247
+ [TYPES.float64array]: [
3248
+ ...number(8, Valtype.i32),
3249
+ [ Opcodes.i32_mul ],
3250
+ [ Opcodes.i32_add ],
3251
+
3252
+ [ Opcodes.f64_load, 0, 4 ]
3253
+ ],
3254
+ }, {
3255
+ prelude: [
3256
+ ...setType(scope, leftName, TYPES.number),
3257
+
3258
+ [ Opcodes.loop, Blocktype.void ],
3259
+
3260
+ [ Opcodes.local_get, pointer ],
3261
+ [ Opcodes.local_get, counter ]
3262
+ ],
3263
+ postlude: [
3264
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3265
+
3266
+ [ Opcodes.block, Blocktype.void ],
3267
+ [ Opcodes.block, Blocktype.void ],
3268
+ ...generate(scope, decl.body),
3269
+ [ Opcodes.end ],
3270
+
3271
+ // increment counter by 1
3272
+ [ Opcodes.local_get, counter ],
3273
+ ...number(1, Valtype.i32),
3274
+ [ Opcodes.i32_add ],
3275
+ [ Opcodes.local_tee, counter ],
3276
+
3277
+ // loop if counter != length
3278
+ [ Opcodes.local_get, length ],
3279
+ [ Opcodes.i32_ne ],
3280
+ [ Opcodes.br_if, 1 ],
3281
+
3282
+ [ Opcodes.end ],
3283
+ [ Opcodes.end ]
3284
+ ]
3285
+ }),
3286
+
2960
3287
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2961
3288
  }, Blocktype.void));
2962
3289
 
@@ -3029,32 +3356,102 @@ const generateLabel = (scope, decl) => {
3029
3356
  const generateThrow = (scope, decl) => {
3030
3357
  scope.throws = true;
3031
3358
 
3032
- let message = decl.argument.value, constructor = null;
3359
+ const exceptionMode = Prefs.exceptionMode ?? 'lut';
3360
+ if (exceptionMode === 'lut') {
3361
+ let message = decl.argument.value, constructor = null;
3033
3362
 
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 ?? '';
3363
+ // support `throw (new)? Error(...)`
3364
+ if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
3365
+ constructor = decl.argument.callee.name;
3366
+ message = decl.argument.arguments[0]?.value ?? '';
3367
+ }
3368
+
3369
+ if (tags.length === 0) tags.push({
3370
+ params: [ Valtype.i32 ],
3371
+ results: [],
3372
+ idx: tags.length
3373
+ });
3374
+
3375
+ let exceptId = exceptions.findIndex(x => x.constructor === constructor && x.message === message);
3376
+ if (exceptId === -1) exceptId = exceptions.push({ constructor, message }) - 1;
3377
+
3378
+ scope.exceptions ??= [];
3379
+ scope.exceptions.push(exceptId);
3380
+
3381
+ return [
3382
+ ...number(exceptId, Valtype.i32),
3383
+ [ Opcodes.throw, tags[0].idx ]
3384
+ ];
3038
3385
  }
3039
3386
 
3040
- if (tags.length === 0) tags.push({
3041
- params: [ Valtype.i32 ],
3042
- results: [],
3043
- idx: tags.length
3044
- });
3387
+ if (exceptionMode === 'stack') {
3388
+ if (tags.length === 0) tags.push({
3389
+ params: [ valtypeBinary, Valtype.i32 ],
3390
+ results: [],
3391
+ idx: tags.length
3392
+ });
3045
3393
 
3046
- let exceptId = exceptions.push({ constructor, message }) - 1;
3047
- let tagIdx = tags[0].idx;
3394
+ return [
3395
+ ...generate(scope, decl.argument),
3396
+ ...getNodeType(scope, decl.argument),
3397
+ [ Opcodes.throw, tags[0].idx ]
3398
+ ];
3399
+ }
3048
3400
 
3049
- scope.exceptions ??= [];
3050
- scope.exceptions.push(exceptId);
3401
+ if (exceptionMode === 'stackest') {
3402
+ let message = decl.argument, constructor = null;
3051
3403
 
3052
- // todo: write a description of how this works lol
3404
+ // support `throw (new)? Error(...)`
3405
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3406
+ constructor = decl.argument.callee;
3407
+ message = decl.argument.arguments[0];
3408
+ }
3053
3409
 
3054
- return [
3055
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3056
- [ Opcodes.throw, tagIdx ]
3057
- ];
3410
+ message ??= DEFAULT_VALUE;
3411
+
3412
+ if (tags.length === 0) tags.push({
3413
+ params: [ valtypeBinary, valtypeBinary, Valtype.i32 ],
3414
+ results: [],
3415
+ idx: tags.length
3416
+ });
3417
+
3418
+ return [
3419
+ ...(constructor == null ? number(-1) : generate(scope, constructor)),
3420
+ ...generate(scope, message),
3421
+ ...getNodeType(scope, message),
3422
+ [ Opcodes.throw, tags[0].idx ]
3423
+ ];
3424
+ }
3425
+
3426
+ if (exceptionMode === 'partial') {
3427
+ let message = decl.argument, constructor = null;
3428
+
3429
+ // support `throw (new)? Error(...)`
3430
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3431
+ constructor = decl.argument.callee.name;
3432
+ message = decl.argument.arguments[0];
3433
+ }
3434
+
3435
+ message ??= DEFAULT_VALUE;
3436
+
3437
+ if (tags.length === 0) tags.push({
3438
+ params: [ Valtype.i32, valtypeBinary, Valtype.i32 ],
3439
+ results: [],
3440
+ idx: tags.length
3441
+ });
3442
+
3443
+ let exceptId = exceptions.push({ constructor }) - 1;
3444
+
3445
+ scope.exceptions ??= [];
3446
+ scope.exceptions.push(exceptId);
3447
+
3448
+ return [
3449
+ ...number(exceptId, Valtype.i32),
3450
+ ...generate(scope, message),
3451
+ ...getNodeType(scope, message),
3452
+ [ Opcodes.throw, tags[0].idx ]
3453
+ ];
3454
+ }
3058
3455
  };
3059
3456
 
3060
3457
  const generateTry = (scope, decl) => {
@@ -3407,6 +3804,19 @@ const withType = (scope, wasm, type) => [
3407
3804
  ...setLastType(scope, type)
3408
3805
  ];
3409
3806
 
3807
+ const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
3808
+ const out = {};
3809
+ for (const x in bc) {
3810
+ out[x] = [
3811
+ ...prelude,
3812
+ ...bc[x],
3813
+ ...postlude
3814
+ ];
3815
+ }
3816
+
3817
+ return out;
3818
+ };
3819
+
3410
3820
  const generateMember = (scope, decl, _global, _name) => {
3411
3821
  const name = decl.object.name;
3412
3822
 
@@ -3460,7 +3870,7 @@ const generateMember = (scope, decl, _global, _name) => {
3460
3870
  const type = getNodeType(scope, decl.object);
3461
3871
  const known = knownType(scope, type);
3462
3872
  if (known != null) {
3463
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3873
+ if (typeHasFlag(known, TYPE_FLAGS.length)) return [
3464
3874
  ...generate(scope, decl.object),
3465
3875
  Opcodes.i32_to_u,
3466
3876
 
@@ -3472,7 +3882,9 @@ const generateMember = (scope, decl, _global, _name) => {
3472
3882
  }
3473
3883
 
3474
3884
  return [
3475
- ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3885
+ ...getNodeType(scope, decl.object),
3886
+ ...number(TYPE_FLAGS.length, Valtype.i32),
3887
+ [ Opcodes.i32_and ],
3476
3888
  [ Opcodes.if, valtypeBinary ],
3477
3889
  ...generate(scope, decl.object),
3478
3890
  Opcodes.i32_to_u,
@@ -3489,7 +3901,7 @@ const generateMember = (scope, decl, _global, _name) => {
3489
3901
  }
3490
3902
 
3491
3903
  // todo: generate this array procedurally during builtinFuncs creation
3492
- if (['size', 'description'].includes(decl.property.name)) {
3904
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3493
3905
  const bc = {};
3494
3906
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3495
3907
 
@@ -3521,7 +3933,9 @@ const generateMember = (scope, decl, _global, _name) => {
3521
3933
  // // todo: we should only do this for strings but we don't know at compile-time :(
3522
3934
  // hack: this is naughty and will break things!
3523
3935
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3524
- if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
3936
+
3937
+ const known = knownType(scope, getNodeType(scope, decl.object));
3938
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
3525
3939
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3526
3940
  0, [ newOut, newPointer ] = makeArray(scope, {
3527
3941
  rawElements: new Array(0)
@@ -3595,6 +4009,82 @@ const generateMember = (scope, decl, _global, _name) => {
3595
4009
  ...setLastType(scope, TYPES.bytestring)
3596
4010
  ],
3597
4011
 
4012
+ ...wrapBC({
4013
+ [TYPES.uint8array]: [
4014
+ [ Opcodes.i32_add ],
4015
+
4016
+ [ Opcodes.i32_load8_u, 0, 4 ],
4017
+ Opcodes.i32_from_u
4018
+ ],
4019
+ [TYPES.uint8clampedarray]: [
4020
+ [ Opcodes.i32_add ],
4021
+
4022
+ [ Opcodes.i32_load8_u, 0, 4 ],
4023
+ Opcodes.i32_from_u
4024
+ ],
4025
+ [TYPES.int8array]: [
4026
+ [ Opcodes.i32_add ],
4027
+
4028
+ [ Opcodes.i32_load8_s, 0, 4 ],
4029
+ Opcodes.i32_from
4030
+ ],
4031
+ [TYPES.uint16array]: [
4032
+ ...number(2, Valtype.i32),
4033
+ [ Opcodes.i32_mul ],
4034
+ [ Opcodes.i32_add ],
4035
+
4036
+ [ Opcodes.i32_load16_u, 0, 4 ],
4037
+ Opcodes.i32_from_u
4038
+ ],
4039
+ [TYPES.int16array]: [
4040
+ ...number(2, Valtype.i32),
4041
+ [ Opcodes.i32_mul ],
4042
+ [ Opcodes.i32_add ],
4043
+
4044
+ [ Opcodes.i32_load16_s, 0, 4 ],
4045
+ Opcodes.i32_from
4046
+ ],
4047
+ [TYPES.uint32array]: [
4048
+ ...number(4, Valtype.i32),
4049
+ [ Opcodes.i32_mul ],
4050
+ [ Opcodes.i32_add ],
4051
+
4052
+ [ Opcodes.i32_load, 0, 4 ],
4053
+ Opcodes.i32_from_u
4054
+ ],
4055
+ [TYPES.int32array]: [
4056
+ ...number(4, Valtype.i32),
4057
+ [ Opcodes.i32_mul ],
4058
+ [ Opcodes.i32_add ],
4059
+
4060
+ [ Opcodes.i32_load, 0, 4 ],
4061
+ Opcodes.i32_from
4062
+ ],
4063
+ [TYPES.float32array]: [
4064
+ ...number(4, Valtype.i32),
4065
+ [ Opcodes.i32_mul ],
4066
+ [ Opcodes.i32_add ],
4067
+
4068
+ [ Opcodes.f32_load, 0, 4 ],
4069
+ [ Opcodes.f64_promote_f32 ]
4070
+ ],
4071
+ [TYPES.float64array]: [
4072
+ ...number(8, Valtype.i32),
4073
+ [ Opcodes.i32_mul ],
4074
+ [ Opcodes.i32_add ],
4075
+
4076
+ [ Opcodes.f64_load, 0, 4 ]
4077
+ ],
4078
+ }, {
4079
+ prelude: [
4080
+ ...object,
4081
+ Opcodes.i32_to_u,
4082
+ ...property,
4083
+ Opcodes.i32_to_u
4084
+ ],
4085
+ postlude: setLastType(scope, TYPES.number)
4086
+ }),
4087
+
3598
4088
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3599
4089
  });
3600
4090
  };
@@ -3617,7 +4107,7 @@ const objectHack = node => {
3617
4107
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3618
4108
 
3619
4109
  // if .name or .length, give up (hack within a hack!)
3620
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4110
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3621
4111
  node.object = objectHack(node.object);
3622
4112
  return;
3623
4113
  }
@@ -3772,7 +4262,7 @@ const internalConstrs = {
3772
4262
 
3773
4263
  // todo: check in wasm instead of here
3774
4264
  const literalValue = arg.value ?? 0;
3775
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
4265
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeError', 'Invalid array length', true);
3776
4266
 
3777
4267
  return [
3778
4268
  ...out,