porffor 0.2.0-08a272e → 0.2.0-1afe9b8

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);
@@ -189,19 +194,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
189
194
  }
190
195
 
191
196
  return out;
192
- },
193
-
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
196
-
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
200
-
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
197
  }
206
198
  }
207
199
 
@@ -269,25 +261,25 @@ const generateIdent = (scope, decl) => {
269
261
  const name = mapName(rawName);
270
262
  let local = scope.locals[rawName];
271
263
 
272
- if (builtinVars[name]) {
264
+ if (Object.hasOwn(builtinVars, name)) {
273
265
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
266
  return builtinVars[name];
275
267
  }
276
268
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
269
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
270
  // todo: return an actual something
279
271
  return number(1);
280
272
  }
281
273
 
282
- if (local === undefined) {
274
+ if (local?.idx === undefined) {
283
275
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
276
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
277
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
278
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
279
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
280
  }
289
281
 
290
- if (local === undefined && rawName.startsWith('__')) {
282
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
283
  // return undefined if unknown key in already known var
292
284
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
285
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +288,7 @@ const generateIdent = (scope, decl) => {
296
288
  if (!parentLookup[1]) return number(UNDEFINED);
297
289
  }
298
290
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
291
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
292
 
301
293
  return [ [ Opcodes.local_get, local.idx ] ];
302
294
  };
@@ -680,6 +672,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
672
  [ Opcodes.i32_eqz ], */
681
673
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
674
  ],
675
+ [TYPES._bytestring]: [ // duplicate of string
676
+ [ Opcodes.local_get, tmp ],
677
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
678
+
679
+ // get length
680
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
681
+
682
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
683
+ ],
683
684
  default: def
684
685
  }, intOut ? Valtype.i32 : valtypeBinary)
685
686
  ];
@@ -707,6 +708,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
707
708
  [ Opcodes.i32_eqz ],
708
709
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
710
  ],
711
+ [TYPES._bytestring]: [ // duplicate of string
712
+ [ Opcodes.local_get, tmp ],
713
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
714
+
715
+ // get length
716
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
717
+
718
+ // if length == 0
719
+ [ Opcodes.i32_eqz ],
720
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
721
+ ],
710
722
  default: [
711
723
  // if value == 0
712
724
  [ Opcodes.local_get, tmp ],
@@ -947,6 +959,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
959
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
960
  }
949
961
 
962
+ if (typeof wasm === 'function') {
963
+ const scope = {
964
+ name,
965
+ params,
966
+ locals,
967
+ returns,
968
+ localInd: allLocals.length,
969
+ };
970
+
971
+ wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
972
+ }
973
+
950
974
  let baseGlobalIdx, i = 0;
