porffor 0.0.0-828ee15 → 0.0.0-bddcdc3

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
@@ -101,6 +101,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
101
  case 'WhileStatement':
102
102
  return generateWhile(scope, decl);
103
103
 
104
+ /* case 'ForOfStatement':
105
+ return generateForOf(scope, decl); */
106
+
104
107
  case 'BreakStatement':
105
108
  return generateBreak(scope, decl);
106
109
 
@@ -327,8 +330,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
327
330
 
328
331
  scope.memory = true;
329
332
 
330
- const getLocalTmp = name => localTmp(scope, name + binaryExpDepth);
331
-
332
333
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
334
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
335
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -457,6 +458,111 @@ const concatStrings = (scope, left, right, global, name, assign) => {
457
458
  ];
458
459
  };
459
460
 
461
+ const compareStrings = (scope, left, right) => {
462
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
463
+ // todo: convert left and right to strings if not
464
+ // todo: optimize by looking up names in arrays and using that if exists?
465
+ // todo: optimize this if using literals/known lengths?
466
+
467
+ scope.memory = true;
468
+
469
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
470
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
471
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
472
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
473
+
474
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
475
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
476
+
477
+ return [
478
+ // setup left
479
+ ...left,
480
+ Opcodes.i32_to_u,
481
+ [ Opcodes.local_tee, leftPointer ],
482
+
483
+ // setup right
484
+ ...right,
485
+ Opcodes.i32_to_u,
486
+ [ Opcodes.local_tee, rightPointer ],
487
+
488
+ // fast path: check leftPointer == rightPointer
489
+ // use if (block) for everything after to "return" a value early
490
+ [ Opcodes.i32_ne ],
491
+ [ Opcodes.if, Valtype.i32 ],
492
+
493
+ // get lengths
494
+ [ Opcodes.local_get, leftPointer ],
495
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
496
+ [ Opcodes.local_tee, leftLength ],
497
+
498
+ [ Opcodes.local_get, rightPointer ],
499
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
500
+ [ Opcodes.local_tee, rightLength ],
501
+
502
+ // fast path: check leftLength != rightLength
503
+ [ Opcodes.i32_ne ],
504
+ [ Opcodes.if, Blocktype.void ],
505
+ ...number(0, Valtype.i32),
506
+ [ Opcodes.br, 1 ],
507
+ [ Opcodes.end ],
508
+
509
+ // no fast path for length = 0 as it would probably be slower for most of the time?
510
+
511
+ // setup index end as length * sizeof i16 (2)
512
+ // we do this instead of having to do mul/div each iter for perf™
513
+ [ Opcodes.local_get, leftLength ],
514
+ ...number(ValtypeSize.i16, Valtype.i32),
515
+ [ Opcodes.i32_mul ],
516
+ [ Opcodes.local_set, indexEnd ],
517
+
518
+ // iterate over each char and check if eq
519
+ [ Opcodes.loop, Blocktype.void ],
520
+
521
+ // fetch left
522
+ [ Opcodes.local_get, index ],
523
+ [ Opcodes.local_get, leftPointer ],
524
+ [ Opcodes.i32_add ],
525
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
526
+
527
+ // fetch right
528
+ [ Opcodes.local_get, index ],
529
+ [ Opcodes.local_get, rightPointer ],
530
+ [ Opcodes.i32_add ],
531
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
532
+
533
+ // not equal, "return" false
534
+ [ Opcodes.i32_ne ],
535
+ [ Opcodes.if, Blocktype.void ],
536
+ ...number(0, Valtype.i32),
537
+ [ Opcodes.br, 2 ],
538
+ [ Opcodes.end ],
539
+
540
+ // index += sizeof i16 (2)
541
+ [ Opcodes.local_get, index ],
542
+ ...number(ValtypeSize.i16, Valtype.i32),
543
+ [ Opcodes.i32_add ],
544
+ [ Opcodes.local_tee, index ],
545
+
546
+ // if index != index end (length * sizeof 16), loop
547
+ [ Opcodes.local_get, indexEnd ],
548
+ [ Opcodes.i32_ne ],
549
+ [ Opcodes.br_if, 0 ],
550
+ [ Opcodes.end ],
551
+
552
+ // no failed checks, so true!
553
+ ...number(1, Valtype.i32),
554
+
555
+ // pointers match, so true
556
+ [ Opcodes.else ],
557
+ ...number(1, Valtype.i32),
558
+ [ Opcodes.end ],
559
+
560
+ // convert i32 result to valtype
561
+ // do not do as automatically added by binary exp gen for equality ops
562
+ // Opcodes.i32_from_u
563
+ ];
564
+ };
565
+
460
566
  const falsy = (scope, wasm, type) => {
461
567
  // arrays are always truthy
462
568
  if (type === TYPES._array) return [
@@ -528,6 +634,23 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
528
634
  return performLogicOp(scope, op, left, right);
529
635
  }
530
636
 
637
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
638
+
639
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
640
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
641
+ return [
642
+ ...left,
643
+ ...right,
644
+
645
+ // drop values
646
+ [ Opcodes.drop ],
647
+ [ Opcodes.drop ],
648
+
649
+ // return false (===)/true (!==)
650
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
651
+ ];
652
+ }
653
+
531
654
  if (leftType === TYPES.string || rightType === TYPES.string) {
532
655
  if (op === '+') {
533
656
  // string concat (a + b)
@@ -538,9 +661,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
538
661
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
539
662
 
540
663
  // else leave bool ops
541
- // todo: convert string to number if string and number
664
+ // todo: convert string to number if string and number/bool
542
665
  // todo: string (>|>=|<|<=) string
543
- // todo: string equality
666
+
667
+ // string comparison
668
+ if (op === '===' || op === '==') {
669
+ return compareStrings(scope, left, right);
670
+ }
671
+
672
+ if (op === '!==' || op === '!=') {
673
+ return [
674
+ ...compareStrings(scope, left, right),
675
+ [ Opcodes.i32_eqz ]
676
+ ];
677
+ }
544
678
  }
545
679
 
546
680
  let ops = operatorOpcode[valtype][op];
@@ -686,6 +820,7 @@ const getType = (scope, _name) => {
686
820
 
687
821
  const getNodeType = (scope, node) => {
688
822
  if (node.type === 'Literal') {
823
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
689
824
  return TYPES[typeof node.value];
690
825
  }
691
826
 
@@ -700,7 +835,7 @@ const getNodeType = (scope, node) => {
700
835
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
701
836
  const name = node.callee.name;
702
837
  const func = funcs.find(x => x.name === name);
703
- if (func) return func.returnType ?? TYPES.number;
838
+ if (func) return func.returnType;
704
839
 
705
840
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
706
841
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -725,9 +860,7 @@ const getNodeType = (scope, node) => {
725
860
  protoFunc = prototypeFuncs[baseType]?.[func];
726
861
  }
727
862
 
728
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
729
-
730
- return TYPES.number;
863
+ if (protoFunc) return protoFunc.returnType;
731
864
  }
732
865
 
733
866
  if (node.type === 'ExpressionStatement') {
@@ -759,9 +892,6 @@ const getNodeType = (scope, node) => {
759
892
 
760
893
  if (objectType === TYPES.string && node.computed) return TYPES.string;
761
894
  }
762
-
763
- // default to number
764
- return TYPES.number;
765
895
  };
766
896
 
767
897
  const generateLiteral = (scope, decl, global, name) => {
@@ -1291,7 +1421,7 @@ const generateUnary = (scope, decl) => {
1291
1421
  return out;
1292
1422
 
1293
1423
  case 'typeof':
1294
- const type = getNodeType(scope, decl.argument);
1424
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1295
1425
 
1296
1426
  // for custom types, just return object
1297
1427
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1424,9 +1554,28 @@ const generateWhile = (scope, decl) => {
1424
1554
  return out;
1425
1555
  };
1426
1556
 
1557
+ const generateForOf = (scope, decl) => {
1558
+ const out = [];
1559
+
1560
+ out.push([ Opcodes.loop, Blocktype.void ]);
1561
+ depth.push('while');
1562
+
1563
+ out.push(...generate(scope, decl.test));
1564
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1565
+ depth.push('if');
1566
+
1567
+ out.push(...generate(scope, decl.body));
1568
+
1569
+ out.push([ Opcodes.br, 1 ]);
1570
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1571
+ depth.pop(); depth.pop();
1572
+
1573
+ return out;
1574
+ };
1575
+
1427
1576
  const getNearestLoop = () => {
1428
1577
  for (let i = depth.length - 1; i >= 0; i--) {
1429
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1578
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1430
1579
  }
1431
1580
 
1432
1581
  return -1;
@@ -1754,7 +1903,7 @@ const generateFunc = (scope, decl) => {
1754
1903
  name,
1755
1904
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1756
1905
  returns: innerScope.returns,
1757
- returnType: innerScope.returnType ?? TYPES.number,
1906
+ returnType: innerScope.returnType,
1758
1907
  locals: innerScope.locals,
1759
1908
  memory: innerScope.memory,
1760
1909
  throws: innerScope.throws,
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`));
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-828ee15",
4
+ "version": "0.0.0-bddcdc3",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/index.js CHANGED
@@ -30,5 +30,6 @@ try {
30
30
  exports.main();
31
31
  if (cache) process.stdout.write(cache);
32
32
  } catch (e) {
33
+ if (cache) process.stdout.write(cache);
33
34
  console.error(`${e.constructor.name}: ${e.message}`);
34
35
  }
package/runner/repl.js CHANGED
@@ -48,10 +48,12 @@ const memoryToString = mem => {
48
48
  return out;
49
49
  };
50
50
 
51
+ const alwaysPrev = process.argv.includes('-always-prev');
52
+
51
53
  let prev = '';
52
54
  const run = async (source, _context, _filename, callback, run = true) => {
53
55
  let toRun = prev + source.trim();
54
- // prev = toRun + ';\n';
56
+ if (alwaysPrev) prev = toRun + ';\n';
55
57
 
56
58
  const { exports, wasm, pages } = await compile(toRun, []);
57
59
  fs.writeFileSync('out.wasm', Buffer.from(wasm));
@@ -64,7 +66,7 @@ const run = async (source, _context, _filename, callback, run = true) => {
64
66
  const ret = run ? exports.main() : undefined;
65
67
  callback(null, ret);
66
68
 
67
- if (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function ')) prev = toRun + ';\n';
69
+ if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
68
70
  // prev = toRun + ';\n';
69
71
  };
70
72
 
@@ -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);
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");