porffor 0.2.0-29c477a → 0.2.0-30ecc4a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -209,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
209
209
  - `test262`: test262 runner and utils
210
210
 
211
211
  ## Usecases
212
- Basically none (other than giving people headaches). Potential ideas to come?
212
+ Basically none right now (other than giving people headaches). Potential ideas:
213
+ - Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
214
+ - Compiling JS to native binaries. This is still very early, [`2c`](#2c) is not that good yet :(
215
+ - More in future probably?
213
216
 
214
217
  ## Usage
215
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
216
219
 
217
220
  1. Clone repo
218
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) {
@@ -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,9 @@ 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
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1264
+
1265
+ // presume
1231
1266
  return TYPES.number;
1232
1267
  }
1233
1268
 
@@ -1299,9 +1334,9 @@ const countLeftover = wasm => {
1299
1334
 
1300
1335
  if (depth === 0)
1301
1336
  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)) {}
1337
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1303
1338
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1304
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1339
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1305
1340
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1306
1341
  else if (inst[0] === Opcodes.return) count = 0;
1307
1342
  else if (inst[0] === Opcodes.call) {
@@ -1327,7 +1362,7 @@ const disposeLeftover = wasm => {
1327
1362
  const generateExp = (scope, decl) => {
1328
1363
  const expression = decl.expression;
1329
1364
 
1330
- const out = generate(scope, expression);
1365
+ const out = generate(scope, expression, undefined, undefined, true);
1331
1366
  disposeLeftover(out);
1332
1367
 
1333
1368
  return out;
@@ -1385,7 +1420,7 @@ const RTArrayUtil = {
1385
1420
  ]
1386
1421
  };
1387
1422
 
1388
- const generateCall = (scope, decl, _global, _name) => {
1423
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1389
1424
  /* const callee = decl.callee;
1390
1425
  const args = decl.arguments;
1391
1426
 
@@ -1504,10 +1539,18 @@ const generateCall = (scope, decl, _global, _name) => {
1504
1539
  // use local for cached i32 length as commonly used
1505
1540
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1506
1541
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1507
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1508
1542
 
1509
1543
  // TODO: long-term, prototypes should be their individual separate funcs
1510
1544
 
1545
+ const rawPointer = [
1546
+ ...generate(scope, target),
1547
+ Opcodes.i32_to_u
1548
+ ];
1549
+
1550
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1551
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1552
+
1553
+ let allOptUnused = true;
1511
1554
  let lengthI32CacheUsed = false;
1512
1555
  const protoBC = {};
1513
1556
  for (const x in protoCands) {
@@ -1527,6 +1570,7 @@ const generateCall = (scope, decl, _global, _name) => {
1527
1570
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1528
1571
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1529
1572
 
1573
+ let optUnused = false;
1530
1574
  const protoOut = protoFunc(getPointer, {
1531
1575
  getCachedI32: () => {
1532
1576
  lengthI32CacheUsed = true;
@@ -1541,10 +1585,15 @@ const generateCall = (scope, decl, _global, _name) => {
1541
1585
  return makeArray(scope, {
1542
1586
  rawElements: new Array(length)
1543
1587
  }, _global, _name, true, itemType);
1588
+ }, () => {
1589
+ optUnused = true;
1590
+ return unusedValue;
1544
1591
  });
1545
1592
 
1593
+ if (!optUnused) allOptUnused = false;
1594
+
1546
1595
  protoBC[x] = [
1547
- [ Opcodes.block, valtypeBinary ],
1596
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1548
1597
  ...protoOut,
1549
1598
 
1550
1599
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1553,11 +1602,13 @@ const generateCall = (scope, decl, _global, _name) => {
1553
1602
  ];
1554
1603
  }
1555
1604
 
1556
- return [
1557
- ...generate(scope, target),
1605
+ // todo: if some cands use optUnused and some don't, we will probably crash
1558
1606
 
1559
- Opcodes.i32_to_u,
1560
- [ Opcodes.local_set, pointerLocal ],
1607
+ return [
1608
+ ...(usePointerCache ? [
1609
+ ...rawPointer,
1610
+ [ Opcodes.local_set, pointerLocal ],
1611
+ ] : []),
1561
1612
 
1562
1613
  ...(!lengthI32CacheUsed ? [] : [
1563
1614
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1569,7 +1620,7 @@ const generateCall = (scope, decl, _global, _name) => {
1569
1620
 
1570
1621
  // TODO: error better
1571
1622
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1572
- }, valtypeBinary),
1623
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1573
1624
  ];
1574
1625
  }
1575
1626
  }
@@ -1777,6 +1828,8 @@ const brTable = (input, bc, returns) => {
1777
1828
  };
1778
1829
 
1779
1830
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1832
+
1780
1833
  const known = knownType(scope, type);
1781
1834
  if (known != null) {
1782
1835
  return bc[known] ?? bc.default;
@@ -2167,6 +2220,8 @@ const generateUnary = (scope, decl) => {
2167
2220
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2168
2221
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2169
2222
 
2223
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2224
+
2170
2225
  // object and internal types
2171
2226
  default: makeString(scope, 'object', false, '#typeof_result'),
2172
2227
  });
@@ -2348,13 +2403,14 @@ const generateForOf = (scope, decl) => {
2348
2403
  // // todo: we should only do this for strings but we don't know at compile-time :(
2349
2404
  // hack: this is naughty and will break things!
2350
2405
  let newOut = number(0, Valtype.f64), newPointer = -1;
2351
- if (pages.hasString) {
2406
+ if (pages.hasAnyString) {
2352
2407
  0, [ newOut, newPointer ] = makeArray(scope, {
2353
2408
  rawElements: new Array(1)
2354
2409
  }, isGlobal, leftName, true, 'i16');
2355
2410
  }
2356
2411
 
2357
2412
  // set type for local
2413
+ // todo: optimize away counter and use end pointer
2358
2414
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2359
2415
  [TYPES._array]: [
2360
2416
  ...setType(scope, leftName, TYPES.number),
@@ -2540,6 +2596,8 @@ const allocPage = (reason, type) => {
2540
2596
 
2541
2597
  if (reason.startsWith('array:')) pages.hasArray = true;
2542
2598
  if (reason.startsWith('string:')) pages.hasString = true;
2599
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2600
+ if (reason.includes('string:')) pages.hasAnyString = true;
2543
2601
 
2544
2602
  const ind = pages.size;
2545
2603
  pages.set(reason, { ind, type });
@@ -2573,7 +2631,8 @@ const StoreOps = {
2573
2631
  f64: Opcodes.f64_store,
2574
2632
 
2575
2633
  // expects i32 input!
2576
- i16: Opcodes.i32_store16
2634
+ i8: Opcodes.i32_store8,
2635
+ i16: Opcodes.i32_store16,
2577
2636
  };
2578
2637
 
2579
2638
  let data = [];
@@ -2592,6 +2651,15 @@ const compileBytes = (val, itemType, signed = true) => {
2592
2651
  }
2593
2652
  };
2594
2653
 
2654
+ const getAllocType = itemType => {
2655
+ switch (itemType) {
2656
+ case 'i8': return 'bytestring';
2657
+ case 'i16': return 'string';
2658
+
2659
+ default: return 'array';
2660
+ }
2661
+ };
2662
+
2595
2663
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2596
2664
  const out = [];
2597
2665
 
@@ -2601,7 +2669,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2601
2669
 
2602
2670
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2603
2671
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2604
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2672
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2605
2673
  }
2606
2674
 
2607
2675
  const pointer = arrays.get(name);
@@ -2647,7 +2715,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2647
2715
  out.push(
2648
2716
  ...number(0, Valtype.i32),
2649
2717
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2650
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2718
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2651
2719
  );
2652
2720
  }
2653
2721
 
@@ -2657,15 +2725,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2657
2725
  return [ out, pointer ];
2658
2726
  };
2659
2727
 
2728
+ const byteStringable = str => {
2729
+ if (!process.argv.includes('-bytestring')) return false;
2730
+
2731
+ for (let i = 0; i < str.length; i++) {
2732
+ if (str.charCodeAt(i) > 0xFF) return false;
2733
+ }
2734
+
2735
+ return true;
2736
+ };
2737
+
2660
2738
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2661
2739
  const rawElements = new Array(str.length);
2740
+ let byteStringable = process.argv.includes('-bytestring');
2662
2741
  for (let i = 0; i < str.length; i++) {
2663
- rawElements[i] = str.charCodeAt(i);
2742
+ const c = str.charCodeAt(i);
2743
+ rawElements[i] = c;
2744
+
2745
+ if (byteStringable && c > 0xFF) byteStringable = false;
2664
2746
  }
2665
2747
 
2666
2748
  return makeArray(scope, {
2667
2749
  rawElements
2668
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2669
2751
  };
2670
2752
 
2671
2753
  let arrays = new Map();
@@ -2693,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2693
2775
  ];
2694
2776
  }
2695
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2696
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2697
2782
  // hack: this is naughty and will break things!
2698
2783
  let newOut = number(0, valtypeBinary), newPointer = -1;
2699
- if (pages.hasString) {
2784
+ if (pages.hasAnyString) {
2700
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2701
2786
  rawElements: new Array(1)
2702
2787
  }, _global, _name, true, 'i16');
@@ -2705,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2705
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2706
2791
  [TYPES._array]: [
2707
2792
  // get index as valtype
2708
- ...generate(scope, decl.property),
2793
+ ...property,
2709
2794
 
2710
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2711
2796
  Opcodes.i32_to_u,
@@ -2713,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2713
2798
  [ Opcodes.i32_mul ],
2714
2799
 
2715
2800
  ...(aotPointer ? [] : [
2716
- ...generate(scope, decl.object),
2801
+ ...object,
2717
2802
  Opcodes.i32_to_u,
2718
2803
  [ Opcodes.i32_add ]
2719
2804
  ]),
@@ -2732,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2732
2817
 
2733
2818
  ...number(0, Valtype.i32), // base 0 for store later
2734
2819
 
2735
- ...generate(scope, decl.property),
2736
-
2820
+ ...property,
2737
2821
  Opcodes.i32_to_u,
2822
+
2738
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2739
2824
  [ Opcodes.i32_mul ],
2740
2825
 
2741
2826
  ...(aotPointer ? [] : [
2742
- ...generate(scope, decl.object),
2827
+ ...object,
2743
2828
  Opcodes.i32_to_u,
2744
2829
  [ Opcodes.i32_add ]
2745
2830
  ]),
@@ -2756,6 +2841,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2756
2841
  ...number(TYPES.string, Valtype.i32),
2757
2842
  setLastType(scope)
2758
2843
  ],
2844
+ [TYPES._bytestring]: [
2845
+ // setup new/out array
2846
+ ...newOut,
2847
+ [ Opcodes.drop ],
2848
+
2849
+ ...number(0, Valtype.i32), // base 0 for store later
2850
+
2851
+ ...property,
2852
+ Opcodes.i32_to_u,
2853
+
2854
+ ...(aotPointer ? [] : [
2855
+ ...object,
2856
+ Opcodes.i32_to_u,
2857
+ [ Opcodes.i32_add ]
2858
+ ]),
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
+
2863
+ // store to new string ind 0
2864
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
+
2866
+ // return new string (page)
2867
+ ...number(newPointer),
2868
+
2869
+ ...number(TYPES._bytestring, Valtype.i32),
2870
+ setLastType(scope)
2871
+ ],
2759
2872
 
2760
2873
  default: [ [ Opcodes.unreachable ] ]
2761
2874
  });
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)) => {
@@ -177,6 +178,13 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
177
178
  return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
178
179
  }
179
180
 
181
+ case '_bytestring': {
182
+ const pointer = ret;
183
+ const length = new Int32Array(memory.buffer, pointer, 1);
184
+
185
+ return Array.from(new Uint8Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
186
+ }
187
+
180
188
  case 'function': {
181
189
  // wasm func index, including all imports
182
190
  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-29c477a",
4
+ "version": "0.2.0-30ecc4a",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {