porffor 0.0.0-579ef36 → 0.0.0-61de729

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
 
@@ -295,10 +305,10 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
305
  return idx;
296
306
  };
297
307
 
298
- const performLogicOp = (scope, op, left, right) => {
308
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
309
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
310
+ '||': falsy,
311
+ '&&': truthy,
302
312
  // todo: ??
303
313
  };
304
314
 
@@ -310,7 +320,8 @@ const performLogicOp = (scope, op, left, right) => {
310
320
  return [
311
321
  ...left,
312
322
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
323
+ ...checks[op](scope, [], leftType),
324
+ Opcodes.i32_to,
314
325
  [ Opcodes.if, valtypeBinary ],
315
326
  ...right,
316
327
  [ Opcodes.else ],
@@ -327,13 +338,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
327
338
 
328
339
  scope.memory = true;
329
340
 
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
341
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
342
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
343
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
344
 
336
345
  if (assign) {
346
+ const pointer = arrays.get(name ?? '$undeclared');
347
+
337
348
  return [
338
349
  // setup right
339
350
  ...right,
@@ -384,15 +395,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
395
 
385
396
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
397
 
387
- const newOut = makeArray(scope, {
398
+ // alloc/assign array
399
+ const [ , pointer ] = makeArray(scope, {
388
400
  rawElements: new Array(0)
389
401
  }, global, name, true, 'i16');
390
402
 
391
403
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
404
  // setup left
397
405
  ...left,
398
406
  Opcodes.i32_to_u,
@@ -458,75 +466,199 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
466
  ];
459
467
  };
460
468
 
461
- const falsy = (scope, wasm, type) => {
469
+ const compareStrings = (scope, left, right) => {
470
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
471
+ // todo: convert left and right to strings if not
472
+ // todo: optimize by looking up names in arrays and using that if exists?
473
+ // todo: optimize this if using literals/known lengths?
474
+
475
+ scope.memory = true;
476
+
477
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
478
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
479
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
480
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
481
+
482
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
483
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
484
+
485
+ return [
486
+ // setup left
487
+ ...left,
488
+ Opcodes.i32_to_u,
489
+ [ Opcodes.local_tee, leftPointer ],
490
+
491
+ // setup right
492
+ ...right,
493
+ Opcodes.i32_to_u,
494
+ [ Opcodes.local_tee, rightPointer ],
495
+
496
+ // fast path: check leftPointer == rightPointer
497
+ // use if (block) for everything after to "return" a value early
498
+ [ Opcodes.i32_ne ],
499
+ [ Opcodes.if, Valtype.i32 ],
500
+
501
+ // get lengths
502
+ [ Opcodes.local_get, leftPointer ],
503
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
504
+ [ Opcodes.local_tee, leftLength ],
505
+
506
+ [ Opcodes.local_get, rightPointer ],
507
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
508
+ [ Opcodes.local_tee, rightLength ],
509
+
510
+ // fast path: check leftLength != rightLength
511
+ [ Opcodes.i32_ne ],
512
+ [ Opcodes.if, Blocktype.void ],
513
+ ...number(0, Valtype.i32),
514
+ [ Opcodes.br, 1 ],
515
+ [ Opcodes.end ],
516
+
517
+ // no fast path for length = 0 as it would probably be slower for most of the time?
518
+
519
+ // setup index end as length * sizeof i16 (2)
520
+ // we do this instead of having to do mul/div each iter for perf™
521
+ [ Opcodes.local_get, leftLength ],
522
+ ...number(ValtypeSize.i16, Valtype.i32),
523
+ [ Opcodes.i32_mul ],
524
+ [ Opcodes.local_set, indexEnd ],
525
+
526
+ // iterate over each char and check if eq
527
+ [ Opcodes.loop, Blocktype.void ],
528
+
529
+ // fetch left
530
+ [ Opcodes.local_get, index ],
531
+ [ Opcodes.local_get, leftPointer ],
532
+ [ Opcodes.i32_add ],
533
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
534
+
535
+ // fetch right
536
+ [ Opcodes.local_get, index ],
537
+ [ Opcodes.local_get, rightPointer ],
538
+ [ Opcodes.i32_add ],
539
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
540
+
541
+ // not equal, "return" false
542
+ [ Opcodes.i32_ne ],
543
+ [ Opcodes.if, Blocktype.void ],
544
+ ...number(0, Valtype.i32),
545
+ [ Opcodes.br, 2 ],
546
+ [ Opcodes.end ],
547
+
548
+ // index += sizeof i16 (2)
549
+ [ Opcodes.local_get, index ],
550
+ ...number(ValtypeSize.i16, Valtype.i32),
551
+ [ Opcodes.i32_add ],
552
+ [ Opcodes.local_tee, index ],
553
+
554
+ // if index != index end (length * sizeof 16), loop
555
+ [ Opcodes.local_get, indexEnd ],
556
+ [ Opcodes.i32_ne ],
557
+ [ Opcodes.br_if, 0 ],
558
+ [ Opcodes.end ],
559
+
560
+ // no failed checks, so true!
561
+ ...number(1, Valtype.i32),
562
+
563
+ // pointers match, so true
564
+ [ Opcodes.else ],
565
+ ...number(1, Valtype.i32),
566
+ [ Opcodes.end ],
567
+
568
+ // convert i32 result to valtype
569
+ // do not do as automatically added by binary exp gen for equality ops
570
+ // Opcodes.i32_from_u
571
+ ];
572
+ };
573
+
574
+ const truthy = (scope, wasm, type) => {
462
575
  // arrays are always truthy
463
576
  if (type === TYPES._array) return [
464
577
  ...wasm,
465
- [ Opcodes.drop ],
466
- number(0)
578
+ ...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
579
+ number(1)
467
580
  ];
468
581
 
469
582
  if (type === TYPES.string) {
470
- // if "" (length = 0)
583
+ // if not "" (length = 0)
471
584
  return [
472
585
  // pointer
473
586
  ...wasm,
587
+ Opcodes.i32_to_u,
474
588
 
475
589
  // get length
476
590
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
477
591
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
592
+ // if length != 0
593
+ /* [ Opcodes.i32_eqz ],
594
+ [ Opcodes.i32_eqz ], */
480
595
  Opcodes.i32_from_u
481
596
  ]
482
597
  }
483
598
 
484
- // if = 0
599
+ // if != 0
485
600
  return [
486
601
  ...wasm,
487
602
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
603
+ /* Opcodes.eqz,
604
+ [ Opcodes.i32_eqz ],
605
+ Opcodes.i32_from */
490
606
  ];
491
607
  };
492
608
 
493
- const truthy = (scope, wasm, type) => {
609
+ const falsy = (scope, wasm, type) => {
494
610
  // arrays are always truthy
495
611
  if (type === TYPES._array) return [
496
612
  ...wasm,
497
- [ Opcodes.drop ],
498
- number(1)
613
+ ...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
614
+ number(0)
499
615
  ];
500
616
 
501
617
  if (type === TYPES.string) {
502
- // if not "" (length = 0)
618
+ // if "" (length = 0)
503
619
  return [
504
620
  // pointer
505
621
  ...wasm,
622
+ Opcodes.i32_to_u,
506
623
 
507
624
  // get length
508
625
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
509
626
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
627
+ // if length == 0
628
+ [ Opcodes.i32_eqz ],
513
629
  Opcodes.i32_from_u
514
630
  ]
515
631
  }
516
632
 
517
- // if != 0
633
+ // if = 0
518
634
  return [
519
635
  ...wasm,
520
636
 
521
- /* Opcodes.eqz,
522
- [ Opcodes.i32_eqz ],
523
- Opcodes.i32_from */
637
+ ...Opcodes.eqz,
638
+ Opcodes.i32_from_u
524
639
  ];
525
640
  };
526
641
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
642
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
643
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
644
+ return performLogicOp(scope, op, left, right, leftType, rightType);
645
+ }
646
+
647
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
648
+
649
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
650
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
651
+ return [
652
+ ...left,
653
+ ...right,
654
+
655
+ // drop values
656
+ [ Opcodes.drop ],
657
+ [ Opcodes.drop ],
658
+
659
+ // return false (===)/true (!==)
660
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
661
+ ];
530
662
  }
531
663
 
532
664
  if (leftType === TYPES.string || rightType === TYPES.string) {
@@ -539,8 +671,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
539
671
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
540
672
 
541
673
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
674
+ // todo: convert string to number if string and number/bool
675
+ // todo: string (>|>=|<|<=) string
676
+
677
+ // string comparison
678
+ if (op === '===' || op === '==') {
679
+ return compareStrings(scope, left, right);
680
+ }
681
+
682
+ if (op === '!==' || op === '!=') {
683
+ return [
684
+ ...compareStrings(scope, left, right),
685
+ [ Opcodes.i32_eqz ]
686
+ ];
687
+ }
544
688
  }
545
689
 
546
690
  let ops = operatorOpcode[valtype][op];
@@ -569,13 +713,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
713
  ];
570
714
  };
571
715
 
716
+ let binaryExpDepth = 0;
572
717
  const generateBinaryExp = (scope, decl, _global, _name) => {
718
+ binaryExpDepth++;
719
+
573
720
  const out = [
574
721
  ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
722
  ];
576
723
 
577
724
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
725
 
726
+ binaryExpDepth--;
579
727
  return out;
580
728
  };
581
729
 
@@ -634,7 +782,7 @@ const includeBuiltin = (scope, builtin) => {
634
782
  };
635
783
 
636
784
  const generateLogicExp = (scope, decl) => {
637
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
785
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
638
786
  };
639
787
 
640
788
  const TYPES = {
@@ -682,6 +830,7 @@ const getType = (scope, _name) => {
682
830
 
683
831
  const getNodeType = (scope, node) => {
684
832
  if (node.type === 'Literal') {
833
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
685
834
  return TYPES[typeof node.value];
686
835
  }
687
836
 
@@ -696,7 +845,7 @@ const getNodeType = (scope, node) => {
696
845
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
846
  const name = node.callee.name;
698
847
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
848
+ if (func) return func.returnType;
700
849
 
701
850
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
851
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -721,9 +870,7 @@ const getNodeType = (scope, node) => {
721
870
  protoFunc = prototypeFuncs[baseType]?.[func];
722
871
  }
723
872
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
873
+ if (protoFunc) return protoFunc.returnType;
727
874
  }
728
875
 
729
876
  if (node.type === 'ExpressionStatement') {
@@ -755,9 +902,6 @@ const getNodeType = (scope, node) => {
755
902
 
756
903
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
904
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
905
  };
762
906
 
763
907
  const generateLiteral = (scope, decl, global, name) => {
@@ -792,7 +936,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
936
 
793
937
  return makeArray(scope, {
794
938
  rawElements
795
- }, global, name, false, 'i16');
939
+ }, global, name, false, 'i16')[0];
796
940
 
797
941
  default:
798
942
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -941,10 +1085,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1085
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1086
 
943
1087
  // register array
944
- makeArray(scope, {
1088
+ const [ , pointer ] = makeArray(scope, {
945
1089
  rawElements: new Array(0)
946
1090
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1091
 
949
1092
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1093
 
@@ -980,10 +1123,9 @@ const generateCall = (scope, decl, _global, _name) => {
980
1123
  set: value => arrayUtil.setLength(pointer, value),
981
1124
  setI32: value => arrayUtil.setLengthI32(pointer, value)
982
1125
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
1126
+ return makeArray(scope, {
984
1127
  rawElements: new Array(length)
985
1128
  }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
1129
  }),
988
1130
  [ Opcodes.end ]
989
1131
  ];
@@ -1057,7 +1199,7 @@ const generateNew = (scope, decl, _global, _name) => {
1057
1199
  // hack: basically treat this as a normal call for builtins for now
1058
1200
  const name = mapName(decl.callee.name);
1059
1201
  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)})`);
1202
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1203
 
1062
1204
  return generateCall(scope, decl, _global, _name);
1063
1205
  };
@@ -1245,7 +1387,7 @@ const generateUnary = (scope, decl) => {
1245
1387
 
1246
1388
  case '!':
1247
1389
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1390
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1249
1391
 
1250
1392
  case '~':
1251
1393
  return [
@@ -1289,7 +1431,7 @@ const generateUnary = (scope, decl) => {
1289
1431
  return out;
1290
1432
 
1291
1433
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1434
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1435
 
1294
1436
  // for custom types, just return object
1295
1437
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1422,9 +1564,28 @@ const generateWhile = (scope, decl) => {
1422
1564
  return out;
1423
1565
  };
1424
1566
 
1567
+ const generateForOf = (scope, decl) => {
1568
+ const out = [];
1569
+
1570
+ out.push([ Opcodes.loop, Blocktype.void ]);
1571
+ depth.push('while');
1572
+
1573
+ out.push(...generate(scope, decl.test));
1574
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1575
+ depth.push('if');
1576
+
1577
+ out.push(...generate(scope, decl.body));
1578
+
1579
+ out.push([ Opcodes.br, 1 ]);
1580
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1581
+ depth.pop(); depth.pop();
1582
+
1583
+ return out;
1584
+ };
1585
+
1425
1586
  const getNearestLoop = () => {
1426
1587
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1588
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1589
  }
1429
1590
 
1430
1591
  return -1;
@@ -1514,7 +1675,16 @@ const allocPage = reason => {
1514
1675
  let ind = pages.size;
1515
1676
  pages.set(reason, ind);
1516
1677
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1678
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
1679
+
1680
+ return ind;
1681
+ };
1682
+
1683
+ const freePage = reason => {
1684
+ let ind = pages.get(reason);
1685
+ pages.delete(reason);
1686
+
1687
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
1688
 
1519
1689
  return ind;
1520
1690
  };
@@ -1578,12 +1748,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1578
1748
 
1579
1749
  scope.memory = true;
1580
1750
 
1581
- return out;
1751
+ return [ out, pointer ];
1582
1752
  };
1583
1753
 
1584
1754
  let arrays = new Map();
1585
1755
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
1756
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
1757
  };
1588
1758
 
1589
1759
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1644,10 +1814,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
1814
 
1645
1815
  // string
1646
1816
 
1647
- const newOut = makeArray(scope, {
1817
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
1818
  rawElements: new Array(1)
1649
1819
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
1820
 
1652
1821
  return [
1653
1822
  // setup new/out array
@@ -1753,7 +1922,7 @@ const generateFunc = (scope, decl) => {
1753
1922
  name,
1754
1923
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
1924
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1925
+ returnType: innerScope.returnType,
1757
1926
  locals: innerScope.locals,
1758
1927
  memory: innerScope.memory,
1759
1928
  throws: innerScope.throws,
@@ -1912,10 +2081,9 @@ const internalConstrs = {
1912
2081
 
1913
2082
  // new Array(n)
1914
2083
 
1915
- makeArray(scope, {
2084
+ const [ , pointer ] = makeArray(scope, {
1916
2085
  rawElements: new Array(0)
1917
2086
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2087
 
1920
2088
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2089
 
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-61de729",
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");