porffor 0.2.0-a759814 → 0.2.0-af678f0

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);
@@ -155,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
155
160
 
156
161
  case 'TaggedTemplateExpression': {
157
162
  const funcs = {
158
- asm: str => {
163
+ __Porffor_asm: str => {
159
164
  let out = [];
160
165
 
161
166
  for (const line of str.split('\n')) {
@@ -191,19 +196,19 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
196
  return out;
192
197
  },
193
198
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
199
+ __Porffor_bs: str => [
200
+ ...makeString(scope, str, undefined, undefined, true),
196
201
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
202
+ ...number(TYPES._bytestring, Valtype.i32),
203
+ setLastType(scope)
204
+ ],
205
+ __Porffor_s: str => [
206
+ ...makeString(scope, str, undefined, undefined, false),
200
207
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
208
+ ...number(TYPES.string, Valtype.i32),
209
+ setLastType(scope)
210
+ ],
211
+ };
207
212
 
208
213
  const name = decl.tag.name;
209
214
  // hack for inline asm
@@ -269,25 +274,25 @@ const generateIdent = (scope, decl) => {
269
274
  const name = mapName(rawName);
270
275
  let local = scope.locals[rawName];
271
276
 
272
- if (builtinVars[name]) {
277
+ if (Object.hasOwn(builtinVars, name)) {
273
278
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
279
  return builtinVars[name];
275
280
  }
276
281
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
282
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
283
  // todo: return an actual something
279
284
  return number(1);
280
285
  }
281
286
 
282
- if (local === undefined) {
287
+ if (local?.idx === undefined) {
283
288
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
289
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
290
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
291
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
292
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
293
  }
289
294
 
290
- if (local === undefined && rawName.startsWith('__')) {
295
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
296
  // return undefined if unknown key in already known var
292
297
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
298
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +301,7 @@ const generateIdent = (scope, decl) => {
296
301
  if (!parentLookup[1]) return number(UNDEFINED);
297
302
  }
298
303
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
304
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
305
 
301
306
  return [ [ Opcodes.local_get, local.idx ] ];
302
307
  };
@@ -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 ],
@@ -900,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
900
925
  [ Opcodes.i32_or ],
901
926
  [ Opcodes.if, Blocktype.void ],
902
927
  ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
928
+ [ Opcodes.br, 2 ],
904
929
  [ Opcodes.end ],
905
930
 
906
931
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -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, TYPE_NAMES, typeSwitch, makeArray, makeString });
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
 
@@ -1230,8 +1286,8 @@ const getNodeType = (scope, node) => {
1230
1286
  const generateLiteral = (scope, decl, global, name) => {
1231
1287
  if (decl.value === null) return number(NULL);
1232
1288
 
1289
+ // hack: just return 1 for regex literals
1233
1290
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1291
  return number(1);
1236
1292
  }
1237
1293
 
@@ -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
 
@@ -1426,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
1426
1473
  // literal.func()
1427
1474
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1475
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1476
+ const funcName = decl.callee.property.name;
1477
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1478
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1479
 
1433
1480
  funcIndex[func.name] = func.index;
@@ -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 };
@@ -1778,6 +1932,8 @@ const generateVar = (scope, decl) => {
1778
1932
  for (const x of decl.declarations) {
1779
1933
  const name = mapName(x.id.name);
1780
1934
 
1935
+ if (!name) return todo('destructuring is not supported yet');
1936
+
1781
1937
  if (x.init && isFuncType(x.init.type)) {
1782
1938
  // hack for let a = function () { ... }
1783
1939
  x.init.id = { name };
@@ -1794,6 +1950,11 @@ const generateVar = (scope, decl) => {
1794
1950
  }
1795
1951
 
1796
1952
  let idx = allocVar(scope, name, global);
1953
+
1954
+ if (typedInput && x.id.typeAnnotation) {
1955
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
+ }
1957
+
1797
1958
  if (x.init) {
1798
1959
  out = out.concat(generate(scope, x.init, global, name));
1799
1960
 
@@ -1803,10 +1964,6 @@ const generateVar = (scope, decl) => {
1803
1964
 
1804
1965
  // hack: this follows spec properly but is mostly unneeded 😅
1805
1966
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1806
-
1807
- if (typedInput && x.id.typeAnnotation) {
1808
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1809
- }
1810
1967
  }
1811
1968
 
1812
1969
  return out;
@@ -1915,6 +2072,8 @@ const generateAssign = (scope, decl) => {
1915
2072
  ];
1916
2073
  }
1917
2074
 
2075
+ if (!name) return todo('destructuring is not supported yet');
2076
+
1918
2077
  const [ local, isGlobal ] = lookupName(scope, name);
1919
2078
 
1920
2079
  if (local === undefined) {
@@ -2053,6 +2212,8 @@ const generateUnary = (scope, decl) => {
2053
2212
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2054
2213
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2055
2214
 
2215
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
+
2056
2217
  // object and internal types
2057
2218
  default: makeString(scope, 'object', false, '#typeof_result'),
2058
2219
  });
@@ -2158,8 +2319,10 @@ const generateFor = (scope, decl) => {
2158
2319
  out.push([ Opcodes.loop, Blocktype.void ]);
2159
2320
  depth.push('for');
2160
2321
 
2161
- out.push(...generate(scope, decl.test));
2162
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2322
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2323
+ else out.push(...number(1, Valtype.i32));
2324
+
2325
+ out.push([ Opcodes.if, Blocktype.void ]);
2163
2326
  depth.push('if');
2164
2327
 
2165
2328
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2167,8 +2330,7 @@ const generateFor = (scope, decl) => {
2167
2330
  out.push(...generate(scope, decl.body));
2168
2331
  out.push([ Opcodes.end ]);
2169
2332
 
2170
- out.push(...generate(scope, decl.update));
2171
- depth.pop();
2333
+ if (decl.update) out.push(...generate(scope, decl.update));
2172
2334
 
2173
2335
  out.push([ Opcodes.br, 1 ]);
2174
2336
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2225,7 +2387,13 @@ const generateForOf = (scope, decl) => {
2225
2387
  // setup local for left
2226
2388
  generate(scope, decl.left);
2227
2389
 
2228
- const leftName = decl.left.declarations[0].id.name;
2390
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2391
+ if (!leftName && decl.left.name) {
2392
+ leftName = decl.left.name;
2393
+
2394
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
+ }
2396
+
2229
2397
  const [ local, isGlobal ] = lookupName(scope, leftName);
2230
2398
 
2231
2399
  depth.push('block');
@@ -2234,13 +2402,14 @@ const generateForOf = (scope, decl) => {
2234
2402
  // // todo: we should only do this for strings but we don't know at compile-time :(
2235
2403
  // hack: this is naughty and will break things!
2236
2404
  let newOut = number(0, Valtype.f64), newPointer = -1;
2237
- if (pages.hasString) {
2405
+ if (pages.hasAnyString) {
2238
2406
  0, [ newOut, newPointer ] = makeArray(scope, {
2239
2407
  rawElements: new Array(1)
2240
2408
  }, isGlobal, leftName, true, 'i16');
2241
2409
  }
2242
2410
 
2243
2411
  // set type for local
2412
+ // todo: optimize away counter and use end pointer
2244
2413
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2245
2414
  [TYPES._array]: [
2246
2415
  ...setType(scope, leftName, TYPES.number),
@@ -2365,7 +2534,7 @@ const generateThrow = (scope, decl) => {
2365
2534
  // hack: throw new X("...") -> throw "..."
2366
2535
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2367
2536
  constructor = decl.argument.callee.name;
2368
- message = decl.argument.arguments[0].value;
2537
+ message = decl.argument.arguments[0]?.value ?? '';
2369
2538
  }
2370
2539
 
2371
2540
  if (tags.length === 0) tags.push({
@@ -2426,6 +2595,8 @@ const allocPage = (reason, type) => {
2426
2595
 
2427
2596
  if (reason.startsWith('array:')) pages.hasArray = true;
2428
2597
  if (reason.startsWith('string:')) pages.hasString = true;
2598
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2599
+ if (reason.includes('string:')) pages.hasAnyString = true;
2429
2600
 
2430
2601
  const ind = pages.size;
2431
2602
  pages.set(reason, { ind, type });
@@ -2459,25 +2630,34 @@ const StoreOps = {
2459
2630
  f64: Opcodes.f64_store,
2460
2631
 
2461
2632
  // expects i32 input!
2462
- i16: Opcodes.i32_store16
2633
+ i8: Opcodes.i32_store8,
2634
+ i16: Opcodes.i32_store16,
2463
2635
  };
2464
2636
 
2465
2637
  let data = [];
2466
2638
 
2467
- const compileBytes = (val, itemType, signed = true) => {
2639
+ const compileBytes = (val, itemType) => {
2468
2640
  // todo: this is a mess and needs confirming / ????
2469
2641
  switch (itemType) {
2470
2642
  case 'i8': return [ val % 256 ];
2471
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2472
-
2473
- case 'i32':
2474
- case 'i64':
2475
- return enforceFourBytes(signedLEB128(val));
2643
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2644
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2645
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2646
+ // todo: i64
2476
2647
 
2477
2648
  case 'f64': return ieee754_binary64(val);
2478
2649
  }
2479
2650
  };
2480
2651
 
2652
+ const getAllocType = itemType => {
2653
+ switch (itemType) {
2654
+ case 'i8': return 'bytestring';
2655
+ case 'i16': return 'string';
2656
+
2657
+ default: return 'array';
2658
+ }
2659
+ };
2660
+
2481
2661
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2482
2662
  const out = [];
2483
2663
 
@@ -2487,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2487
2667
 
2488
2668
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2489
2669
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2490
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2670
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2491
2671
  }
2492
2672
 
2493
2673
  const pointer = arrays.get(name);
@@ -2533,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2533
2713
  out.push(
2534
2714
  ...number(0, Valtype.i32),
2535
2715
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2536
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2716
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2537
2717
  );
2538
2718
  }
2539
2719
 
@@ -2543,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2543
2723
  return [ out, pointer ];
2544
2724
  };
2545
2725
 
2546
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2726
+ const byteStringable = str => {
2727
+ if (!process.argv.includes('-bytestring')) return false;
2728
+
2729
+ for (let i = 0; i < str.length; i++) {
2730
+ if (str.charCodeAt(i) > 0xFF) return false;
2731
+ }
2732
+
2733
+ return true;
2734
+ };
2735
+
2736
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2547
2737
  const rawElements = new Array(str.length);
2738
+ let byteStringable = process.argv.includes('-bytestring');
2548
2739
  for (let i = 0; i < str.length; i++) {
2549
- rawElements[i] = str.charCodeAt(i);
2740
+ const c = str.charCodeAt(i);
2741
+ rawElements[i] = c;
2742
+
2743
+ if (byteStringable && c > 0xFF) byteStringable = false;
2550
2744
  }
2551
2745
 
2746
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2747
+
2552
2748
  return makeArray(scope, {
2553
2749
  rawElements
2554
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2555
2751
  };
2556
2752
 
2557
2753
  let arrays = new Map();
@@ -2579,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2579
2775
  ];
2580
2776
  }
2581
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2582
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2583
2782
  // hack: this is naughty and will break things!
2584
- let newOut = number(0, Valtype.f64), newPointer = -1;
2585
- if (pages.hasString) {
2783
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2784
+ if (pages.hasAnyString) {
2586
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2587
2786
  rawElements: new Array(1)
2588
2787
  }, _global, _name, true, 'i16');
@@ -2591,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2591
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2592
2791
  [TYPES._array]: [
2593
2792
  // get index as valtype
2594
- ...generate(scope, decl.property),
2793
+ ...property,
2595
2794
 
2596
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2597
2796
  Opcodes.i32_to_u,
@@ -2599,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2599
2798
  [ Opcodes.i32_mul ],
2600
2799
 
2601
2800
  ...(aotPointer ? [] : [
2602
- ...generate(scope, decl.object),
2801
+ ...object,
2603
2802
  Opcodes.i32_to_u,
2604
2803
  [ Opcodes.i32_add ]
2605
2804
  ]),
@@ -2618,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2618
2817
 
2619
2818
  ...number(0, Valtype.i32), // base 0 for store later
2620
2819
 
2621
- ...generate(scope, decl.property),
2622
-
2820
+ ...property,
2623
2821
  Opcodes.i32_to_u,
2822
+
2624
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2625
2824
  [ Opcodes.i32_mul ],
2626
2825
 
2627
2826
  ...(aotPointer ? [] : [
2628
- ...generate(scope, decl.object),
2827
+ ...object,
2629
2828
  Opcodes.i32_to_u,
2630
2829
  [ Opcodes.i32_add ]
2631
2830
  ]),
@@ -2642,8 +2841,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2642
2841
  ...number(TYPES.string, Valtype.i32),
2643
2842
  setLastType(scope)
2644
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,
2645
2853
 
2646
- default: [ [ Opcodes.unreachable ] ]
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
+ ],
2872
+
2873
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2647
2874
  });
2648
2875
  };
2649
2876
 
@@ -2660,11 +2887,14 @@ const objectHack = node => {
2660
2887
  // if object is not identifier or another member exp, give up
2661
2888
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2662
2889
 
2663
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2890
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2664
2891
 
2665
2892
  // if .length, give up (hack within a hack!)
2666
2893
  if (node.property.name === 'length') return node;
2667
2894
 
2895
+ // no object name, give up
2896
+ if (!objectName) return node;
2897
+
2668
2898
  const name = '__' + objectName + '_' + node.property.name;
2669
2899
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2670
2900
 
@@ -2723,10 +2953,8 @@ const generateFunc = (scope, decl) => {
2723
2953
  const func = {
2724
2954
  name,
2725
2955
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2726
- returns: innerScope.returns,
2727
- locals: innerScope.locals,
2728
- throws: innerScope.throws,
2729
- index: currentFuncIndex++
2956
+ index: currentFuncIndex++,
2957
+ ...innerScope
2730
2958
  };
2731
2959
  funcIndex[name] = func.index;
2732
2960
 
@@ -2764,6 +2992,16 @@ const generateCode = (scope, decl) => {
2764
2992
  };
2765
2993
 
2766
2994
  const internalConstrs = {
2995
+ Boolean: {
2996
+ generate: (scope, decl) => {
2997
+ if (decl.arguments.length === 0) return number(0);
2998
+
2999
+ // should generate/run all args
3000
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3001
+ },
3002
+ type: TYPES.boolean
3003
+ },
3004
+
2767
3005
  Array: {
2768
3006
  generate: (scope, decl, global, name) => {
2769
3007
  // new Array(i0, i1, ...)