porffor 0.0.0-745e995 → 0.0.0-758fed5

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
 
@@ -99,18 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
99
101
  - intrinsic functions (see below)
100
102
  - inlining wasm via ``asm`...``\` "macro"
101
103
 
102
- ## soon todo
104
+ ## todo
105
+ no particular order and no guarentees, just what could happen soon™
106
+
103
107
  - arrays
104
108
  - member setting (`arr[0] = 2`)
105
109
  - more of `Array` prototype
106
110
  - arrays/strings inside arrays
111
+ - destructuring
112
+ - for .. of
107
113
  - strings
108
114
  - member setting
109
- - equality
115
+ - objects
116
+ - basic object expressions (eg `{}`, `{ a: 0 }`)
117
+ - wasm
118
+ - *basic* wasm engine (interpreter) in js
119
+ - regex
120
+ - *basic* regex engine (in wasm compiled aot or js interpreter?)
110
121
  - more math operators (`**`, etc)
111
122
  - `do { ... } while (...)`
123
+ - rewrite `console.log` to work with strings/arrays
112
124
  - exceptions
113
- - `try { } finally {}`
125
+ - rewrite to use actual strings (optional?)
126
+ - `try { } finally { }`
114
127
  - rethrowing inside catch
115
128
  - optimizations
116
129
  - rewrite local indexes per func for smallest local header and remove unused idxs
@@ -118,6 +131,11 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
118
131
  - remove const ifs (`if (true)`, etc)
119
132
  - use data segments for initing arrays
120
133
 
134
+ ## porfformance
135
+ *for the things it supports*, porffor is blazingly faster compared to most interpreters, and engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be.
136
+
137
+ ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
138
+
121
139
  ## test262
122
140
  porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
123
141
 
@@ -135,6 +153,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
135
153
  - `i64.extend_i32_s`, `i32.wrap_i64` -> ``
136
154
  - `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
137
155
  - `return`, `end` -> `end`
156
+ - change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
138
157
  - remove some redundant sets/gets
139
158
  - remove unneeded single just used vars
140
159
  - remove unneeded blocks (no `br`s inside)
@@ -190,11 +209,12 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
190
209
  - `-no-run` to not run wasm output, just compile
191
210
  - `-opt-log` to log some opts
192
211
  - `-code-log` to log some codegen (you probably want `-funcs`)
193
- - `-funcs` to log funcs (internal representations)
212
+ - `-funcs` to log funcs
194
213
  - `-opt-funcs` to log funcs after opt
195
214
  - `-sections` to log sections as hex
196
215
  - `-opt-no-inline` to not inline any funcs
197
- - `-tail-call` to enable tail calls (not widely implemented)
216
+ - `-tail-call` to enable tail calls (experimental + not widely implemented)
217
+ - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
198
218
 
199
219
  ## vscode extension
200
220
  there is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
@@ -35,7 +35,14 @@ const debug = str => {
35
35
  };
36
36
 
37
37
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
38
+ class TodoError extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = 'TodoError';
42
+ }
43
+ }
44
+
45
+ throw new TodoError(`todo: ${msg}`);
39
46
 
40
47
  const code = [];
41
48
 
@@ -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,8 +335,6 @@ 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
338
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
331
339
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
332
340
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -455,38 +463,37 @@ const concatStrings = (scope, left, right, global, name, assign) => {
455
463
  ];
456
464
  };
457
465
 
