porffor 0.2.0-f2bbe1f → 0.2.0-fbab1de

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.
package/README.md CHANGED
@@ -121,7 +121,7 @@ No particular order and no guarentees, just what could happen soon™
121
121
  - *Basic* Wasm engine (interpreter) in JS
122
122
  - More math operators (`**`, etc)
123
123
  - `do { ... } while (...)`
124
- - Rewrite `console.log` to work with strings/arrays
124
+ - Typed export inputs (array)
125
125
  - Exceptions
126
126
  - Rewrite to use actual strings (optional?)
127
127
  - `try { } finally { }`
@@ -130,7 +130,7 @@ No particular order and no guarentees, just what could happen soon™
130
130
  - Rewrite local indexes per func for smallest local header and remove unused idxs
131
131
  - Smarter inline selection (snapshots?)
132
132
  - Remove const ifs (`if (true)`, etc)
133
- - Experiment with byte strings?
133
+ - Memory alignment
134
134
  - Runtime
135
135
  - WASI target
136
136
  - Run precompiled Wasm file if given
@@ -147,7 +147,7 @@ No particular order and no guarentees, just what could happen soon™
147
147
  - Self hosted testing?
148
148
 
149
149
  ## Performance
150
- *For the things it supports most of the time*, Porffor is blazingly fast compared to most interpreters, and common engines running without JIT. For those with JIT, it is not that much slower like a traditional interpreter would be; mostly the same or a bit faster/slower depending on what.
150
+ *For the things it supports most of the time*, Porffor is *blazingly fast* compared to most interpreters, and common engines running without JIT. For those with JIT, it is usually slower by default, but can catch up with compiler arguments and typed input.
151
151
 
152
152
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
153
153
 
@@ -215,7 +215,7 @@ Basically none right now (other than giving people headaches). Potential ideas:
215
215
  - More in future probably?
216
216
 
217
217
  ## Usage
218
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
219
219
 
220
220
  1. Clone repo
221
221
  2. `npm install`
@@ -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);
@@ -86,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
86
86
  return generateExp(scope, decl);
87
87
 
88
88
  case 'CallExpression':
89
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
90
90
 
91
91
  case 'NewExpression':
92
92
  return generateNew(scope, decl, global, name);
@@ -685,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
685
  [ Opcodes.i32_eqz ], */
686
686
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
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
+ ],
688
697
  default: def
689
698
  }, intOut ? Valtype.i32 : valtypeBinary)
690
699
  ];
@@ -712,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
712
721
  [ Opcodes.i32_eqz ],