951
975
  for (const type of globalTypes) {
952
976
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1051,8 @@ const TYPES = {
1027
1051
 
1028
1052
  // these are not "typeof" types but tracked internally
1029
1053
  _array: 0x10,
1030
- _regexp: 0x11
1054
+ _regexp: 0x11,
1055
+ _bytestring: 0x12
1031
1056
  };
1032
1057
 
1033
1058
  const TYPE_NAMES = {
@@ -1041,7 +1066,8 @@ const TYPE_NAMES = {
1041
1066
  [TYPES.bigint]: 'BigInt',
1042
1067
 
1043
1068
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1069
+ [TYPES._regexp]: 'RegExp',
1070
+ [TYPES._bytestring]: 'ByteString'
1045
1071
  };
1046
1072
 
1047
1073
  const getType = (scope, _name) => {
@@ -1094,6 +1120,8 @@ const getNodeType = (scope, node) => {
1094
1120
  if (node.type === 'Literal') {
1095
1121
  if (node.regex) return TYPES._regexp;
1096
1122
 
1123
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1124
+
1097
1125
  return TYPES[typeof node.value];
1098
1126
  }
1099
1127
 
@@ -1107,6 +1135,15 @@ const getNodeType = (scope, node) => {
1107
1135
 
1108
1136
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1137
  const name = node.callee.name;
1138
+ if (!name) {
1139
+ // iife
1140
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1141
+
1142
+ // presume
1143
+ // todo: warn here?
1144
+ return TYPES.number;
1145
+ }
1146
+
1110
1147
  const func = funcs.find(x => x.name === name);
1111
1148
 
1112
1149
  if (func) {
@@ -1125,7 +1162,7 @@ const getNodeType = (scope, node) => {
1125
1162
  const spl = name.slice(2).split('_');
1126
1163
 
1127
1164
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1165
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1166
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1167
  }
1131
1168
 
@@ -1201,7 +1238,7 @@ const getNodeType = (scope, node) => {
1201
1238
  if (node.operator === '!') return TYPES.boolean;
1202
1239
  if (node.operator === 'void') return TYPES.undefined;
1203
1240
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1241
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1205
1242
 
1206
1243
  return TYPES.number;
1207
1244
  }
@@ -1210,7 +1247,13 @@ const getNodeType = (scope, node) => {
1210
1247
  // hack: if something.length, number type
1211
1248
  if (node.property.name === 'length') return TYPES.number;
1212
1249
 
1213
- // we cannot guess
1250
+ // ts hack
1251
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1252
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1253
+
1254
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1255
+
1256
+ // presume
1214
1257
  return TYPES.number;
1215
1258
  }
1216
1259
 
@@ -1230,8 +1273,8 @@ const getNodeType = (scope, node) => {
1230
1273
  const generateLiteral = (scope, decl, global, name) => {
1231
1274
  if (decl.value === null) return number(NULL);
1232
1275
 
1276
+ // hack: just return 1 for regex literals
1233
1277
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1278
  return number(1);
1236
1279
  }
1237
1280
 
@@ -1244,16 +1287,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1287
  return number(decl.value ? 1 : 0);
1245
1288
 
1246
1289
  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];
1290
+ return makeString(scope, decl.value, global, name);
1257
1291
 
1258
1292
  default:
1259
1293
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1274,9 +1308,9 @@ const countLeftover = wasm => {
1274
1308
 
1275
1309
  if (depth === 0)
1276
1310
  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)) {}
1311
+ 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
1312
  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;
1313
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1314
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1315
  else if (inst[0] === Opcodes.return) count = 0;
1282
1316
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1336,7 @@ const disposeLeftover = wasm => {
1302
1336
  const generateExp = (scope, decl) => {
1303
1337
  const expression = decl.expression;
1304
1338
 
1305
- const out = generate(scope, expression);
1339
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1340
  disposeLeftover(out);
1307
1341
 
1308
1342
  return out;
@@ -1360,7 +1394,7 @@ const RTArrayUtil = {
1360
1394
  ]
1361
1395
  };
1362
1396
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1397
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1398
  /* const callee = decl.callee;
1365
1399
  const args = decl.arguments;
1366
1400
 
@@ -1426,8 +1460,8 @@ const generateCall = (scope, decl, _global, _name) => {
1426
1460
  // literal.func()
1427
1461
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1462
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1463
+ const funcName = decl.callee.property.name;
1464
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1465
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1466
 
1433
1467
  funcIndex[func.name] = func.index;
@@ -1469,8 +1503,7 @@ const generateCall = (scope, decl, _global, _name) => {
1469
1503
 
1470
1504
  if (protoName) {
1471
1505
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1506
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1507
  return acc;
1475
1508
  }, {});
1476
1509
 
@@ -1479,10 +1512,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1512
  // use local for cached i32 length as commonly used
1480
1513
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1514
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1515
 
1484
1516
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1517
 
1518
+ const rawPointer = [
1519
+ ...generate(scope, target),
1520
+ Opcodes.i32_to_u
1521
+ ];
1522
+
1523
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1524
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1525
+
1526
+ let allOptUnused = true;
1486
1527
  let lengthI32CacheUsed = false;
1487
1528
  const protoBC = {};
1488
1529
  for (const x in protoCands) {
@@ -1502,6 +1543,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1543
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1544
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1545
 
1546
+ let optUnused = false;
1505
1547
  const protoOut = protoFunc(getPointer, {
1506
1548
  getCachedI32: () => {
1507
1549
  lengthI32CacheUsed = true;
@@ -1516,10 +1558,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1558
  return makeArray(scope, {
1517
1559
  rawElements: new Array(length)
1518
1560
  }, _global, _name, true, itemType);
1561
+ }, () => {
1562
+ optUnused = true;
1563
+ return unusedValue;
1519
1564
  });
1520
1565
 
1566
+ if (!optUnused) allOptUnused = false;
1567
+
1521
1568
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1569
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1570
  ...protoOut,
1524
1571
 
1525
1572
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1575,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1575
  ];
1529
1576
  }
1530
1577
 
1531
- return [
1532
- ...generate(scope, target),
1578
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1579
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1580
+ return [
1581
+ ...(usePointerCache ? [
1582
+ ...rawPointer,
1583
+ [ Opcodes.local_set, pointerLocal ],
1584
+ ] : []),
1536
1585
 
1537
1586
  ...(!lengthI32CacheUsed ? [] : [
1538
1587
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1593,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1593
 
1545
1594
  // TODO: error better
1546
1595
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1596
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1597
  ];
1549
1598
  }
1550
1599
  }
@@ -1591,7 +1640,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1640
  const func = funcs.find(x => x.index === idx);
1592
1641
 
1593
1642
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1643
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1644
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1645
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1646
 
1596
1647
  let args = decl.arguments;
1597
1648
  if (func && args.length < paramCount) {
@@ -1609,12 +1660,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1660
  let out = [];
1610
1661
  for (const arg of args) {
1611
1662
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1663
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1664
  }
1614
1665
 
1615
1666
  out.push([ Opcodes.call, idx ]);
1616
1667
 
1617
- if (!userFunc) {
1668
+ if (!typedReturn) {
1618
1669
  // let type;
1619
1670
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1671
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1716,102 @@ const knownType = (scope, type) => {
1665
1716
  return null;
1666
1717
  };
1667
1718
 
1719
+ const brTable = (input, bc, returns) => {
1720
+ const out = [];
1721
+ const keys = Object.keys(bc);
1722
+ const count = keys.length;
1723
+
1724
+ if (count === 1) {
1725
+ // return [
1726
+ // ...input,
1727
+ // ...bc[keys[0]]
1728
+ // ];
1729
+ return bc[keys[0]];
1730
+ }
1731
+
1732
+ if (count === 2) {
1733
+ // just use if else
1734
+ const other = keys.find(x => x !== 'default');
1735
+ return [
1736
+ ...input,
1737
+ ...number(other, Valtype.i32),
1738
+ [ Opcodes.i32_eq ],
1739
+ [ Opcodes.if, returns ],
1740
+ ...bc[other],
1741
+ [ Opcodes.else ],
1742
+ ...bc.default,
1743
+ [ Opcodes.end ]
1744
+ ];
1745
+ }
1746
+
1747
+ for (let i = 0; i < count; i++) {
1748
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1749
+ else out.push([ Opcodes.block, Blocktype.void ]);
1750
+ }
1751
+
1752
+ const nums = keys.filter(x => +x);
1753
+ const offset = Math.min(...nums);
1754
+ const max = Math.max(...nums);
1755
+
1756
+ const table = [];
1757
+ let br = 1;
1758
+
1759
+ for (let i = offset; i <= max; i++) {
1760
+ // if branch for this num, go to that block
1761
+ if (bc[i]) {
1762
+ table.push(br);
1763
+ br++;
1764
+ continue;
1765
+ }
1766
+
1767
+ // else default
1768
+ table.push(0);
1769
+ }
1770
+
1771
+ out.push(
1772
+ [ Opcodes.block, Blocktype.void ],
1773
+ ...input,
1774
+ ...(offset > 0 ? [
1775
+ ...number(offset, Valtype.i32),
1776
+ [ Opcodes.i32_sub ]
1777
+ ] : []),
1778
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1779
+ );
1780
+
1781
+ // if you can guess why we sort the wrong way and then reverse
1782
+ // (instead of just sorting the correct way)
1783
+ // dm me and if you are correct and the first person
1784
+ // I will somehow shout you out or something
1785
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1786
+
1787
+ br = count - 1;
1788
+ for (const x of orderedBc) {
1789
+ out.push(
1790
+ [ Opcodes.end ],
1791
+ ...bc[x],
1792
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1793
+ );
1794
+ br--;
1795
+ }
1796
+
1797
+ return [
1798
+ ...out,
1799
+ [ Opcodes.end, 'br table end' ]
1800
+ ];
1801
+ };
1802
+
1668
1803
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1804
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1805
+
1669
1806
  const known = knownType(scope, type);
1670
1807
  if (known != null) {
1671
1808
  return bc[known] ?? bc.default;
1672
1809
  }
1673
1810
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1811
+ if (process.argv.includes('-typeswitch-use-brtable'))
1812
+ return brTable(type, bc, returns);
1675
1813
 
1814
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1815
  const out = [
1677
1816
  ...type,
1678
1817
  [ Opcodes.local_set, tmp ],
@@ -1762,6 +1901,8 @@ const extractTypeAnnotation = decl => {
1762
1901
  const typeName = type;
1763
1902
  type = typeAnnoToPorfType(type);
1764
1903
 
1904
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1905
+
1765
1906
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
1907
 
1767
1908
  return { type, typeName, elementType };
@@ -1778,6 +1919,8 @@ const generateVar = (scope, decl) => {
1778
1919
  for (const x of decl.declarations) {
1779
1920
  const name = mapName(x.id.name);
1780
1921
 
1922
+ if (!name) return todo('destructuring is not supported yet');
1923
+
1781
1924
  if (x.init && isFuncType(x.init.type)) {
1782
1925
  // hack for let a = function () { ... }
1783
1926
  x.init.id = { name };
@@ -1916,6 +2059,8 @@ const generateAssign = (scope, decl) => {
1916
2059
  ];
1917
2060
  }
1918
2061
 
2062
+ if (!name) return todo('destructuring is not supported yet');
2063
+
1919
2064
  const [ local, isGlobal ] = lookupName(scope, name);
1920
2065
 
1921
2066
  if (local === undefined) {
@@ -2054,6 +2199,8 @@ const generateUnary = (scope, decl) => {
2054
2199
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2055
2200
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2056
2201
 
2202
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2203
+
2057
2204
  // object and internal types
2058
2205
  default: makeString(scope, 'object', false, '#typeof_result'),
2059
2206
  });
@@ -2159,8 +2306,10 @@ const generateFor = (scope, decl) => {
2159
2306
  out.push([ Opcodes.loop, Blocktype.void ]);
2160
2307
  depth.push('for');
2161
2308
 
2162
- out.push(...generate(scope, decl.test));
2163
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2309
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2310
+ else out.push(...number(1, Valtype.i32));
2311
+
2312
+ out.push([ Opcodes.if, Blocktype.void ]);
2164
2313
  depth.push('if');
2165
2314
 
2166
2315
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2168,8 +2317,7 @@ const generateFor = (scope, decl) => {
2168
2317
  out.push(...generate(scope, decl.body));
2169
2318
  out.push([ Opcodes.end ]);
2170
2319
 
2171
- out.push(...generate(scope, decl.update));
2172
- depth.pop();
2320
+ if (decl.update) out.push(...generate(scope, decl.update));
2173
2321
 
2174
2322
  out.push([ Opcodes.br, 1 ]);
2175
2323
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2226,7 +2374,13 @@ const generateForOf = (scope, decl) => {
2226
2374
  // setup local for left
2227
2375
  generate(scope, decl.left);
2228
2376
 
2229
- const leftName = decl.left.declarations[0].id.name;
2377
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2378
+ if (!leftName && decl.left.name) {
2379
+ leftName = decl.left.name;
2380
+
2381
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2382
+ }
2383
+
2230
2384
  const [ local, isGlobal ] = lookupName(scope, leftName);
2231
2385
 
2232
2386
  depth.push('block');
@@ -2235,13 +2389,14 @@ const generateForOf = (scope, decl) => {
2235
2389
  // // todo: we should only do this for strings but we don't know at compile-time :(
2236
2390
  // hack: this is naughty and will break things!
2237
2391
  let newOut = number(0, Valtype.f64), newPointer = -1;
2238
- if (pages.hasString) {
2392
+ if (pages.hasAnyString) {
2239
2393
  0, [ newOut, newPointer ] = makeArray(scope, {
2240
2394
  rawElements: new Array(1)
2241
2395
  }, isGlobal, leftName, true, 'i16');
2242
2396
  }
2243
2397
 
2244
2398
  // set type for local
2399
+ // todo: optimize away counter and use end pointer
2245
2400
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2246
2401
  [TYPES._array]: [
2247
2402
  ...setType(scope, leftName, TYPES.number),
@@ -2366,7 +2521,7 @@ const generateThrow = (scope, decl) => {
2366
2521
  // hack: throw new X("...") -> throw "..."
2367
2522
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2368
2523
  constructor = decl.argument.callee.name;
2369
- message = decl.argument.arguments[0].value;
2524
+ message = decl.argument.arguments[0]?.value ?? '';
2370
2525
  }
2371
2526
 
2372
2527
  if (tags.length === 0) tags.push({
@@ -2427,6 +2582,8 @@ const allocPage = (reason, type) => {
2427
2582
 
2428
2583
  if (reason.startsWith('array:')) pages.hasArray = true;
2429
2584
  if (reason.startsWith('string:')) pages.hasString = true;
2585
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2586
+ if (reason.includes('string:')) pages.hasAnyString = true;
2430
2587
 
2431
2588
  const ind = pages.size;
2432
2589
  pages.set(reason, { ind, type });
@@ -2460,7 +2617,8 @@ const StoreOps = {
2460
2617
  f64: Opcodes.f64_store,
2461
2618
 
2462
2619
  // expects i32 input!
2463
- i16: Opcodes.i32_store16
2620
+ i8: Opcodes.i32_store8,
2621
+ i16: Opcodes.i32_store16,
2464
2622
  };
2465
2623
 
2466
2624
  let data = [];
@@ -2479,6 +2637,15 @@ const compileBytes = (val, itemType, signed = true) => {
2479
2637
  }
2480
2638
  };
2481
2639
 
2640
+ const getAllocType = itemType => {
2641
+ switch (itemType) {
2642
+ case 'i8': return 'bytestring';
2643
+ case 'i16': return 'string';
2644
+
2645
+ default: return 'array';
2646
+ }
2647
+ };
2648
+
2482
2649
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2483
2650
  const out = [];
2484
2651
 
@@ -2488,7 +2655,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2488
2655
 
2489
2656
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2490
2657
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2491
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2658
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2492
2659
  }
2493
2660
 
2494
2661
  const pointer = arrays.get(name);
@@ -2534,7 +2701,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2534
2701
  out.push(
2535
2702
  ...number(0, Valtype.i32),
2536
2703
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2537
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2704
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2538
2705
  );
2539
2706
  }
2540
2707
 
@@ -2544,15 +2711,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2544
2711
  return [ out, pointer ];
2545
2712
  };
2546
2713
 
2714
+ const byteStringable = str => {
2715
+ if (!process.argv.includes('-bytestring')) return false;
2716
+
2717
+ for (let i = 0; i < str.length; i++) {
2718
+ if (str.charCodeAt(i) > 0xFF) return false;
2719
+ }
2720
+
2721
+ return true;
2722
+ };
2723
+
2547
2724
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2548
2725
  const rawElements = new Array(str.length);
2726
+ let byteStringable = process.argv.includes('-bytestring');
2549
2727
  for (let i = 0; i < str.length; i++) {
2550
- rawElements[i] = str.charCodeAt(i);
2728
+ const c = str.charCodeAt(i);
2729
+ rawElements[i] = c;
2730
+
2731
+ if (byteStringable && c > 0xFF) byteStringable = false;
2551
2732
  }
2552
2733
 
2553
2734
  return makeArray(scope, {
2554
2735
  rawElements
2555
- }, global, name, false, 'i16')[0];
2736
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2556
2737
  };
2557
2738
 
2558
2739
  let arrays = new Map();
@@ -2580,10 +2761,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2580
2761
  ];
2581
2762
  }
2582
2763
 
2764
+ const object = generate(scope, decl.object);
2765
+ const property = generate(scope, decl.property);
2766
+
2583
2767
  // // todo: we should only do this for strings but we don't know at compile-time :(
2584
2768
  // hack: this is naughty and will break things!
2585
2769
  let newOut = number(0, valtypeBinary), newPointer = -1;
2586
- if (pages.hasString) {
2770
+ if (pages.hasAnyString) {
2587
2771
  0, [ newOut, newPointer ] = makeArray(scope, {
2588
2772
  rawElements: new Array(1)
2589
2773
  }, _global, _name, true, 'i16');
@@ -2592,7 +2776,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2592
2776
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2593
2777
  [TYPES._array]: [
2594
2778
  // get index as valtype
2595
- ...generate(scope, decl.property),
2779
+ ...property,
2596
2780
 
2597
2781
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2598
2782
  Opcodes.i32_to_u,
@@ -2600,7 +2784,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2600
2784
  [ Opcodes.i32_mul ],
2601
2785
 
2602
2786
  ...(aotPointer ? [] : [
2603
- ...generate(scope, decl.object),
2787
+ ...object,
2604
2788
  Opcodes.i32_to_u,
2605
2789
  [ Opcodes.i32_add ]
2606
2790
  ]),
@@ -2619,14 +2803,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2619
2803
 
2620
2804
  ...number(0, Valtype.i32), // base 0 for store later
2621
2805
 
2622
- ...generate(scope, decl.property),
2623
-
2806
+ ...property,
2624
2807
  Opcodes.i32_to_u,
2808
+
2625
2809
  ...number(ValtypeSize.i16, Valtype.i32),
2626
2810
  [ Opcodes.i32_mul ],
2627
2811
 
2628
2812
  ...(aotPointer ? [] : [
2629
- ...generate(scope, decl.object),
2813
+ ...object,
2630
2814
  Opcodes.i32_to_u,
2631
2815
  [ Opcodes.i32_add ]
2632
2816
  ]),
@@ -2643,8 +2827,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2643
2827
  ...number(TYPES.string, Valtype.i32),
2644
2828
  setLastType(scope)
2645
2829
  ],
2830
+ [TYPES._bytestring]: [
2831
+ // setup new/out array
2832
+ ...newOut,
2833
+ [ Opcodes.drop ],
2834
+
2835
+ ...number(0, Valtype.i32), // base 0 for store later
2836
+
2837
+ ...property,
2838
+ Opcodes.i32_to_u,
2839
+
2840
+ ...(aotPointer ? [] : [
2841
+ ...object,
2842
+ Opcodes.i32_to_u,
2843
+ [ Opcodes.i32_add ]
2844
+ ]),
2845
+
2846
+ // load current string ind {arg}
2847
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2848
+
2849
+ // store to new string ind 0
2850
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2851
+
2852
+ // return new string (page)
2853
+ ...number(newPointer),
2854
+
2855
+ ...number(TYPES._bytestring, Valtype.i32),
2856
+ setLastType(scope)
2857
+ ],
2646
2858
 
2647
- default: [ [ Opcodes.unreachable ] ]
2859
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2648
2860
  });
2649
2861
  };
2650
2862
 
@@ -2661,11 +2873,14 @@ const objectHack = node => {
2661
2873
  // if object is not identifier or another member exp, give up
2662
2874
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2663
2875
 
2664
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2876
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2665
2877
 
2666
2878
  // if .length, give up (hack within a hack!)
2667
2879
  if (node.property.name === 'length') return node;
2668
2880
 
2881
+ // no object name, give up
2882
+ if (!objectName) return node;
2883
+
2669
2884
  const name = '__' + objectName + '_' + node.property.name;
2670
2885
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2671
2886
 
@@ -2724,10 +2939,8 @@ const generateFunc = (scope, decl) => {
2724
2939
  const func = {
2725
2940
  name,
2726
2941
  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++
2942
+ index: currentFuncIndex++,
2943
+ ...innerScope
2731
2944
  };
2732
2945
  funcIndex[name] = func.index;
2733
2946
 
@@ -2765,6 +2978,16 @@ const generateCode = (scope, decl) => {
2765
2978
  };
2766
2979
 
2767
2980
  const internalConstrs = {
2981
+ Boolean: {
2982
+ generate: (scope, decl) => {
2983
+ if (decl.arguments.length === 0) return number(0);
2984
+
2985
+ // should generate/run all args
2986
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
2987
+ },
2988
+ type: TYPES.boolean
2989
+ },
2990
+
2768
2991
  Array: {
2769
2992
  generate: (scope, decl, global, name) => {
2770
2993
  // new Array(i0, i1, ...)