porffor 0.0.0-1989c22 → 0.0.0-425ea20

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
@@ -135,6 +148,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
135
148
  - `i64.extend_i32_s`, `i32.wrap_i64` -> ``
136
149
  - `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
137
150
  - `return`, `end` -> `end`
151
+ - change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
138
152
  - remove some redundant sets/gets
139
153
  - remove unneeded single just used vars
140
154
  - 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,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);
@@ -461,8 +469,6 @@ const compareStrings = (scope, left, right) => {
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;
465
-
466
472
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
467
473
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
468
474
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -472,9 +478,6 @@ const compareStrings = (scope, left, right) => {
472
478
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
473
479
 
474
480
  return [
475
- // use block to "return" a value early
476
- [ Opcodes.block, Valtype.i32 ],
477
-
478
481
  // setup left
479
482
  ...left,
480
483
  Opcodes.i32_to_u,
@@ -486,11 +489,9 @@ const compareStrings = (scope, left, right) => {
486
489
  [ Opcodes.local_tee, rightPointer ],
487
490
 
488
491
  // fast path: check leftPointer == rightPointer
489
- [ Opcodes.i32_eq ],
490
- [ Opcodes.if, Blocktype.void ],
491
- ...number(1, Valtype.i32),
492
- [ Opcodes.br, 1 ],
493
- [ Opcodes.end ],
492
+ // use if (block) for everything after to "return" a value early
493
+ [ Opcodes.i32_ne ],
494
+ [ Opcodes.if, Valtype.i32 ],
494
495
 
495
496
  // get lengths
496
497
  [ Opcodes.local_get, leftPointer ],
@@ -553,6 +554,10 @@ const compareStrings = (scope, left, right) => {
553
554
 
554
555
  // no failed checks, so true!
555
556
  ...number(1, Valtype.i32),
557
+
558
+ // pointers match, so true
559
+ [ Opcodes.else ],
560
+ ...number(1, Valtype.i32),
556
561
  [ Opcodes.end ],
557
562
 
558
563
  // convert i32 result to valtype
@@ -561,75 +566,100 @@ const compareStrings = (scope, left, right) => {
561
566
  ];
562
567
  };
563
568
 
564
- const falsy = (scope, wasm, type) => {
569
+ const truthy = (scope, wasm, type) => {
565
570
  // arrays are always truthy
566
571
  if (type === TYPES._array) return [
567
572
  ...wasm,
568
573
  [ Opcodes.drop ],
569
- number(0)
574
+ ...number(1)
570
575
  ];
571
576
 
572
577
  if (type === TYPES.string) {
573
- // if "" (length = 0)
578
+ // if not "" (length = 0)
574
579
  return [
575
580
  // pointer
576
581
  ...wasm,
582
+ Opcodes.i32_to_u,
577
583
 
578
584
  // get length
579
585
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
580
586
 
581
- // if length == 0
582
- [ Opcodes.i32_eqz ],
587
+ // if length != 0
588
+ /* [ Opcodes.i32_eqz ],
589
+ [ Opcodes.i32_eqz ], */
583
590
  Opcodes.i32_from_u
584
591
  ]
585
592
  }
586
593
 
587
- // if = 0
594
+ // if != 0
588
595
  return [
589
596
  ...wasm,
590
597
 
591
- ...Opcodes.eqz,
592
- Opcodes.i32_from_u
598
+ /* Opcodes.eqz,
599
+ [ Opcodes.i32_eqz ],
600
+ Opcodes.i32_from */
593
601
  ];
594
602
  };
595
603
 
596
- const truthy = (scope, wasm, type) => {
604
+ const falsy = (scope, wasm, type) => {
597
605
  // arrays are always truthy
598
606
  if (type === TYPES._array) return [
599
607
  ...wasm,
600
608
  [ Opcodes.drop ],
601
- number(1)
609
+ ...number(0)
602
610
  ];
603
611
 
604
612
  if (type === TYPES.string) {
605
- // if not "" (length = 0)
613
+ // if "" (length = 0)
606
614
  return [
607
615
  // pointer
608
616
  ...wasm,
617
+ Opcodes.i32_to_u,
609
618
 
610
619
  // get length
611
620
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
612
621
 
613
- // if length != 0
614
- /* [ Opcodes.i32_eqz ],
615
- [ Opcodes.i32_eqz ], */
622
+ // if length == 0
623
+ [ Opcodes.i32_eqz ],
616
624
  Opcodes.i32_from_u
617
625
  ]
618
626
  }
619
627
 
620
- // if != 0
628
+ // if = 0
621
629
  return [
622
630
  ...wasm,
623
631
 
624
- /* Opcodes.eqz,
625
- [ Opcodes.i32_eqz ],
626
- 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)
627
657
  ];
628
658
  };
629
659
 
630
660
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
631
661
  if (op === '||' || op === '&&' || op === '??') {
632
- return performLogicOp(scope, op, left, right);
662
+ return performLogicOp(scope, op, left, right, leftType, rightType);
633
663
  }
634
664
 
635
665
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
@@ -770,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
770
800
  };
771
801
 
772
802
  const generateLogicExp = (scope, decl) => {
773
- 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));
774
804
  };
775
805
 
776
806
  const TYPES = {
@@ -934,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
934
964
  const countLeftover = wasm => {
935
965
  let count = 0, depth = 0;
936
966
 
937
- for (const inst of wasm) {
967
+ for (let i = 0; i < wasm.length; i++) {
968
+ const inst = wasm[i];
938
969
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
939
970
  if (inst[0] === Opcodes.if) count--;
940
971
  if (inst[1] !== Blocktype.void) count++;
@@ -1038,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
1038
1069
  }
1039
1070
 
1040
1071
  let out = [];
1041
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1072
+ let protoFunc, protoName, baseType, baseName;
1042
1073
  // ident.func()
1043
1074
  if (name && name.startsWith('__')) {
1044
1075
  const spl = name.slice(2).split('_');
@@ -1061,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
1061
1092
 
1062
1093
  out = generate(scope, decl.callee.object);
1063
1094
  out.push([ Opcodes.drop ]);
1095
+
1096
+ baseName = [...arrays.keys()].pop();
1064
1097
  }
1065
1098
 
1066
1099
  if (protoFunc) {
1067
- scope.memory = true;
1068
-
1069
1100
  let pointer = arrays.get(baseName);
1070
1101
 
1071
1102
  if (pointer == null) {
@@ -1073,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
1073
1104
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1074
1105
 
1075
1106
  // register array
1076
- const [ , pointer ] = makeArray(scope, {
1107
+ 0, [ , pointer ] = makeArray(scope, {
1077
1108
  rawElements: new Array(0)
1078
1109
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1079
1110
 
@@ -1171,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
1171
1202
  args = args.slice(0, func.params.length);
1172
1203
  }
1173
1204
 
1174
- if (func && func.memory) scope.memory = true;
1175
1205
  if (func && func.throws) scope.throws = true;
1176
1206
 
1177
1207
  for (const arg of args) {
@@ -1187,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
1187
1217
  // hack: basically treat this as a normal call for builtins for now
1188
1218
  const name = mapName(decl.callee.name);
1189
1219
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1190
- 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)})`);
1191
1221
 
