porffor 0.2.0-5e33105 → 0.2.0-623cdf0

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.
@@ -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
 
@@ -1227,28 +1283,11 @@ const getNodeType = (scope, node) => {
1227
1283
  return ret;
1228
1284
  };
1229
1285
 
1230
- const toString = (scope, wasm, type) => {
1231
- const tmp = localTmp(scope, '#tostring_tmp');
1232
- return [
1233
- ...wasm,
1234
- [ Opcodes.local_set, tmp ],
1235
-
1236
- ...typeSwitch(scope, type, {
1237
- [TYPES.string]: [
1238
- [ Opcodes.local_get, tmp ]
1239
- ],
1240
- [TYPES.undefined]: [
1241
- // [ Opcodes.]
1242
- ]
1243
- })
1244
- ]
1245
- };
1246
-
1247
1286
  const generateLiteral = (scope, decl, global, name) => {
1248
1287
  if (decl.value === null) return number(NULL);
1249
1288
 
1289
+ // hack: just return 1 for regex literals
1250
1290
  if (decl.regex) {
1251
- scope.regex[name] = decl.regex;
1252
1291
  return number(1);
1253
1292
  }
1254
1293
 
@@ -1261,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
1261
1300
  return number(decl.value ? 1 : 0);
1262
1301
 
1263
1302
  case 'string':
1264
- const str = decl.value;
1265
- const rawElements = new Array(str.length);
1266
- let j = 0;
1267
- for (let i = 0; i < str.length; i++) {
1268
- rawElements[i] = str.charCodeAt(i);
1269
- }
1270
-
1271
- return makeArray(scope, {
1272
- rawElements
1273
- }, global, name, false, 'i16')[0];
1303
+ return makeString(scope, decl.value, global, name);
1274
1304
 
1275
1305
  default:
1276
1306
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1291,9 +1321,9 @@ const countLeftover = wasm => {
1291
1321
 
1292
1322
  if (depth === 0)
1293
1323
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1294
- 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)) {}
1295
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++;
1296
- 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;
1297
1327
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1298
1328
  else if (inst[0] === Opcodes.return) count = 0;