713
722
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
714
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
+ ],
715
735
  default: [
716
736
  // if value == 0
717
737
  [ Opcodes.local_get, tmp ],
@@ -1044,7 +1064,8 @@ const TYPES = {
1044
1064
 
1045
1065
  // these are not "typeof" types but tracked internally
1046
1066
  _array: 0x10,
1047
- _regexp: 0x11
1067
+ _regexp: 0x11,
1068
+ _bytestring: 0x12
1048
1069
  };
1049
1070
 
1050
1071
  const TYPE_NAMES = {
@@ -1058,7 +1079,8 @@ const TYPE_NAMES = {
1058
1079
  [TYPES.bigint]: 'BigInt',
1059
1080
 
1060
1081
  [TYPES._array]: 'Array',
1061
- [TYPES._regexp]: 'RegExp'
1082
+ [TYPES._regexp]: 'RegExp',
1083
+ [TYPES._bytestring]: 'ByteString'
1062
1084
  };
1063
1085
 
1064
1086
  const getType = (scope, _name) => {
@@ -1111,6 +1133,8 @@ const getNodeType = (scope, node) => {
1111
1133
  if (node.type === 'Literal') {
1112
1134
  if (node.regex) return TYPES._regexp;
1113
1135
 
1136
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1137
+
1114
1138
  return TYPES[typeof node.value];
1115
1139
  }
1116
1140
 
@@ -1124,6 +1148,15 @@ const getNodeType = (scope, node) => {
1124
1148
 
1125
1149
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1126
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
+
1127
1160
  const func = funcs.find(x => x.name === name);
1128
1161
 
1129
1162
  if (func) {
@@ -1142,7 +1175,7 @@ const getNodeType = (scope, node) => {
1142
1175
  const spl = name.slice(2).split('_');
1143
1176
 
1144
1177
  const func = spl[spl.length - 1];
1145
- 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);
1146
1179
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1147
1180
  }
1148
1181
 
@@ -1218,7 +1251,7 @@ const getNodeType = (scope, node) => {
1218
1251
  if (node.operator === '!') return TYPES.boolean;
1219
1252
  if (node.operator === 'void') return TYPES.undefined;
1220
1253
  if (node.operator === 'delete') return TYPES.boolean;
1221
- if (node.operator === 'typeof') return TYPES.string;
1254
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1222
1255
 
1223
1256
  return TYPES.number;
1224
1257
  }
@@ -1227,7 +1260,13 @@ const getNodeType = (scope, node) => {
1227
1260
  // hack: if something.length, number type
1228
1261
  if (node.property.name === 'length') return TYPES.number;
1229
1262
 
1230
- // 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
1231
1270
  return TYPES.number;
1232
1271
  }
1233
1272
 
@@ -1244,23 +1283,6 @@ const getNodeType = (scope, node) => {
1244
1283
  return ret;
1245
1284
  };
1246
1285
 
1247
- const toString = (scope, wasm, type) => {
1248
- const tmp = localTmp(scope, '#tostring_tmp');
1249
- return [
1250
- ...wasm,
1251
- [ Opcodes.local_set, tmp ],
1252
-
1253
- ...typeSwitch(scope, type, {
1254
- [TYPES.string]: [
1255
- [ Opcodes.local_get, tmp ]
1256
- ],
1257
- [TYPES.undefined]: [
1258
- // [ Opcodes.]
1259
- ]
1260
- })
1261
- ]
1262
- };
1263
-
1264
1286
  const generateLiteral = (scope, decl, global, name) => {
1265
1287
  if (decl.value === null) return number(NULL);
1266
1288
 
@@ -1299,9 +1321,9 @@ const countLeftover = wasm => {
1299
1321
 
1300
1322
  if (depth === 0)
1301
1323
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1302
- 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)) {}
1303
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++;
1304
- 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;
1305
1327
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1306
1328
  else if (inst[0] === Opcodes.return) count = 0;
1307
1329
  else if (inst[0] === Opcodes.call) {
@@ -1327,7 +1349,7 @@ const disposeLeftover = wasm => {
1327
1349
  const generateExp = (scope, decl) => {
1328
1350
  const expression = decl.expression;
1329
1351
 
1330
- const out = generate(scope, expression);
1352
+ const out = generate(scope, expression, undefined, undefined, true);
1331
1353
  disposeLeftover(out);
1332
1354
 
1333
1355
  return out;
@@ -1385,7 +1407,7 @@ const RTArrayUtil = {
1385
1407
  ]
1386
1408
  };
1387
1409
 
1388
- const generateCall = (scope, decl, _global, _name) => {
1410
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1389
1411
  /* const callee = decl.callee;
1390
1412
  const args = decl.arguments;
1391
1413
 
@@ -1451,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
1451
1473
  // literal.func()
1452
1474
  if (!name && decl.callee.type === 'MemberExpression') {
1453
1475
  // megahack for /regex/.func()
1454
- if (decl.callee.object.regex) {
1455
- const funcName = decl.callee.property.name;
1476
+ const funcName = decl.callee.property.name;
1477
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1456
1478
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1457
1479
 
1458
1480
  funcIndex[func.name] = func.index;
@@ -1494,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
1494
1516
 
1495
1517
  if (protoName) {
1496
1518
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1497
- const f = prototypeFuncs[x][protoName];
1498
- if (f) acc[x] = f;
1519
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1499
1520
  return acc;
1500
1521
  }, {});
1501
1522
 
@@ -1504,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
1504
1525
  // use local for cached i32 length as commonly used
1505
1526
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1506
1527
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1507
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1508
1528
 
1509
1529
  // TODO: long-term, prototypes should be their individual separate funcs
1510
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;
1511
1540
  let lengthI32CacheUsed = false;
1512
1541
  const protoBC = {};
1513
1542
  for (const x in protoCands) {
@@ -1527,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
1527
1556
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1528
1557
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1529
1558
 
1559
+ let optUnused = false;
1530
1560
  const protoOut = protoFunc(getPointer, {
1531
1561
  getCachedI32: () => {
1532
1562
  lengthI32CacheUsed = true;
@@ -1541,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
1541
1571
  return makeArray(scope, {
1542
1572
  rawElements: new Array(length)
1543
1573
  }, _global, _name, true, itemType);
1574
+ }, () => {
1575
+ optUnused = true;
1576
+ return unusedValue;
1544
1577
  });
1545
1578
 
1579
+ if (!optUnused) allOptUnused = false;
1580
+
1546
1581
  protoBC[x] = [
1547
- [ Opcodes.block, valtypeBinary ],
1582
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1548
1583
  ...protoOut,
1549
1584
 
1550
1585
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1553,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
1553
1588
  ];
1554
1589
  }
1555
1590
 
1556
- return [
1557
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1558
1592
 
1559
- Opcodes.i32_to_u,
1560
- [ Opcodes.local_set, pointerLocal ],
1593
+ return [
1594
+ ...(usePointerCache ? [
1595
+ ...rawPointer,
1596
+ [ Opcodes.local_set, pointerLocal ],
1597
+ ] : []),
1561
1598
 
1562
1599
  ...(!lengthI32CacheUsed ? [] : [
1563
1600
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1569,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1569
1606
 
1570
1607
  // TODO: error better
1571
1608
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1572
- }, valtypeBinary),
1609
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1573
1610
  ];
1574
1611
  }
1575
1612
  }
@@ -1777,6 +1814,8 @@ const brTable = (input, bc, returns) => {
1777
1814
  };
1778
1815
 
1779
1816
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1818
+
1780
1819
  const known = knownType(scope, type);
1781
1820
  if (known != null) {
1782
1821
  return bc[known] ?? bc.default;
@@ -1875,6 +1914,8 @@ const extractTypeAnnotation = decl => {
1875
1914
  const typeName = type;
1876
1915
  type = typeAnnoToPorfType(type);
1877
1916
 
1917
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1918
+
1878
1919
  // if (decl.name) console.log(decl.name, { type, elementType });
1879
1920
 
1880
1921
  return { type, typeName, elementType };
@@ -2167,6 +2208,8 @@ const generateUnary = (scope, decl) => {
2167
2208
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2168
2209
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2169
2210
 
2211
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2212
+
2170
2213
  // object and internal types
2171
2214
  default: makeString(scope, 'object', false, '#typeof_result'),
2172
2215
  });
@@ -2272,8 +2315,10 @@ const generateFor = (scope, decl) => {
2272
2315
  out.push([ Opcodes.loop, Blocktype.void ]);
2273
2316
  depth.push('for');
2274
2317
 
2275
- out.push(...generate(scope, decl.test));
2276
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2318
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2319
+ else out.push(...number(1, Valtype.i32));
2320
+
2321
+ out.push([ Opcodes.if, Blocktype.void ]);
2277
2322
  depth.push('if');
2278
2323
 
2279
2324
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2281,8 +2326,7 @@ const generateFor = (scope, decl) => {
2281
2326
  out.push(...generate(scope, decl.body));
2282
2327
  out.push([ Opcodes.end ]);
2283
2328
 
2284
- out.push(...generate(scope, decl.update));
2285
- depth.pop();
2329
+ if (decl.update) out.push(...generate(scope, decl.update));
2286
2330
 
2287
2331
  out.push([ Opcodes.br, 1 ]);
2288
2332
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2348,13 +2392,14 @@ const generateForOf = (scope, decl) => {
2348
2392
  // // todo: we should only do this for strings but we don't know at compile-time :(
2349
2393
  // hack: this is naughty and will break things!
2350
2394
  let newOut = number(0, Valtype.f64), newPointer = -1;
2351
- if (pages.hasString) {
2395
+ if (pages.hasAnyString) {
2352
2396
  0, [ newOut, newPointer ] = makeArray(scope, {
2353
2397
  rawElements: new Array(1)
2354
2398
  }, isGlobal, leftName, true, 'i16');
2355
2399
  }
2356
2400
 
2357
2401
  // set type for local
2402
+ // todo: optimize away counter and use end pointer
2358
2403
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2359
2404
  [TYPES._array]: [
2360
2405
  ...setType(scope, leftName, TYPES.number),
@@ -2479,7 +2524,7 @@ const generateThrow = (scope, decl) => {
2479
2524
  // hack: throw new X("...") -> throw "..."
2480
2525
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2481
2526
  constructor = decl.argument.callee.name;
2482
- message = decl.argument.arguments[0].value;
2527
+ message = decl.argument.arguments[0]?.value ?? '';
2483
2528
  }
2484
2529
 
2485
2530
  if (tags.length === 0) tags.push({
@@ -2540,6 +2585,8 @@ const allocPage = (reason, type) => {
2540
2585
 
2541
2586
  if (reason.startsWith('array:')) pages.hasArray = true;
2542
2587
  if (reason.startsWith('string:')) pages.hasString = true;
2588
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2589
+ if (reason.includes('string:')) pages.hasAnyString = true;
2543
2590
 
2544
2591
  const ind = pages.size;
2545
2592
  pages.set(reason, { ind, type });
@@ -2573,7 +2620,8 @@ const StoreOps = {
2573
2620
  f64: Opcodes.f64_store,
2574
2621
 
2575
2622
  // expects i32 input!
2576
- i16: Opcodes.i32_store16
2623
+ i8: Opcodes.i32_store8,
2624
+ i16: Opcodes.i32_store16,
2577
2625
  };
2578
2626
 
2579
2627
  let data = [];
@@ -2592,6 +2640,15 @@ const compileBytes = (val, itemType, signed = true) => {
2592
2640
  }
2593
2641
  };
2594
2642
 
2643
+ const getAllocType = itemType => {
2644
+ switch (itemType) {
2645
+ case 'i8': return 'bytestring';
2646
+ case 'i16': return 'string';
2647
+
2648
+ default: return 'array';
2649
+ }
2650
+ };
2651
+
2595
2652
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2596
2653
  const out = [];
2597
2654
 
@@ -2601,7 +2658,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2601
2658
 
2602
2659
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2603
2660
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2604
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2661
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2605
2662
  }
2606
2663
 
2607
2664
  const pointer = arrays.get(name);
@@ -2647,7 +2704,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2647
2704
  out.push(
2648
2705
  ...number(0, Valtype.i32),
2649
2706
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2650
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2707
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2651
2708
  );
2652
2709
  }
2653
2710
 
@@ -2657,15 +2714,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2657
2714
  return [ out, pointer ];
2658
2715
  };
2659
2716
 
2717
+ const byteStringable = str => {
2718
+ if (!process.argv.includes('-bytestring')) return false;
2719
+
2720
+ for (let i = 0; i < str.length; i++) {
2721
+ if (str.charCodeAt(i) > 0xFF) return false;
2722
+ }
2723
+
2724
+ return true;
2725
+ };
2726
+
2660
2727
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2661
2728
  const rawElements = new Array(str.length);
2729
+ let byteStringable = process.argv.includes('-bytestring');
2662
2730
  for (let i = 0; i < str.length; i++) {
2663
- rawElements[i] = str.charCodeAt(i);
2731
+ const c = str.charCodeAt(i);
2732
+ rawElements[i] = c;
2733
+
2734
+ if (byteStringable && c > 0xFF) byteStringable = false;
2664
2735
  }
2665
2736
 
2666
2737
  return makeArray(scope, {
2667
2738
  rawElements
2668
- }, global, name, false, 'i16')[0];
2739
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2669
2740
  };
2670
2741
 
2671
2742
  let arrays = new Map();
@@ -2693,10 +2764,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2693
2764
  ];
2694
2765
  }
2695
2766
 
2767
+ const object = generate(scope, decl.object);
2768
+ const property = generate(scope, decl.property);
2769
+
2696
2770
  // // todo: we should only do this for strings but we don't know at compile-time :(
2697
2771
  // hack: this is naughty and will break things!
2698
2772
  let newOut = number(0, valtypeBinary), newPointer = -1;
2699
- if (pages.hasString) {
2773
+ if (pages.hasAnyString) {
2700
2774
  0, [ newOut, newPointer ] = makeArray(scope, {
2701
2775
  rawElements: new Array(1)
2702
2776
  }, _global, _name, true, 'i16');
@@ -2705,7 +2779,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2705
2779
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2706
2780
  [TYPES._array]: [
2707
2781
  // get index as valtype
2708
- ...generate(scope, decl.property),
2782
+ ...property,
2709
2783
 
2710
2784
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2711
2785
  Opcodes.i32_to_u,
@@ -2713,7 +2787,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2713
2787
  [ Opcodes.i32_mul ],
2714
2788
 
2715
2789
  ...(aotPointer ? [] : [
2716
- ...generate(scope, decl.object),
2790
+ ...object,
2717
2791
  Opcodes.i32_to_u,
2718
2792
  [ Opcodes.i32_add ]
2719
2793
  ]),
@@ -2732,14 +2806,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2732
2806
 
2733
2807
  ...number(0, Valtype.i32), // base 0 for store later
2734
2808
 
2735
- ...generate(scope, decl.property),
2736
-
2809
+ ...property,
2737
2810
  Opcodes.i32_to_u,
2811
+
2738
2812
  ...number(ValtypeSize.i16, Valtype.i32),
2739
2813
  [ Opcodes.i32_mul ],
2740
2814
 
2741
2815
  ...(aotPointer ? [] : [
2742
- ...generate(scope, decl.object),
2816
+ ...object,
2743
2817
  Opcodes.i32_to_u,
2744
2818
  [ Opcodes.i32_add ]
2745
2819
  ]),
@@ -2756,6 +2830,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2756
2830
  ...number(TYPES.string, Valtype.i32),
2757
2831
  setLastType(scope)
2758
2832
  ],
2833
+ [TYPES._bytestring]: [
2834
+ // setup new/out array
2835
+ ...newOut,
2836
+ [ Opcodes.drop ],
2837
+
2838
+ ...number(0, Valtype.i32), // base 0 for store later
2839
+
2840
+ ...property,
2841
+ Opcodes.i32_to_u,
2842
+
2843
+ ...(aotPointer ? [] : [
2844
+ ...object,
2845
+ Opcodes.i32_to_u,
2846
+ [ Opcodes.i32_add ]
2847
+ ]),
2848
+
2849
+ // load current string ind {arg}
2850
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2851
+
2852
+ // store to new string ind 0
2853
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2854
+
2855
+ // return new string (page)
2856
+ ...number(newPointer),
2857
+
2858
+ ...number(TYPES._bytestring, Valtype.i32),
2859
+ setLastType(scope)
2860
+ ],
2759
2861
 
2760
2862
  default: [ [ Opcodes.unreachable ] ]
2761
2863
  });
@@ -2774,11 +2876,14 @@ const objectHack = node => {
2774
2876
  // if object is not identifier or another member exp, give up
2775
2877
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2776
2878
 
2777
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2879
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2778
2880
 
2779
2881
  // if .length, give up (hack within a hack!)
2780
2882
  if (node.property.name === 'length') return node;
2781
2883
 
2884
+ // no object name, give up
2885
+ if (!objectName) return node;
2886
+
2782
2887
  const name = '__' + objectName + '_' + node.property.name;
2783
2888
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2784
2889
 
@@ -2837,10 +2942,8 @@ const generateFunc = (scope, decl) => {
2837
2942
  const func = {
2838
2943
  name,
2839
2944
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2840
- returns: innerScope.returns,
2841
- locals: innerScope.locals,
2842
- throws: innerScope.throws,
2843
- index: currentFuncIndex++
2945
+ index: currentFuncIndex++,
2946
+ ...innerScope
2844
2947
  };
2845
2948
  funcIndex[name] = func.index;
2846
2949
 
package/compiler/opt.js CHANGED
@@ -192,6 +192,7 @@ export default (funcs, globals, pages, tags) => {
192
192
  let missing = false;
193
193
  if (type === 'Array') missing = !pages.hasArray;
194
194
  if (type === 'String') missing = !pages.hasString;
195
+ if (type === 'ByteString') missing = !pages.hasByteString;
195
196
 
196
197
  if (missing) {
197
198
  let j = i, depth = 0;
@@ -16,7 +16,8 @@ const TYPES = {
16
16
 
17
17
  // these are not "typeof" types but tracked internally
18
18
  _array: 0x10,
19
- _regexp: 0x11
19
+ _regexp: 0x11,
20
+ _bytestring: 0x12
20
21
  };
21
22
 
22
23
  // todo: turn these into built-ins once arrays and these become less hacky
@@ -71,7 +72,7 @@ export const PrototypeFuncs = function() {
71
72
  ],
72
73
 
73
74
  // todo: only for 1 argument
74
- push: (pointer, length, wNewMember) => [
75
+ push: (pointer, length, wNewMember, _1, _2, _3, unusedValue) => [
75
76
  // get memory offset of array at last index (length)
76
77
  ...length.getCachedI32(),
77
78
  ...number(ValtypeSize[valtype], Valtype.i32),
@@ -92,22 +93,28 @@ export const PrototypeFuncs = function() {
92
93
  ...number(1, Valtype.i32),
93
94
  [ Opcodes.i32_add ],
94
95
 
95
- ...length.setCachedI32(),
96
- ...length.getCachedI32(),
96
+ ...(unusedValue() ? [] : [
97
+ ...length.setCachedI32(),
98
+ ...length.getCachedI32(),
99
+ ])
97
100
  ]),
98
101
 
99
- ...length.getCachedI32(),
100
- Opcodes.i32_from_u
102
+ ...(unusedValue() ? [] : [
103
+ ...length.getCachedI32(),
104
+ Opcodes.i32_from_u
105
+ ])
101
106
 
102
107
  // ...length.get()
103
108
  ],
104
109
 
105
- pop: (pointer, length) => [
110
+ pop: (pointer, length, _1, _2, _3, _4, unusedValue) => [
106
111
  // if length == 0, noop
107
112
  ...length.getCachedI32(),
108
113
  [ Opcodes.i32_eqz ],
109
114
  [ Opcodes.if, Blocktype.void ],
110
- ...number(UNDEFINED),
115
+ ...(unusedValue() ? [] : [
116
+ ...number(UNDEFINED),
117
+ ]),
111
118
  [ Opcodes.br, 1 ],
112
119
  [ Opcodes.end ],
113
120
 
@@ -119,19 +126,23 @@ export const PrototypeFuncs = function() {
119
126
  ...number(1, Valtype.i32),
120
127
  [ Opcodes.i32_sub ],
121
128
 
122
- ...length.setCachedI32(),
123
- ...length.getCachedI32(),
129
+ ...(unusedValue() ? [] : [
130
+ ...length.setCachedI32(),
131
+ ...length.getCachedI32(),
132
+ ])
124
133
  ]),
125
134
 
126
135
  // load last element
127
- ...length.getCachedI32(),
128
- ...number(ValtypeSize[valtype], Valtype.i32),
129
- [ Opcodes.i32_mul ],
136
+ ...(unusedValue() ? [] : [
137
+ ...length.getCachedI32(),
138
+ ...number(ValtypeSize[valtype], Valtype.i32),
139
+ [ Opcodes.i32_mul ],
130
140
 
131
- ...pointer,
132
- [ Opcodes.i32_add ],
141
+ ...pointer,
142
+ [ Opcodes.i32_add ],
133
143
 
134
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
144
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
145
+ ])
135
146
  ],
136
147
 
137
148
  shift: (pointer, length) => [
@@ -472,8 +483,152 @@ export const PrototypeFuncs = function() {
472
483
  this[TYPES.string].at.returnType = TYPES.string;
473
484
  this[TYPES.string].charAt.returnType = TYPES.string;
474
485
  this[TYPES.string].charCodeAt.local = Valtype.i32;
486
+ this[TYPES.string].charCodeAt.noPointerCache = zeroChecks.charcodeat;
475
487
 
476
488
  this[TYPES.string].isWellFormed.local = Valtype.i32;
477
489
  this[TYPES.string].isWellFormed.local2 = Valtype.i32;
478
490
  this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
491
+
492
+ if (process.argv.includes('-bytestring')) {
493
+ this[TYPES._bytestring] = {
494
+ at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
495
+ const [ newOut, newPointer ] = arrayShell(1, 'i16');
496
+
497
+ return [
498
+ // setup new/out array
499
+ ...newOut,
500
+ [ Opcodes.drop ],
501
+
502
+ ...number(0, Valtype.i32), // base 0 for store later
503
+
504
+ ...wIndex,
505
+ Opcodes.i32_to_u,
506
+ [ Opcodes.local_tee, iTmp ],
507
+
508
+ // if index < 0: access index + array length
509
+ ...number(0, Valtype.i32),
510
+ [ Opcodes.i32_lt_s ],
511
+ [ Opcodes.if, Blocktype.void ],
512
+ [ Opcodes.local_get, iTmp ],
513
+ ...length.getCachedI32(),
514
+ [ Opcodes.i32_add ],
515
+ [ Opcodes.local_set, iTmp ],
516
+ [ Opcodes.end ],
517
+
518
+ // if still < 0 or >= length: return undefined
519
+ [ Opcodes.local_get, iTmp ],
520
+ ...number(0, Valtype.i32),
521
+ [ Opcodes.i32_lt_s ],
522
+
523
+ [ Opcodes.local_get, iTmp ],
524
+ ...length.getCachedI32(),
525
+ [ Opcodes.i32_ge_s ],
526
+ [ Opcodes.i32_or ],
527
+
528
+ [ Opcodes.if, Blocktype.void ],
529
+ ...number(UNDEFINED),
530
+ [ Opcodes.br, 1 ],
531
+ [ Opcodes.end ],
532
+
533
+ [ Opcodes.local_get, iTmp ],
534
+
535
+ ...pointer,
536
+ [ Opcodes.i32_add ],
537
+
538
+ // load current string ind {arg}
539
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
540
+
541
+ // store to new string ind 0
542
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
543
+
544
+ // return new string (pointer)
545
+ ...number(newPointer)
546
+ ];
547
+ },
548
+
549
+ // todo: out of bounds properly
550
+ charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
551
+ const [ newOut, newPointer ] = arrayShell(1, 'i16');
552
+
553
+ return [
554
+ // setup new/out array
555
+ ...newOut,
556
+ [ Opcodes.drop ],
557
+
558
+ ...number(0, Valtype.i32), // base 0 for store later
559
+
560
+ ...wIndex,
561
+
562
+ Opcodes.i32_to,
563
+
564
+ ...pointer,
565
+ [ Opcodes.i32_add ],
566
+
567
+ // load current string ind {arg}
568
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
569
+
570
+ // store to new string ind 0
571
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
572
+
573
+ // return new string (page)
574
+ ...number(newPointer)
575
+ ];
576
+ },
577
+
578
+ charCodeAt: (pointer, length, wIndex, iTmp) => {
579
+ return [
580
+ ...wIndex,
581
+ Opcodes.i32_to,
582
+
583
+ ...(zeroChecks.charcodeat ? [] : [
584
+ [ Opcodes.local_set, iTmp ],
585
+
586
+ // index < 0
587
+ ...(noUnlikelyChecks ? [] : [
588
+ [ Opcodes.local_get, iTmp ],
589
+ ...number(0, Valtype.i32),
590
+ [ Opcodes.i32_lt_s ],
591
+ ]),
592
+
593
+ // index >= length
594
+ [ Opcodes.local_get, iTmp ],
595
+ ...length.getCachedI32(),
596
+ [ Opcodes.i32_ge_s ],
597
+
598
+ ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
599
+ [ Opcodes.if, Blocktype.void ],
600
+ ...number(NaN),
601
+ [ Opcodes.br, 1 ],
602
+ [ Opcodes.end ],
603
+
604
+ [ Opcodes.local_get, iTmp ],
605
+ ]),
606
+
607
+ ...pointer,
608
+ [ Opcodes.i32_add ],
609
+
610
+ // load current string ind {arg}
611
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
612
+ Opcodes.i32_from_u
613
+ ];
614
+ },
615
+
616
+ isWellFormed: () => {
617
+ return [
618
+ // we know it must be true as it is a bytestring lol
619
+ ...number(1)
620
+ ]
621
+ }
622
+ };
623
+
624
+ this[TYPES._bytestring].at.local = Valtype.i32;
625
+ this[TYPES._bytestring].at.returnType = TYPES._bytestring;
626
+ this[TYPES._bytestring].charAt.returnType = TYPES._bytestring;
627
+ this[TYPES._bytestring].charCodeAt.local = Valtype.i32;
628
+ this[TYPES._bytestring].charCodeAt.noPointerCache = zeroChecks.charcodeat;
629
+
630
+ this[TYPES._bytestring].isWellFormed.local = Valtype.i32;
631
+ this[TYPES._bytestring].isWellFormed.local2 = Valtype.i32;
632
+ this[TYPES._bytestring].isWellFormed.returnType = TYPES.boolean;
633
+ }
479
634
  };
@@ -57,9 +57,12 @@ export const Opcodes = {
57
57
  i64_load: 0x29,
58
58
  f64_load: 0x2b,
59
59
 
60
+ i32_load8_s: 0x2c,
61
+ i32_load8_u: 0x2d,
60
62
  i32_load16_s: 0x2e,
61
63
  i32_load16_u: 0x2f,
62
64
 
65
+ i32_store8: 0x3a,
63
66
  i32_store16: 0x3b,
64
67
 
65
68
  i32_store: 0x36,
package/compiler/wrap.js CHANGED
@@ -19,7 +19,8 @@ const TYPES = {
19
19
 
20
20
  // internal
21
21
  [internalTypeBase]: '_array',
22
- [internalTypeBase + 1]: '_regexp'
22
+ [internalTypeBase + 1]: '_regexp',
23
+ [internalTypeBase + 2]: '_bytestring'
23
24
  };
24
25
 
25
26
  export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
@@ -48,6 +49,9 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
48
49
  }
49
50
  });
50
51
  } catch (e) {
52
+ // only backtrace for runner, not test262/etc
53
+ if (!process.argv[1].includes('/runner')) throw e;
54
+
51
55
  const funcInd = parseInt(e.message.match(/function #([0-9]+) /)[1]);
52
56
  const blobOffset = parseInt(e.message.split('@')[1]);
53
57
 
@@ -177,6 +181,13 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
177
181
  return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
178
182
  }
179
183
 
184
+ case '_bytestring': {
185
+ const pointer = ret;
186
+ const length = new Int32Array(memory.buffer, pointer, 1);
187
+
188
+ return Array.from(new Uint8Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
189
+ }
190
+
180
191
  case 'function': {
181
192
  // wasm func index, including all imports
182
193
  const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.2.0-f2bbe1f",
4
+ "version": "0.2.0-fbab1de",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/r.js ADDED
@@ -0,0 +1,9 @@
1
+ var count = 0;
2
+ for (let x = 0; x < 10;) {
3
+ x++;
4
+ count++;
5
+ {
6
+ let x = "hello";
7
+ continue;
8
+ }
9
+ }