porffor 0.0.0-8c0bdaa → 0.0.0-c743344

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,8 @@ 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'`)
80
+ - nullish coalescing operator (`??`)
79
81
 
80
82
  ### built-ins
81
83
 
@@ -106,7 +108,6 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
106
108
  - arrays/strings inside arrays
107
109
  - strings
108
110
  - member setting
109
- - equality
110
111
  - more math operators (`**`, etc)
111
112
  - `do { ... } while (...)`
112
113
  - exceptions
@@ -135,6 +136,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
135
136
  - `i64.extend_i32_s`, `i32.wrap_i64` -> ``
136
137
  - `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
137
138
  - `return`, `end` -> `end`
139
+ - change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
138
140
  - remove some redundant sets/gets
139
141
  - remove unneeded single just used vars
140
142
  - remove unneeded blocks (no `br`s inside)
@@ -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
 
@@ -164,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
164
174
  }
165
175
 
166
176
  if (asm[0] === 'memory') {
167
- scope.memory = true;
168
177
  allocPage('asm instrinsic');
169
178
  // todo: add to store/load offset insts
170
179
  continue;
@@ -278,7 +287,7 @@ const generateReturn = (scope, decl) => {
278
287
  ];
279
288
  }
280
289
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
290
+ scope.returnType = getNodeType(scope, decl.argument);
282
291
 
283
292
  return [
284
293
  ...generate(scope, decl.argument),
@@ -295,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
304
  return idx;
296
305
  };
297
306
 
298
- const performLogicOp = (scope, op, left, right) => {
307
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
308
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
309
+ '||': falsy,
310
+ '&&': truthy,
311
+ '??': nullish
303
312
  };
304
313
 
305
314
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -310,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
310
319
  return [
311
320
  ...left,
312
321
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
322
+ ...checks[op](scope, [], leftType),
323
+ Opcodes.i32_to,
314
324
  [ Opcodes.if, valtypeBinary ],
315
325
  ...right,
316
326
  [ Opcodes.else ],
@@ -325,15 +335,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
335
  // todo: optimize by looking up names in arrays and using that if exists?
326
336
  // todo: optimize this if using literals/known lengths?
327
337
 
328
- scope.memory = true;
329
-
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
338
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
339
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
340
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
341
 
336
342
  if (assign) {
343
+ const pointer = arrays.get(name ?? '$undeclared');
344
+
337
345
  return [
338
346
  // setup right
339
347
  ...right,
@@ -384,15 +392,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
392
 
385
393
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
394
 
387
- const newOut = makeArray(scope, {
395
+ // alloc/assign array
396
+ const [ , pointer ] = makeArray(scope, {
388
397
  rawElements: new Array(0)
389
398
  }, global, name, true, 'i16');
390
399
 
391
400
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
401
  // setup left
397
402
  ...left,
398
403
  Opcodes.i32_to_u,
@@ -458,75 +463,220 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
463
  ];
459
464
  };
460
465
 
461
- const falsy = (scope, wasm, type) => {
466
+ const compareStrings = (scope, left, right) => {
467
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
468
+ // todo: convert left and right to strings if not
469
+ // todo: optimize by looking up names in arrays and using that if exists?
470
+ // todo: optimize this if using literals/known lengths?
471
+
472
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
473
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
474
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
475
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
476
+
477
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
478
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
479
+
480
+ return [
481
+ // setup left
482
+ ...left,
483
+ Opcodes.i32_to_u,
484
+ [ Opcodes.local_tee, leftPointer ],
485
+
486
+ // setup right
487
+ ...right,
488
+ Opcodes.i32_to_u,
489
+ [ Opcodes.local_tee, rightPointer ],
490
+
491
+ // fast path: check leftPointer == rightPointer
492
+ // use if (block) for everything after to "return" a value early
493
+ [ Opcodes.i32_ne ],
494
+ [ Opcodes.if, Valtype.i32 ],
495
+
496
+ // get lengths
497
+ [ Opcodes.local_get, leftPointer ],
498
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
499
+ [ Opcodes.local_tee, leftLength ],
500
+
501
+ [ Opcodes.local_get, rightPointer ],
502
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
503
+ [ Opcodes.local_tee, rightLength ],
504
+
505
+ // fast path: check leftLength != rightLength
506
+ [ Opcodes.i32_ne ],
507
+ [ Opcodes.if, Blocktype.void ],
508
+ ...number(0, Valtype.i32),
509
+ [ Opcodes.br, 1 ],
510
+ [ Opcodes.end ],
511
+
512
+ // no fast path for length = 0 as it would probably be slower for most of the time?
513
+
514
+ // setup index end as length * sizeof i16 (2)
515
+ // we do this instead of having to do mul/div each iter for perf™
516
+ [ Opcodes.local_get, leftLength ],
517
+ ...number(ValtypeSize.i16, Valtype.i32),
518
+ [ Opcodes.i32_mul ],
519
+ [ Opcodes.local_set, indexEnd ],
520
+
521
+ // iterate over each char and check if eq
522
+ [ Opcodes.loop, Blocktype.void ],
523
+
524
+ // fetch left
525
+ [ Opcodes.local_get, index ],
526
+ [ Opcodes.local_get, leftPointer ],
527
+ [ Opcodes.i32_add ],
528
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
529
+
530
+ // fetch right
531
+ [ Opcodes.local_get, index ],
532
+ [ Opcodes.local_get, rightPointer ],
533
+ [ Opcodes.i32_add ],
534
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
535
+
536
+ // not equal, "return" false
537
+ [ Opcodes.i32_ne ],
538
+ [ Opcodes.if, Blocktype.void ],
539
+ ...number(0, Valtype.i32),
540
+ [ Opcodes.br, 2 ],
541
+ [ Opcodes.end ],
542
+
543
+ // index += sizeof i16 (2)
544
+ [ Opcodes.local_get, index ],
545
+ ...number(ValtypeSize.i16, Valtype.i32),
546
+ [ Opcodes.i32_add ],
547
+ [ Opcodes.local_tee, index ],
548
+
549
+ // if index != index end (length * sizeof 16), loop
550
+ [ Opcodes.local_get, indexEnd ],
551
+ [ Opcodes.i32_ne ],
552
+ [ Opcodes.br_if, 0 ],
553
+ [ Opcodes.end ],
554
+
555
+ // no failed checks, so true!
556
+ ...number(1, Valtype.i32),
557
+
558
+ // pointers match, so true
559
+ [ Opcodes.else ],
560
+ ...number(1, Valtype.i32),
561
+ [ Opcodes.end ],
562
+
563
+ // convert i32 result to valtype
564
+ // do not do as automatically added by binary exp gen for equality ops
565
+ // Opcodes.i32_from_u
566
+ ];
567
+ };
568
+
569
+ const truthy = (scope, wasm, type) => {
462
570
  // arrays are always truthy
463
571
  if (type === TYPES._array) return [
464
572
  ...wasm,
465
573
  [ Opcodes.drop ],
466
- number(0)
574
+ ...number(1)
467
575
  ];
468
576
 
469
577
  if (type === TYPES.string) {
470
- // if "" (length = 0)
578
+ // if not "" (length = 0)
471
579
  return [
472
580
  // pointer
473
581
  ...wasm,
582
+ Opcodes.i32_to_u,
474
583
 
475
584
  // get length
476
585
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
477
586
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
587
+ // if length != 0
588
+ /* [ Opcodes.i32_eqz ],
589
+ [ Opcodes.i32_eqz ], */
480
590
  Opcodes.i32_from_u
481
591
  ]
482
592
  }
483
593
 
484
- // if = 0
594
+ // if != 0
485
595
  return [
486
596
  ...wasm,
487
597
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
598
+ /* Opcodes.eqz,
599
+ [ Opcodes.i32_eqz ],
600
+ Opcodes.i32_from */
490
601
  ];
491
602
  };
492
603
 
493
- const truthy = (scope, wasm, type) => {
604
+ const falsy = (scope, wasm, type) => {
494
605
  // arrays are always truthy
495
606
  if (type === TYPES._array) return [
496
607
  ...wasm,
497
608
  [ Opcodes.drop ],
498
- number(1)
609
+ ...number(0)
499
610
  ];
500
611
 
501
612
  if (type === TYPES.string) {
502
- // if not "" (length = 0)
613
+ // if "" (length = 0)
503
614
  return [
504
615
  // pointer
505
616
  ...wasm,
617
+ Opcodes.i32_to_u,
506
618
 
507
619
  // get length
508
620
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
509
621
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
622
+ // if length == 0
623
+ [ Opcodes.i32_eqz ],
513
624
  Opcodes.i32_from_u
514
625
  ]
515
626
  }
516
627
 
517
- // if != 0
628
+ // if = 0
518
629
  return [
519
630
  ...wasm,
520
631
 
521
- /* Opcodes.eqz,
522
- [ Opcodes.i32_eqz ],
523
- 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)
524
657
  ];
525
658
  };
526
659
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
660
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
661
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
662
+ return performLogicOp(scope, op, left, right, leftType, rightType);
663
+ }
664
+
665
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
666
+
667
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
668
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
669
+ return [
670
+ ...left,
671
+ ...right,
672
+
673
+ // drop values
674
+ [ Opcodes.drop ],
675
+ [ Opcodes.drop ],
676
+
677
+ // return false (===)/true (!==)
678
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
679
+ ];
530
680
  }
531
681
 
532
682
  if (leftType === TYPES.string || rightType === TYPES.string) {
@@ -539,8 +689,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
539
689
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
540
690
 
541
691
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
692
+ // todo: convert string to number if string and number/bool
693
+ // todo: string (>|>=|<|<=) string
694
+
695
+ // string comparison
696
+ if (op === '===' || op === '==') {
697
+ return compareStrings(scope, left, right);
698
+ }
699
+
700
+ if (op === '!==' || op === '!=') {
701
+ return [
702
+ ...compareStrings(scope, left, right),
703
+ [ Opcodes.i32_eqz ]
704
+ ];
705
+ }
544
706
  }
545
707
 
546
708
  let ops = operatorOpcode[valtype][op];
@@ -569,13 +731,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
731
  ];
570
732
  };
571
733
 
734
+ let binaryExpDepth = 0;
572
735
  const generateBinaryExp = (scope, decl, _global, _name) => {
736
+ binaryExpDepth++;
737
+
573
738
  const out = [
574
739
  ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
740
  ];
576
741
 
577
742
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
743
 
744
+ binaryExpDepth--;
579
745
  return out;
580
746
  };
581
747
 
@@ -634,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
634
800
  };
635
801
 
636
802
  const generateLogicExp = (scope, decl) => {
637
- 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));
638
804
  };
639
805
 
640
806
  const TYPES = {
@@ -682,6 +848,7 @@ const getType = (scope, _name) => {
682
848
 
683
849
  const getNodeType = (scope, node) => {
684
850
  if (node.type === 'Literal') {
851
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
685
852
  return TYPES[typeof node.value];
686
853
  }
687
854
 
@@ -696,7 +863,7 @@ const getNodeType = (scope, node) => {
696
863
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
864
  const name = node.callee.name;
698
865
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
866
+ if (func) return func.returnType;
700
867
 
701
868
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
869
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -721,9 +888,7 @@ const getNodeType = (scope, node) => {
721
888
  protoFunc = prototypeFuncs[baseType]?.[func];
722
889
  }
723
890
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
891
+ if (protoFunc) return protoFunc.returnType;
727
892
  }
728
893
 
729
894
  if (node.type === 'ExpressionStatement') {
@@ -755,9 +920,6 @@ const getNodeType = (scope, node) => {
755
920
 
756
921
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
922
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
923
  };
762
924
 
763
925
  const generateLiteral = (scope, decl, global, name) => {
@@ -792,7 +954,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
954
 
793
955
  return makeArray(scope, {
794
956
  rawElements
795
- }, global, name, false, 'i16');
957
+ }, global, name, false, 'i16')[0];
796
958
 
797
959
  default:
798
960
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
964
  const countLeftover = wasm => {
803
965
  let count = 0, depth = 0;
804
966
 
805
- for (const inst of wasm) {
967
+ for (let i = 0; i < wasm.length; i++) {
968
+ const inst = wasm[i];
806
969
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
970
  if (inst[0] === Opcodes.if) count--;
808
971
  if (inst[1] !== Blocktype.void) count++;
@@ -906,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
906
1069
  }
907
1070
 
908
1071
  let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1072
+ let protoFunc, protoName, baseType, baseName;
910
1073
  // ident.func()
911
1074
  if (name && name.startsWith('__')) {
912
1075
  const spl = name.slice(2).split('_');
@@ -929,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
929
1092
 
930
1093
  out = generate(scope, decl.callee.object);
931
1094
  out.push([ Opcodes.drop ]);
1095
+
1096
+ baseName = [...arrays.keys()].pop();
932
1097
  }
933
1098
 
934
1099
  if (protoFunc) {
935
- scope.memory = true;
936
-
937
1100
  let pointer = arrays.get(baseName);
938
1101
 
939
1102
  if (pointer == null) {
@@ -941,10 +1104,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1104
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1105
 
943
1106
  // register array
944
- makeArray(scope, {
1107
+ 0, [ , pointer ] = makeArray(scope, {
945
1108
  rawElements: new Array(0)
946
1109
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1110
 
949
1111
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1112
 
@@ -980,10 +1142,9 @@ const generateCall = (scope, decl, _global, _name) => {
980
1142
  set: value => arrayUtil.setLength(pointer, value),
981
1143
  setI32: value => arrayUtil.setLengthI32(pointer, value)
982
1144
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
1145
+ return makeArray(scope, {
984
1146
  rawElements: new Array(length)
985
1147
  }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
1148
  }),
988
1149
  [ Opcodes.end ]
989
1150
  ];
@@ -1041,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
1041
1202
  args = args.slice(0, func.params.length);
1042
1203
  }
1043
1204
 
1044
- if (func && func.memory) scope.memory = true;
1045
1205
  if (func && func.throws) scope.throws = true;
1046
1206
 
1047
1207
  for (const arg of args) {
@@ -1057,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
1057
1217
  // hack: basically treat this as a normal call for builtins for now
1058
1218
  const name = mapName(decl.callee.name);
1059
1219
  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)})`);
