porffor 0.17.0-bf4206d7b → 0.17.0-c2af76d41

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);
@@ -990,6 +1026,7 @@ const asmFuncToAsm = (func, scope) => {
990
1026
  idx = funcIndex[n];
991
1027
  }
992
1028
 
1029
+ if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
993
1030
  return idx;
994
1031
  }
995
1032
  });
@@ -1028,7 +1065,10 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1028
1065
  funcs.push(func);
1029
1066
  funcIndex[name] = func.index;
1030
1067
 
1031
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1068
+ if (typeof wasm === 'function') {
1069
+ if (globalThis.precompile) wasm = [];
1070
+ else wasm = asmFuncToAsm(wasm, func);
1071
+ }
1032
1072
 
1033
1073
  let baseGlobalIdx, i = 0;
1034
1074
  for (const type of globalTypes) {
@@ -1279,7 +1319,7 @@ const getNodeType = (scope, node) => {
1279
1319
  }
1280
1320
 
1281
1321
  if (node.type === 'BinaryExpression') {
1282
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1322
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<=', 'instanceof'].includes(node.operator)) return TYPES.boolean;
1283
1323
  if (node.operator !== '+') return TYPES.number;
1284
1324
 
1285
1325
  const knownLeft = knownType(scope, getNodeType(scope, node.left));
@@ -1312,7 +1352,7 @@ const getNodeType = (scope, node) => {
1312
1352
  const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1313
1353
  if (objectKnownType != null) {
1314
1354
  if (name === 'length') {
1315
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1355
+ if (typeHasFlag(objectKnownType, TYPE_FLAGS.length)) return TYPES.number;
1316
1356
  else return TYPES.undefined;
1317
1357
  }
1318
1358
 
@@ -1384,9 +1424,9 @@ const countLeftover = wasm => {
1384
1424
 
1385
1425
  if (depth === 0)
1386
1426
  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)) {}
1427
+ 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
1428
  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;
1429
+ 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
1430
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1391
1431
  else if (inst[0] === Opcodes.return) count = 0;
1392
1432
  else if (inst[0] === Opcodes.call) {
@@ -1424,6 +1464,18 @@ const generateExp = (scope, decl) => {
1424
1464
  return out;
1425
1465
  };
1426
1466
 
1467
+ const generateSequence = (scope, decl) => {
1468
+ let out = [];
1469
+
1470
+ const exprs = decl.expressions;
1471
+ for (let i = 0; i < exprs.length; i++) {
1472
+ if (i > 0) disposeLeftover(out);
1473
+ out.push(...generate(scope, exprs[i]));
1474
+ }
1475
+
1476
+ return out;
1477
+ };
1478
+
1427
1479
  const CTArrayUtil = {
1428
1480
  getLengthI32: pointer => [
1429
1481
  ...number(0, Valtype.i32),
@@ -1542,9 +1594,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1542
1594
  if (!name && decl.callee.type === 'MemberExpression') {
1543
1595
  // megahack for /regex/.func()
1544
1596
  const funcName = decl.callee.property.name;
1545
- if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1597
+ if (decl.callee.object.regex && ['test'].includes(funcName)) {
1546
1598
  const regex = decl.callee.object.regex.pattern;
1547
- const rhemynName = `regex_${funcName}_${regex}`;
1599
+ const rhemynName = `regex_${funcName}_${sanitize(regex)}`;
1548
1600
 
1549
1601
  if (!funcIndex[rhemynName]) {
1550
1602
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
@@ -1565,7 +1617,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1565
1617
  [ Opcodes.call, idx ],
1566
1618
  Opcodes.i32_from_u,
1567
1619
 
1568
- ...setLastType(scope, TYPES.boolean)
1620
+ ...setLastType(scope, Rhemyn.types[funcName])
1569
1621
  ];
1570
1622
  }
1571
1623
 
@@ -1574,24 +1626,44 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1574
1626
  target = decl.callee.object;
1575
1627
  }
1576
1628
 
1577
- // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1578
- // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1629
+ if (protoName) {
1630
+ if (['search'].includes(protoName)) {
1631
+ const regex = decl.arguments[0]?.regex?.pattern;
1632
+ if (!regex) return [
1633
+ // no/bad regex arg, return -1/0 for now
1634
+ ...generate(scope, target),
1635
+ [ Opcodes.drop ],
1579
1636
 
1580
- // funcIndex[func.name] = func.index;
1581
- // funcs.push(func);
1637
+ ...number(Rhemyn.types[protoName] === TYPES.number ? -1 : 0),
1638
+ ...setLastType(scope, Rhemyn.types[protoName])
1639
+ ];
1582
1640
 
1583
- // return [
1584
- // generate(scope, decl.callee.object)
1641
+ const rhemynName = `regex_${protoName}_${sanitize(regex)}`;
1585
1642
 
1586
- // // call regex func
1587
- // [ Opcodes.call, func.index ],
1588
- // Opcodes.i32_from_u
1589
- // ];
1590
- // }
1643
+ if (!funcIndex[rhemynName]) {
1644
+ const func = Rhemyn[protoName](regex, currentFuncIndex++, rhemynName);
1645
+ func.internal = true;
1591
1646
 
1592
- if (protoName) {
1593
- const protoBC = {};
1647
+ funcIndex[func.name] = func.index;
1648
+ funcs.push(func);
1649
+ }
1650
+
1651
+ const idx = funcIndex[rhemynName];
1652
+ return [
1653
+ // make string arg
1654
+ ...generate(scope, target),
1655
+ Opcodes.i32_to_u,
1656
+ ...getNodeType(scope, target),
1657
+
1658
+ // call regex func
1659
+ [ Opcodes.call, idx ],
1660
+ Opcodes.i32_from,
1594
1661
 
1662
+ ...setLastType(scope, Rhemyn.types[protoName])
1663
+ ];
1664
+ }
1665
+
1666
+ const protoBC = {};
1595
1667
  const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1596
1668
 
1597
1669
  if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
@@ -2005,6 +2077,16 @@ const DEFAULT_VALUE = {
2005
2077
  name: 'undefined'
2006
2078
  };
2007
2079
 
2080
+ const codeToSanitizedStr = code => {
2081
+ let out = '';
2082
+ while (code > 0) {
2083
+ out += String.fromCharCode(97 + code % 26);
2084
+ code -= 26;
2085
+ }
2086
+ return out;
2087
+ };
2088
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
2089
+
2008
2090
  const unhackName = name => {
2009
2091
  if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
2010
2092
  return name;
@@ -2012,7 +2094,7 @@ const unhackName = name => {
2012
2094
 
2013
2095
  const knownType = (scope, type) => {
2014
2096
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2015
- return type[0][1];
2097
+ return read_signedLEB128(type[0].slice(1));
2016
2098
  }
2017
2099
 
2018
2100
  if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
@@ -2299,11 +2381,6 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2299
2381
  const { type, name } = decl.left;
2300
2382
  const [ local, isGlobal ] = lookupName(scope, name);
2301
2383
 
2302
- if (type === 'ObjectPattern') {
2303
- // hack: ignore object parts of `var a = {} = 2`
2304
- return generate(scope, decl.right);
2305
- }
2306
-
2307
2384
  if (isFuncType(decl.right.type)) {
2308
2385
  // hack for a = function () { ... }
2309
2386
  decl.right.id = { name };
@@ -2376,10 +2453,160 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2376
2453
  [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2377
2454
  ], getNodeType(scope, decl.right), false, name, true)),
2378
2455
  [ Opcodes.local_tee, newValueTmp ],
2379
-
2380
2456
  [ Opcodes.store, 0, ValtypeSize.i32 ]
2381
2457
  ],
2382
2458
 
2459
+ ...wrapBC({
2460
+ [TYPES.uint8array]: [
2461
+ [ Opcodes.i32_add ],
2462
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2463
+
2464
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2465
+ [ Opcodes.local_get, pointerTmp ],
2466
+ [ Opcodes.i32_load8_u, 0, 4 ],
2467
+ Opcodes.i32_from_u
2468
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2469
+ [ Opcodes.local_tee, newValueTmp ],
2470
+
2471
+ Opcodes.i32_to_u,
2472
+ [ Opcodes.i32_store8, 0, 4 ]
2473
+ ],
2474
+ [TYPES.uint8clampedarray]: [
2475
+ [ Opcodes.i32_add ],
2476
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2477
+
2478
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2479
+ [ Opcodes.local_get, pointerTmp ],
2480
+ [ Opcodes.i32_load8_u, 0, 4 ],
2481
+ Opcodes.i32_from_u
2482
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2483
+ [ Opcodes.local_tee, newValueTmp ],
2484
+
2485
+ ...number(0),
2486
+ [ Opcodes.f64_max ],
2487
+ ...number(255),
2488
+ [ Opcodes.f64_min ],
2489
+ Opcodes.i32_to_u,
2490
+ [ Opcodes.i32_store8, 0, 4 ]
2491
+ ],
2492
+ [TYPES.int8array]: [
2493
+ [ Opcodes.i32_add ],
2494
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2495
+
2496
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2497
+ [ Opcodes.local_get, pointerTmp ],
2498
+ [ Opcodes.i32_load8_s, 0, 4 ],
2499
+ Opcodes.i32_from
2500
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2501
+ [ Opcodes.local_tee, newValueTmp ],
2502
+
2503
+ Opcodes.i32_to,
2504
+ [ Opcodes.i32_store8, 0, 4 ]
2505
+ ],
2506
+ [TYPES.uint16array]: [
2507
+ ...number(2, Valtype.i32),
2508
+ [ Opcodes.i32_mul ],
2509
+ [ Opcodes.i32_add ],
2510
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2511
+
2512
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2513
+ [ Opcodes.local_get, pointerTmp ],
2514
+ [ Opcodes.i32_load16_u, 0, 4 ],
2515
+ Opcodes.i32_from_u
2516
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2517
+ [ Opcodes.local_tee, newValueTmp ],
2518
+
2519
+ Opcodes.i32_to_u,
2520
+ [ Opcodes.i32_store16, 0, 4 ]
2521
+ ],
2522
+ [TYPES.int16array]: [
2523
+ ...number(2, Valtype.i32),
2524
+ [ Opcodes.i32_mul ],
2525
+ [ Opcodes.i32_add ],
2526
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2527
+
2528
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2529
+ [ Opcodes.local_get, pointerTmp ],
2530
+ [ Opcodes.i32_load16_s, 0, 4 ],
2531
+ Opcodes.i32_from
2532
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2533
+ [ Opcodes.local_tee, newValueTmp ],
2534
+
2535
+ Opcodes.i32_to,
2536
+ [ Opcodes.i32_store16, 0, 4 ]
2537
+ ],
2538
+ [TYPES.uint32array]: [
2539
+ ...number(4, Valtype.i32),
2540
+ [ Opcodes.i32_mul ],
2541
+ [ Opcodes.i32_add ],
2542
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2543
+
2544
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2545
+ [ Opcodes.local_get, pointerTmp ],
2546
+ [ Opcodes.i32_load, 0, 4 ],
2547
+ Opcodes.i32_from_u
2548
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2549
+ [ Opcodes.local_tee, newValueTmp ],
2550
+
2551
+ Opcodes.i32_to_u,
2552
+ [ Opcodes.i32_store, 0, 4 ]
2553
+ ],
2554
+ [TYPES.int32array]: [
2555
+ ...number(4, Valtype.i32),
2556
+ [ Opcodes.i32_mul ],
2557
+ [ Opcodes.i32_add ],
2558
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2559
+
2560
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2561
+ [ Opcodes.local_get, pointerTmp ],
2562
+ [ Opcodes.i32_load, 0, 4 ],
2563
+ Opcodes.i32_from
2564
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2565
+ [ Opcodes.local_tee, newValueTmp ],
2566
+
2567
+ Opcodes.i32_to,
2568
+ [ Opcodes.i32_store, 0, 4 ]
2569
+ ],
2570
+ [TYPES.float32array]: [
2571
+ ...number(4, Valtype.i32),
2572
+ [ Opcodes.i32_mul ],
2573
+ [ Opcodes.i32_add ],
2574
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2575
+
2576
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2577
+ [ Opcodes.local_get, pointerTmp ],
2578
+ [ Opcodes.f32_load, 0, 4 ],
2579
+ [ Opcodes.f64_promote_f32 ]
2580
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2581
+ [ Opcodes.local_tee, newValueTmp ],
2582
+
2583
+ [ Opcodes.f32_demote_f64 ],
2584
+ [ Opcodes.f32_store, 0, 4 ]
2585
+ ],
2586
+ [TYPES.float64array]: [
2587
+ ...number(8, Valtype.i32),
2588
+ [ Opcodes.i32_mul ],
2589
+ [ Opcodes.i32_add ],
2590
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2591
+
2592
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2593
+ [ Opcodes.local_get, pointerTmp ],
2594
+ [ Opcodes.f64_load, 0, 4 ]
2595
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2596
+ [ Opcodes.local_tee, newValueTmp ],
2597
+
2598
+ [ Opcodes.f64_store, 0, 4 ]
2599
+ ],
2600
+ }, {
2601
+ prelude: [
2602
+ ...generate(scope, decl.left.object),
2603
+ Opcodes.i32_to_u,
2604
+ ...generate(scope, decl.left.property),
2605
+ Opcodes.i32_to_u,
2606
+ ],
2607
+ postlude: setLastType(scope, TYPES.number)
2608
+ }),
2609
+
2383
2610
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2384
2611
  }, Blocktype.void),
2385
2612
 
@@ -2763,7 +2990,9 @@ const generateForOf = (scope, decl) => {
2763
2990
  // // todo: we should only do this for strings but we don't know at compile-time :(
2764
2991
  // hack: this is naughty and will break things!
2765
2992
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2766
- if (pages.hasAnyString) {
2993
+
2994
+ const known = knownType(scope, getNodeType(scope, decl.right));
2995
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
2767
2996
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2768
2997
  0, [ newOut, newPointer ] = makeArray(scope, {
2769
2998
  rawElements: new Array(0)
@@ -2811,6 +3040,7 @@ const generateForOf = (scope, decl) => {
2811
3040
  [ Opcodes.end ],
2812
3041
  [ Opcodes.end ]
2813
3042
  ],
3043
+
2814
3044
  [TYPES.string]: [
2815
3045
  ...setType(scope, leftName, TYPES.string),
2816
3046
 
@@ -2919,6 +3149,7 @@ const generateForOf = (scope, decl) => {
2919
3149
  [ Opcodes.end ],
2920
3150
  [ Opcodes.end ]
2921
3151
  ],
3152
+
2922
3153
  [TYPES.set]: [
2923
3154
  [ Opcodes.loop, Blocktype.void ],
2924
3155
 
@@ -2957,6 +3188,106 @@ const generateForOf = (scope, decl) => {
2957
3188
  [ Opcodes.end ],
2958
3189
  [ Opcodes.end ]
2959
3190
  ],
3191
+
3192
+ ...wrapBC({
3193
+ [TYPES.uint8array]: [
3194
+ [ Opcodes.i32_add ],
3195
+
3196
+ [ Opcodes.i32_load8_u, 0, 4 ],
3197
+ Opcodes.i32_from_u
3198
+ ],
3199
+ [TYPES.uint8clampedarray]: [
3200
+ [ Opcodes.i32_add ],
3201
+
3202
+ [ Opcodes.i32_load8_u, 0, 4 ],
3203
+ Opcodes.i32_from_u
3204
+ ],
3205
+ [TYPES.int8array]: [
3206
+ [ Opcodes.i32_add ],
3207
+
3208
+ [ Opcodes.i32_load8_s, 0, 4 ],
3209
+ Opcodes.i32_from
3210
+ ],
3211
+ [TYPES.uint16array]: [
3212
+ ...number(2, Valtype.i32),
3213
+ [ Opcodes.i32_mul ],
3214
+ [ Opcodes.i32_add ],
3215
+
3216
+ [ Opcodes.i32_load16_u, 0, 4 ],
3217
+ Opcodes.i32_from_u
3218
+ ],
3219
+ [TYPES.int16array]: [
3220
+ ...number(2, Valtype.i32),
3221
+ [ Opcodes.i32_mul ],
3222
+ [ Opcodes.i32_add ],
3223
+
3224
+ [ Opcodes.i32_load16_s, 0, 4 ],
3225
+ Opcodes.i32_from
3226
+ ],
3227
+ [TYPES.uint32array]: [
3228
+ ...number(4, Valtype.i32),
3229
+ [ Opcodes.i32_mul ],
3230
+ [ Opcodes.i32_add ],
3231
+
3232
+ [ Opcodes.i32_load, 0, 4 ],
3233
+ Opcodes.i32_from_u
3234
+ ],
3235
+ [TYPES.int32array]: [
3236
+ ...number(4, Valtype.i32),
3237
+ [ Opcodes.i32_mul ],
3238
+ [ Opcodes.i32_add ],
3239
+
3240
+ [ Opcodes.i32_load, 0, 4 ],
3241
+ Opcodes.i32_from
3242
+ ],
3243
+ [TYPES.float32array]: [
3244
+ ...number(4, Valtype.i32),
3245
+ [ Opcodes.i32_mul ],
3246
+ [ Opcodes.i32_add ],
3247
+
3248
+ [ Opcodes.f32_load, 0, 4 ],
3249
+ [ Opcodes.f64_promote_f32 ]
3250
+ ],
3251
+ [TYPES.float64array]: [
3252
+ ...number(8, Valtype.i32),
3253
+ [ Opcodes.i32_mul ],
3254
+ [ Opcodes.i32_add ],
3255
+
3256
+ [ Opcodes.f64_load, 0, 4 ]
3257
+ ],
3258
+ }, {
3259
+ prelude: [
3260
+ ...setType(scope, leftName, TYPES.number),
3261
+
3262
+ [ Opcodes.loop, Blocktype.void ],
3263
+
3264
+ [ Opcodes.local_get, pointer ],
3265
+ [ Opcodes.local_get, counter ]
3266
+ ],
3267
+ postlude: [
3268
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3269
+
3270
+ [ Opcodes.block, Blocktype.void ],
3271
+ [ Opcodes.block, Blocktype.void ],
3272
+ ...generate(scope, decl.body),
3273
+ [ Opcodes.end ],
3274
+
3275
+ // increment counter by 1
3276
+ [ Opcodes.local_get, counter ],
3277
+ ...number(1, Valtype.i32),
3278
+ [ Opcodes.i32_add ],
3279
+ [ Opcodes.local_tee, counter ],
3280
+
3281
+ // loop if counter != length
3282
+ [ Opcodes.local_get, length ],
3283
+ [ Opcodes.i32_ne ],
3284
+ [ Opcodes.br_if, 1 ],
3285
+
3286
+ [ Opcodes.end ],
3287
+ [ Opcodes.end ]
3288
+ ]
3289
+ }),
3290
+
2960
3291
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2961
3292
  }, Blocktype.void));
2962
3293
 
@@ -3029,32 +3360,102 @@ const generateLabel = (scope, decl) => {
3029
3360
  const generateThrow = (scope, decl) => {
3030
3361
  scope.throws = true;
3031
3362
 
3032
- let message = decl.argument.value, constructor = null;
3363
+ const exceptionMode = Prefs.exceptionMode ?? 'lut';
3364
+ if (exceptionMode === 'lut') {
3365
+ let message = decl.argument.value, constructor = null;
3366
+
3367
+ // support `throw (new)? Error(...)`
3368
+ if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
3369
+ constructor = decl.argument.callee.name;
3370
+ message = decl.argument.arguments[0]?.value ?? '';
3371
+ }
3372
+
3373
+ if (tags.length === 0) tags.push({
3374
+ params: [ Valtype.i32 ],
3375
+ results: [],
3376
+ idx: tags.length
3377
+ });
3378
+
3379
+ let exceptId = exceptions.findIndex(x => x.constructor === constructor && x.message === message);
3380
+ if (exceptId === -1) exceptId = exceptions.push({ constructor, message }) - 1;
3381
+
3382
+ scope.exceptions ??= [];
3383
+ scope.exceptions.push(exceptId);
3384
+
3385
+ return [
3386
+ ...number(exceptId, Valtype.i32),
3387
+ [ Opcodes.throw, tags[0].idx ]
3388
+ ];
3389
+ }
3390
+
3391
+ if (exceptionMode === 'stack') {
3392
+ if (tags.length === 0) tags.push({
3393
+ params: [ valtypeBinary, Valtype.i32 ],
3394
+ results: [],
3395
+ idx: tags.length
3396
+ });
3033
3397
 
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 ?? '';
3398
+ return [
3399
+ ...generate(scope, decl.argument),
3400
+ ...getNodeType(scope, decl.argument),
3401
+ [ Opcodes.throw, tags[0].idx ]
3402
+ ];
3038
3403
  }
3039
3404
 
3040
- if (tags.length === 0) tags.push({
3041
- params: [ Valtype.i32 ],
3042
- results: [],
3043
- idx: tags.length
3044
- });
3405
+ if (exceptionMode === 'stackest') {
3406
+ let message = decl.argument, constructor = null;
3045
3407
 
3046
- let exceptId = exceptions.push({ constructor, message }) - 1;
3047
- let tagIdx = tags[0].idx;
3408
+ // support `throw (new)? Error(...)`
3409
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3410
+ constructor = decl.argument.callee;
3411
+ message = decl.argument.arguments[0];
3412
+ }
3048
3413
 
3049
- scope.exceptions ??= [];
3050
- scope.exceptions.push(exceptId);
3414
+ message ??= DEFAULT_VALUE;
3051
3415
 
3052
- // todo: write a description of how this works lol
3416
+ if (tags.length === 0) tags.push({
3417
+ params: [ valtypeBinary, valtypeBinary, Valtype.i32 ],
3418
+ results: [],
3419
+ idx: tags.length
3420
+ });
3053
3421
 
3054
- return [
3055
- [ Opcodes.i32_const, signedLEB128(exceptId) ],
3056
- [ Opcodes.throw, tagIdx ]
3057
- ];
3422
+ return [
3423
+ ...(constructor == null ? number(-1) : generate(scope, constructor)),
3424
+ ...generate(scope, message),
3425
+ ...getNodeType(scope, message),
3426
+ [ Opcodes.throw, tags[0].idx ]
3427
+ ];
3428
+ }
3429
+
3430
+ if (exceptionMode === 'partial') {
3431
+ let message = decl.argument, constructor = null;
3432
+
3433
+ // support `throw (new)? Error(...)`
3434
+ if (message.type === 'NewExpression' || message.type === 'CallExpression') {
3435
+ constructor = decl.argument.callee.name;
3436
+ message = decl.argument.arguments[0];
3437
+ }
3438
+
3439
+ message ??= DEFAULT_VALUE;
3440
+
3441
+ if (tags.length === 0) tags.push({
3442
+ params: [ Valtype.i32, valtypeBinary, Valtype.i32 ],
3443
+ results: [],
3444
+ idx: tags.length
3445
+ });
3446
+
3447
+ let exceptId = exceptions.push({ constructor }) - 1;
3448
+
3449
+ scope.exceptions ??= [];
3450
+ scope.exceptions.push(exceptId);
3451
+
3452
+ return [
3453
+ ...number(exceptId, Valtype.i32),
3454
+ ...generate(scope, message),
3455
+ ...getNodeType(scope, message),
3456
+ [ Opcodes.throw, tags[0].idx ]
3457
+ ];
3458
+ }
3058
3459
  };
3059
3460
 
3060
3461
  const generateTry = (scope, decl) => {
@@ -3407,6 +3808,19 @@ const withType = (scope, wasm, type) => [
3407
3808
  ...setLastType(scope, type)
3408
3809
  ];
3409
3810
 
3811
+ const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
3812
+ const out = {};
3813
+ for (const x in bc) {
3814
+ out[x] = [
3815
+ ...prelude,
3816
+ ...bc[x],
3817
+ ...postlude
3818
+ ];
3819
+ }
3820
+
3821
+ return out;
3822
+ };
3823
+
3410
3824
  const generateMember = (scope, decl, _global, _name) => {
3411
3825
  const name = decl.object.name;
3412
3826
 
@@ -3460,7 +3874,7 @@ const generateMember = (scope, decl, _global, _name) => {
3460
3874
  const type = getNodeType(scope, decl.object);
3461
3875
  const known = knownType(scope, type);
3462
3876
  if (known != null) {
3463
- if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3877
+ if (typeHasFlag(known, TYPE_FLAGS.length)) return [
3464
3878
  ...generate(scope, decl.object),
3465
3879
  Opcodes.i32_to_u,
3466
3880
 
@@ -3472,7 +3886,9 @@ const generateMember = (scope, decl, _global, _name) => {
3472
3886
  }
3473
3887
 
3474
3888
  return [
3475
- ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3889
+ ...getNodeType(scope, decl.object),
3890
+ ...number(TYPE_FLAGS.length, Valtype.i32),
3891
+ [ Opcodes.i32_and ],
3476
3892
  [ Opcodes.if, valtypeBinary ],
3477
3893
  ...generate(scope, decl.object),
3478
3894
  Opcodes.i32_to_u,
@@ -3489,7 +3905,7 @@ const generateMember = (scope, decl, _global, _name) => {
3489
3905
  }
3490
3906
 
3491
3907
  // todo: generate this array procedurally during builtinFuncs creation
3492
- if (['size', 'description'].includes(decl.property.name)) {
3908
+ if (['size', 'description', 'byteLength'].includes(decl.property.name)) {
3493
3909
  const bc = {};
3494
3910
  const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3495
3911
 
@@ -3521,7 +3937,9 @@ const generateMember = (scope, decl, _global, _name) => {
3521
3937
  // // todo: we should only do this for strings but we don't know at compile-time :(
3522
3938
  // hack: this is naughty and will break things!
3523
3939
  let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3524
- if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
3940
+
3941
+ const known = knownType(scope, getNodeType(scope, decl.object));
3942
+ if ((known === TYPES.string || known === TYPES.bytestring) || (pages.hasAnyString && known == null)) {
3525
3943
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3526
3944
  0, [ newOut, newPointer ] = makeArray(scope, {
3527
3945
  rawElements: new Array(0)
@@ -3595,6 +4013,82 @@ const generateMember = (scope, decl, _global, _name) => {
3595
4013
  ...setLastType(scope, TYPES.bytestring)
3596
4014
  ],
3597
4015
 
4016
+ ...wrapBC({
4017
+ [TYPES.uint8array]: [
4018
+ [ Opcodes.i32_add ],
4019
+
4020
+ [ Opcodes.i32_load8_u, 0, 4 ],
4021
+ Opcodes.i32_from_u
4022
+ ],
4023
+ [TYPES.uint8clampedarray]: [
4024
+ [ Opcodes.i32_add ],
4025
+
4026
+ [ Opcodes.i32_load8_u, 0, 4 ],
4027
+ Opcodes.i32_from_u
4028
+ ],
4029
+ [TYPES.int8array]: [
4030
+ [ Opcodes.i32_add ],
4031
+
4032
+ [ Opcodes.i32_load8_s, 0, 4 ],
4033
+ Opcodes.i32_from
4034
+ ],
4035
+ [TYPES.uint16array]: [
4036
+ ...number(2, Valtype.i32),
4037
+ [ Opcodes.i32_mul ],
4038
+ [ Opcodes.i32_add ],
4039
+
4040
+ [ Opcodes.i32_load16_u, 0, 4 ],
4041
+ Opcodes.i32_from_u
4042
+ ],
4043
+ [TYPES.int16array]: [
4044
+ ...number(2, Valtype.i32),
4045
+ [ Opcodes.i32_mul ],
4046
+ [ Opcodes.i32_add ],
4047
+
4048
+ [ Opcodes.i32_load16_s, 0, 4 ],
4049
+ Opcodes.i32_from
4050
+ ],
4051
+ [TYPES.uint32array]: [
4052
+ ...number(4, Valtype.i32),
4053
+ [ Opcodes.i32_mul ],
4054
+ [ Opcodes.i32_add ],
4055
+
4056
+ [ Opcodes.i32_load, 0, 4 ],
4057
+ Opcodes.i32_from_u
4058
+ ],
4059
+ [TYPES.int32array]: [
4060
+ ...number(4, Valtype.i32),
4061
+ [ Opcodes.i32_mul ],
4062
+ [ Opcodes.i32_add ],
4063
+
4064
+ [ Opcodes.i32_load, 0, 4 ],
4065
+ Opcodes.i32_from
4066
+ ],
4067
+ [TYPES.float32array]: [
4068
+ ...number(4, Valtype.i32),
4069
+ [ Opcodes.i32_mul ],
4070
+ [ Opcodes.i32_add ],
4071
+
4072
+ [ Opcodes.f32_load, 0, 4 ],
4073
+ [ Opcodes.f64_promote_f32 ]
4074
+ ],
4075
+ [TYPES.float64array]: [
4076
+ ...number(8, Valtype.i32),
4077
+ [ Opcodes.i32_mul ],
4078
+ [ Opcodes.i32_add ],
4079
+
4080
+ [ Opcodes.f64_load, 0, 4 ]
4081
+ ],
4082
+ }, {
4083
+ prelude: [
4084
+ ...object,
4085
+ Opcodes.i32_to_u,
4086
+ ...property,
4087
+ Opcodes.i32_to_u
4088
+ ],
4089
+ postlude: setLastType(scope, TYPES.number)
4090
+ }),
4091
+
3598
4092
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3599
4093
  });
3600
4094
  };
@@ -3617,7 +4111,7 @@ const objectHack = node => {
3617
4111
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3618
4112
 
3619
4113
  // if .name or .length, give up (hack within a hack!)
3620
- if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
4114
+ if (['name', 'length', 'size', 'description', 'byteLength'].includes(node.property.name)) {
3621
4115
  node.object = objectHack(node.object);
3622
4116
  return;
3623
4117
  }
@@ -3772,7 +4266,7 @@ const internalConstrs = {
3772
4266
 
3773
4267
  // todo: check in wasm instead of here
3774
4268
  const literalValue = arg.value ?? 0;
3775
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
4269
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeError', 'Invalid array length', true);
3776
4270
 
3777
4271
  return [
3778
4272
  ...out,