1299
1329
  else if (inst[0] === Opcodes.call) {
@@ -1319,7 +1349,7 @@ const disposeLeftover = wasm => {
1319
1349
  const generateExp = (scope, decl) => {
1320
1350
  const expression = decl.expression;
1321
1351
 
1322
- const out = generate(scope, expression);
1352
+ const out = generate(scope, expression, undefined, undefined, true);
1323
1353
  disposeLeftover(out);
1324
1354
 
1325
1355
  return out;
@@ -1377,7 +1407,7 @@ const RTArrayUtil = {
1377
1407
  ]
1378
1408
  };
1379
1409
 
1380
- const generateCall = (scope, decl, _global, _name) => {
1410
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1381
1411
  /* const callee = decl.callee;
1382
1412
  const args = decl.arguments;
1383
1413
 
@@ -1443,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
1443
1473
  // literal.func()
1444
1474
  if (!name && decl.callee.type === 'MemberExpression') {
1445
1475
  // megahack for /regex/.func()
1446
- if (decl.callee.object.regex) {
1447
- const funcName = decl.callee.property.name;
1476
+ const funcName = decl.callee.property.name;
1477
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1448
1478
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1449
1479
 
1450
1480
  funcIndex[func.name] = func.index;
@@ -1486,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
1486
1516
 
1487
1517
  if (protoName) {
1488
1518
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1489
- const f = prototypeFuncs[x][protoName];
1490
- if (f) acc[x] = f;
1519
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1491
1520
  return acc;
1492
1521
  }, {});
1493
1522
 
@@ -1496,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
1496
1525
  // use local for cached i32 length as commonly used
1497
1526
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1498
1527
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1499
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1500
1528
 
1501
1529
  // TODO: long-term, prototypes should be their individual separate funcs
1502
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;
1503
1540
  let lengthI32CacheUsed = false;
1504
1541
  const protoBC = {};
1505
1542
  for (const x in protoCands) {
@@ -1519,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
1519
1556
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1520
1557
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1521
1558
 
1559
+ let optUnused = false;
1522
1560
  const protoOut = protoFunc(getPointer, {
1523
1561
  getCachedI32: () => {
1524
1562
  lengthI32CacheUsed = true;
@@ -1533,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
1533
1571
  return makeArray(scope, {
1534
1572
  rawElements: new Array(length)
1535
1573
  }, _global, _name, true, itemType);
1574
+ }, () => {
1575
+ optUnused = true;
1576
+ return unusedValue;
1536
1577
  });
1537
1578
 
1579
+ if (!optUnused) allOptUnused = false;
1580
+
1538
1581
  protoBC[x] = [
1539
- [ Opcodes.block, valtypeBinary ],
1582
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1540
1583
  ...protoOut,
1541
1584
 
1542
1585
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1545,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
1545
1588
  ];
1546
1589
  }
1547
1590
 
1548
- return [
1549
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1550
1592
 
1551
- Opcodes.i32_to_u,
1552
- [ Opcodes.local_set, pointerLocal ],
1593
+ return [
1594
+ ...(usePointerCache ? [
1595
+ ...rawPointer,
1596
+ [ Opcodes.local_set, pointerLocal ],
1597
+ ] : []),
1553
1598
 
1554
1599
  ...(!lengthI32CacheUsed ? [] : [
1555
1600
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1561,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1561
1606
 
1562
1607
  // TODO: error better
1563
1608
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1564
- }, valtypeBinary),
1609
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1565
1610
  ];
1566
1611
  }
1567
1612
  }
@@ -1608,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
1608
1653
  const func = funcs.find(x => x.index === idx);
1609
1654
 
1610
1655
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1611
- 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);
1612
1659
 
1613
1660
  let args = decl.arguments;
1614
1661
  if (func && args.length < paramCount) {
@@ -1626,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
1626
1673
  let out = [];
1627
1674
  for (const arg of args) {
1628
1675
  out = out.concat(generate(scope, arg));
1629
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1676
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1630
1677
  }
1631
1678
 
1632
1679
  out.push([ Opcodes.call, idx ]);
1633
1680
 
1634
- if (!userFunc) {
1681
+ if (!typedReturn) {
1635
1682
  // let type;
1636
1683
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1637
1684
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1767,6 +1814,8 @@ const brTable = (input, bc, returns) => {
1767
1814
  };
1768
1815
 
1769
1816
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1818
+
1770
1819
  const known = knownType(scope, type);
1771
1820
  if (known != null) {
1772
1821
  return bc[known] ?? bc.default;
@@ -1865,6 +1914,8 @@ const extractTypeAnnotation = decl => {
1865
1914
  const typeName = type;
1866
1915
  type = typeAnnoToPorfType(type);
1867
1916
 
1917
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1918
+
1868
1919
  // if (decl.name) console.log(decl.name, { type, elementType });
1869
1920
 
1870
1921
  return { type, typeName, elementType };
@@ -1881,6 +1932,8 @@ const generateVar = (scope, decl) => {
1881
1932
  for (const x of decl.declarations) {
1882
1933
  const name = mapName(x.id.name);
1883
1934
 
1935
+ if (!name) return todo('destructuring is not supported yet');
1936
+
1884
1937
  if (x.init && isFuncType(x.init.type)) {
1885
1938
  // hack for let a = function () { ... }
1886
1939
  x.init.id = { name };
@@ -2019,6 +2072,8 @@ const generateAssign = (scope, decl) => {
2019
2072
  ];
2020
2073
  }
2021
2074
 
2075
+ if (!name) return todo('destructuring is not supported yet');
2076
+
2022
2077
  const [ local, isGlobal ] = lookupName(scope, name);
2023
2078
 
2024
2079
  if (local === undefined) {
@@ -2157,6 +2212,8 @@ const generateUnary = (scope, decl) => {
2157
2212
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2158
2213
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2159
2214
 
2215
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
+
2160
2217
  // object and internal types
2161
2218
  default: makeString(scope, 'object', false, '#typeof_result'),
2162
2219
  });
@@ -2262,8 +2319,10 @@ const generateFor = (scope, decl) => {
2262
2319
  out.push([ Opcodes.loop, Blocktype.void ]);
2263
2320
  depth.push('for');
2264
2321
 
2265
- out.push(...generate(scope, decl.test));
2266
- 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 ]);
2267
2326
  depth.push('if');
2268
2327
 
2269
2328
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2271,8 +2330,7 @@ const generateFor = (scope, decl) => {
2271
2330
  out.push(...generate(scope, decl.body));
2272
2331
  out.push([ Opcodes.end ]);
2273
2332
 
2274
- out.push(...generate(scope, decl.update));
2275
- depth.pop();
2333
+ if (decl.update) out.push(...generate(scope, decl.update));
2276
2334
 
2277
2335
  out.push([ Opcodes.br, 1 ]);
2278
2336
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2329,7 +2387,13 @@ const generateForOf = (scope, decl) => {
2329
2387
  // setup local for left
2330
2388
  generate(scope, decl.left);
2331
2389
 
2332
- 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
+
2333
2397
  const [ local, isGlobal ] = lookupName(scope, leftName);
2334
2398
 
2335
2399
  depth.push('block');
@@ -2338,13 +2402,14 @@ const generateForOf = (scope, decl) => {
2338
2402
  // // todo: we should only do this for strings but we don't know at compile-time :(
2339
2403
  // hack: this is naughty and will break things!
2340
2404
  let newOut = number(0, Valtype.f64), newPointer = -1;
2341
- if (pages.hasString) {
2405
+ if (pages.hasAnyString) {
2342
2406
  0, [ newOut, newPointer ] = makeArray(scope, {
2343
2407
  rawElements: new Array(1)
2344
2408
  }, isGlobal, leftName, true, 'i16');
2345
2409
  }
2346
2410
 
2347
2411
  // set type for local
2412
+ // todo: optimize away counter and use end pointer
2348
2413
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2349
2414
  [TYPES._array]: [
2350
2415
  ...setType(scope, leftName, TYPES.number),
@@ -2469,7 +2534,7 @@ const generateThrow = (scope, decl) => {
2469
2534
  // hack: throw new X("...") -> throw "..."
2470
2535
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2471
2536
  constructor = decl.argument.callee.name;
2472
- message = decl.argument.arguments[0].value;
2537
+ message = decl.argument.arguments[0]?.value ?? '';
2473
2538
  }
2474
2539
 
2475
2540
  if (tags.length === 0) tags.push({
@@ -2530,6 +2595,8 @@ const allocPage = (reason, type) => {
2530
2595
 
2531
2596
  if (reason.startsWith('array:')) pages.hasArray = true;
2532
2597
  if (reason.startsWith('string:')) pages.hasString = true;
2598
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2599
+ if (reason.includes('string:')) pages.hasAnyString = true;
2533
2600
 
2534
2601
  const ind = pages.size;
2535
2602
  pages.set(reason, { ind, type });
@@ -2563,7 +2630,8 @@ const StoreOps = {
2563
2630
  f64: Opcodes.f64_store,
2564
2631
 
2565
2632
  // expects i32 input!
2566
- i16: Opcodes.i32_store16
2633
+ i8: Opcodes.i32_store8,
2634
+ i16: Opcodes.i32_store16,
2567
2635
  };
2568
2636
 
2569
2637
  let data = [];
@@ -2582,6 +2650,15 @@ const compileBytes = (val, itemType, signed = true) => {
2582
2650
  }
2583
2651
  };
2584
2652
 
2653
+ const getAllocType = itemType => {
2654
+ switch (itemType) {
2655
+ case 'i8': return 'bytestring';
2656
+ case 'i16': return 'string';
2657
+
2658
+ default: return 'array';
2659
+ }
2660
+ };
2661
+
2585
2662
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2586
2663
  const out = [];
2587
2664
 
@@ -2591,7 +2668,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2591
2668
 
2592
2669
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2593
2670
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2594
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2671
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2595
2672
  }
2596
2673
 
2597
2674
  const pointer = arrays.get(name);
@@ -2637,7 +2714,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2637
2714
  out.push(
2638
2715
  ...number(0, Valtype.i32),
2639
2716
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2640
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2717
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2641
2718
  );
2642
2719
  }
2643
2720
 
@@ -2647,15 +2724,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2647
2724
  return [ out, pointer ];
2648
2725
  };
2649
2726
 
2650
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2727
+ const byteStringable = str => {
2728
+ if (!process.argv.includes('-bytestring')) return false;
2729
+
2730
+ for (let i = 0; i < str.length; i++) {
2731
+ if (str.charCodeAt(i) > 0xFF) return false;
2732
+ }
2733
+
2734
+ return true;
2735
+ };
2736
+
2737
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2651
2738
  const rawElements = new Array(str.length);
2739
+ let byteStringable = process.argv.includes('-bytestring');
2652
2740
  for (let i = 0; i < str.length; i++) {
2653
- rawElements[i] = str.charCodeAt(i);
2741
+ const c = str.charCodeAt(i);
2742
+ rawElements[i] = c;
2743
+
2744
+ if (byteStringable && c > 0xFF) byteStringable = false;
2654
2745
  }
2655
2746
 
2747
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2748
+
2656
2749
  return makeArray(scope, {
2657
2750
  rawElements
2658
- }, global, name, false, 'i16')[0];
2751
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2659
2752
  };
2660
2753
 
2661
2754
  let arrays = new Map();
@@ -2683,10 +2776,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2683
2776
  ];
2684
2777
  }
2685
2778
 
2779
+ const object = generate(scope, decl.object);
2780
+ const property = generate(scope, decl.property);
2781
+
2686
2782
  // // todo: we should only do this for strings but we don't know at compile-time :(
2687
2783
  // hack: this is naughty and will break things!
2688
2784
  let newOut = number(0, valtypeBinary), newPointer = -1;
2689
- if (pages.hasString) {
2785
+ if (pages.hasAnyString) {
2690
2786
  0, [ newOut, newPointer ] = makeArray(scope, {
2691
2787
  rawElements: new Array(1)
2692
2788
  }, _global, _name, true, 'i16');
@@ -2695,7 +2791,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2695
2791
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2696
2792
  [TYPES._array]: [
2697
2793
  // get index as valtype
2698
- ...generate(scope, decl.property),
2794
+ ...property,
2699
2795
 
2700
2796
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2701
2797
  Opcodes.i32_to_u,
@@ -2703,7 +2799,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2703
2799
  [ Opcodes.i32_mul ],
2704
2800
 
2705
2801
  ...(aotPointer ? [] : [
2706
- ...generate(scope, decl.object),
2802
+ ...object,
2707
2803
  Opcodes.i32_to_u,
2708
2804
  [ Opcodes.i32_add ]
2709
2805
  ]),
@@ -2722,14 +2818,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
2818
 
2723
2819
  ...number(0, Valtype.i32), // base 0 for store later
2724
2820
 
2725
- ...generate(scope, decl.property),
2726
-
2821
+ ...property,
2727
2822
  Opcodes.i32_to_u,
2823
+
2728
2824
  ...number(ValtypeSize.i16, Valtype.i32),
2729
2825
  [ Opcodes.i32_mul ],
2730
2826
 
2731
2827
  ...(aotPointer ? [] : [
2732
- ...generate(scope, decl.object),
2828
+ ...object,
2733
2829
  Opcodes.i32_to_u,
2734
2830
  [ Opcodes.i32_add ]
2735
2831
  ]),
@@ -2746,8 +2842,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2746
2842
  ...number(TYPES.string, Valtype.i32),
2747
2843
  setLastType(scope)
2748
2844
  ],
2845
+ [TYPES._bytestring]: [
2846
+ // setup new/out array
2847
+ ...newOut,
2848
+ [ Opcodes.drop ],
2849
+
2850
+ ...number(0, Valtype.i32), // base 0 for store later
2851
+
2852
+ ...property,
2853
+ Opcodes.i32_to_u,
2854
+
2855
+ ...(aotPointer ? [] : [
2856
+ ...object,
2857
+ Opcodes.i32_to_u,
2858
+ [ Opcodes.i32_add ]
2859
+ ]),
2860
+
2861
+ // load current string ind {arg}
2862
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2863
+
2864
+ // store to new string ind 0
2865
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2866
+
2867
+ // return new string (page)
2868
+ ...number(newPointer),
2869
+
2870
+ ...number(TYPES._bytestring, Valtype.i32),
2871
+ setLastType(scope)
2872
+ ],
2749
2873
 
2750
- default: [ [ Opcodes.unreachable ] ]
2874
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2751
2875
  });
2752
2876
  };
2753
2877
 
@@ -2764,11 +2888,14 @@ const objectHack = node => {
2764
2888
  // if object is not identifier or another member exp, give up
2765
2889
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2766
2890
 
2767
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2891
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2768
2892
 
2769
2893
  // if .length, give up (hack within a hack!)
2770
2894
  if (node.property.name === 'length') return node;
2771
2895
 
2896
+ // no object name, give up
2897
+ if (!objectName) return node;
2898
+
2772
2899
  const name = '__' + objectName + '_' + node.property.name;
2773
2900
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2774
2901
 
@@ -2827,10 +2954,8 @@ const generateFunc = (scope, decl) => {
2827
2954
  const func = {
2828
2955
  name,
2829
2956
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2830
- returns: innerScope.returns,
2831
- locals: innerScope.locals,
2832
- throws: innerScope.throws,
2833
- index: currentFuncIndex++
2957
+ index: currentFuncIndex++,
2958
+ ...innerScope
2834
2959
  };
2835
2960
  funcIndex[name] = func.index;
2836
2961
 
@@ -2868,6 +2993,16 @@ const generateCode = (scope, decl) => {
2868
2993
  };
2869
2994
 
2870
2995
  const internalConstrs = {
2996
+ Boolean: {
2997
+ generate: (scope, decl) => {
2998
+ if (decl.arguments.length === 0) return number(0);
2999
+
3000
+ // should generate/run all args
3001
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3002
+ },
3003
+ type: TYPES.boolean
3004
+ },
3005
+
2871
3006
  Array: {
2872
3007
  generate: (scope, decl, global, name) => {
2873
3008
  // new Array(i0, i1, ...)