porffor 0.2.0-aea77ff → 0.2.0-c597461

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
  }
@@ -1278,16 +1311,7 @@ const generateLiteral = (scope, decl, global, name) => {
1278
1311
  return number(decl.value ? 1 : 0);
1279
1312
 
1280
1313
  case 'string':
1281
- const str = decl.value;
1282
- const rawElements = new Array(str.length);
1283
- let j = 0;
1284
- for (let i = 0; i < str.length; i++) {
1285
- rawElements[i] = str.charCodeAt(i);
1286
- }
1287
-
1288
- return makeArray(scope, {
1289
- rawElements
1290
- }, global, name, false, 'i16')[0];
1314
+ return makeString(scope, decl.value, global, name);
1291
1315
 
1292
1316
  default:
1293
1317
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1308,9 +1332,9 @@ const countLeftover = wasm => {
1308
1332
 
1309
1333
  if (depth === 0)
1310
1334
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1311
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1335
+ 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)) {}
1312
1336
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1313
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1337
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1314
1338
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1315
1339
  else if (inst[0] === Opcodes.return) count = 0;
1316
1340
  else if (inst[0] === Opcodes.call) {
@@ -1336,7 +1360,7 @@ const disposeLeftover = wasm => {
1336
1360
  const generateExp = (scope, decl) => {
1337
1361
  const expression = decl.expression;
1338
1362
 
1339
- const out = generate(scope, expression);
1363
+ const out = generate(scope, expression, undefined, undefined, true);
1340
1364
  disposeLeftover(out);
1341
1365
 
1342
1366
  return out;
@@ -1394,7 +1418,7 @@ const RTArrayUtil = {
1394
1418
  ]
1395
1419
  };
1396
1420
 
1397
- const generateCall = (scope, decl, _global, _name) => {
1421
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1398
1422
  /* const callee = decl.callee;
1399
1423
  const args = decl.arguments;
1400
1424
 
@@ -1513,10 +1537,18 @@ const generateCall = (scope, decl, _global, _name) => {
1513
1537
  // use local for cached i32 length as commonly used
1514
1538
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1515
1539
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1516
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1517
1540
 
1518
1541
  // TODO: long-term, prototypes should be their individual separate funcs
1519
1542
 
1543
+ const rawPointer = [
1544
+ ...generate(scope, target),
1545
+ Opcodes.i32_to_u
1546
+ ];
1547
+
1548
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1549
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1550
+
1551
+ let allOptUnused = true;
1520
1552
  let lengthI32CacheUsed = false;
1521
1553
  const protoBC = {};
1522
1554
  for (const x in protoCands) {
@@ -1536,6 +1568,7 @@ const generateCall = (scope, decl, _global, _name) => {
1536
1568
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1537
1569
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1538
1570
 
1571
+ let optUnused = false;
1539
1572
  const protoOut = protoFunc(getPointer, {
1540
1573
  getCachedI32: () => {
1541
1574
  lengthI32CacheUsed = true;
@@ -1550,10 +1583,15 @@ const generateCall = (scope, decl, _global, _name) => {
1550
1583
  return makeArray(scope, {
1551
1584
  rawElements: new Array(length)
1552
1585
  }, _global, _name, true, itemType);
1586
+ }, () => {
1587
+ optUnused = true;
1588
+ return unusedValue;
1553
1589
  });
1554
1590
 
1591
+ if (!optUnused) allOptUnused = false;
1592
+
1555
1593
  protoBC[x] = [
1556
- [ Opcodes.block, valtypeBinary ],
1594
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1557
1595
  ...protoOut,
1558
1596
 
1559
1597
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1562,11 +1600,13 @@ const generateCall = (scope, decl, _global, _name) => {
1562
1600
  ];
1563
1601
  }
1564
1602
 
1565
- return [
1566
- ...generate(scope, target),
1603
+ // todo: if some cands use optUnused and some don't, we will probably crash
1567
1604
 
1568
- Opcodes.i32_to_u,
1569
- [ Opcodes.local_set, pointerLocal ],
1605
+ return [
1606
+ ...(usePointerCache ? [
1607
+ ...rawPointer,
1608
+ [ Opcodes.local_set, pointerLocal ],
1609
+ ] : []),
1570
1610
 
1571
1611
  ...(!lengthI32CacheUsed ? [] : [
1572
1612
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1578,7 +1618,7 @@ const generateCall = (scope, decl, _global, _name) => {
1578
1618
 
1579
1619
  // TODO: error better
1580
1620
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1581
- }, valtypeBinary),
1621
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1582
1622
  ];
1583
1623
  }
1584
1624
  }
@@ -1786,6 +1826,8 @@ const brTable = (input, bc, returns) => {
1786
1826
  };
1787
1827
 
1788
1828
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1829
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1830
+
1789
1831
  const known = knownType(scope, type);
1790
1832
  if (known != null) {
1791
1833
  return bc[known] ?? bc.default;
@@ -2176,6 +2218,8 @@ const generateUnary = (scope, decl) => {
2176
2218
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2177
2219
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2178
2220
 
2221
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2222
+
2179
2223
  // object and internal types
2180
2224
  default: makeString(scope, 'object', false, '#typeof_result'),
2181
2225
  });
@@ -2364,6 +2408,7 @@ const generateForOf = (scope, decl) => {
2364
2408
  }
2365
2409
 
2366
2410
  // set type for local
2411
+ // todo: optimize away counter and use end pointer
2367
2412
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2368
2413
  [TYPES._array]: [
2369
2414
  ...setType(scope, leftName, TYPES.number),
@@ -2582,7 +2627,8 @@ const StoreOps = {
2582
2627
  f64: Opcodes.f64_store,
2583
2628
 
2584
2629
  // expects i32 input!
2585
- i16: Opcodes.i32_store16
2630
+ i8: Opcodes.i32_store8,
2631
+ i16: Opcodes.i32_store16,
2586
2632
  };
2587
2633
 
2588
2634
  let data = [];
@@ -2601,6 +2647,15 @@ const compileBytes = (val, itemType, signed = true) => {
2601
2647
  }
2602
2648
  };
2603
2649
 
2650
+ const getAllocType = itemType => {
2651
+ switch (itemType) {
2652
+ case 'i8': return 'bytestring';
2653
+ case 'i16': return 'string';
2654
+
2655
+ default: return 'array';
2656
+ }
2657
+ };
2658
+
2604
2659
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2605
2660
  const out = [];
2606
2661
 
@@ -2610,7 +2665,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2610
2665
 
2611
2666
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2612
2667
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2613
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2668
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2614
2669
  }
2615
2670
 
2616
2671
  const pointer = arrays.get(name);
@@ -2656,7 +2711,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2656
2711
  out.push(
2657
2712
  ...number(0, Valtype.i32),
2658
2713
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2659
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2714
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2660
2715
  );
2661
2716
  }
2662
2717
 
@@ -2666,15 +2721,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
2721
  return [ out, pointer ];
2667
2722
  };
2668
2723
 
2724
+ const byteStringable = str => {
2725
+ if (!process.argv.includes('-bytestring')) return false;
2726
+
2727
+ for (let i = 0; i < str.length; i++) {
2728
+ if (str.charCodeAt(i) > 0xFF) return false;
2729
+ }
2730
+
2731
+ return true;
2732
+ };
2733
+
2669
2734
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2670
2735
  const rawElements = new Array(str.length);
2736
+ let byteStringable = process.argv.includes('-bytestring');
2671
2737
  for (let i = 0; i < str.length; i++) {
2672
- rawElements[i] = str.charCodeAt(i);
2738
+ const c = str.charCodeAt(i);
2739
+ rawElements[i] = c;
2740
+
2741
+ if (byteStringable && c > 0xFF) byteStringable = false;
2673
2742
  }
2674
2743
 
2675
2744
  return makeArray(scope, {
2676
2745
  rawElements
2677
- }, global, name, false, 'i16')[0];
2746
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2678
2747
  };
2679
2748
 
2680
2749
  let arrays = new Map();
@@ -2765,6 +2834,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2765
2834
  ...number(TYPES.string, Valtype.i32),
2766
2835
  setLastType(scope)
2767
2836
  ],
2837
+ [TYPES._bytestring]: [
2838
+ // setup new/out array
2839
+ ...newOut,
2840
+ [ Opcodes.drop ],
2841
+
2842
+ ...number(0, Valtype.i32), // base 0 for store later
2843
+
2844
+ ...generate(scope, decl.property),
2845
+ Opcodes.i32_to_u,
2846
+
2847
+ ...(aotPointer ? [] : [
2848
+ ...generate(scope, decl.object),
2849
+ Opcodes.i32_to_u,
2850
+ [ Opcodes.i32_add ]
2851
+ ]),
2852
+
2853
+ // load current string ind {arg}
2854
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2855
+
2856
+ // store to new string ind 0
2857
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2858
+
2859
+ // return new string (page)
2860
+ ...number(newPointer),
2861
+
2862
+ ...number(TYPES._bytestring, Valtype.i32),
2863
+ setLastType(scope)
2864
+ ],
2768
2865
 
2769
2866
  default: [ [ Opcodes.unreachable ] ]
2770
2867
  });
@@ -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-aea77ff",
4
+ "version": "0.2.0-c597461",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {