porffor 0.2.0-31c2539 → 0.2.0-4035760

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,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } 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";
@@ -55,7 +55,7 @@ const todo = msg => {
55
55
  };
56
56
 
57
57
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
58
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
59
  switch (decl.type) {
60
60
  case 'BinaryExpression':
61
61
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
68
 
69
69
  case 'ArrowFunctionExpression':
70
70
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
71
+ const func = generateFunc(scope, decl);
72
+
73
+ if (decl.type.endsWith('Expression')) {
74
+ return number(func.index);
75
+ }
76
+
72
77
  return [];
73
78
 
74
79
  case 'BlockStatement':
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
86
  return generateExp(scope, decl);
82
87
 
83
88
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
85
90
 
86
91
  case 'NewExpression':
87
92
  return generateNew(scope, decl, global, name);
@@ -680,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
685
  [ Opcodes.i32_eqz ], */
681
686
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
687
  ],
688
+ [TYPES._bytestring]: [ // duplicate of string
689
+ [ Opcodes.local_get, tmp ],
690
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
+
692
+ // get length
693
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
694
+
695
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
696
+ ],
683
697
  default: def
684
698
  }, intOut ? Valtype.i32 : valtypeBinary)
685
699
  ];
@@ -707,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
707
721
  [ Opcodes.i32_eqz ],
708
722
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
723
  ],
724
+ [TYPES._bytestring]: [ // duplicate of string
725
+ [ Opcodes.local_get, tmp ],
726
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
+
728
+ // get length
729
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
730
+
731
+ // if length == 0
732
+ [ Opcodes.i32_eqz ],
733
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
734
+ ],
710
735
  default: [
711
736
  // if value == 0
712
737
  [ Opcodes.local_get, tmp ],
@@ -947,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
972
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
973
  }
949
974
 
975
+ if (typeof wasm === 'function') {
976
+ const scope = {
977
+ name,
978
+ params,
979
+ locals,
980
+ returns,
981
+ localInd: allLocals.length,
982
+ };
983
+
984
+ wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
985
+ }
986
+
950
987
  let baseGlobalIdx, i = 0;
951
988
  for (const type of globalTypes) {
952
989
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1064,8 @@ const TYPES = {
1027
1064
 
1028
1065
  // these are not "typeof" types but tracked internally
1029
1066
  _array: 0x10,
1030
- _regexp: 0x11
1067
+ _regexp: 0x11,
1068
+ _bytestring: 0x12
1031
1069
  };
1032
1070
 
1033
1071
  const TYPE_NAMES = {
@@ -1041,7 +1079,8 @@ const TYPE_NAMES = {
1041
1079
  [TYPES.bigint]: 'BigInt',
1042
1080
 
1043
1081
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1082
+ [TYPES._regexp]: 'RegExp',
1083
+ [TYPES._bytestring]: 'ByteString'
1045
1084
  };
1046
1085
 
1047
1086
  const getType = (scope, _name) => {
@@ -1094,6 +1133,8 @@ const getNodeType = (scope, node) => {
1094
1133
  if (node.type === 'Literal') {
1095
1134
  if (node.regex) return TYPES._regexp;
1096
1135
 
1136
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1137
+
1097
1138
  return TYPES[typeof node.value];
1098
1139
  }
1099
1140
 
@@ -1107,6 +1148,15 @@ const getNodeType = (scope, node) => {
1107
1148
 
1108
1149
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1150
  const name = node.callee.name;
1151
+ if (!name) {
1152
+ // iife
1153
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1154
+
1155
+ // presume
1156
+ // todo: warn here?
1157
+ return TYPES.number;
1158
+ }
1159
+
1110
1160
  const func = funcs.find(x => x.name === name);
1111
1161
 
1112
1162
  if (func) {
@@ -1125,7 +1175,7 @@ const getNodeType = (scope, node) => {
1125
1175
  const spl = name.slice(2).split('_');
1126
1176
 
1127
1177
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1178
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1179
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1180
  }
1131
1181
 
@@ -1201,7 +1251,7 @@ const getNodeType = (scope, node) => {
1201
1251
  if (node.operator === '!') return TYPES.boolean;
1202
1252
  if (node.operator === 'void') return TYPES.undefined;
1203
1253
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1254
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1205
1255
 
1206
1256
  return TYPES.number;
1207
1257
  }
@@ -1210,7 +1260,13 @@ const getNodeType = (scope, node) => {
1210
1260
  // hack: if something.length, number type
1211
1261
  if (node.property.name === 'length') return TYPES.number;
1212
1262
 
1213
- // we cannot guess
1263
+ // ts hack
1264
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1265
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1266
+
1267
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1268
+
1269
+ // presume
1214
1270
  return TYPES.number;
1215
1271
  }
1216
1272
 
@@ -1244,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1300
  return number(decl.value ? 1 : 0);
1245
1301
 
1246
1302
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1303
+ return makeString(scope, decl.value, global, name);
1257
1304
 
1258
1305
  default:
1259
1306
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1274,9 +1321,9 @@ const countLeftover = wasm => {
1274
1321
 
1275
1322
  if (depth === 0)
1276
1323
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- 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.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1324
+ 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] < 0x0a)) {}
1278
1325
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1279
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1326
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1327
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1328
  else if (inst[0] === Opcodes.return) count = 0;
1282
1329
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1349,7 @@ const disposeLeftover = wasm => {
1302
1349
  const generateExp = (scope, decl) => {
1303
1350
  const expression = decl.expression;
1304
1351
 
1305
- const out = generate(scope, expression);
1352
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1353
  disposeLeftover(out);
1307
1354
 
1308
1355
  return out;
@@ -1360,7 +1407,7 @@ const RTArrayUtil = {
1360
1407
  ]
1361
1408
  };
1362
1409
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1410
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1411
  /* const callee = decl.callee;
1365
1412
  const args = decl.arguments;
1366
1413
 
@@ -1469,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
1469
1516
 
1470
1517
  if (protoName) {
1471
1518
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1519
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1520
  return acc;
1475
1521
  }, {});
1476
1522
 
@@ -1479,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1525
  // use local for cached i32 length as commonly used
1480
1526
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1527
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1528
 
1484
1529
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1530
 
1531
+ const rawPointer = [
1532
+ ...generate(scope, target),
1533
+ Opcodes.i32_to_u
1534
+ ];
1535
+
1536
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1537
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1538
+
1539
+ let allOptUnused = true;
1486
1540
  let lengthI32CacheUsed = false;
1487
1541
  const protoBC = {};
1488
1542
  for (const x in protoCands) {
@@ -1502,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1556
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1557
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1558
 
1559
+ let optUnused = false;
1505
1560
  const protoOut = protoFunc(getPointer, {
1506
1561
  getCachedI32: () => {
1507
1562
  lengthI32CacheUsed = true;
@@ -1516,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1571
  return makeArray(scope, {
1517
1572
  rawElements: new Array(length)
1518
1573
  }, _global, _name, true, itemType);
1574
+ }, () => {
1575
+ optUnused = true;
1576
+ return unusedValue;
1519
1577
  });
1520
1578
 
1579
+ if (!optUnused) allOptUnused = false;
1580
+
1521
1581
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1582
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1583
  ...protoOut,
1524
1584
 
1525
1585
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1588
  ];
1529
1589
  }
1530
1590
 
1531
- return [
1532
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1592
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1593
+ return [
1594
+ ...(usePointerCache ? [
1595
+ ...rawPointer,
1596
+ [ Opcodes.local_set, pointerLocal ],
1597
+ ] : []),
1536
1598
 
1537
1599
  ...(!lengthI32CacheUsed ? [] : [
1538
1600
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1606
 
1545
1607
  // TODO: error better
1546
1608
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1609
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1610
  ];
1549
1611
  }
1550
1612
  }
@@ -1591,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1653
  const func = funcs.find(x => x.index === idx);
1592
1654
 
1593
1655
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1656
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1658
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1659
 
1596
1660
  let args = decl.arguments;
1597
1661
  if (func && args.length < paramCount) {
@@ -1609,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1673
  let out = [];
1610
1674
  for (const arg of args) {
1611
1675
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1676
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1677
  }
1614
1678
 
1615
1679
  out.push([ Opcodes.call, idx ]);
1616
1680
 
1617
- if (!userFunc) {
1681
+ if (!typedReturn) {
1618
1682
  // let type;
1619
1683
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1684
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1729,102 @@ const knownType = (scope, type) => {
1665
1729
  return null;
1666
1730
  };
1667
1731
 
1732
+ const brTable = (input, bc, returns) => {
1733
+ const out = [];
1734
+ const keys = Object.keys(bc);
1735
+ const count = keys.length;
1736
+
1737
+ if (count === 1) {
1738
+ // return [
1739
+ // ...input,
1740
+ // ...bc[keys[0]]
1741
+ // ];
1742
+ return bc[keys[0]];
1743
+ }
1744
+
1745
+ if (count === 2) {
1746
+ // just use if else
1747
+ const other = keys.find(x => x !== 'default');
1748
+ return [
1749
+ ...input,
1750
+ ...number(other, Valtype.i32),
1751
+ [ Opcodes.i32_eq ],
1752
+ [ Opcodes.if, returns ],
1753
+ ...bc[other],
1754
+ [ Opcodes.else ],
1755
+ ...bc.default,
1756
+ [ Opcodes.end ]
1757
+ ];
1758
+ }
1759
+
1760
+ for (let i = 0; i < count; i++) {
1761
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1762
+ else out.push([ Opcodes.block, Blocktype.void ]);
1763
+ }
1764
+
1765
+ const nums = keys.filter(x => +x);
1766
+ const offset = Math.min(...nums);
1767
+ const max = Math.max(...nums);
1768
+
1769
+ const table = [];
1770
+ let br = 1;
1771
+
1772
+ for (let i = offset; i <= max; i++) {
1773
+ // if branch for this num, go to that block
1774
+ if (bc[i]) {
1775
+ table.push(br);
1776
+ br++;
1777
+ continue;
1778
+ }
1779
+
1780
+ // else default
1781
+ table.push(0);
1782
+ }
1783
+
1784
+ out.push(
1785
+ [ Opcodes.block, Blocktype.void ],
1786
+ ...input,
1787
+ ...(offset > 0 ? [
1788
+ ...number(offset, Valtype.i32),
1789
+ [ Opcodes.i32_sub ]
1790
+ ] : []),
1791
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1792
+ );
1793
+
1794
+ // if you can guess why we sort the wrong way and then reverse
1795
+ // (instead of just sorting the correct way)
1796
+ // dm me and if you are correct and the first person
1797
+ // I will somehow shout you out or something
1798
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1799
+
1800
+ br = count - 1;
1801
+ for (const x of orderedBc) {
1802
+ out.push(
1803
+ [ Opcodes.end ],
1804
+ ...bc[x],
1805
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1806
+ );
1807
+ br--;
1808
+ }
1809
+
1810
+ return [
1811
+ ...out,
1812
+ [ Opcodes.end, 'br table end' ]
1813
+ ];
1814
+ };
1815
+
1668
1816
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1818
+
1669
1819
  const known = knownType(scope, type);
1670
1820
  if (known != null) {
1671
1821
  return bc[known] ?? bc.default;
1672
1822
  }
1673
1823
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1824
+ if (process.argv.includes('-typeswitch-use-brtable'))
1825
+ return brTable(type, bc, returns);
1675
1826
 
1827
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1828
  const out = [
1677
1829
  ...type,
1678
1830
  [ Opcodes.local_set, tmp ],
@@ -1762,6 +1914,8 @@ const extractTypeAnnotation = decl => {
1762
1914
  const typeName = type;
1763
1915
  type = typeAnnoToPorfType(type);
1764
1916
 
1917
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1918
+
1765
1919
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
1920
 
1767
1921
  return { type, typeName, elementType };
@@ -2054,6 +2208,8 @@ const generateUnary = (scope, decl) => {
2054
2208
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2055
2209
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2056
2210
 
2211
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2212
+
2057
2213
  // object and internal types
2058
2214
  default: makeString(scope, 'object', false, '#typeof_result'),
2059
2215
  });
@@ -2235,13 +2391,14 @@ const generateForOf = (scope, decl) => {
2235
2391
  // // todo: we should only do this for strings but we don't know at compile-time :(
2236
2392
  // hack: this is naughty and will break things!
2237
2393
  let newOut = number(0, Valtype.f64), newPointer = -1;
2238
- if (pages.hasString) {
2394
+ if (pages.hasAnyString) {
2239
2395
  0, [ newOut, newPointer ] = makeArray(scope, {
2240
2396
  rawElements: new Array(1)
2241
2397
  }, isGlobal, leftName, true, 'i16');
2242
2398
  }
2243
2399
 
2244
2400
  // set type for local
2401
+ // todo: optimize away counter and use end pointer
2245
2402
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2246
2403
  [TYPES._array]: [
2247
2404
  ...setType(scope, leftName, TYPES.number),
@@ -2366,7 +2523,7 @@ const generateThrow = (scope, decl) => {
2366
2523
  // hack: throw new X("...") -> throw "..."
2367
2524
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2368
2525
  constructor = decl.argument.callee.name;
2369
- message = decl.argument.arguments[0].value;
2526
+ message = decl.argument.arguments[0]?.value ?? '';
2370
2527
  }
2371
2528
 
2372
2529
  if (tags.length === 0) tags.push({
@@ -2427,6 +2584,8 @@ const allocPage = (reason, type) => {
2427
2584
 
2428
2585
  if (reason.startsWith('array:')) pages.hasArray = true;
2429
2586
  if (reason.startsWith('string:')) pages.hasString = true;
2587
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2588
+ if (reason.includes('string:')) pages.hasAnyString = true;
2430
2589
 
2431
2590
  const ind = pages.size;
2432
2591
  pages.set(reason, { ind, type });
@@ -2460,7 +2619,8 @@ const StoreOps = {
2460
2619
  f64: Opcodes.f64_store,
2461
2620
 
2462
2621
  // expects i32 input!
2463
- i16: Opcodes.i32_store16
2622
+ i8: Opcodes.i32_store8,
2623
+ i16: Opcodes.i32_store16,
2464
2624
  };
2465
2625
 
2466
2626
  let data = [];
@@ -2479,6 +2639,15 @@ const compileBytes = (val, itemType, signed = true) => {
2479
2639
  }
2480
2640
  };
2481
2641
 
2642
+ const getAllocType = itemType => {
2643
+ switch (itemType) {
2644
+ case 'i8': return 'bytestring';
2645
+ case 'i16': return 'string';
2646
+
2647
+ default: return 'array';
2648
+ }
2649
+ };
2650
+
2482
2651
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2483
2652
  const out = [];
2484
2653
 
@@ -2488,7 +2657,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2488
2657
 
2489
2658
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2490
2659
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2491
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2660
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2492
2661
  }
2493
2662
 
2494
2663
  const pointer = arrays.get(name);
@@ -2534,7 +2703,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2534
2703
  out.push(
2535
2704
  ...number(0, Valtype.i32),
2536
2705
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2537
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2706
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2538
2707
  );
2539
2708
  }
2540
2709
 
@@ -2544,15 +2713,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2544
2713
  return [ out, pointer ];
2545
2714
  };
2546
2715
 
2716
+ const byteStringable = str => {
2717
+ if (!process.argv.includes('-bytestring')) return false;
2718
+
2719
+ for (let i = 0; i < str.length; i++) {
2720
+ if (str.charCodeAt(i) > 0xFF) return false;
2721
+ }
2722
+
2723
+ return true;
2724
+ };
2725
+
2547
2726
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2548
2727
  const rawElements = new Array(str.length);
2728
+ let byteStringable = process.argv.includes('-bytestring');
2549
2729
  for (let i = 0; i < str.length; i++) {
2550
- rawElements[i] = str.charCodeAt(i);
2730
+ const c = str.charCodeAt(i);
2731
+ rawElements[i] = c;
2732
+
2733
+ if (byteStringable && c > 0xFF) byteStringable = false;
2551
2734
  }
2552
2735
 
2553
2736
  return makeArray(scope, {
2554
2737
  rawElements
2555
- }, global, name, false, 'i16')[0];
2738
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2556
2739
  };
2557
2740
 
2558
2741
  let arrays = new Map();
@@ -2580,10 +2763,15 @@ export const generateMember = (scope, decl, _global, _name) => {
2580
2763
  ];
2581
2764
  }
2582
2765
 
2766
+ const object = generate(scope, decl.object);
2767
+ const property = generate(scope, decl.property);
2768
+
2769
+ console.log(decl.property, property);
2770
+
2583
2771
  // // todo: we should only do this for strings but we don't know at compile-time :(
2584
2772
  // hack: this is naughty and will break things!
2585
2773
  let newOut = number(0, valtypeBinary), newPointer = -1;
2586
- if (pages.hasString) {
2774
+ if (pages.hasAnyString) {
2587
2775
  0, [ newOut, newPointer ] = makeArray(scope, {
2588
2776
  rawElements: new Array(1)
2589
2777
  }, _global, _name, true, 'i16');
@@ -2592,7 +2780,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2592
2780
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2593
2781
  [TYPES._array]: [
2594
2782
  // get index as valtype
2595
- ...generate(scope, decl.property),
2783
+ ...property,
2596
2784
 
2597
2785
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2598
2786
  Opcodes.i32_to_u,
@@ -2600,7 +2788,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2600
2788
  [ Opcodes.i32_mul ],
2601
2789
 
2602
2790
  ...(aotPointer ? [] : [
2603
- ...generate(scope, decl.object),
2791
+ ...object,
2604
2792
  Opcodes.i32_to_u,
2605
2793
  [ Opcodes.i32_add ]
2606
2794
  ]),
@@ -2619,14 +2807,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2619
2807
 
2620
2808
  ...number(0, Valtype.i32), // base 0 for store later
2621
2809
 
2622
- ...generate(scope, decl.property),
2623
-
2810
+ ...property,
2624
2811
  Opcodes.i32_to_u,
2812
+
2625
2813
  ...number(ValtypeSize.i16, Valtype.i32),
2626
2814
  [ Opcodes.i32_mul ],
2627
2815
 
2628
2816
  ...(aotPointer ? [] : [
2629
- ...generate(scope, decl.object),
2817
+ ...object,
2630
2818
  Opcodes.i32_to_u,
2631
2819
  [ Opcodes.i32_add ]
2632
2820
  ]),
@@ -2643,6 +2831,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2643
2831
  ...number(TYPES.string, Valtype.i32),
2644
2832
  setLastType(scope)
2645
2833
  ],
2834
+ [TYPES._bytestring]: [
2835
+ // setup new/out array
2836
+ ...newOut,
2837
+ [ Opcodes.drop ],
2838
+
2839
+ ...number(0, Valtype.i32), // base 0 for store later
2840
+
2841
+ ...property,
2842
+ Opcodes.i32_to_u,
2843
+
2844
+ ...(aotPointer ? [] : [
2845
+ ...object,
2846
+ Opcodes.i32_to_u,
2847
+ [ Opcodes.i32_add ]
2848
+ ]),
2849
+
2850
+ // load current string ind {arg}
2851
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2852
+
2853
+ // store to new string ind 0
2854
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2855
+
2856
+ // return new string (page)
2857
+ ...number(newPointer),
2858
+
2859
+ ...number(TYPES._bytestring, Valtype.i32),
2860
+ setLastType(scope)
2861
+ ],
2646
2862
 
2647
2863
  default: [ [ Opcodes.unreachable ] ]
2648
2864
  });
@@ -2661,11 +2877,14 @@ const objectHack = node => {
2661
2877
  // if object is not identifier or another member exp, give up
2662
2878
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2663
2879
 
2664
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2880
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2665
2881
 
2666
2882
  // if .length, give up (hack within a hack!)
2667
2883
  if (node.property.name === 'length') return node;
2668
2884
 
2885
+ // no object name, give up
2886
+ if (!objectName) return node;
2887
+
2669
2888
  const name = '__' + objectName + '_' + node.property.name;
2670
2889
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2671
2890
 
@@ -2724,10 +2943,8 @@ const generateFunc = (scope, decl) => {
2724
2943
  const func = {
2725
2944
  name,
2726
2945
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2727
- returns: innerScope.returns,
2728
- locals: innerScope.locals,
2729
- throws: innerScope.throws,
2730
- index: currentFuncIndex++
2946
+ index: currentFuncIndex++,
2947
+ ...innerScope
2731
2948
  };
2732
2949
  funcIndex[name] = func.index;
2733
2950
 
@@ -15,7 +15,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
15
15
  if (name) out += `${makeSignature(params, returns)} ;; $${name} (${ind})\n`;
16
16
 
17
17
  const justLocals = Object.values(locals).sort((a, b) => a.idx - b.idx).slice(params.length);
18
- if (justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
18
+ if (name && justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
19
19
 
20
20
  let i = -1, lastInst;
21
21
  let byte = 0;
@@ -32,7 +32,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
32
32
  inst = [ [ inst[0], inst[1] ], ...inst.slice(2) ];
33
33
  }
34
34
 
35
- if (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all) depth--;
35
+ if (depth > 0 && (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all)) depth--;
36
36
 
37
37
  out += ' '.repeat(Math.max(0, depth * 2));
38
38
 
@@ -119,7 +119,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
119
119
  export const highlightAsm = asm => asm
120
120
  .replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
121
121
  .replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
122
- .replace(/[^m](i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `${_[0]}\x1B[36m${_.slice(1)}\x1B[0m`)
122
+ .replace(/(i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `\x1B[36m${_}\x1B[0m`)
123
123
  .replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
124
124
  .replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
125
125
  .replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)