porffor 0.0.0-e6047ea → 0.0.0-e975d7a

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.
@@ -1,9 +1,9 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4 } from "./embedding.js";
6
+ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
8
  import * as Rhemyn from "../rhemyn/compile.js";
9
9
 
@@ -326,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
326
326
  return idx;
327
327
  };
328
328
 
329
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
330
+
329
331
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
330
332
  const checks = {
331
333
  '||': falsy,
@@ -338,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
338
340
  // generic structure for {a} OP {b}
339
341
  // -->
340
342
  // _ = {a}; if (OP_CHECK) {b} else _
343
+
344
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
345
+ // (like if we are in an if condition - very common)
346
+ const leftIsInt = isIntOp(left[left.length - 1]);
347
+ const rightIsInt = isIntOp(right[right.length - 1]);
348
+
349
+ const canInt = leftIsInt && rightIsInt;
350
+
351
+ if (canInt) {
352
+ // remove int -> float conversions from left and right
353
+ left.pop();
354
+ right.pop();
355
+
356
+ return [
357
+ ...left,
358
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
359
+ ...checks[op](scope, [], leftType, true),
360
+ [ Opcodes.if, Valtype.i32 ],
361
+ ...right,
362
+ [ Opcodes.else ],
363
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
364
+ [ Opcodes.end ],
365
+ Opcodes.i32_from
366
+ ];
367
+ }
368
+
341
369
  return [
342
370
  ...left,
343
371
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
@@ -361,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
361
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
362
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
363
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
364
395
  if (assign) {
365
396
  const pointer = arrays.get(name ?? '$undeclared');
366
397
 
@@ -588,12 +619,12 @@ const compareStrings = (scope, left, right) => {
588
619
  ];
589
620
  };
590
621
 
591
- const truthy = (scope, wasm, type) => {
622
+ const truthy = (scope, wasm, type, int = false) => {
592
623
  // arrays are always truthy
593
624
  if (type === TYPES._array) return [
594
625
  ...wasm,
595
626
  [ Opcodes.drop ],
596
- ...number(1)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
597
628
  ];
598
629
 
599
630
  if (type === TYPES.string) {
@@ -609,8 +640,8 @@ const truthy = (scope, wasm, type) => {
609
640
  // if length != 0
610
641
  /* [ Opcodes.i32_eqz ],
611
642
  [ Opcodes.i32_eqz ], */
612
- Opcodes.i32_from_u
613
- ]
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
614
645
  }
615
646
 
616
647
  // if != 0
@@ -623,12 +654,12 @@ const truthy = (scope, wasm, type) => {
623
654
  ];
624
655
  };
625
656
 
626
- const falsy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
627
658
  // arrays are always truthy
628
659
  if (type === TYPES._array) return [
629
660
  ...wasm,
630
661
  [ Opcodes.drop ],
631
- ...number(0)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
632
663
  ];
633
664
 
634
665
  if (type === TYPES.string) {
@@ -643,7 +674,7 @@ const falsy = (scope, wasm, type) => {
643
674
 
644
675
  // if length == 0
645
676
  [ Opcodes.i32_eqz ],
646
- Opcodes.i32_from_u
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
647
678
  ]
648
679
  }
649
680
 
@@ -651,31 +682,29 @@ const falsy = (scope, wasm, type) => {
651
682
  return [
652
683
  ...wasm,
653
684
 
654
- ...Opcodes.eqz,
655
- Opcodes.i32_from_u
685
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
656
686
  ];
657
687
  };
658
688
 
659
- const nullish = (scope, wasm, type) => {
689
+ const nullish = (scope, wasm, type, int = false) => {
660
690
  // undefined
661
691
  if (type === TYPES.undefined) return [
662
692
  ...wasm,
663
693
  [ Opcodes.drop ],
664
- ...number(1)
694
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
665
695
  ];
666
696
 
667
697
  // null (if object and = "0")
668
698
  if (type === TYPES.object) return [
669
699
  ...wasm,
670
- ...Opcodes.eqz,
671
- Opcodes.i32_from_u
700
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
672
701
  ];
673
702
 
674
703
  // not
675
704
  return [
676
705
  ...wasm,
677
706
  [ Opcodes.drop ],
678
- ...number(0)
707
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
679
708
  ];
680
709
  };
681
710
 
@@ -686,29 +715,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
686
715
 
687
716
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
688
717
 
689
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
690
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
718
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
719
+
720
+ if (leftType && rightType && (
721
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
722
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
723
+
724
+ // if equality op and an operand is undefined, return false
725
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
726
+ )) {
727
+ // undefined == null
728
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
729
+ ...(leftType === TYPES.object ? left : right),
730
+ ...Opcodes.eqz,
731
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
732
+ ];
733
+
691
734
  return [
692
735
  ...left,
693
- ...right,
694
-
695
- // drop values
696
736
  [ Opcodes.drop ],
737
+
738
+ ...right,
697
739
  [ Opcodes.drop ],
698
740
 
699
- // return false (===)/true (!==)
700
- ...number(op === '===' ? 0 : 1, Valtype.i32)
741
+ // return true (!=/!==) or false (else)
742
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
701
743
  ];
702
744
  }
703
745
 
746
+ // todo: niche null hell with 0
747
+
704
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
705
749
  if (op === '+') {
706
750
  // string concat (a + b)
707
751
  return concatStrings(scope, left, right, _global, _name, assign);
708
752
  }
709
753
 
710
- // any other math op, NaN
711
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
712
756
 
713
757
  // else leave bool ops
714
758
  // todo: convert string to number if string and number/bool
@@ -974,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
974
1018
  case 'bigint': return number(TYPES.bigint);
975
1019
  }
976
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
977
1024
  const str = decl.value;
978
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
979
1027
  for (let i = 0; i < str.length; i++) {
980
1028
  rawElements[i] = str.charCodeAt(i);
1029
+
1030
+ if (wellFormed) {
1031
+ // check if surrogate
1032
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1033
+ // unpaired trailing surrogate
1034
+ if (str.charCodeAt(j) >= 0xDC00) {
1035
+ wellFormed = false;
1036
+ }
1037
+
1038
+ // unpaired leading surrogate
1039
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1040
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1041
+ wellFormed = false;
1042
+ }
1043
+ }
1044
+
1045
+ j++;
1046
+ }
981
1047
  }
