porffor 0.0.0-bddcdc3 → 0.0.0-ebc0491

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
@@ -77,6 +77,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
77
77
  - string concat (`+`) (eg `'a' + 'b'`)
78
78
  - truthy/falsy (eg `!'' == true`)
79
79
  - string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
80
+ - nullish coalescing operator (`??`)
80
81
 
81
82
  ### built-ins
82
83
 
@@ -100,17 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
100
101
  - intrinsic functions (see below)
101
102
  - inlining wasm via ``asm`...``\` "macro"
102
103
 
103
- ## soon todo
104
+ ## todo
105
+ no particular order and no guarentees, just what could happen soon™
106
+
104
107
  - arrays
105
108
  - member setting (`arr[0] = 2`)
106
109
  - more of `Array` prototype
107
110
  - arrays/strings inside arrays
111
+ - destructuring
112
+ - for .. of
108
113
  - strings
109
114
  - member setting
115
+ - objects
116
+ - basic object expressions (eg `{}`, `{ a: 0 }`)
117
+ - wasm
118
+ - *basic* wasm engine (interpreter) in js
119
+ - regex
120
+ - *basic* regex engine (in wasm compiled aot or js interpreter?)
110
121
  - more math operators (`**`, etc)
111
122
  - `do { ... } while (...)`
123
+ - rewrite `console.log` to work with strings/arrays
112
124
  - exceptions
113
- - `try { } finally {}`
125
+ - rewrite to use actual strings (optional?)
126
+ - `try { } finally { }`
114
127
  - rethrowing inside catch
115
128
  - optimizations
116
129
  - rewrite local indexes per func for smallest local header and remove unused idxs
@@ -118,6 +131,11 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
118
131
  - remove const ifs (`if (true)`, etc)
119
132
  - use data segments for initing arrays
120
133
 
134
+ ## porfformance
135
+ *for the things it supports*, porffor is blazingly faster compared to most interpreters, and engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be.
136
+
137
+ ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
138
+
121
139
  ## test262
122
140
  porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
123
141
 
@@ -135,6 +153,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
135
153
  - `i64.extend_i32_s`, `i32.wrap_i64` -> ``
136
154
  - `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
137
155
  - `return`, `end` -> `end`
156
+ - change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
138
157
  - remove some redundant sets/gets
139
158
  - remove unneeded single just used vars
140
159
  - remove unneeded blocks (no `br`s inside)
@@ -190,11 +209,12 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
190
209
  - `-no-run` to not run wasm output, just compile
191
210
  - `-opt-log` to log some opts
192
211
  - `-code-log` to log some codegen (you probably want `-funcs`)
193
- - `-funcs` to log funcs (internal representations)
212
+ - `-funcs` to log funcs
194
213
  - `-opt-funcs` to log funcs after opt
195
214
  - `-sections` to log sections as hex
196
215
  - `-opt-no-inline` to not inline any funcs
197
- - `-tail-call` to enable tail calls (not widely implemented)
216
+ - `-tail-call` to enable tail calls (experimental + not widely implemented)
217
+ - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
198
218
 
199
219
  ## vscode extension
200
220
  there is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
@@ -35,7 +35,14 @@ const debug = str => {
35
35
  };
36
36
 
37
37
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
38
+ class TodoError extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = 'TodoError';
42
+ }
43
+ }
44
+
45
+ throw new TodoError(`todo: ${msg}`);
39
46
 
40
47
  const code = [];
41
48
 
@@ -167,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
167
174
  }
168
175
 
169
176
  if (asm[0] === 'memory') {
170
- scope.memory = true;
171
177
  allocPage('asm instrinsic');
172
178
  // todo: add to store/load offset insts
173
179
  continue;
@@ -281,7 +287,7 @@ const generateReturn = (scope, decl) => {
281
287
  ];
282
288
  }
283
289
 
284
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
290
+ scope.returnType = getNodeType(scope, decl.argument);
285
291
 
286
292
  return [
287
293
  ...generate(scope, decl.argument),
@@ -298,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
298
304
  return idx;
299
305
  };
300
306
 
