porffor 0.2.0-09999e8 → 0.2.0-30ecc4a

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) {
@@ -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,9 @@ 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
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1264
+
1265
+ // presume
1214
1266
  return TYPES.number;
1215
1267
  }
1216
1268
 
@@ -1227,6 +1279,23 @@ const getNodeType = (scope, node) => {
1227
1279
  return ret;
1228
1280
  };
1229
1281
 
1282
+ const toString = (scope, wasm, type) => {
1283
+ const tmp = localTmp(scope, '#tostring_tmp');
1284
+ return [
1285
+ ...wasm,
1286
+ [ Opcodes.local_set, tmp ],
1287
+
1288
+ ...typeSwitch(scope, type, {
1289
+ [TYPES.string]: [
1290
+ [ Opcodes.local_get, tmp ]
1291
+ ],
1292
+ [TYPES.undefined]: [
1293
+ // [ Opcodes.]
1294
+ ]
1295
+ })
1296
+ ]
1297
+ };
1298
+
1230
1299
  const generateLiteral = (scope, decl, global, name) => {
1231
1300
  if (decl.value === null) return number(NULL);
1232
1301
 
@@ -1244,16 +1313,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1313
  return number(decl.value ? 1 : 0);
1245
1314
 
1246
1315
  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];
1316
+ return makeString(scope, decl.value, global, name);
1257
1317
 
1258
1318
  default:
1259
1319
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1274,9 +1334,9 @@ const countLeftover = wasm => {
1274
1334
 
1275
1335
  if (depth === 0)
1276
1336
  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)) {}
1337
+ 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
1338
  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;
1339
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1340
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1341
  else if (inst[0] === Opcodes.return) count = 0;