982
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
983
1053
  return makeArray(scope, {
984
1054
  rawElements
985
1055
  }, global, name, false, 'i16')[0];
@@ -1198,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
1198
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1199
1269
 
1200
1270
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1271
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1201
1272
 
1202
1273
  // use local for cached i32 length as commonly used
1203
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1204
1275
 
1276
+ let lengthI32CacheUsed = false;
1277
+
1278
+ const protoOut = protoFunc(pointer, {
1279
+ getCachedI32: () => {
1280
+ lengthI32CacheUsed = true;
1281
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1282
+ },
1283
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1284
+ get: () => arrayUtil.getLength(pointer),
1285
+ getI32: () => arrayUtil.getLengthI32(pointer),
1286
+ set: value => arrayUtil.setLength(pointer, value),
1287
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1288
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1289
+ return makeArray(scope, {
1290
+ rawElements: new Array(length)
1291
+ }, _global, _name, true, itemType);
1292
+ }, varMetadata.get(baseName));
1293
+
1205
1294
  return [
1206
1295
  ...out,
1207
1296
 
1208
- ...arrayUtil.getLengthI32(pointer),
1209
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
1210
1301
 
1211
1302
  [ Opcodes.block, valtypeBinary ],
1212
- ...protoFunc(pointer, {
1213
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1214
- get: arrayUtil.getLength(pointer),
1215
- getI32: arrayUtil.getLengthI32(pointer),
1216
- set: value => arrayUtil.setLength(pointer, value),
1217
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1218
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1219
- return makeArray(scope, {
1220
- rawElements: new Array(length)
1221
- }, _global, _name, true, itemType);
1222
- }),
1303
+ ...protoOut,
1223
1304
  [ Opcodes.end ]
1224
1305
  ];