1192
1222
  return generateCall(scope, decl, _global, _name);
1193
1223
  };
@@ -1295,8 +1325,6 @@ const generateAssign = (scope, decl) => {
1295
1325
  const name = decl.left.object.name;
1296
1326
  const pointer = arrays.get(name);
1297
1327
 
1298
- scope.memory = true;
1299
-
1300
1328
  const aotPointer = pointer != null;
1301
1329
 
1302
1330
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1347,8 +1375,27 @@ const generateAssign = (scope, decl) => {
1347
1375
  ];
1348
1376
  }
1349
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
+
1350
1397
  return [
1351
- ...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),
1352
1399
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1353
1400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1354
1401
  ];
@@ -1375,13 +1422,14 @@ const generateUnary = (scope, decl) => {
1375
1422
 
1376
1423
  case '!':
1377
1424
  // !=
1378
- return falsy(scope, generate(scope, decl.argument));
1425
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1379
1426
 
1380
1427
  case '~':
1428
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1381
1429
  return [
1382
1430
  ...generate(scope, decl.argument),
1383
1431
  Opcodes.i32_to,
1384
- [ Opcodes.i32_const, signedLEB128(-1) ],
1432
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1385
1433
  [ Opcodes.i32_xor ],
1386
1434
  Opcodes.i32_from
1387
1435
  ];
@@ -1552,6 +1600,25 @@ const generateWhile = (scope, decl) => {
1552
1600
  return out;
1553
1601
  };
1554
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
+
1555
1622
  const getNearestLoop = () => {
1556
1623
  for (let i = depth.length - 1; i >= 0; i--) {
1557
1624
  if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
@@ -1644,7 +1711,16 @@ const allocPage = reason => {
1644
1711
  let ind = pages.size;
1645
1712
  pages.set(reason, ind);
1646
1713
 
1647
- 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}`);
1648
1724
 
1649
1725
  return ind;
1650
1726
  };
@@ -1706,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1706
1782
  // local value as pointer
1707
1783
  out.push(...number(pointer));
1708
1784
 
1709
- scope.memory = true;
1710
-
1711
1785
  return [ out, pointer ];
1712
1786
  };
1713
1787
 
@@ -1726,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1726
1800
  const name = decl.object.name;
1727
1801
  const pointer = arrays.get(name);
1728
1802
 
1729
- scope.memory = true;
1730
-
1731
1803
  const aotPointer = pointer != null;
1732
1804
 
1733
1805
  return [
@@ -1747,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
1819
  const name = decl.object.name;
1748
1820
  const pointer = arrays.get(name);
1749
1821
 
1750
- scope.memory = true;
1751
-
1752
1822
  const aotPointer = pointer != null;
1753
1823
 
1754
1824
  if (type === TYPES._array) {
@@ -1858,6 +1928,7 @@ const generateFunc = (scope, decl) => {
1858
1928
  locals: {},
1859
1929
  localInd: 0,
1860
1930
  returns: [ valtypeBinary ],
1931
+ returnType: null,
1861
1932
  memory: false,
1862
1933
  throws: false,
1863
1934
  name
@@ -1884,7 +1955,6 @@ const generateFunc = (scope, decl) => {
1884
1955
  returns: innerScope.returns,
1885
1956
  returnType: innerScope.returnType,
1886
1957
  locals: innerScope.locals,
1887
- memory: innerScope.memory,
1888
1958
  throws: innerScope.throws,
1889
1959
  index: currentFuncIndex++
1890
1960
  };
@@ -1899,6 +1969,8 @@ const generateFunc = (scope, decl) => {
1899
1969
 
1900
1970
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1901
1971
  wasm.push(...number(0), [ Opcodes.return ]);
1972
+
1973
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1902
1974
  }
1903
1975
 
1904
1976
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1909,9 +1981,7 @@ const generateFunc = (scope, decl) => {
1909
1981
  if (local.type === Valtype.v128) {
1910
1982
  vecParams++;
1911
1983
 
1912
- /* func.memory = true; // mark func as using memory
1913
-
1914
- wasm.unshift( // add v128 load for param
1984
+ /* wasm.unshift( // add v128 load for param
1915
1985
  [ Opcodes.i32_const, 0 ],
1916
1986
  [ ...Opcodes.v128_load, 0, i * 16 ],
1917
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;
@@ -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
@@ -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-1989c22",
4
+ "version": "0.0.0-425ea20",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/index.js CHANGED
@@ -5,6 +5,12 @@ import fs from 'node:fs';
5
5
 
6
6
  const file = process.argv.slice(2).find(x => x[0] !== '-');
7
7
  if (!file) {
8
+ if (process.argv.includes('-v')) {
9
+ // just print version
10
+ console.log((await import('./version.js')).default);
11
+ process.exit(0);
12
+ }
13
+
8
14
  // run repl if no file given
9
15
  await import('./repl.js');
10
16
 
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
 
@@ -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;