458
- const compareStrings = (scope, left, right, global, name) => {
466
+ const compareStrings = (scope, left, right) => {
459
467
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
460
468
  // todo: convert left and right to strings if not
461
469
  // todo: optimize by looking up names in arrays and using that if exists?
462
470
  // todo: optimize this if using literals/known lengths?
463
471
 
464
- scope.memory = true;
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);
465
476
 
466
- const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
467
- const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
468
- const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
469
- const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
470
-
471
- // alloc/assign array
472
- const [ , pointer ] = makeArray(scope, {
473
- rawElements: new Array(0)
474
- }, global, name, true, 'i16');
477
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
478
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
475
479
 
476
480
  return [
477
481
  // setup left
478
482
  ...left,
479
483
  Opcodes.i32_to_u,
480
- [ Opcodes.local_set, leftPointer ],
484
+ [ Opcodes.local_tee, leftPointer ],
481
485
 
482
486
  // setup right
483
487
  ...right,
484
488
  Opcodes.i32_to_u,
485
- [ Opcodes.local_set, rightPointer ],
489
+ [ Opcodes.local_tee, rightPointer ],
486
490
 
487
- // calculate length
488
- ...number(0, Valtype.i32), // base 0 for store later
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 ],
489
495
 
496
+ // get lengths
490
497
  [ Opcodes.local_get, leftPointer ],
491
498
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
492
499
  [ Opcodes.local_tee, leftLength ],
@@ -495,119 +502,164 @@ const compareStrings = (scope, left, right, global, name) => {
495
502
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
496
503
  [ Opcodes.local_tee, rightLength ],
497
504
 
498
- [ Opcodes.i32_add ],
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 ],
499
511
 
500
- // store length
501
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
512
+ // no fast path for length = 0 as it would probably be slower for most of the time?
502
513
 
503
- // copy left
504
- // dst = out pointer + length size
505
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
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 ],
506
520
 
507
- // src = left pointer + length size
521
+ // iterate over each char and check if eq
522
+ [ Opcodes.loop, Blocktype.void ],
523
+
524
+ // fetch left
525
+ [ Opcodes.local_get, index ],
508
526
  [ Opcodes.local_get, leftPointer ],
509
- ...number(ValtypeSize.i32, Valtype.i32),
510
527
  [ Opcodes.i32_add ],
528
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
511
529
 
512
- // size = PageSize - length size. we do not need to calculate length as init value
513
- ...number(pageSize - ValtypeSize.i32, Valtype.i32),
514
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
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) ],
515
535
 
516
- // copy right
517
- // dst = out pointer + length size + left length * i16 size
518
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
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 ],
519
542
 
520
- [ Opcodes.local_get, leftLength ],
543
+ // index += sizeof i16 (2)
544
+ [ Opcodes.local_get, index ],
521
545
  ...number(ValtypeSize.i16, Valtype.i32),
522
- [ Opcodes.i32_mul ],
523
546
  [ Opcodes.i32_add ],
547
+ [ Opcodes.local_tee, index ],
524
548
 
525
- // src = right pointer + length size
526
- [ Opcodes.local_get, rightPointer ],
527
- ...number(ValtypeSize.i32, Valtype.i32),
528
- [ Opcodes.i32_add ],
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 ],
529
554
 
530
- // size = right length * i16 size
531
- [ Opcodes.local_get, rightLength ],
532
- ...number(ValtypeSize.i16, Valtype.i32),
533
- [ Opcodes.i32_mul ],
555
+ // no failed checks, so true!
556
+ ...number(1, Valtype.i32),
534
557
 
535
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
558
+ // pointers match, so true
559
+ [ Opcodes.else ],
560
+ ...number(1, Valtype.i32),
561
+ [ Opcodes.end ],
536
562
 
537
- // return new string (page)
538
- ...number(pointer)
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
539
566
  ];
540
567
  };
541
568
 