1225
1306
  }
@@ -1279,7 +1360,7 @@ const generateCall = (scope, decl, _global, _name) => {
1279
1360
  if (func && func.throws) scope.throws = true;
1280
1361
 
1281
1362
  for (const arg of args) {
1282
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1283
1364
  }
1284
1365
 
1285
1366
  out.push([ Opcodes.call, idx ]);
@@ -1290,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
1290
1371
  const generateNew = (scope, decl, _global, _name) => {
1291
1372
  // hack: basically treat this as a normal call for builtins for now
1292
1373
  const name = mapName(decl.callee.name);
1293
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1374
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1294
1375
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1295
1376
 
1296
1377
  return generateCall(scope, decl, _global, _name);
@@ -1308,12 +1389,12 @@ const unhackName = name => {
1308
1389
  };
1309
1390
 
1310
1391
  const generateVar = (scope, decl) => {
1311
- const out = [];
1392
+ let out = [];
1312
1393
 
1313
1394
  const topLevel = scope.name === 'main';
1314
1395
 
1315
1396
  // global variable if in top scope (main) and var ..., or if wanted
1316
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1317
1398
  const target = global ? globals : scope.locals;
1318
1399
 
1319
1400
  for (const x of decl.declarations) {
@@ -1350,7 +1431,7 @@ const generateVar = (scope, decl) => {
1350
1431
 
1351
1432
  // x.init ??= DEFAULT_VALUE;
1352
1433
  if (x.init) {
1353
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1354
1435
 
1355
1436
  // if our value is the result of a function, infer the type from that func's return value
1356
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1419,20 +1500,22 @@ const generateAssign = (scope, decl) => {
1419
1500
  ];
1420
1501
  }
1421
1502
 
1503
+ const op = decl.operator.slice(0, -1) || '=';
1504
+
1505
+ // arr[i] | str[i]
1422
1506
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1423
- // arr[i] | str[i]
1424
1507
  const name = decl.left.object.name;
1425
1508
  const pointer = arrays.get(name);
1426
1509
 
1427
1510
  const aotPointer = pointer != null;
1428
1511
 
1429
- const newValueTmp = localTmp(scope, '__member_setter_tmp');
1512
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1513
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1430
1514
 
1431
1515
  const parentType = getNodeType(scope, decl.left.object);
1432
1516
 
1433
- const op = decl.operator.slice(0, -1);
1434
1517
  return [
1435
- ...(aotPointer ? number(0, Valtype.i32) : [
1518
+ ...(aotPointer ? [] : [
1436
1519
  ...generate(scope, decl.left.object),
1437
1520
  Opcodes.i32_to_u
1438
1521
  ]),
@@ -1444,9 +1527,13 @@ const generateAssign = (scope, decl) => {
1444
1527
  Opcodes.i32_to_u,
1445
1528
  ...number(ValtypeSize[valtype], Valtype.i32),
1446
1529
  [ Opcodes.i32_mul ],
1447
- [ Opcodes.i32_add ],
1530
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1531
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1448
1532
 
1449
- ...(op === '' ? generate(scope, decl.right, false, name) : performOp(scope, op, generate(scope, decl.left), generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1533
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1534
+ [ Opcodes.local_get, pointerTmp ],
1535
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1536
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1450
1537
  [ Opcodes.local_tee, newValueTmp ],
1451
1538
 
1452
1539
  ...(parentType === TYPES._array ? [
@@ -1463,10 +1550,10 @@ const generateAssign = (scope, decl) => {
1463
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1464
1551
 
1465
1552
  if (local === undefined) {
1466
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1467
1554
 
1468
1555
  // only allow = for this
1469
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1470
1557
 
1471
1558
  if (builtinVars[name]) {
1472
1559
  // just return rhs (eg `NaN = 2`)
@@ -1475,14 +1562,14 @@ const generateAssign = (scope, decl) => {
1475
1562
 
1476
1563
  // set global and return (eg a = 2)
1477
1564
  return [
1478
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1479
1566
  [ Opcodes.global_get, globals[name].idx ]
1480
1567
  ];
1481
1568
  }
1482
1569
 
1483
1570
  typeStates[name] = getNodeType(scope, decl.right);
1484
1571
 
1485
- if (decl.operator === '=') {
1572
+ if (op === '=') {
1486
1573
  // typeStates[name] = getNodeType(scope, decl.right);
1487
1574
 
1488
1575
  return [
@@ -1492,7 +1579,6 @@ const generateAssign = (scope, decl) => {
1492
1579
  ];
1493
1580
  }
1494
1581
 
1495
- const op = decl.operator.slice(0, -1);
1496
1582
  if (op === '||' || op === '&&' || op === '??') {
1497
1583
  // todo: is this needed?
1498
1584
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1948,10 +2034,28 @@ const StoreOps = {
1948
2034
  i16: Opcodes.i32_store16
1949
2035
  };
1950
2036
 
2037
+ let data = [];
2038
+
2039
+ const compileBytes = (val, itemType, signed = true) => {
2040
+ switch (itemType) {
2041
+ case 'i8': return [ val % 256 ];
2042
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2043
+
2044
+ case 'i32':
2045
+ case 'i64':
2046
+ return enforceFourBytes(signedLEB128(val));
2047
+
2048
+ case 'f64': return ieee754_binary64(val);
2049
+ }
2050
+ };
2051
+
1951
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1952
2053
  const out = [];
1953
2054
 
2055
+ let firstAssign = false;
1954
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1955
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1956
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1957
2061
  arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
@@ -1962,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1962
2066
  const useRawElements = !!decl.rawElements;
1963
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1964
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1965
2070
  const length = elements.length;
1966
2071
 
2072
+ if (firstAssign && useRawElements) {
2073
+ let bytes = compileBytes(length, 'i32');
2074
+
2075
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2076
+ if (elements[i] == null) continue;
2077
+
2078
+ bytes.push(...compileBytes(elements[i], itemType));
2079
+ }
2080
+
2081
+ data.push({
2082
+ offset: pointer,
2083
+ bytes
2084
+ });
2085
+
2086
+ // local value as pointer
2087
+ out.push(...number(pointer));
2088
+
2089
+ return [ out, pointer ];
2090
+ }
2091
+
1967
2092
  // store length as 0th array
1968
2093
  out.push(
1969
2094
  ...number(0, Valtype.i32),
@@ -1972,7 +2097,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1972
2097
  );
1973
2098
 
1974
2099
  const storeOp = StoreOps[itemType];
1975
- const valtype = itemTypeToValtype[itemType];
1976
2100
 
1977
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1978
2102
  if (elements[i] == null) continue;
@@ -1995,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
1995
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1996
2120
  };
1997
2121
 
2122
+ let varMetadata = new Map();
2123
+ const addVarMeta = (_name, obj) => {
2124
+ const name = _name ?? '$undeclared';
2125
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2126
+
2127
+ const meta = varMetadata.get(name);
2128
+ for (const k in obj) {
2129
+ meta[k] = obj[k];
2130
+ }
2131
+ };
2132
+
1998
2133
  export const generateMember = (scope, decl, _global, _name) => {
1999
2134
  const type = getNodeType(scope, decl.object);
2000
2135
 
@@ -2296,10 +2431,10 @@ const generateFunc = (scope, decl) => {
2296
2431
  };
2297
2432
 
2298
2433
  const generateCode = (scope, decl) => {
2299
- const out = [];
2434
+ let out = [];
2300
2435
 
2301
2436
  for (const x of decl.body) {
2302
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
2303
2438
  }
2304
2439
 
2305
2440
  return out;
@@ -2335,6 +2470,18 @@ const internalConstrs = {
2335
2470
  ];
2336
2471
  },
2337
2472
  type: TYPES._array
2473
+ },
2474
+
2475
+ __Array_of: {
2476
+ // this is not a constructor but best fits internal structure here
2477
+ generate: (scope, decl, global, name) => {
2478
+ // Array.of(i0, i1, ...)
2479
+ return generateArray(scope, {
2480
+ elements: decl.arguments
2481
+ }, global, name);
2482
+ },
2483
+ type: TYPES._array,
2484
+ notConstr: true
2338
2485
  }
2339
2486
  };
2340
2487
 
@@ -2348,7 +2495,9 @@ export default program => {
2348
2495
  depth = [];
2349
2496
  typeStates = {};
2350
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
2351
2499
  pages = new Map();
2500
+ data = [];
2352
2501
  currentFuncIndex = importedFuncs.length;
2353
2502
 
2354
2503
  globalThis.valtype = 'f64';
@@ -2425,5 +2574,5 @@ export default program => {
2425
2574
  // if blank main func and other exports, remove it
2426
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2427
2576
 
2428
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2429
2578
  };
@@ -9,11 +9,15 @@ export const number = (n, valtype = valtypeBinary) => {
9
9
  }
10
10
  };
11
11
 
12
- const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
12
+ export const enforceOneByte = arr => [ arr[0] ?? 0 ];
13
+ export const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0 ];
14
+ export const enforceFourBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
15
+ export const enforceEightBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0, arr[4] ?? 0, arr[5] ?? 0, arr[6] ?? 0, arr[7] ?? 0 ];
16
+
13
17
  export const i32x4 = (a, b, c, d) => [ [
14
18
  ...Opcodes.v128_const,
15
- ...enforceTwoBytes(signedLEB128(a)),
16
- ...enforceTwoBytes(signedLEB128(b)),
17
- ...enforceTwoBytes(signedLEB128(c)),
18
- ...enforceTwoBytes(signedLEB128(d))
19
+ ...enforceFourBytes(signedLEB128(a)),
20
+ ...enforceFourBytes(signedLEB128(b)),
21
+ ...enforceFourBytes(signedLEB128(c)),
22
+ ...enforceFourBytes(signedLEB128(d))
19
23
  ] ];
package/compiler/index.js CHANGED
@@ -59,7 +59,7 @@ export default (code, flags) => {
59
59
  if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
60
60
 
61
61
  const t1 = performance.now();
62
- const { funcs, globals, tags, exceptions, pages } = codeGen(program);
62
+ const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
63
63
  if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
64
64
 
65
65
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
@@ -71,14 +71,14 @@ export default (code, flags) => {
71
71
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
72
72
 
73
73
  const t3 = performance.now();
74
- const sections = produceSections(funcs, globals, tags, pages, flags);
74
+ const sections = produceSections(funcs, globals, tags, pages, data, flags);
75
75
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
76
76
 
77
77
  if (allocLog) {
78
78
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
79
79
  const bytes = wasmPages * 65536;
80
80
  log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
81
- // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
81
+ console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
82
82
  }
83
83
 
84
84
  const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
package/compiler/opt.js CHANGED
@@ -317,28 +317,24 @@ export default (funcs, globals) => {
317
317
  continue;
318
318
  }
319
319
 
320
- if (i < 2) continue;
321
- const lastLastInst = wasm[i - 2];
320
+ // remove unneeded before get with update exprs (n++, etc) when value is unused
321
+ if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
322
+ // local.get 1
323
+ // local.get 1
324
+ // -->
325
+ // local.get 1
322
326
 
323
- if (depth.length === 2) {
324
- // hack to remove unneeded before get in for loops with (...; i++)
325
- if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
326
- // local.get 1
327
- // local.get 1
328
- // -->
329
- // local.get 1
330
-
331
- // remove drop at the end as well
332
- if (wasm[i + 4][0] === Opcodes.drop) {
333
- wasm.splice(i + 4, 1);
334
- }
327
+ // remove drop at the end as well
328
+ if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
335
329
 
336
- wasm.splice(i, 1); // remove this inst (second get)
337
- i--;
338
- continue;
339
- }
330
+ wasm.splice(i, 1); // remove this inst (second get)
331
+ i--;
332
+ continue;
340
333
  }
341
334
 
335
+ if (i < 2) continue;
336
+ const lastLastInst = wasm[i - 2];
337
+
342
338
  if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
343
339
  // local.set x
344
340
  // local.tee y