porffor 0.0.0-579ef36 → 0.0.0-70c2792

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
@@ -76,6 +76,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
76
76
  - string member (char) access via `str[ind]` (eg `str[0]`)
77
77
  - string concat (`+`) (eg `'a' + 'b'`)
78
78
  - truthy/falsy (eg `!'' == true`)
79
+ - string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
79
80
 
80
81
  ### built-ins
81
82
 
@@ -106,7 +107,6 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
106
107
  - arrays/strings inside arrays
107
108
  - strings
108
109
  - member setting
109
- - equality
110
110
  - more math operators (`**`, etc)
111
111
  - `do { ... } while (...)`
112
112
  - exceptions
@@ -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
 
@@ -101,6 +108,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
108
  case 'WhileStatement':
102
109
  return generateWhile(scope, decl);
103
110
 
111
+ /* case 'ForOfStatement':
112
+ return generateForOf(scope, decl); */
113
+
104
114
  case 'BreakStatement':
105
115
  return generateBreak(scope, decl);
106
116
 
@@ -327,13 +337,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
327
337
 
328
338
  scope.memory = true;
329
339
 
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
340
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
341
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
342
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
343
 
336
344
  if (assign) {
345
+ const pointer = arrays.get(name ?? '$undeclared');
346
+
337
347
  return [
338
348
  // setup right
339
349
  ...right,
@@ -384,15 +394,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
394
 
385
395
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
396
 
387
- const newOut = makeArray(scope, {
397
+ // alloc/assign array
398
+ const [ , pointer ] = makeArray(scope, {
388
399
  rawElements: new Array(0)
389
400
  }, global, name, true, 'i16');
390
401
 
391
402
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
403
  // setup left
397
404
  ...left,
398
405
  Opcodes.i32_to_u,
@@ -458,6 +465,111 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
465
  ];
459
466
  };
460
467
 
468
+ const compareStrings = (scope, left, right) => {
469
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
470
+ // todo: convert left and right to strings if not
471
+ // todo: optimize by looking up names in arrays and using that if exists?
472
+ // todo: optimize this if using literals/known lengths?
473
+
474
+ scope.memory = true;
475
+
476
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
477
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
478
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
479
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
480
+
481
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
482
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
483
+
484
+ return [
485
+ // setup left
486
+ ...left,
487
+ Opcodes.i32_to_u,
488
+ [ Opcodes.local_tee, leftPointer ],
489
+
490
+ // setup right
491
+ ...right,
492
+ Opcodes.i32_to_u,
493
+ [ Opcodes.local_tee, rightPointer ],
494
+
495
+ // fast path: check leftPointer == rightPointer
496
+ // use if (block) for everything after to "return" a value early
497
+ [ Opcodes.i32_ne ],
498
+ [ Opcodes.if, Valtype.i32 ],
499
+
500
+ // get lengths
501
+ [ Opcodes.local_get, leftPointer ],
502
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
503
+ [ Opcodes.local_tee, leftLength ],
504
+
505
+ [ Opcodes.local_get, rightPointer ],
506
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
507
+ [ Opcodes.local_tee, rightLength ],
508
+
509
+ // fast path: check leftLength != rightLength
510
+ [ Opcodes.i32_ne ],
511
+ [ Opcodes.if, Blocktype.void ],
512
+ ...number(0, Valtype.i32),
513
+ [ Opcodes.br, 1 ],
514
+ [ Opcodes.end ],
515
+
516
+ // no fast path for length = 0 as it would probably be slower for most of the time?
517
+
518
+ // setup index end as length * sizeof i16 (2)
519
+ // we do this instead of having to do mul/div each iter for perf™
520
+ [ Opcodes.local_get, leftLength ],
521
+ ...number(ValtypeSize.i16, Valtype.i32),
522
+ [ Opcodes.i32_mul ],
523
+ [ Opcodes.local_set, indexEnd ],
524
+
525
+ // iterate over each char and check if eq
526
+ [ Opcodes.loop, Blocktype.void ],
527
+
528
+ // fetch left
529
+ [ Opcodes.local_get, index ],
530
+ [ Opcodes.local_get, leftPointer ],
531
+ [ Opcodes.i32_add ],
532
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
533
+
534
+ // fetch right
535
+ [ Opcodes.local_get, index ],
536
+ [ Opcodes.local_get, rightPointer ],
537
+ [ Opcodes.i32_add ],
538
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
539
+
540
+ // not equal, "return" false
541
+ [ Opcodes.i32_ne ],
542
+ [ Opcodes.if, Blocktype.void ],
543
+ ...number(0, Valtype.i32),
544
+ [ Opcodes.br, 2 ],
545
+ [ Opcodes.end ],
546
+
547
+ // index += sizeof i16 (2)
548
+ [ Opcodes.local_get, index ],
549
+ ...number(ValtypeSize.i16, Valtype.i32),
550
+ [ Opcodes.i32_add ],
551
+ [ Opcodes.local_tee, index ],
552
+
553
+ // if index != index end (length * sizeof 16), loop
554
+ [ Opcodes.local_get, indexEnd ],
555
+ [ Opcodes.i32_ne ],
556
+ [ Opcodes.br_if, 0 ],
557
+ [ Opcodes.end ],
558
+
559
+ // no failed checks, so true!
560
+ ...number(1, Valtype.i32),
561
+
562
+ // pointers match, so true
563
+ [ Opcodes.else ],
564
+ ...number(1, Valtype.i32),
565
+ [ Opcodes.end ],
566
+
567
+ // convert i32 result to valtype
568
+ // do not do as automatically added by binary exp gen for equality ops
569
+ // Opcodes.i32_from_u
570
+ ];
571
+ };
572
+
461
573
  const falsy = (scope, wasm, type) => {
462
574
  // arrays are always truthy
463
575
  if (type === TYPES._array) return [
@@ -524,11 +636,28 @@ const truthy = (scope, wasm, type) => {
524
636
  ];
525
637
  };
526
638
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
639
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
640
  if (op === '||' || op === '&&' || op === '??') {
529
641
  return performLogicOp(scope, op, left, right);
530
642
  }
531
643
 
644
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
645
+
646
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
647
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
648
+ return [
649
+ ...left,
650
+ ...right,
651
+
652
+ // drop values
653
+ [ Opcodes.drop ],
654
+ [ Opcodes.drop ],
655
+
656
+ // return false (===)/true (!==)
657
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
658
+ ];
659
+ }
660
+
532
661
  if (leftType === TYPES.string || rightType === TYPES.string) {
533
662
  if (op === '+') {
534
663
  // string concat (a + b)
@@ -539,8 +668,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
539
668
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
540
669
 
541
670
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
671
+ // todo: convert string to number if string and number/bool
672
+ // todo: string (>|>=|<|<=) string
673
+
674
+ // string comparison
675
+ if (op === '===' || op === '==') {
676
+ return compareStrings(scope, left, right);
677
+ }
678
+
679
+ if (op === '!==' || op === '!=') {
680
+ return [
681
+ ...compareStrings(scope, left, right),
682
+ [ Opcodes.i32_eqz ]
683
+ ];
684
+ }
544
685
  }
545
686
 
546
687
  let ops = operatorOpcode[valtype][op];
@@ -569,13 +710,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
710
  ];
570
711
  };
571
712
 
713
+ let binaryExpDepth = 0;
572
714
  const generateBinaryExp = (scope, decl, _global, _name) => {
715
+ binaryExpDepth++;
716
+
573
717
  const out = [
574
718
  ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
719
  ];
576
720
 
577
721
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
722
 
723
+ binaryExpDepth--;
579
724
  return out;
580
725
  };
581
726
 
@@ -682,6 +827,7 @@ const getType = (scope, _name) => {
682
827
 
683
828
  const getNodeType = (scope, node) => {
684
829
  if (node.type === 'Literal') {
830
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
685
831
  return TYPES[typeof node.value];
686
832
  }
687
833
 
@@ -696,7 +842,7 @@ const getNodeType = (scope, node) => {
696
842
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
843
  const name = node.callee.name;
698
844
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
845
+ if (func) return func.returnType;
700
846
 
701
847
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
848
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -721,9 +867,7 @@ const getNodeType = (scope, node) => {
721
867
  protoFunc = prototypeFuncs[baseType]?.[func];
722
868
  }
723
869
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
870
+ if (protoFunc) return protoFunc.returnType;
727
871
  }
728
872
 
729
873
  if (node.type === 'ExpressionStatement') {
@@ -755,9 +899,6 @@ const getNodeType = (scope, node) => {
755
899
 
756
900
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
901
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
902
  };
762
903
 
763
904
  const generateLiteral = (scope, decl, global, name) => {
@@ -792,7 +933,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
933
 
793
934
  return makeArray(scope, {
794
935
  rawElements
795
- }, global, name, false, 'i16');
936
+ }, global, name, false, 'i16')[0];
796
937
 
797
938
  default:
798
939
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -941,10 +1082,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1082
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1083
 
943
1084
  // register array
944
- makeArray(scope, {
1085
+ const [ , pointer ] = makeArray(scope, {
945
1086
  rawElements: new Array(0)
946
1087
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1088
 
949
1089
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1090
 
@@ -980,10 +1120,9 @@ const generateCall = (scope, decl, _global, _name) => {
980
1120
  set: value => arrayUtil.setLength(pointer, value),
981
1121
  setI32: value => arrayUtil.setLengthI32(pointer, value)
982
1122
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
1123
+ return makeArray(scope, {
984
1124
  rawElements: new Array(length)
985
1125
  }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
1126
  }),
988
1127
  [ Opcodes.end ]
989
1128
  ];
@@ -1057,7 +1196,7 @@ const generateNew = (scope, decl, _global, _name) => {
1057
1196
  // hack: basically treat this as a normal call for builtins for now
1058
1197
  const name = mapName(decl.callee.name);
1059
1198
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1060
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1199
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1200
 
1062
1201
  return generateCall(scope, decl, _global, _name);
1063
1202
  };
@@ -1289,7 +1428,7 @@ const generateUnary = (scope, decl) => {
1289
1428
  return out;
1290
1429
 
1291
1430
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1431
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1432
 
1294
1433
  // for custom types, just return object
1295
1434
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1422,9 +1561,28 @@ const generateWhile = (scope, decl) => {
1422
1561
  return out;
1423
1562
  };
1424
1563
 
1564
+ const generateForOf = (scope, decl) => {
1565
+ const out = [];
1566
+
1567
+ out.push([ Opcodes.loop, Blocktype.void ]);
1568
+ depth.push('while');
1569
+
1570
+ out.push(...generate(scope, decl.test));
1571
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1572
+ depth.push('if');
1573
+
1574
+ out.push(...generate(scope, decl.body));
1575
+
1576
+ out.push([ Opcodes.br, 1 ]);
1577
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1578
+ depth.pop(); depth.pop();
1579
+
1580
+ return out;
1581
+ };
1582
+
1425
1583
  const getNearestLoop = () => {
1426
1584
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1585
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1586
  }
1429
1587
 
1430
1588
  return -1;
@@ -1514,7 +1672,16 @@ const allocPage = reason => {
1514
1672
  let ind = pages.size;
1515
1673
  pages.set(reason, ind);
1516
1674
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1675
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
1676
+
1677
+ return ind;
1678
+ };
1679
+
1680
+ const freePage = reason => {
1681
+ let ind = pages.get(reason);
1682
+ pages.delete(reason);
1683
+
1684
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
1685
 
1519
1686
  return ind;
1520
1687
  };
@@ -1578,12 +1745,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1578
1745
 
1579
1746
  scope.memory = true;
1580
1747
 
1581
- return out;
1748
+ return [ out, pointer ];
1582
1749
  };
1583
1750
 
1584
1751
  let arrays = new Map();
1585
1752
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
1753
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
1754
  };
1588
1755
 
1589
1756
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1644,10 +1811,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
1811
 
1645
1812
  // string
1646
1813
 
1647
- const newOut = makeArray(scope, {
1814
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
1815
  rawElements: new Array(1)
1649
1816
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
1817
 
1652
1818
  return [
1653
1819
  // setup new/out array
@@ -1753,7 +1919,7 @@ const generateFunc = (scope, decl) => {
1753
1919
  name,
1754
1920
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
1921
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1922
+ returnType: innerScope.returnType,
1757
1923
  locals: innerScope.locals,
1758
1924
  memory: innerScope.memory,
1759
1925
  throws: innerScope.throws,
@@ -1912,10 +2078,9 @@ const internalConstrs = {
1912
2078
 
1913
2079
  // new Array(n)
1914
2080
 
1915
- makeArray(scope, {
2081
+ const [ , pointer ] = makeArray(scope, {
1916
2082
  rawElements: new Array(0)
1917
2083
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2084
 
1920
2085
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2086
 
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
@@ -20,7 +20,7 @@ export default (funcs, globals) => {
20
20
  if (optLevel === 0) return;
21
21
 
22
22
  const tailCall = process.argv.includes('-tail-call');
23
- if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
23
+ if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
24
24
 
25
25
  if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
26
26
  // inline pass (very WIP)
@@ -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;
@@ -36,7 +36,7 @@ export default (funcs, globals, tags, pages, flags) => {
36
36
  // tree shake imports
37
37
  for (const f of funcs) {
38
38
  for (const inst of f.wasm) {
39
- if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
39
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
40
40
  const idx = inst[1];
41
41
  const func = importedFuncs[idx];
42
42
 
@@ -51,10 +51,11 @@ export default (funcs, globals, tags, pages, flags) => {
51
51
  // fix call indexes for non-imports
52
52
  const delta = importedFuncs.length - importFuncs.length;
53
53
  for (const f of funcs) {
54
+ f.originalIndex = f.index;
54
55
  f.index -= delta;
55
56
 
56
57
  for (const inst of f.wasm) {
57
- if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
58
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
58
59
  inst[1] -= delta;
59
60
  }
60
61
  }
package/compiler/wrap.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
- // import fs from 'node:fs';
3
+ import fs from 'node:fs';
4
4
 
5
5
  const bold = x => `\u001b[1m${x}\u001b[0m`;
6
6
 
@@ -27,7 +27,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
27
27
 
28
28
  if (source.includes('export function')) flags.push('module');
29
29
 
30
- // fs.writeFileSync('out.wasm', Buffer.from(wasm));
30
+ fs.writeFileSync('out.wasm', Buffer.from(wasm));
31
31
 
32
32
  times.push(performance.now() - t1);
33
33
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
@@ -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-579ef36",
4
+ "version": "0.0.0-70c2792",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/index.js CHANGED
@@ -5,6 +5,12 @@ import fs from 'node:fs';
5
5
 
6
6
  const file = process.argv.slice(2).find(x => x[0] !== '-');
7
7
  if (!file) {
8
+ if (process.argv.includes('-v')) {
9
+ // just print version
10
+ console.log((await import('./version.js')).default);
11
+ process.exit(0);
12
+ }
13
+
8
14
  // run repl if no file given
9
15
  await import('./repl.js');
10
16
 
@@ -30,5 +36,6 @@ try {
30
36
  exports.main();
31
37
  if (cache) process.stdout.write(cache);
32
38
  } catch (e) {
39
+ if (cache) process.stdout.write(cache);
33
40
  console.error(`${e.constructor.name}: ${e.message}`);
34
41
  }
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,15 +41,17 @@ const memoryToString = mem => {
48
41
  return out;
49
42
  };
50
43
 
44
+ const alwaysPrev = process.argv.includes('-prev');
45
+
51
46
  let prev = '';
52
47
  const run = async (source, _context, _filename, callback, run = true) => {
53
48
  let toRun = prev + source.trim();
54
- // prev = toRun + ';\n';
49
+ if (alwaysPrev) prev = toRun + ';\n';
55
50
 
56
51
  const { exports, wasm, pages } = await compile(toRun, []);
57
- fs.writeFileSync('out.wasm', Buffer.from(wasm));
52
+ // fs.writeFileSync('out.wasm', Buffer.from(wasm));
58
53
 
59
- if (exports.$) {
54
+ if (run && exports.$) {
60
55
  lastMemory = exports.$;
61
56
  lastPages = [...pages.keys()];
62
57
  }
@@ -64,8 +59,8 @@ const run = async (source, _context, _filename, callback, run = true) => {
64
59
  const ret = run ? exports.main() : undefined;
65
60
  callback(null, ret);
66
61
 
67
- // if (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ')) prev += source + ';\n';
68
- prev = toRun + ';\n';
62
+ if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
63
+ // prev = toRun + ';\n';
69
64
  };
70
65
 
71
66
  const replServer = repl.start({ prompt: '> ', eval: run });
@@ -5,32 +5,10 @@ const file = process.argv.slice(2).find(x => x[0] !== '-');
5
5
 
6
6
  const source = fs.readFileSync(file, 'utf8');
7
7
 
8
- const underline = x => `\u001b[4m\u001b[1m${x}\u001b[0m`;
9
- const bold = x => `\u001b[1m${x}\u001b[0m`;
10
-
11
- let cache = '';
12
- const print = str => {
13
- cache += str;
14
-
15
- if (str === '\n') {
16
- process.stdout.write(cache);
17
- cache = '';
18
- }
19
- };
20
-
21
8
  const { wasm } = await compile(source);
22
9
 
23
- if (!raw && typeof Deno === 'undefined') fs.writeFileSync('out.wasm', Buffer.from(wasm));
24
-
25
- if (!process.argv.includes('-no-run')) {
26
- console.log(`\n\n${underline('output')}`);
27
- const t2 = performance.now();
28
-
29
- exports.main();
30
- print('\n');
31
-
32
- if (!raw) console.log(bold(`\n\nexecuted in ${(performance.now() - t2).toFixed(2)}ms`));
33
- }
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()`;
34
12
 
35
- if (!raw) console.log(bold(`wasm binary is ${wasm.byteLength} bytes`));
36
- if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
13
+ console.log(out);
14
+ 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;
package/t.js ADDED
@@ -0,0 +1,31 @@
1
+ let assert = Object();
2
+
3
+ assert._isSameValue = function (a, b) {
4
+ if (a === b) {
5
+ // Handle +/-0 vs. -/+0
6
+ return a !== 0 || 1 / a === 1 / b;
7
+ }
8
+
9
+ // Handle NaN vs. NaN
10
+ return a !== a && b !== b;
11
+
12
+ // return a === b;
13
+ };
14
+
15
+ assert.sameValue = function (actual, expected) {
16
+ /* try {
17
+ if (assert._isSameValue(actual, expected)) {
18
+ return;
19
+ }
20
+ } catch (error) {
21
+ throw new Test262Error('_isSameValue operation threw');
22
+ } */
23
+
24
+ if (assert._isSameValue(actual, expected)) {
25
+ return;
26
+ }
27
+
28
+ throw new Test262Error('assert.sameValue failed');
29
+ };
30
+
31
+ assert.sameValue("lego".charAt(), "l");