301
- const performLogicOp = (scope, op, left, right) => {
307
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
302
308
  const checks = {
303
- '||': Opcodes.eqz,
304
- '&&': [ Opcodes.i32_to ]
305
- // todo: ??
309
+ '||': falsy,
310
+ '&&': truthy,
311
+ '??': nullish
306
312
  };
307
313
 
308
314
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -313,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
313
319
  return [
314
320
  ...left,
315
321
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
316
- ...checks[op],
322
+ ...checks[op](scope, [], leftType),
323
+ Opcodes.i32_to,
317
324
  [ Opcodes.if, valtypeBinary ],
318
325
  ...right,
319
326
  [ Opcodes.else ],
@@ -328,8 +335,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
328
335
  // todo: optimize by looking up names in arrays and using that if exists?
329
336
  // todo: optimize this if using literals/known lengths?
330
337
 
331
- scope.memory = true;
332
-
333
338
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
334
339
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
335
340
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -464,8 +469,6 @@ const compareStrings = (scope, left, right) => {
464
469
  // todo: optimize by looking up names in arrays and using that if exists?
465
470
  // todo: optimize this if using literals/known lengths?
466
471
 
467
- scope.memory = true;
468
-
469
472
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
470
473
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
471
474
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -563,75 +566,100 @@ const compareStrings = (scope, left, right) => {
563
566
  ];
564
567
  };
565
568
 
566
- const falsy = (scope, wasm, type) => {
569
+ const truthy = (scope, wasm, type) => {
567
570
  // arrays are always truthy
568
571
  if (type === TYPES._array) return [
569
572
  ...wasm,
570
573
  [ Opcodes.drop ],
571
- number(0)
574
+ ...number(1)
572
575
  ];
573
576
 
574
577
  if (type === TYPES.string) {
575
- // if "" (length = 0)
578
+ // if not "" (length = 0)
576
579
  return [
577
580
  // pointer
578
581
  ...wasm,
582
+ Opcodes.i32_to_u,
579
583
 
580
584
  // get length
581
585
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
582
586
 
583
- // if length == 0
584
- [ Opcodes.i32_eqz ],
587
+ // if length != 0
588
+ /* [ Opcodes.i32_eqz ],
589
+ [ Opcodes.i32_eqz ], */
585
590
  Opcodes.i32_from_u
586
591
  ]
587
592
  }
588
593
 
589
- // if = 0
594
+ // if != 0
590
595
  return [
591
596
  ...wasm,
592
597
 
593
- ...Opcodes.eqz,
594
- Opcodes.i32_from_u
598
+ /* Opcodes.eqz,
599
+ [ Opcodes.i32_eqz ],
600
+ Opcodes.i32_from */
595
601
  ];
596
602
  };
597
603
 
598
- const truthy = (scope, wasm, type) => {
604
+ const falsy = (scope, wasm, type) => {
599
605
  // arrays are always truthy
600
606
  if (type === TYPES._array) return [
601
607
  ...wasm,
602
608
  [ Opcodes.drop ],
603
- number(1)
609
+ ...number(0)
604
610
  ];
605
611
 
606
612
  if (type === TYPES.string) {
607
- // if not "" (length = 0)
613
+ // if "" (length = 0)
608
614
  return [
609
615
  // pointer
610
616
  ...wasm,
617
+ Opcodes.i32_to_u,
611
618
 
612
619
  // get length
613
620
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
614
621
 
615
- // if length != 0
616
- /* [ Opcodes.i32_eqz ],
617
- [ Opcodes.i32_eqz ], */
622
+ // if length == 0
623
+ [ Opcodes.i32_eqz ],
618
624
  Opcodes.i32_from_u
619
625
  ]
620
626
  }
621
627
 
622
- // if != 0
628
+ // if = 0
623
629
  return [
624
630
  ...wasm,
625
631
 
626
- /* Opcodes.eqz,
627
- [ Opcodes.i32_eqz ],
628
- Opcodes.i32_from */
632
+ ...Opcodes.eqz,
633
+ Opcodes.i32_from_u
634
+ ];
635
+ };
636
+
637
+ const nullish = (scope, wasm, type) => {
638
+ // undefined
639
+ if (type === TYPES.undefined) return [
640
+ ...wasm,
641
+ [ Opcodes.drop ],
642
+ ...number(1)
643
+ ];
644
+
645
+ // null (if object and = "0")
646
+ if (type === TYPES.object) return [
647
+ ...wasm,
648
+ ...Opcodes.eqz,
649
+ Opcodes.i32_from_u
650
+ ];
651
+
652
+ // not
653
+ return [
654
+ ...wasm,
655
+ [ Opcodes.drop ],
656
+ ...number(0)
629
657
  ];
630
658
  };
631
659
 
632
660
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
633
661
  if (op === '||' || op === '&&' || op === '??') {
634
- return performLogicOp(scope, op, left, right);
662
+ return performLogicOp(scope, op, left, right, leftType, rightType);
635
663
  }
636
664
 
637
665
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
@@ -772,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
772
800
  };
773
801
 
774
802
  const generateLogicExp = (scope, decl) => {
775
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
803
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
776
804
  };
777
805
 
778
806
  const TYPES = {
@@ -936,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
936
964
  const countLeftover = wasm => {
937
965
  let count = 0, depth = 0;
938
966
 
939
- for (const inst of wasm) {
967
+ for (let i = 0; i < wasm.length; i++) {
968
+ const inst = wasm[i];
940
969
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
941
970
  if (inst[0] === Opcodes.if) count--;
942
971
  if (inst[1] !== Blocktype.void) count++;
@@ -1040,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
1040
1069
  }
1041
1070
 
1042
1071
  let out = [];
1043
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1072
+ let protoFunc, protoName, baseType, baseName;
1044
1073
  // ident.func()
1045
1074
  if (name && name.startsWith('__')) {
1046
1075
  const spl = name.slice(2).split('_');
@@ -1063,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1092
 
1064
1093
  out = generate(scope, decl.callee.object);
1065
1094
  out.push([ Opcodes.drop ]);
1095
+
1096
+ baseName = [...arrays.keys()].pop();
1066
1097
  }
1067
1098
 
1068
1099
  if (protoFunc) {
1069
- scope.memory = true;
1070
-
1071
1100
  let pointer = arrays.get(baseName);
1072
1101
 
1073
1102
  if (pointer == null) {
@@ -1075,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
1075
1104
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1076
1105
 
1077
1106
  // register array
1078
- const [ , pointer ] = makeArray(scope, {
1107
+ 0, [ , pointer ] = makeArray(scope, {
1079
1108
  rawElements: new Array(0)
1080
1109
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1081
1110
 
@@ -1173,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
1173
1202
  args = args.slice(0, func.params.length);
1174
1203
  }
1175
1204
 
1176
- if (func && func.memory) scope.memory = true;
1177
1205
  if (func && func.throws) scope.throws = true;
1178
1206
 
1179
1207
  for (const arg of args) {
@@ -1189,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
1189
1217
  // hack: basically treat this as a normal call for builtins for now
1190
1218
  const name = mapName(decl.callee.name);
1191
1219
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1192
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1220
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1193
1221
 
1194
1222
  return generateCall(scope, decl, _global, _name);
1195
1223
  };
@@ -1297,8 +1325,6 @@ const generateAssign = (scope, decl) => {
1297
1325
  const name = decl.left.object.name;
1298
1326
  const pointer = arrays.get(name);
1299
1327
 
1300
- scope.memory = true;
1301
-
1302
1328
  const aotPointer = pointer != null;
1303
1329
 
1304
1330
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1349,8 +1375,27 @@ const generateAssign = (scope, decl) => {
1349
1375
  ];
1350
1376
  }
1351
1377
 
1378
+ const op = decl.operator.slice(0, -1);
1379
+ if (op === '||' || op === '&&' || op === '??') {
1380
+ // todo: is this needed?
1381
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1382
+ // instead, left @ (left = right)
1383
+ // eg, x &&= y ~= x && (x = y)
1384
+
1385
+ return [
1386
+ ...performOp(scope, op, [
1387
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1388
+ ], [
1389
+ ...generate(scope, decl.right),
1390
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1391
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1392
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1393
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1394
+ ];
1395
+ }
1396
+
1352
1397
  return [
1353
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1398
+ ...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1354
1399
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1355
1400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1356
1401
  ];
@@ -1377,13 +1422,14 @@ const generateUnary = (scope, decl) => {
1377
1422
 
1378
1423
  case '!':
1379
1424
  // !=
1380
- return falsy(scope, generate(scope, decl.argument));
1425
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1381
1426
 
1382
1427
  case '~':
1428
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1383
1429
  return [
1384
1430
  ...generate(scope, decl.argument),
1385
1431
  Opcodes.i32_to,
1386
- [ Opcodes.i32_const, signedLEB128(-1) ],
1432
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1387
1433
  [ Opcodes.i32_xor ],
1388
1434
  Opcodes.i32_from
1389
1435
  ];
@@ -1665,7 +1711,16 @@ const allocPage = reason => {
1665
1711
  let ind = pages.size;
1666
1712
  pages.set(reason, ind);
1667
1713
 
1668
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1714
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
1715
+
1716
+ return ind;
1717
+ };
1718
+
1719
+ const freePage = reason => {
1720
+ let ind = pages.get(reason);
1721
+ pages.delete(reason);
1722
+
1723
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1669
1724
 
1670
1725
  return ind;
1671
1726
  };
@@ -1727,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1727
1782
  // local value as pointer
1728
1783
  out.push(...number(pointer));
1729
1784
 
1730
- scope.memory = true;
1731
-
1732
1785
  return [ out, pointer ];
1733
1786
  };
1734
1787
 
@@ -1747,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
1800
  const name = decl.object.name;
1748
1801
  const pointer = arrays.get(name);
1749
1802
 
1750
- scope.memory = true;
1751
-
1752
1803
  const aotPointer = pointer != null;
1753
1804
 
1754
1805
  return [
@@ -1768,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1768
1819
  const name = decl.object.name;
1769
1820
  const pointer = arrays.get(name);
1770
1821
 
1771
- scope.memory = true;
1772
-
1773
1822
  const aotPointer = pointer != null;
1774
1823
 
1775
1824
  if (type === TYPES._array) {
@@ -1879,6 +1928,7 @@ const generateFunc = (scope, decl) => {
1879
1928
  locals: {},
1880
1929
  localInd: 0,
1881
1930
  returns: [ valtypeBinary ],
1931
+ returnType: null,
1882
1932
  memory: false,
1883
1933
  throws: false,
1884
1934
  name
@@ -1905,7 +1955,6 @@ const generateFunc = (scope, decl) => {
1905
1955
  returns: innerScope.returns,
1906
1956
  returnType: innerScope.returnType,
1907
1957
  locals: innerScope.locals,
1908
- memory: innerScope.memory,
1909
1958
  throws: innerScope.throws,
1910
1959
  index: currentFuncIndex++
1911
1960
  };
@@ -1920,6 +1969,8 @@ const generateFunc = (scope, decl) => {
1920
1969
 
1921
1970
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1922
1971
  wasm.push(...number(0), [ Opcodes.return ]);
1972
+
1973
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1923
1974
  }
1924
1975
 
1925
1976
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1930,9 +1981,7 @@ const generateFunc = (scope, decl) => {
1930
1981
  if (local.type === Valtype.v128) {
1931
1982
  vecParams++;
1932
1983
 
1933
- /* func.memory = true; // mark func as using memory
1934
-
1935
- wasm.unshift( // add v128 load for param
1984
+ /* wasm.unshift( // add v128 load for param
1936
1985
  [ Opcodes.i32_const, 0 ],
1937
1986
  [ ...Opcodes.v128_load, 0, i * 16 ],
1938
1987
  [ Opcodes.local_set, local.idx ]
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
42
42
  out += ` ${read_ieee754_binary64(inst.slice(1))}`;
43
43
  } else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
44
44
  out += ` ${read_signedLEB128(inst.slice(1))}`;
45
- } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
46
- out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`
45
+ } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
46
+ out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
47
47
  } else for (const operand of inst.slice(1)) {
48
48
  if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
49
49
  if (operand === Blocktype.void) continue;
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
22
22
  type
23
23
  ];
24
24
 
25
+ // todo: this only works with integers within 32 bit range
25
26
  export const signedLEB128 = n => {
26
- // todo: this only works with integers within 32 bit range
27
+ n |= 0;
27
28
 
28
29
  // just input for small numbers (for perf as common)
29
30
  if (n >= 0 && n <= 63) return [ n ];
30
31
  if (n >= -64 && n <= 0) return [ 128 + n ];
31
32
 
32
33
  const buffer = [];
33
- n |= 0;
34
34
 
35
35
  while (true) {
36
36
  let byte = n & 0x7f;
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
50
50
  };
51
51
 
52
52
  export const unsignedLEB128 = n => {
53
+ n |= 0;
54
+
53
55
  // just input for small numbers (for perf as common)
54
56
  if (n >= 0 && n <= 127) return [ n ];
55
57
 
package/compiler/index.js CHANGED
@@ -14,7 +14,8 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
14
14
  const areaColors = {
15
15
  codegen: [ 20, 80, 250 ],
16
16
  opt: [ 250, 20, 80 ],
17
- sections: [ 20, 250, 80 ]
17
+ sections: [ 20, 250, 80 ],
18
+ alloc: [ 250, 250, 20 ]
18
19
  };
19
20
 
20
21
  globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
@@ -38,6 +39,7 @@ const logFuncs = (funcs, globals, exceptions) => {
38
39
  export default (code, flags) => {
39
40
  globalThis.optLog = process.argv.includes('-opt-log');
40
41
  globalThis.codeLog = process.argv.includes('-code-log');
42
+ globalThis.allocLog = process.argv.includes('-alloc-log');
41
43
 
42
44
  for (const x in BuiltinPreludes) {
43
45
  if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
@@ -63,5 +65,12 @@ export default (code, flags) => {
63
65
  const sections = produceSections(funcs, globals, tags, pages, flags);
64
66
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
65
67
 
68
+ if (allocLog) {
69
+ const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
70
+ const bytes = wasmPages * 65536;
71
+ log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
72
+ // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
73
+ }
74
+
66
75
  return { wasm: sections, funcs, globals, tags, exceptions, pages };
67
76
  };
package/compiler/opt.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
+ import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
3
4
 
4
5
  // deno compat
5
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
20
21
  if (optLevel === 0) return;
21
22
 
22
23
  const tailCall = process.argv.includes('-tail-call');
23
- if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
24
+ if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
24
25
 
25
26
  if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
26
27
  // inline pass (very WIP)
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
95
96
  }
96
97
 
97
98
  if (t.index > c.index) t.index--; // adjust index if after removed func
98
- if (c.memory) t.memory = true;
99
99
  }
100
100
 
101
101
  funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
213
213
  // i32.const 0
214
214
  // drop
215
215
  // -->
216
- // <nothing>>
216
+ // <nothing>
217
217
 
218
- wasm.splice(i - 1, 2); // remove this inst
218
+ wasm.splice(i - 1, 2); // remove these inst
219
219
  i -= 2;
220
220
  continue;
221
221
  }
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
259
259
  continue;
260
260
  }
261
261
 
262
+ if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
263
+ // change const and immediate i32 convert to i32 const
264
+ // f64.const 0
265
+ // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
266
+ // -->
267
+ // i32.const 0
268
+
269
+ wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
270
+
271
+ wasm.splice(i, 1); // remove this inst
272
+ i--;
273
+ if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
274
+ continue;
275
+ }
276
+
277
+ if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
278
+ // change i32 const and immediate convert to const (opposite way of previous)
279
+ // i32.const 0
280
+ // f64.convert_i32_s || f64.convert_i32_u
281
+ // -->
282
+ // f64.const 0
283
+
284
+ wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
285
+
286
+ wasm.splice(i, 1); // remove this inst
287
+ i--;
288
+ if (optLog) log('opt', `converted i32 const -> convert into const`);
289
+ continue;
290
+ }
291
+
262
292
  if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
293
  // replace call, return with tail calls (return_call)
264
294
  // call X
@@ -25,7 +25,6 @@ export const PrototypeFuncs = function() {
25
25
 
26
26
  this[TYPES._array] = {
27
27
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
28
- // todo: out of bounds (>) properly
29
28
  at: (pointer, length, wIndex, iTmp) => [
30
29
  ...wIndex,
31
30
  Opcodes.i32_to,
@@ -147,7 +146,6 @@ export const PrototypeFuncs = function() {
147
146
  this[TYPES._array].push.noArgRetLength = true;
148
147
 
149
148
  this[TYPES.string] = {
150
- // todo: out of bounds properly
151
149
  at: (pointer, length, wIndex, iTmp, arrayShell) => {
152
150
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
153
151
 
@@ -157,9 +155,9 @@ export const PrototypeFuncs = function() {
157
155
  [ Opcodes.drop ],
158
156
 
159
157
  ...number(0, Valtype.i32), // base 0 for store later
160
- Opcodes.i32_to_u,
161
158
 
162
159
  ...wIndex,
160
+ Opcodes.i32_to_u,
163
161
  [ Opcodes.local_tee, iTmp ],
164
162
 
165
163
  // if index < 0: access index + array length
@@ -265,7 +263,7 @@ export const PrototypeFuncs = function() {
265
263
  },
266
264
  };
267
265
 
268
- this[TYPES.string].at.local = valtypeBinary;
266
+ this[TYPES.string].at.local = Valtype.i32;
269
267
  this[TYPES.string].at.returnType = TYPES.string;
270
268
  this[TYPES.string].charAt.returnType = TYPES.string;
271
269
  this[TYPES.string].charCodeAt.local = Valtype.i32;
@@ -0,0 +1,173 @@
1
+ import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from '../wasmSpec.js';
2
+ import { signedLEB128, unsignedLEB128 } from '../encoding.js';
3
+ import parse from './parse.js';
4
+
5
+ // local indexes
6
+ const BasePointer = 0; // base string pointer
7
+ const IterPointer = 1; // this iteration base pointer
8
+ const Counter = 2; // what char we are running on
9
+ const Pointer = 3; // next char BYTE pointer
10
+ const Length = 4;
11
+ const Tmp = 5;
12
+
13
+ const generate = (node, get = true) => {
14
+ let out = [];
15
+ switch (node.type) {
16
+ case 'Expression':
17
+ out = [
18
+ // set length local
19
+ [ Opcodes.local_get, BasePointer ],
20
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(0) ],
21
+ [ Opcodes.local_set, Length ],
22
+
23
+ // set iter pointer local as base + sizeof i32 initially
24
+ [ Opcodes.local_get, BasePointer ],
25
+ ...number(ValtypeSize.i32, Valtype.i32),
26
+ [ Opcodes.i32_add ],
27
+ [ Opcodes.local_set, IterPointer ],
28
+
29
+ [ Opcodes.loop, Blocktype.void ],
30
+
31
+ // reset pointer as iter pointer
32
+ [ Opcodes.local_get, IterPointer ],
33
+ [ Opcodes.local_set, Pointer ],
34
+
35
+ [ Opcodes.block, Blocktype.void ],
36
+ // generate checks
37
+ ...generate(x),
38
+
39
+ // reached end without branching out, successful match
40
+ ...number(1, Valtype.i32),
41
+ [ Opcodes.return ],
42
+
43
+ [ Opcodes.end ],
44
+
45
+ // increment iter pointer by sizeof i16
46
+ [ Opcodes.local_get, IterPointer ],
47
+ ...number(ValtypeSize.i16, Valtype.i32),
48
+ [ Opcodes.i32_add ],
49
+ [ Opcodes.local_set, IterPointer ],
50
+
51
+ // increment counter by 1, check if eq length, if not loop
52
+ [ Opcodes.local_get, Counter ],
53
+ ...number(1, Valtype.i32),
54
+ [ Opcodes.i32_add ],
55
+ [ Opcodes.local_tee, Counter ],
56
+
57
+ [ Opcodes.local_get, Length ],
58
+ [ Opcodes.i32_ne ],
59
+ [ Opcodes.br_if, 1 ],
60
+
61
+ [ Opcodes.end ],
62
+
63
+ // no match, return 0
64
+ ...number(0, Valtype.i32)
65
+ ];
66
+
67
+ break;
68
+
69
+ case 'Character':
70
+ out = generateChar(node, get);
71
+ break;
72
+
73
+ case 'Set':
74
+ out = generateSet(node, get);
75
+ break;
76
+
77
+ case 'Group':
78
+ out = generateGroup(node, get);
79
+ break;
80
+
81
+ case 'Range':
82
+ out = generateRange(node, get);
83
+ break;
84
+ }
85
+
86
+ return out;
87
+ };
88
+
89
+ const getNextChar = () => [
90
+ // get char from pointer
91
+ [ Opcodes.local_get, Pointer ],
92
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(0) ],
93
+
94
+ // pointer += sizeof i16
95
+ [ Opcodes.local_get, Pointer ],
96
+ ...number(ValtypeSize.i16, Valtype.i32),
97
+ [ Opcodes.i32_add ],
98
+ [ Opcodes.local_set, Pointer ]
99
+ ];
100
+
101
+ const checkFailure = () => [
102
+ // surely we do not need to do this for every single mismatch, right?
103
+ /* [ Opcodes.if, Blocktype.void ],
104
+ ...number(0, Valtype.i32),
105
+ [ Opcodes.return ],
106
+ [ Opcodes.end ], */
107
+
108
+ [ Opcodes.br, 1 ]
109
+ ];
110
+
111
+ const generateChar = (node, get) => {
112
+ return [
113
+ ...(get ? getNextChar() : []),
114
+ ...number(String.fromCharCode(node.char), Valtype.i32),
115
+ [ Opcodes.i32_ne ],
116
+ ...checkFailure()
117
+ ];
118
+ };
119
+
120
+ const generateSet = (node, get) => {
121
+ const out = [
122
+ ...(get ? getNextChar() : []),
123
+ [ Opcodes.local_set, Tmp ],
124
+ ];
125
+
126
+ for (const x of node.body) {
127
+ out = [
128
+ ...out,
129
+ [ Opcodes.local_get, Tmp ],
130
+ ...generate(x, false)
131
+ ];
132
+ }
133
+
134
+ out = out.concat(new Array(node.body.length - 1).fill([ Opcodes.i32_or ]));
135
+
136
+ return [
137
+ ...out,
138
+ ...checkFailure()
139
+ ];
140
+ };
141
+
142
+ const generateGroup = (node, get) => {
143
+
144
+ };
145
+
146
+ const generateRange = (node, get) => {
147
+
148
+ };
149
+
150
+
151
+ export const match = regex => {
152
+
153
+ };
154
+
155
+ export const test = regex => {
156
+ const code = generate(parse(regex));
157
+ };
158
+
159
+ const wasmify = code => {
160
+ const funcs = [{
161
+ name: 'exp',
162
+ export: true,
163
+ params: [ Valtype.i32 ],
164
+ returns: [ Valtype.i32 ],
165
+ locals: {
166
+ iterPointer: { idx: 1, type: Valtype.i32 },
167
+ counter: { idx: 2, type: Valtype.i32 },
168
+ pointer: { idx: 3, type: Valtype.i32 },
169
+ length: { idx: 4, type: Valtype.i32 },
170
+ tmp: { idx: 5, type: Valtype.i32 },
171
+ }
172
+ }]
173
+ };
@@ -0,0 +1,125 @@
1
+ const State = {
2
+ none: 0,
3
+ insideSet: 1
4
+ };
5
+
6
+ const Quantifiers = {
7
+ '*': [ 0 ], // 0 -
8
+ '+': [ 1 ], // 1 -
9
+ '?': [ 0, 1 ], // 0 - 1
10
+ };
11
+ const QuantifierKeys = Object.keys(Quantifiers);
12
+
13
+ export default str => {
14
+ const out = {
15
+ type: 'Expression',
16
+ body: []
17
+ };
18
+ let node = out, parents = [];
19
+
20
+ let state = State.none, escape = false;
21
+ for (let i = 0; i < str.length; i++) {
22
+ const c = str[i];
23
+
24
+ const addChar = () => {
25
+ node.body.push({
26
+ type: 'Character',
27
+ char: c
28
+ });
29
+ };
30
+
31
+ const seek = () => {
32
+ const cNext = str[++i];
33
+
34
+ if (cNext === '\\') return str[++i];
35
+ return cNext;
36
+ };
37
+
38
+ if (escape) {
39
+ addChar();
40
+
41
+ escape = false;
42
+ continue;
43
+ }
44
+
45
+ if (c === '\\') {
46
+ escape = true;
47
+ continue;
48
+ }
49
+
50
+ switch (state) {
51
+ case State.none:
52
+ if (c === '[') {
53
+ parents.push(node);
54
+ node = {
55
+ type: 'Set',
56
+ body: []
57
+ };
58
+
59
+ parents.at(-1).body.push(node);
60
+
61
+ state = State.insideSet;
62
+ continue;
63
+ }
64
+
65
+ if (c === '(') {
66
+ parents.push(node);
67
+ node = {
68
+ type: 'Group',
69
+ body: []
70
+ };
71
+
72
+ parents.at(-1).body.push(node);
73
+ continue;
74
+ }
75
+
76
+ if (c === ')') {
77
+ if (node.type !== 'Group') throw new SyntaxError('Unmatched closing parenthesis');
78
+
79
+ node = parents.pop();
80
+ continue;
81
+ }
82
+
83
+ if (QuantifierKeys.includes(c)) {
84
+ const amount = Quantifiers[c];
85
+ node.body.at(-1).quantifier = amount;
86
+ continue;
87
+ }
88
+
89
+ addChar();
90
+ break;
91
+
92
+ case State.insideSet:
93
+ if (c === ']') {
94
+ state = State.none;
95
+ node = parents.pop();
96
+
97
+ continue;
98
+ }
99
+
100
+ // range
101
+ if (c === '-') {
102
+ const from = node.body.pop().char;
103
+ const to = seek();
104
+
105
+ node.body.push({
106
+ type: 'Range',
107
+ from,
108
+ to
109
+ });
110
+ continue;
111
+ }
112
+
113
+ addChar();
114
+ break;
115
+ }
116
+ }
117
+
118
+ // still in a group by the end
119
+ if (node.type !== 'Expression') throw new SyntaxError('Unmatched opening parenthesis');
120
+
121
+ // still in a set by the end
122
+ if (state === State.insideSet) throw new SyntaxError('Unmatched opening square bracket');
123
+
124
+ return out;
125
+ };
@@ -0,0 +1,20 @@
1
+ import util from 'node:util';
2
+
3
+ import parse from '../parse.js';
4
+
5
+ const tests = {
6
+ 'a': {},
7
+ 'a(b)': {},
8
+ 'a(b(c))': {},
9
+ 'ab': {},
10
+ '[ab]': {},
11
+ '[a-z]': {},
12
+ 'a*': {},
13
+ 'a+': {},
14
+ 'a?': {},
15
+ 'a(b)+': {}
16
+ }
17
+
18
+ for (const str in tests) {
19
+ console.log(str, util.inspect(parse(str), false, null, true));
20
+ }
@@ -8,11 +8,26 @@ const createSection = (type, data) => [
8
8
  ...encodeVector(data)
9
9
  ];
10
10
 
11
+ const customSection = (name, data) => [
12
+ Section.custom,
13
+ ...encodeVector([...encodeString(name), ...data])
14
+ ];
15
+
16
+ const chHint = (topTier, baselineTier, strategy) => {
17
+ // 1 byte of 4 2 bit components: spare, top tier, baseline tier, compilation strategy
18
+ // tiers: 0x00 = default, 0x01 = baseline (liftoff), 0x02 = optimized (turbofan)
19
+ // strategy: 0x00 = default, 0x01 = lazy, 0x02 = eager, 0x03 = lazy baseline, eager top tier
20
+ return (strategy | (baselineTier << 2) | (topTier << 4));
21
+ };
22
+
11
23
  export default (funcs, globals, tags, pages, flags) => {
12
24
  const types = [], typeCache = {};
13
25
 
14
26
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
15
27
 
28
+ const compileHints = process.argv.includes('-compile-hints');
29
+ if (compileHints) log('sections', 'warning: compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
30
+
16
31
  const getType = (params, returns) => {
17
32
  const hash = `${params.join(',')}_${returns.join(',')}`;
18
33
  if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
@@ -36,7 +51,7 @@ export default (funcs, globals, tags, pages, flags) => {
36
51
  // tree shake imports
37
52
  for (const f of funcs) {
38
53
  for (const inst of f.wasm) {
39
- if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
54
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
40
55
  const idx = inst[1];
41
56
  const func = importedFuncs[idx];
42
57
 
@@ -51,10 +66,11 @@ export default (funcs, globals, tags, pages, flags) => {
51
66
  // fix call indexes for non-imports
52
67
  const delta = importedFuncs.length - importFuncs.length;
53
68
  for (const f of funcs) {
69
+ f.originalIndex = f.index;
54
70
  f.index -= delta;
55
71
 
56
72
  for (const inst of f.wasm) {
57
- if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
73
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
58
74
  inst[1] -= delta;
59
75
  }
60
76
  }
@@ -73,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
73
89
  encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
74
90
  );
75
91
 
92
+ // compilation hints section - unspec v8 only
93
+ // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
94
+ const chSection = !compileHints ? [] : customSection(
95
+ 'compilationHints',
96
+ // for now just do everything as optimise eager
97
+ encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
98
+ );
99
+
76
100
  const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
77
101
  Section.global,
78
102
  encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
@@ -145,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
145
169
  ...typeSection,
146
170
  ...importSection,
147
171
  ...funcSection,
172
+ ...chSection,
148
173
  ...memorySection,
149
174
  ...tagSection,
150
175
  ...globalSection,
package/compiler/wrap.js CHANGED
@@ -90,6 +90,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
90
90
  return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
91
91
  }
92
92
 
93
+ case 'function': {
94
+ // wasm func index, including all imports
95
+ const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
96
+ if (!func) return ret;
97
+
98
+ // make fake empty func for repl/etc
99
+ return {[func.name]() {}}[func.name];
100
+ }
101
+
93
102
  default: return ret;
94
103
  }
95
104
  } catch (e) {
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.0.0-bddcdc3",
4
+ "version": "0.0.0-ebc0491",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/r.js ADDED
@@ -0,0 +1 @@
1
+ /a(b)/.test('hi');
package/runner/index.js CHANGED
@@ -3,8 +3,26 @@
3
3
  import compile from '../compiler/wrap.js';
4
4
  import fs from 'node:fs';
5
5
 
6
+ if (process.argv.includes('-compile-hints')) {
7
+ const v8 = await import('node:v8');
8
+ v8.setFlagsFromString(`--experimental-wasm-compilation-hints`);
9
+
10
+ // see also these flags:
11
+ // --experimental-wasm-branch-hinting
12
+ // --experimental-wasm-extended-const
13
+ // --experimental-wasm-inlining (?)
14
+ // --experimental-wasm-js-inlining (?)
15
+ // --experimental-wasm-return-call (on by default)
16
+ }
17
+
6
18
  const file = process.argv.slice(2).find(x => x[0] !== '-');
7
19
  if (!file) {
20
+ if (process.argv.includes('-v')) {
21
+ // just print version
22
+ console.log((await import('./version.js')).default);
23
+ process.exit(0);
24
+ }
25
+
8
26
  // run repl if no file given
9
27
  await import('./repl.js');
10
28
 
package/runner/repl.js CHANGED
@@ -1,14 +1,7 @@
1
1
  import compile from '../compiler/wrap.js';
2
+ import rev from './version.js';
2
3
 
3
4
  import repl from 'node:repl';
4
- import fs from 'node:fs';
5
-
6
- let rev = 'unknown';
7
- try {
8
- rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
9
- } catch {
10
- rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1];
11
- }
12
5
 
13
6
  // process.argv.push('-O0'); // disable opts
14
7
 
@@ -48,7 +41,7 @@ const memoryToString = mem => {
48
41
  return out;
49
42
  };
50
43
 
51
- const alwaysPrev = process.argv.includes('-always-prev');
44
+ const alwaysPrev = process.argv.includes('-prev');
52
45
 
53
46
  let prev = '';
54
47
  const run = async (source, _context, _filename, callback, run = true) => {
@@ -56,7 +49,7 @@ const run = async (source, _context, _filename, callback, run = true) => {
56
49
  if (alwaysPrev) prev = toRun + ';\n';
57
50
 
58
51
  const { exports, wasm, pages } = await compile(toRun, []);
59
- fs.writeFileSync('out.wasm', Buffer.from(wasm));
52
+ // fs.writeFileSync('out.wasm', Buffer.from(wasm));
60
53
 
61
54
  if (run && exports.$) {
62
55
  lastMemory = exports.$;
@@ -8,7 +8,8 @@ const source = fs.readFileSync(file, 'utf8');
8
8
  const { wasm } = await compile(source);
9
9
 
10
10
  // const out = `(async () => { const print = str => process.stdout.write(str); (await WebAssembly.instantiate(Uint8Array.from([${wasm.toString()}]), {'': { p: i => print(i.toString()), c: i => print(String.fromCharCode(i))}})).instance.exports.m()})()`;
11
- const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
11
+ // const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
12
+ const out = `const a=new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])));const b=a.exports.m();console.log(Array.from(new Uint16Array(a.exports.$.buffer,b+4,new Int32Array(a.exports.$.buffer,b,1))).map(x=>String.fromCharCode(x)).join(''))`;
12
13
 
13
14
  console.log(out);
14
15
  eval(out);
@@ -0,0 +1,10 @@
1
+ import fs from 'node:fs';
2
+
3
+ let rev = 'unknown';
4
+ try {
5
+ rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
6
+ } catch {
7
+ rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1].slice(0, 7);
8
+ }
9
+
10
+ export default rev;