1220
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1221
 
1062
1222
  return generateCall(scope, decl, _global, _name);
1063
1223
  };
@@ -1165,8 +1325,6 @@ const generateAssign = (scope, decl) => {
1165
1325
  const name = decl.left.object.name;
1166
1326
  const pointer = arrays.get(name);
1167
1327
 
1168
- scope.memory = true;
1169
-
1170
1328
  const aotPointer = pointer != null;
1171
1329
 
1172
1330
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1217,8 +1375,27 @@ const generateAssign = (scope, decl) => {
1217
1375
  ];
1218
1376
  }
1219
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
+
1220
1397
  return [
1221
- ...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),
1222
1399
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
1400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1224
1401
  ];
@@ -1245,13 +1422,14 @@ const generateUnary = (scope, decl) => {
1245
1422
 
1246
1423
  case '!':
1247
1424
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1425
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1249
1426
 
1250
1427
  case '~':
1428
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
1429
  return [
1252
1430
  ...generate(scope, decl.argument),
1253
1431
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
1432
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
1433
  [ Opcodes.i32_xor ],
1256
1434
  Opcodes.i32_from
1257
1435
  ];
@@ -1289,7 +1467,7 @@ const generateUnary = (scope, decl) => {
1289
1467
  return out;
1290
1468
 
1291
1469
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1470
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1471
 
1294
1472
  // for custom types, just return object
1295
1473
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1422,9 +1600,28 @@ const generateWhile = (scope, decl) => {
1422
1600
  return out;
1423
1601
  };
1424
1602
 
1603
+ const generateForOf = (scope, decl) => {
1604
+ const out = [];
1605
+
1606
+ out.push([ Opcodes.loop, Blocktype.void ]);
1607
+ depth.push('while');
1608
+
1609
+ out.push(...generate(scope, decl.test));
1610
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1611
+ depth.push('if');
1612
+
1613
+ out.push(...generate(scope, decl.body));
1614
+
1615
+ out.push([ Opcodes.br, 1 ]);
1616
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1617
+ depth.pop(); depth.pop();
1618
+
1619
+ return out;
1620
+ };
1621
+
1425
1622
  const getNearestLoop = () => {
1426
1623
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1624
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1625
  }
1429
1626
 
1430
1627
  return -1;
@@ -1514,7 +1711,16 @@ const allocPage = reason => {
1514
1711
  let ind = pages.size;
1515
1712
  pages.set(reason, ind);
1516
1713
 
1517
- 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}`);
1518
1724
 
1519
1725
  return ind;
1520
1726
  };
@@ -1576,14 +1782,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
1782
  // local value as pointer
1577
1783
  out.push(...number(pointer));
1578
1784
 
1579
- scope.memory = true;
1580
-
1581
- return out;
1785
+ return [ out, pointer ];
1582
1786
  };
1583
1787
 
1584
1788
  let arrays = new Map();
1585
1789
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
1790
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
1791
  };
1588
1792
 
1589
1793
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1596,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1596
1800
  const name = decl.object.name;
1597
1801
  const pointer = arrays.get(name);
1598
1802
 
1599
- scope.memory = true;
1600
-
1601
1803
  const aotPointer = pointer != null;
1602
1804
 
1603
1805
  return [
@@ -1617,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1617
1819
  const name = decl.object.name;
1618
1820
  const pointer = arrays.get(name);
1619
1821
 
1620
- scope.memory = true;
1621
-
1622
1822
  const aotPointer = pointer != null;
1623
1823
 
1624
1824
  if (type === TYPES._array) {
@@ -1644,10 +1844,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
1844
 
1645
1845
  // string
1646
1846
 
1647
- const newOut = makeArray(scope, {
1847
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
1848
  rawElements: new Array(1)
1649
1849
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
1850
 
1652
1851
  return [
1653
1852
  // setup new/out array
@@ -1729,6 +1928,7 @@ const generateFunc = (scope, decl) => {
1729
1928
  locals: {},
1730
1929
  localInd: 0,
1731
1930
  returns: [ valtypeBinary ],
1931
+ returnType: null,
1732
1932
  memory: false,
1733
1933
  throws: false,
1734
1934
  name
@@ -1753,9 +1953,8 @@ const generateFunc = (scope, decl) => {
1753
1953
  name,
1754
1954
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
1955
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1956
+ returnType: innerScope.returnType,
1757
1957
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
1958
  throws: innerScope.throws,
1760
1959
  index: currentFuncIndex++
1761
1960
  };
@@ -1770,6 +1969,8 @@ const generateFunc = (scope, decl) => {
1770
1969
 
1771
1970
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1772
1971
  wasm.push(...number(0), [ Opcodes.return ]);
1972
+
1973
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1773
1974
  }
1774
1975
 
1775
1976
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1780,9 +1981,7 @@ const generateFunc = (scope, decl) => {
1780
1981
  if (local.type === Valtype.v128) {
1781
1982
  vecParams++;
1782
1983
 
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
1984
+ /* wasm.unshift( // add v128 load for param
1786
1985
  [ Opcodes.i32_const, 0 ],
1787
1986
  [ ...Opcodes.v128_load, 0, i * 16 ],
1788
1987
  [ Opcodes.local_set, local.idx ]
@@ -1912,10 +2111,9 @@ const internalConstrs = {
1912
2111
 
1913
2112
  // new Array(n)
1914
2113
 
1915
- makeArray(scope, {
2114
+ const [ , pointer ] = makeArray(scope, {
1916
2115
  rawElements: new Array(0)
1917
2116
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2117
 
1920
2118
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2119
 
@@ -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;
@@ -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-8c0bdaa",
4
+ "version": "0.0.0-c743344",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/publish.js CHANGED
@@ -6,6 +6,8 @@ const rev = fs.readFileSync('.git/refs/heads/main', 'utf8').trim().slice(0, 7);
6
6
  const packageJson = fs.readFileSync('package.json', 'utf8');
7
7
  fs.writeFileSync('package.json', packageJson.replace('"0.0.0"', `"0.0.0-${rev}"`));
8
8
 
9
+ console.log(rev, packageJson);
10
+
9
11
  execSync(`npm publish`, { stdio: 'inherit' });
10
12
 
11
- execSync(`git checkout HEAD -- package.json`, { stdio: 'inherit' });
13
+ fs.writeFileSync('package.json', packageJson);
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
 
@@ -17,7 +10,7 @@ globalThis.valtype = 'f64';
17
10
  const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
18
11
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
19
12
 
20
- console.log(`welcome to porffor rev ${rev}`);
13
+ console.log(`welcome to porffor rev ${rev.slice(0, 7)}`);
21
14
  console.log(`info: using opt ${process.argv.find(x => x.startsWith('-O')) ?? '-O1'} and valtype ${valtype}`);
22
15
  console.log();
23
16
 
@@ -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,7 +59,7 @@ 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';
62
+ if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
68
63
  // prev = toRun + ';\n';
69
64
  };
70
65
 
@@ -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");