542
- const falsy = (scope, wasm, type) => {
569
+ const truthy = (scope, wasm, type) => {
543
570
  // arrays are always truthy
544
571
  if (type === TYPES._array) return [
545
572
  ...wasm,
546
573
  [ Opcodes.drop ],
547
- number(0)
574
+ ...number(1)
548
575
  ];
549
576
 
550
577
  if (type === TYPES.string) {
551
- // if "" (length = 0)
578
+ // if not "" (length = 0)
552
579
  return [
553
580
  // pointer
554
581
  ...wasm,
582
+ Opcodes.i32_to_u,
555
583
 
556
584
  // get length
557
585
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
558
586
 
559
- // if length == 0
560
- [ Opcodes.i32_eqz ],
587
+ // if length != 0
588
+ /* [ Opcodes.i32_eqz ],
589
+ [ Opcodes.i32_eqz ], */
561
590
  Opcodes.i32_from_u
562
591
  ]
563
592
  }
564
593
 
565
- // if = 0
594
+ // if != 0
566
595
  return [
567
596
  ...wasm,
568
597
 
569
- ...Opcodes.eqz,
570
- Opcodes.i32_from_u
598
+ /* Opcodes.eqz,
599
+ [ Opcodes.i32_eqz ],
600
+ Opcodes.i32_from */
571
601
  ];
572
602
  };
573
603
 
574
- const truthy = (scope, wasm, type) => {
604
+ const falsy = (scope, wasm, type) => {
575
605
  // arrays are always truthy
576
606
  if (type === TYPES._array) return [
577
607
  ...wasm,
578
608
  [ Opcodes.drop ],
579
- number(1)
609
+ ...number(0)
580
610
  ];
581
611
 
582
612
  if (type === TYPES.string) {
583
- // if not "" (length = 0)
613
+ // if "" (length = 0)
584
614
  return [
585
615
  // pointer
586
616
  ...wasm,
617
+ Opcodes.i32_to_u,
587
618
 
588
619
  // get length
589
620
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
590
621
 
591
- // if length != 0
592
- /* [ Opcodes.i32_eqz ],
593
- [ Opcodes.i32_eqz ], */
622
+ // if length == 0
623
+ [ Opcodes.i32_eqz ],
594
624
  Opcodes.i32_from_u
595
625
  ]
596
626
  }
597
627
 
598
- // if != 0
628
+ // if = 0
599
629
  return [
600
630
  ...wasm,
601
631
 
602
- /* Opcodes.eqz,
603
- [ Opcodes.i32_eqz ],
604
- 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)
605
657
  ];
606
658
  };
607
659
 
608
660
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
609
661
  if (op === '||' || op === '&&' || op === '??') {
610
- return performLogicOp(scope, op, left, right);
662
+ return performLogicOp(scope, op, left, right, leftType, rightType);
611
663
  }
612
664
 
613
665
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
@@ -640,9 +692,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
640
692
  // todo: convert string to number if string and number/bool
641
693
  // todo: string (>|>=|<|<=) string
642
694
 
643
- // string equality
644
- if (op === '===') {
695
+ // string comparison
696
+ if (op === '===' || op === '==') {
697
+ return compareStrings(scope, left, right);
698
+ }
645
699
 
700
+ if (op === '!==' || op === '!=') {
701
+ return [
702
+ ...compareStrings(scope, left, right),
703
+ [ Opcodes.i32_eqz ]
704
+ ];
646
705
  }
647
706
  }
648
707
 
@@ -741,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
741
800
  };
742
801
 
743
802
  const generateLogicExp = (scope, decl) => {
744
- 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));
745
804
  };
746
805
 
747
806
  const TYPES = {
@@ -789,6 +848,7 @@ const getType = (scope, _name) => {
789
848
 
790
849
  const getNodeType = (scope, node) => {
791
850
  if (node.type === 'Literal') {
851
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
792
852
  return TYPES[typeof node.value];
793
853
  }
794
854
 
@@ -904,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
904
964
  const countLeftover = wasm => {
905
965
  let count = 0, depth = 0;
906
966
 
907
- for (const inst of wasm) {
967
+ for (let i = 0; i < wasm.length; i++) {
968
+ const inst = wasm[i];
908
969
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
909
970
  if (inst[0] === Opcodes.if) count--;
910
971
  if (inst[1] !== Blocktype.void) count++;
@@ -1008,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
1008
1069
  }
1009
1070
 
1010
1071
  let out = [];
1011
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1072
+ let protoFunc, protoName, baseType, baseName;
1012
1073
  // ident.func()
1013
1074
  if (name && name.startsWith('__')) {
1014
1075
  const spl = name.slice(2).split('_');
@@ -1031,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
1031
1092
 
1032
1093
  out = generate(scope, decl.callee.object);
1033
1094
  out.push([ Opcodes.drop ]);
1095
+
1096
+ baseName = [...arrays.keys()].pop();
1034
1097
  }
1035
1098
 
1036
1099
  if (protoFunc) {
1037
- scope.memory = true;
1038
-
1039
1100
  let pointer = arrays.get(baseName);
1040
1101
 
1041
1102
  if (pointer == null) {
@@ -1043,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
1043
1104
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1044
1105
 
1045
1106
  // register array
1046
- const [ , pointer ] = makeArray(scope, {
1107
+ 0, [ , pointer ] = makeArray(scope, {
1047
1108
  rawElements: new Array(0)
1048
1109
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1049
1110
 
@@ -1141,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
1141
1202
  args = args.slice(0, func.params.length);
1142
1203
  }
1143
1204
 
1144
- if (func && func.memory) scope.memory = true;
1145
1205
  if (func && func.throws) scope.throws = true;
1146
1206
 
1147
1207
  for (const arg of args) {
@@ -1157,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
1157
1217
  // hack: basically treat this as a normal call for builtins for now
1158
1218
  const name = mapName(decl.callee.name);
1159
1219
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1160
- 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)})`);
1161
1221
 
1162
1222
  return generateCall(scope, decl, _global, _name);
1163
1223
  };
@@ -1265,8 +1325,6 @@ const generateAssign = (scope, decl) => {
1265
1325
  const name = decl.left.object.name;
1266
1326
  const pointer = arrays.get(name);
1267
1327
 
1268
- scope.memory = true;
1269
-
1270
1328
  const aotPointer = pointer != null;
1271
1329
 
1272
1330
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1317,8 +1375,27 @@ const generateAssign = (scope, decl) => {
1317
1375
  ];
1318
1376
  }
1319
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
+
1320
1397
  return [
1321
- ...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),
1322
1399
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1323
1400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1324
1401
  ];
@@ -1345,13 +1422,14 @@ const generateUnary = (scope, decl) => {
1345
1422
 
1346
1423
  case '!':
1347
1424
  // !=
1348
- return falsy(scope, generate(scope, decl.argument));
1425
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1349
1426
 
1350
1427
  case '~':
1428
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1351
1429
  return [
1352
1430
  ...generate(scope, decl.argument),
1353
1431
  Opcodes.i32_to,
1354
- [ Opcodes.i32_const, signedLEB128(-1) ],
1432
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1355
1433
  [ Opcodes.i32_xor ],
1356
1434
  Opcodes.i32_from
1357
1435
  ];
@@ -1522,9 +1600,28 @@ const generateWhile = (scope, decl) => {
1522
1600
  return out;
1523
1601
  };
1524
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
+
1525
1622
  const getNearestLoop = () => {
1526
1623
  for (let i = depth.length - 1; i >= 0; i--) {
1527
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1624
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1528
1625
  }
1529
1626
 
1530
1627
  return -1;
@@ -1614,7 +1711,16 @@ const allocPage = reason => {
1614
1711
  let ind = pages.size;
1615
1712
  pages.set(reason, ind);
1616
1713
 
1617
- 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}`);
1618
1724
 
1619
1725
  return ind;
1620
1726
  };
@@ -1676,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1676
1782
  // local value as pointer
1677
1783
  out.push(...number(pointer));
1678
1784
 
1679
- scope.memory = true;
1680
-
1681
1785
  return [ out, pointer ];
1682
1786
  };
1683
1787
 
@@ -1696,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1696
1800
  const name = decl.object.name;
1697
1801
  const pointer = arrays.get(name);
1698
1802
 
1699
- scope.memory = true;
1700
-
1701
1803
  const aotPointer = pointer != null;
1702
1804
 
1703
1805
  return [
@@ -1717,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1717
1819
  const name = decl.object.name;
1718
1820
  const pointer = arrays.get(name);
1719
1821
 
1720
- scope.memory = true;
1721
-
1722
1822
  const aotPointer = pointer != null;
1723
1823
 
1724
1824
  if (type === TYPES._array) {
@@ -1828,6 +1928,7 @@ const generateFunc = (scope, decl) => {
1828
1928
  locals: {},
1829
1929
  localInd: 0,
1830
1930
  returns: [ valtypeBinary ],
1931
+ returnType: null,
1831
1932
  memory: false,
1832
1933
  throws: false,
1833
1934
  name
@@ -1854,7 +1955,6 @@ const generateFunc = (scope, decl) => {
1854
1955
  returns: innerScope.returns,
1855
1956
  returnType: innerScope.returnType,
1856
1957
  locals: innerScope.locals,
1857
- memory: innerScope.memory,
1858
1958
  throws: innerScope.throws,
1859
1959
  index: currentFuncIndex++
1860
1960
  };
@@ -1869,6 +1969,8 @@ const generateFunc = (scope, decl) => {
1869
1969
 
1870
1970
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1871
1971
  wasm.push(...number(0), [ Opcodes.return ]);
1972
+
1973
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1872
1974
  }
1873
1975
 
1874
1976
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1879,9 +1981,7 @@ const generateFunc = (scope, decl) => {
1879
1981
  if (local.type === Valtype.v128) {
1880
1982
  vecParams++;
1881
1983
 
1882
- /* func.memory = true; // mark func as using memory
1883
-
1884
- wasm.unshift( // add v128 load for param
1984
+ /* wasm.unshift( // add v128 load for param
1885
1985
  [ Opcodes.i32_const, 0 ],
1886
1986
  [ ...Opcodes.v128_load, 0, i * 16 ],
1887
1987
  [ Opcodes.local_set, local.idx ]
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
42
42
  out += ` ${read_ieee754_binary64(inst.slice(1))}`;
43
43
  } else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
44
44
  out += ` ${read_signedLEB128(inst.slice(1))}`;
45
- } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
46
- out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`
45
+ } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
46
+ out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
47
47
  } else for (const operand of inst.slice(1)) {
48
48
  if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
49
49
  if (operand === Blocktype.void) continue;
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
22
22
  type
23
23
  ];
24
24
 
25
+ // todo: this only works with integers within 32 bit range
25
26
  export const signedLEB128 = n => {
26
- // todo: this only works with integers within 32 bit range
27
+ n |= 0;
27
28
 
28
29
  // just input for small numbers (for perf as common)
29
30
  if (n >= 0 && n <= 63) return [ n ];
30
31
  if (n >= -64 && n <= 0) return [ 128 + n ];
31
32
 
32
33
  const buffer = [];
33
- n |= 0;
34
34
 
35
35
  while (true) {
36
36
  let byte = n & 0x7f;
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
50
50
  };
51
51
 
52
52
  export const unsignedLEB128 = n => {
53
+ n |= 0;
54
+
53
55
  // just input for small numbers (for perf as common)
54
56
  if (n >= 0 && n <= 127) return [ n ];
55
57
 
package/compiler/index.js CHANGED
@@ -14,7 +14,8 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
14
14
  const areaColors = {
15
15
  codegen: [ 20, 80, 250 ],
16
16
  opt: [ 250, 20, 80 ],
17
- sections: [ 20, 250, 80 ]
17
+ sections: [ 20, 250, 80 ],
18
+ alloc: [ 250, 250, 20 ]
18
19
  };
19
20
 
20
21
  globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
@@ -38,6 +39,7 @@ const logFuncs = (funcs, globals, exceptions) => {
38
39
  export default (code, flags) => {
39
40
  globalThis.optLog = process.argv.includes('-opt-log');
40
41
  globalThis.codeLog = process.argv.includes('-code-log');
42
+ globalThis.allocLog = process.argv.includes('-alloc-log');
41
43
 
42
44
  for (const x in BuiltinPreludes) {
43
45
  if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
@@ -63,5 +65,12 @@ export default (code, flags) => {
63
65
  const sections = produceSections(funcs, globals, tags, pages, flags);
64
66
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
65
67
 
68
+ if (allocLog) {
69
+ const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
70
+ const bytes = wasmPages * 65536;
71
+ log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
72
+ // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
73
+ }
74
+
66
75
  return { wasm: sections, funcs, globals, tags, exceptions, pages };
67
76
  };
package/compiler/opt.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
+ import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
3
4
 
4
5
  // deno compat
5
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
20
21
  if (optLevel === 0) return;
21
22
 
22
23
  const tailCall = process.argv.includes('-tail-call');
23
- if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
24
+ if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
24
25
 
25
26
  if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
26
27
  // inline pass (very WIP)
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
95
96
  }
96
97
 
97
98
  if (t.index > c.index) t.index--; // adjust index if after removed func
98
- if (c.memory) t.memory = true;
99
99
  }
100
100
 
101
101
  funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
213
213
  // i32.const 0
214
214
  // drop
215
215
  // -->
216
- // <nothing>>
216
+ // <nothing>
217
217
 
218
- wasm.splice(i - 1, 2); // remove this inst
218
+ wasm.splice(i - 1, 2); // remove these inst
219
219
  i -= 2;
220
220
  continue;
221
221
  }
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
259
259
  continue;
260
260
  }
261
261
 
262
+ if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
263
+ // change const and immediate i32 convert to i32 const
264
+ // f64.const 0
265
+ // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
266
+ // -->
267
+ // i32.const 0
268
+
269
+ wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
270
+
271
+ wasm.splice(i, 1); // remove this inst
272
+ i--;
273
+ if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
274
+ continue;
275
+ }
276
+
277
+ if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
278
+ // change i32 const and immediate convert to const (opposite way of previous)
279
+ // i32.const 0
280
+ // f64.convert_i32_s || f64.convert_i32_u
281
+ // -->
282
+ // f64.const 0
283
+
284
+ wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
285
+
286
+ wasm.splice(i, 1); // remove this inst
287
+ i--;
288
+ if (optLog) log('opt', `converted i32 const -> convert into const`);
289
+ continue;
290
+ }
291
+
262
292
  if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
293
  // replace call, return with tail calls (return_call)
264
294
  // call X
@@ -25,7 +25,6 @@ export const PrototypeFuncs = function() {
25
25
 
26
26
  this[TYPES._array] = {
27
27
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
28
- // todo: out of bounds (>) properly
29
28
  at: (pointer, length, wIndex, iTmp) => [
30
29
  ...wIndex,
31
30
  Opcodes.i32_to,
@@ -147,7 +146,6 @@ export const PrototypeFuncs = function() {
147
146
  this[TYPES._array].push.noArgRetLength = true;
148
147
 
149
148
  this[TYPES.string] = {
150
- // todo: out of bounds properly
151
149
  at: (pointer, length, wIndex, iTmp, arrayShell) => {
152
150
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
153
151
 
@@ -157,9 +155,9 @@ export const PrototypeFuncs = function() {
157
155
  [ Opcodes.drop ],
158
156
 
159
157
  ...number(0, Valtype.i32), // base 0 for store later
160
- Opcodes.i32_to_u,
161
158
 
162
159
  ...wIndex,
160
+ Opcodes.i32_to_u,
163
161
  [ Opcodes.local_tee, iTmp ],
164
162
 
165
163
  // if index < 0: access index + array length
@@ -265,7 +263,7 @@ export const PrototypeFuncs = function() {
265
263
  },
266
264
  };
267
265
 
268
- this[TYPES.string].at.local = valtypeBinary;
266
+ this[TYPES.string].at.local = Valtype.i32;
269
267
  this[TYPES.string].at.returnType = TYPES.string;
270
268
  this[TYPES.string].charAt.returnType = TYPES.string;
271
269
  this[TYPES.string].charCodeAt.local = Valtype.i32;
@@ -8,11 +8,26 @@ const createSection = (type, data) => [
8
8
  ...encodeVector(data)
9
9
  ];
10
10
 
11
+ const customSection = (name, data) => [
12
+ Section.custom,
13
+ ...encodeVector([...encodeString(name), ...data])
14
+ ];
15
+
16
+ const chHint = (topTier, baselineTier, strategy) => {
17
+ // 1 byte of 4 2 bit components: spare, top tier, baseline tier, compilation strategy
18
+ // tiers: 0x00 = default, 0x01 = baseline (liftoff), 0x02 = optimized (turbofan)
19
+ // strategy: 0x00 = default, 0x01 = lazy, 0x02 = eager, 0x03 = lazy baseline, eager top tier
20
+ return (strategy | (baselineTier << 2) | (topTier << 4));
21
+ };
22
+
11
23
  export default (funcs, globals, tags, pages, flags) => {
12
24
  const types = [], typeCache = {};
13
25
 
14
26
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
15
27
 
28
+ const compileHints = process.argv.includes('-compile-hints');
29
+ if (compileHints) log('sections', 'warning: compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
30
+
16
31
  const getType = (params, returns) => {
17
32
  const hash = `${params.join(',')}_${returns.join(',')}`;
18
33
  if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
@@ -36,7 +51,7 @@ export default (funcs, globals, tags, pages, flags) => {
36
51
  // tree shake imports
37
52
  for (const f of funcs) {
38
53
  for (const inst of f.wasm) {
39
- if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
54
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
40
55
  const idx = inst[1];
41
56
  const func = importedFuncs[idx];
42
57
 
@@ -51,10 +66,11 @@ export default (funcs, globals, tags, pages, flags) => {
51
66
  // fix call indexes for non-imports
52
67
  const delta = importedFuncs.length - importFuncs.length;
53
68
  for (const f of funcs) {
69
+ f.originalIndex = f.index;
54
70
  f.index -= delta;
55
71
 
56
72
  for (const inst of f.wasm) {
57
- if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
73
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
58
74
  inst[1] -= delta;
59
75
  }
60
76
  }
@@ -73,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
73
89
  encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
74
90
  );
75
91
 
92
+ // compilation hints section - unspec v8 only
93
+ // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
94
+ const chSection = !compileHints ? [] : customSection(
95
+ 'compilationHints',
96
+ // for now just do everything as optimise eager
97
+ encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
98
+ );
99
+
76
100
  const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
77
101
  Section.global,
78
102
  encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
@@ -145,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
145
169
  ...typeSection,
146
170
  ...importSection,
147
171
  ...funcSection,
172
+ ...chSection,
148
173
  ...memorySection,
149
174
  ...tagSection,
150
175
  ...globalSection,
package/compiler/wrap.js CHANGED
@@ -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-745e995",
4
+ "version": "0.0.0-758fed5",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/index.js CHANGED
@@ -3,8 +3,26 @@
3
3
  import compile from '../compiler/wrap.js';
4
4
  import fs from 'node:fs';
5
5
 
6
+ if (process.argv.includes('-compile-hints')) {
7
+ const v8 = await import('node:v8');
8
+ v8.setFlagsFromString(`--experimental-wasm-compilation-hints`);
9
+
10
+ // see also these flags:
11
+ // --experimental-wasm-branch-hinting
12
+ // --experimental-wasm-extended-const
13
+ // --experimental-wasm-inlining (?)
14
+ // --experimental-wasm-js-inlining (?)
15
+ // --experimental-wasm-return-call (on by default)
16
+ }
17
+
6
18
  const file = process.argv.slice(2).find(x => x[0] !== '-');
7
19
  if (!file) {
20
+ if (process.argv.includes('-v')) {
21
+ // just print version
22
+ console.log((await import('./version.js')).default);
23
+ process.exit(0);
24
+ }
25
+
8
26
  // run repl if no file given
9
27
  await import('./repl.js');
10
28
 
@@ -30,5 +48,6 @@ try {
30
48
  exports.main();
31
49
  if (cache) process.stdout.write(cache);
32
50
  } catch (e) {
51
+ if (cache) process.stdout.write(cache);
33
52
  console.error(`${e.constructor.name}: ${e.message}`);
34
53
  }
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,13 +41,15 @@ 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
54
  if (run && exports.$) {
60
55
  lastMemory = exports.$;
@@ -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 ') || source.includes('function ')) prev = toRun + ';\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");