1282
1342
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1362,7 @@ const disposeLeftover = wasm => {
1302
1362
  const generateExp = (scope, decl) => {
1303
1363
  const expression = decl.expression;
1304
1364
 
1305
- const out = generate(scope, expression);
1365
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1366
  disposeLeftover(out);
1307
1367
 
1308
1368
  return out;
@@ -1360,7 +1420,7 @@ const RTArrayUtil = {
1360
1420
  ]
1361
1421
  };
1362
1422
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1423
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1424
  /* const callee = decl.callee;
1365
1425
  const args = decl.arguments;
1366
1426
 
@@ -1479,10 +1539,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1539
  // use local for cached i32 length as commonly used
1480
1540
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1541
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1542
 
1484
1543
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1544
 
1545
+ const rawPointer = [
1546
+ ...generate(scope, target),
1547
+ Opcodes.i32_to_u
1548
+ ];
1549
+
1550
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1551
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1552
+
1553
+ let allOptUnused = true;
1486
1554
  let lengthI32CacheUsed = false;
1487
1555
  const protoBC = {};
1488
1556
  for (const x in protoCands) {
@@ -1502,6 +1570,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1570
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1571
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1572
 
1573
+ let optUnused = false;
1505
1574
  const protoOut = protoFunc(getPointer, {
1506
1575
  getCachedI32: () => {
1507
1576
  lengthI32CacheUsed = true;
@@ -1516,10 +1585,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1585
  return makeArray(scope, {
1517
1586
  rawElements: new Array(length)
1518
1587
  }, _global, _name, true, itemType);
1588
+ }, () => {
1589
+ optUnused = true;
1590
+ return unusedValue;
1519
1591
  });
1520
1592
 
1593
+ if (!optUnused) allOptUnused = false;
1594
+
1521
1595
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1596
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1597
  ...protoOut,
1524
1598
 
1525
1599
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1602,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1602
  ];
1529
1603
  }
1530
1604
 
1531
- return [
1532
- ...generate(scope, target),
1605
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1606
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1607
+ return [
1608
+ ...(usePointerCache ? [
1609
+ ...rawPointer,
1610
+ [ Opcodes.local_set, pointerLocal ],
1611
+ ] : []),
1536
1612
 
1537
1613
  ...(!lengthI32CacheUsed ? [] : [
1538
1614
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1620,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1620
 
1545
1621
  // TODO: error better
1546
1622
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1623
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1624
  ];
1549
1625
  }
1550
1626
  }
@@ -1591,7 +1667,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1667
  const func = funcs.find(x => x.index === idx);
1592
1668
 
1593
1669
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1670
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1671
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1672
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1673
 
1596
1674
  let args = decl.arguments;
1597
1675
  if (func && args.length < paramCount) {
@@ -1609,12 +1687,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1687
  let out = [];
1610
1688
  for (const arg of args) {
1611
1689
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1690
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1691
  }
1614
1692
 
1615
1693
  out.push([ Opcodes.call, idx ]);
1616
1694
 
1617
- if (!userFunc) {
1695
+ if (!typedReturn) {
1618
1696
  // let type;
1619
1697
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1698
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1743,102 @@ const knownType = (scope, type) => {
1665
1743
  return null;
1666
1744
  };
1667
1745
 
1746
+ const brTable = (input, bc, returns) => {
1747
+ const out = [];
1748
+ const keys = Object.keys(bc);
1749
+ const count = keys.length;
1750
+
1751
+ if (count === 1) {
1752
+ // return [
1753
+ // ...input,
1754
+ // ...bc[keys[0]]
1755
+ // ];
1756
+ return bc[keys[0]];
1757
+ }
1758
+
1759
+ if (count === 2) {
1760
+ // just use if else
1761
+ const other = keys.find(x => x !== 'default');
1762
+ return [
1763
+ ...input,
1764
+ ...number(other, Valtype.i32),
1765
+ [ Opcodes.i32_eq ],
1766
+ [ Opcodes.if, returns ],
1767
+ ...bc[other],
1768
+ [ Opcodes.else ],
1769
+ ...bc.default,
1770
+ [ Opcodes.end ]
1771
+ ];
1772
+ }
1773
+
1774
+ for (let i = 0; i < count; i++) {
1775
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1776
+ else out.push([ Opcodes.block, Blocktype.void ]);
1777
+ }
1778
+
1779
+ const nums = keys.filter(x => +x);
1780
+ const offset = Math.min(...nums);
1781
+ const max = Math.max(...nums);
1782
+
1783
+ const table = [];
1784
+ let br = 1;
1785
+
1786
+ for (let i = offset; i <= max; i++) {
1787
+ // if branch for this num, go to that block
1788
+ if (bc[i]) {
1789
+ table.push(br);
1790
+ br++;
1791
+ continue;
1792
+ }
1793
+
1794
+ // else default
1795
+ table.push(0);
1796
+ }
1797
+
1798
+ out.push(
1799
+ [ Opcodes.block, Blocktype.void ],
1800
+ ...input,
1801
+ ...(offset > 0 ? [
1802
+ ...number(offset, Valtype.i32),
1803
+ [ Opcodes.i32_sub ]
1804
+ ] : []),
1805
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1806
+ );
1807
+
1808
+ // if you can guess why we sort the wrong way and then reverse
1809
+ // (instead of just sorting the correct way)
1810
+ // dm me and if you are correct and the first person
1811
+ // I will somehow shout you out or something
1812
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1813
+
1814
+ br = count - 1;
1815
+ for (const x of orderedBc) {
1816
+ out.push(
1817
+ [ Opcodes.end ],
1818
+ ...bc[x],
1819
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1820
+ );
1821
+ br--;
1822
+ }
1823
+
1824
+ return [
1825
+ ...out,
1826
+ [ Opcodes.end, 'br table end' ]
1827
+ ];
1828
+ };
1829
+
1668
1830
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1832
+
1669
1833
  const known = knownType(scope, type);
1670
1834
  if (known != null) {
1671
1835
  return bc[known] ?? bc.default;
1672
1836
  }
1673
1837
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1838
+ if (process.argv.includes('-typeswitch-use-brtable'))
1839
+ return brTable(type, bc, returns);
1675
1840
 
1841
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1842
  const out = [
1677
1843
  ...type,
1678
1844
  [ Opcodes.local_set, tmp ],
@@ -1759,11 +1925,12 @@ const extractTypeAnnotation = decl => {
1759
1925
  elementType = extractTypeAnnotation(a.elementType).type;
1760
1926
  }
1761
1927
 
1928
+ const typeName = type;
1762
1929
  type = typeAnnoToPorfType(type);
1763
1930
 
1764
1931
  // if (decl.name) console.log(decl.name, { type, elementType });
1765
1932
 
1766
- return { type, elementType };
1933
+ return { type, typeName, elementType };
1767
1934
  };
1768
1935
 
1769
1936
  const generateVar = (scope, decl) => {
@@ -1793,6 +1960,11 @@ const generateVar = (scope, decl) => {
1793
1960
  }
1794
1961
 
1795
1962
  let idx = allocVar(scope, name, global);
1963
+
1964
+ if (typedInput && x.id.typeAnnotation) {
1965
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1966
+ }
1967
+
1796
1968
  if (x.init) {
1797
1969
  out = out.concat(generate(scope, x.init, global, name));
1798
1970
 
@@ -1802,10 +1974,6 @@ const generateVar = (scope, decl) => {
1802
1974
 
1803
1975
  // hack: this follows spec properly but is mostly unneeded 😅
1804
1976
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1805
-
1806
- if (typedInput && x.id.typeAnnotation) {
1807
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1808
- }
1809
1977
  }
1810
1978
 
1811
1979
  return out;
@@ -2052,6 +2220,8 @@ const generateUnary = (scope, decl) => {
2052
2220
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2053
2221
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2054
2222
 
2223
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2224
+
2055
2225
  // object and internal types
2056
2226
  default: makeString(scope, 'object', false, '#typeof_result'),
2057
2227
  });
@@ -2233,13 +2403,14 @@ const generateForOf = (scope, decl) => {
2233
2403
  // // todo: we should only do this for strings but we don't know at compile-time :(
2234
2404
  // hack: this is naughty and will break things!
2235
2405
  let newOut = number(0, Valtype.f64), newPointer = -1;
2236
- if (pages.hasString) {
2406
+ if (pages.hasAnyString) {
2237
2407
  0, [ newOut, newPointer ] = makeArray(scope, {
2238
2408
  rawElements: new Array(1)
2239
2409
  }, isGlobal, leftName, true, 'i16');
2240
2410
  }
2241
2411
 
2242
2412
  // set type for local
2413
+ // todo: optimize away counter and use end pointer
2243
2414
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2244
2415
  [TYPES._array]: [
2245
2416
  ...setType(scope, leftName, TYPES.number),
@@ -2425,6 +2596,8 @@ const allocPage = (reason, type) => {
2425
2596
 
2426
2597
  if (reason.startsWith('array:')) pages.hasArray = true;
2427
2598
  if (reason.startsWith('string:')) pages.hasString = true;
2599
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2600
+ if (reason.includes('string:')) pages.hasAnyString = true;
2428
2601
 
2429
2602
  const ind = pages.size;
2430
2603
  pages.set(reason, { ind, type });
@@ -2458,7 +2631,8 @@ const StoreOps = {
2458
2631
  f64: Opcodes.f64_store,
2459
2632
 
2460
2633
  // expects i32 input!
2461
- i16: Opcodes.i32_store16
2634
+ i8: Opcodes.i32_store8,
2635
+ i16: Opcodes.i32_store16,
2462
2636
  };
2463
2637
 
2464
2638
  let data = [];
@@ -2477,6 +2651,15 @@ const compileBytes = (val, itemType, signed = true) => {
2477
2651
  }
2478
2652
  };
2479
2653
 
2654
+ const getAllocType = itemType => {
2655
+ switch (itemType) {
2656
+ case 'i8': return 'bytestring';
2657
+ case 'i16': return 'string';
2658
+
2659
+ default: return 'array';
2660
+ }
2661
+ };
2662
+
2480
2663
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2481
2664
  const out = [];
2482
2665
 
@@ -2486,7 +2669,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2486
2669
 
2487
2670
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2488
2671
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2489
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2672
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2490
2673
  }
2491
2674
 
2492
2675
  const pointer = arrays.get(name);
@@ -2532,7 +2715,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2532
2715
  out.push(
2533
2716
  ...number(0, Valtype.i32),
2534
2717
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2535
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2718
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2536
2719
  );
2537
2720
  }
2538
2721
 
@@ -2542,15 +2725,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2542
2725
  return [ out, pointer ];
2543
2726
  };
2544
2727
 
2728
+ const byteStringable = str => {
2729
+ if (!process.argv.includes('-bytestring')) return false;
2730
+
2731
+ for (let i = 0; i < str.length; i++) {
2732
+ if (str.charCodeAt(i) > 0xFF) return false;
2733
+ }
2734
+
2735
+ return true;
2736
+ };
2737
+
2545
2738
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2546
2739
  const rawElements = new Array(str.length);
2740
+ let byteStringable = process.argv.includes('-bytestring');
2547
2741
  for (let i = 0; i < str.length; i++) {
2548
- rawElements[i] = str.charCodeAt(i);
2742
+ const c = str.charCodeAt(i);
2743
+ rawElements[i] = c;
2744
+
2745
+ if (byteStringable && c > 0xFF) byteStringable = false;
2549
2746
  }
2550
2747
 
2551
2748
  return makeArray(scope, {
2552
2749
  rawElements
2553
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2554
2751
  };
2555
2752
 
2556
2753
  let arrays = new Map();
@@ -2578,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2578
2775
  ];
2579
2776
  }
2580
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2581
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2582
2782
  // hack: this is naughty and will break things!
2583
- let newOut = number(0, Valtype.f64), newPointer = -1;
2584
- if (pages.hasString) {
2783
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2784
+ if (pages.hasAnyString) {
2585
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2586
2786
  rawElements: new Array(1)
2587
2787
  }, _global, _name, true, 'i16');
@@ -2590,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2590
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2591
2791
  [TYPES._array]: [
2592
2792
  // get index as valtype
2593
- ...generate(scope, decl.property),
2793
+ ...property,
2594
2794
 
2595
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2596
2796
  Opcodes.i32_to_u,
@@ -2598,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2598
2798
  [ Opcodes.i32_mul ],
2599
2799
 
2600
2800
  ...(aotPointer ? [] : [
2601
- ...generate(scope, decl.object),
2801
+ ...object,
2602
2802
  Opcodes.i32_to_u,
2603
2803
  [ Opcodes.i32_add ]
2604
2804
  ]),
@@ -2617,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2617
2817
 
2618
2818
  ...number(0, Valtype.i32), // base 0 for store later
2619
2819
 
2620
- ...generate(scope, decl.property),
2621
-
2820
+ ...property,
2622
2821
  Opcodes.i32_to_u,
2822
+
2623
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2624
2824
  [ Opcodes.i32_mul ],
2625
2825
 
2626
2826
  ...(aotPointer ? [] : [
2627
- ...generate(scope, decl.object),
2827
+ ...object,
2628
2828
  Opcodes.i32_to_u,
2629
2829
  [ Opcodes.i32_add ]
2630
2830
  ]),
@@ -2641,6 +2841,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2641
2841
  ...number(TYPES.string, Valtype.i32),
2642
2842
  setLastType(scope)
2643
2843
  ],
2844
+ [TYPES._bytestring]: [
2845
+ // setup new/out array
2846
+ ...newOut,
2847
+ [ Opcodes.drop ],
2848
+
2849
+ ...number(0, Valtype.i32), // base 0 for store later
2850
+
2851
+ ...property,
2852
+ Opcodes.i32_to_u,
2853
+
2854
+ ...(aotPointer ? [] : [
2855
+ ...object,
2856
+ Opcodes.i32_to_u,
2857
+ [ Opcodes.i32_add ]
2858
+ ]),
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
+
2863
+ // store to new string ind 0
2864
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
+
2866
+ // return new string (page)
2867
+ ...number(newPointer),
2868
+
2869
+ ...number(TYPES._bytestring, Valtype.i32),
2870
+ setLastType(scope)
2871
+ ],
2644
2872
 
2645
2873
  default: [ [ Opcodes.unreachable ] ]
2646
2874
  });
@@ -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`)
package/compiler/index.js CHANGED
@@ -52,7 +52,7 @@ export default (code, flags) => {
52
52
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
53
53
 
54
54
  const t2 = performance.now();
55
- opt(funcs, globals, pages);
55
+ opt(funcs, globals, pages, tags);
56
56
  if (flags.includes('info')) console.log(`3. optimized code in ${(performance.now() - t2).toFixed(2)}ms`);
57
57
 
58
58
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);