porffor 0.2.0-c6c8c81 → 0.2.0-e562242

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
@@ -1,5 +1,5 @@
1
1
  # Porffor &nbsp;<sup><sub>/ˈpɔrfɔr/ &nbsp;*(poor-for)*</sup></sub>
2
- A from-scratch experimental **AOT** optimizing JS -> Wasm/C engine/compiler/runtime in JS. Not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
2
+ A from-scratch experimental **AOT** optimizing JS/TS -> Wasm/C engine/compiler/runtime in JS. Not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
3
3
  Age: ~6 months (very on and off)
4
4
 
5
5
  ## Design
@@ -118,7 +118,7 @@ No particular order and no guarentees, just what could happen soon™
118
118
  - Objects
119
119
  - Basic object expressions (eg `{}`, `{ a: 0 }`)
120
120
  - Wasm
121
- - *Basic* Wasm engine (interpreter) in js
121
+ - *Basic* Wasm engine (interpreter) in JS
122
122
  - More math operators (`**`, etc)
123
123
  - `do { ... } while (...)`
124
124
  - Rewrite `console.log` to work with strings/arrays
@@ -130,7 +130,21 @@ No particular order and no guarentees, just what could happen soon™
130
130
  - Rewrite local indexes per func for smallest local header and remove unused idxs
131
131
  - Smarter inline selection (snapshots?)
132
132
  - Remove const ifs (`if (true)`, etc)
133
- - Use type(script) information to remove unneeded typechecker code
133
+ - Experiment with byte strings?
134
+ - Runtime
135
+ - WASI target
136
+ - Run precompiled Wasm file if given
137
+ - Cool proposals
138
+ - [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
139
+ - [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
140
+ - [Array Equality](https://github.com/tc39/proposal-array-equality)
141
+ - [Declarations in Conditionals](https://github.com/tc39/proposal-Declarations-in-Conditionals)
142
+ - [Seeded Pseudo-Random Numbers](https://github.com/tc39/proposal-seeded-random)
143
+ - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
144
+ - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
+ - Posts
146
+ - Type annotations for performance
147
+ - Inlining investigation
134
148
 
135
149
  ## Performance
136
150
  *For the things it supports most of the time*, Porffor is blazingly fast compared to most interpreters, and common engines running without JIT. For those with JIT, it is not that much slower like a traditional interpreter would be; mostly the same or a bit faster/slower depending on what.
@@ -157,10 +171,12 @@ Mostly for reducing size. I do not really care about compiler perf/time as long
157
171
  - Remove unneeded blocks (no `br`s inside)
158
172
  - Remove unused imports
159
173
  - Use data segments for initing arrays/strings
174
+ - (Likely more not documented yet, todo)
160
175
 
161
176
  ### Wasm module
162
177
  - Type cache/index (no repeated types)
163
178
  - No main func if empty (and other exports)
179
+ - No tags if unused/optimized out
164
180
 
165
181
  ## Test262
166
182
  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.
@@ -180,7 +196,7 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
180
196
  - `wasmSpec.js`: "enums"/info from wasm spec
181
197
  - `wrap.js`: wrapper for compiler which instantiates and produces nice exports
182
198
 
183
- - `runner`: contains utils for running js with the compiler
199
+ - `runner`: contains utils for running JS with the compiler
184
200
  - `index.js`: the main file, you probably want to use this
185
201
  - `info.js`: runs with extra info printed
186
202
  - `repl.js`: basic repl (uses `node:repl`)
@@ -212,11 +228,14 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
212
228
  - `-target=native` only:
213
229
  - `-compiler=clang` to set compiler binary (path/name) to use to compile
214
230
  - `-cO=O3` to set compiler opt argument
231
+ - `-parser=acorn|@babel/parser|meriyah|hermes-parser` (default: `acorn`) to set which parser to use
232
+ - `-parse-types` to enable parsing type annotations/typescript. if `-parser` is unset, changes default to `@babel/parser`. does not type check
233
+ - `-opt-types` to perform optimizations using type annotations as compiler hints. does not type check
215
234
  - `-valtype=i32|i64|f64` (default: `f64`) to set valtype
216
235
  - `-O0` to disable opt
217
236
  - `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
218
- - `-O2` to enable advanced opt (inlining)
219
- - `-O3` to enable advanceder opt (precompute const math)
237
+ - `-O2` to enable advanced opt (inlining). unstable
238
+ - `-O3` to enable advanceder opt (precompute const math). unstable
220
239
  - `-no-run` to not run wasm output, just compile
221
240
  - `-opt-log` to log some opts
222
241
  - `-code-log` to log some codegen (you probably want `-funcs`)
@@ -230,20 +249,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
230
249
  - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
231
250
 
232
251
  ## VSCode extension
233
- 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).
252
+ 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).
234
253
 
235
254
  ## Isn't this the same as AssemblyScript/other Wasm langs?
236
255
  No. they are not alike at all internally and have very different goals/ideals:
237
256
  - Porffor is made as a generic JS engine, not for Wasm stuff specifically
238
- - Porffor takes in JS, not a different language or typescript
239
- - Porffor is made in pure JS and compiles itself, not using Binaryen/etc
257
+ - Porffor primarily consumes JS
258
+ - Porffor is written in pure JS and compiles itself, not using Binaryen/etc
240
259
  - (Also I didn't know it existed when I started this, lol)
241
260
 
242
261
  ## FAQ
243
262
 
244
263
  ### 1. Why the name?
245
264
  `purple` in Welsh is `porffor`. Why purple?
246
- - No other js engine is purple colored
265
+ - No other JS engine is purple colored
247
266
  - Purple is pretty cool
248
267
  - Purple apparently represents "ambition", which is.. one word to describe this project
249
268
  - The hard to speak name is also the noise your brain makes in reaction to this idea!
package/compiler/2c.js CHANGED
@@ -35,7 +35,10 @@ const todo = msg => {
35
35
  throw new TodoError(`todo: ${msg}`);
36
36
  };
37
37
 
38
- const removeBrackets = str => str.startsWith('(') && str.endsWith(')') ? str.slice(1, -1) : str;
38
+ const removeBrackets = str => {
39
+ if (str.startsWith('(long)(unsigned long)')) return '(long)(unsigned long)(' + removeBrackets(str.slice(22, -1)) + ')';
40
+ return str.startsWith('(') && str.endsWith(')') ? str.slice(1, -1) : str;
41
+ };
39
42
 
40
43
  export default ({ funcs, globals, tags, exceptions, pages }) => {
41
44
  const invOperatorOpcode = Object.values(operatorOpcode).reduce((acc, x) => {
@@ -121,7 +124,7 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
121
124
  const returns = f.returns.length > 0;
122
125
 
123
126
  const shouldInline = f.internal;
124
- out += `${f.name === 'main' ? 'int' : (f.internal ? 'double' : 'struct ReturnValue')} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
127
+ out += `${f.name === 'main' ? 'int' : (f.internal ? (returns ? 'double' : 'void') : 'struct ReturnValue')} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
125
128
 
126
129
  const localKeys = Object.keys(f.locals).sort((a, b) => f.locals[a].idx - f.locals[b].idx).slice(f.params.length).sort((a, b) => f.locals[a].idx - f.locals[b].idx);
127
130
  for (const x of localKeys) {
@@ -364,6 +367,11 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
364
367
  line(`return ${vals.pop()}`);
365
368
  }
366
369
 
370
+ if (f.name === 'main') {
371
+ out += '\n';
372
+ line(`return 0`);
373
+ }
374
+
367
375
  out += '}\n\n';
368
376
  }
369
377
 
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -214,6 +214,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
214
214
  }
215
215
 
216
216
  default:
217
+ if (decl.type.startsWith('TS')) {
218
+ // ignore typescript nodes
219
+ return [];
220
+ }
221
+
217
222
  return todo(`no generation for ${decl.type}!`);
218
223
  }
219
224
  };
@@ -360,12 +365,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
365
  ...right,
361
366
  // note type
362
367
  ...rightType,
363
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
368
+ setLastType(scope),
364
369
  [ Opcodes.else ],
365
370
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
371
  // note type
367
372
  ...leftType,
368
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
373
+ setLastType(scope),
369
374
  [ Opcodes.end ],
370
375
  Opcodes.i32_from
371
376
  ];
@@ -379,12 +384,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
379
384
  ...right,
380
385
  // note type
381
386
  ...rightType,
382
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
387
+ setLastType(scope),
383
388
  [ Opcodes.else ],
384
389
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
390
  // note type
386
391
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
392
+ setLastType(scope),
388
393
  [ Opcodes.end ]
389
394
  ];
390
395
  };
@@ -852,7 +857,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
852
857
 
853
858
  let tmpLeft, tmpRight;
854
859
  // if equal op, check if strings for compareStrings
855
- if (op === '===' || op === '==' || op === '!==' || op === '!=') {
860
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
+ const knownLeft = knownType(scope, leftType);
862
+ const knownRight = knownType(scope, rightType);
863
+
864
+ // todo: intelligent partial skip later
865
+ // if neither known are string, stop this madness
866
+ if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
867
+ return;
868
+ }
869
+
856
870
  tmpLeft = localTmp(scope, '__tmpop_left');
857
871
  tmpRight = localTmp(scope, '__tmpop_right');
858
872
 
@@ -902,7 +916,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
902
916
  // endOut.push(stringOnly([ Opcodes.end ]));
903
917
  endOut.unshift(stringOnly([ Opcodes.end ]));
904
918
  // }
905
- }
919
+ })();
906
920
 
907
921
  return finalise([
908
922
  ...left,
@@ -1051,11 +1065,13 @@ const setType = (scope, _name, type) => {
1051
1065
 
1052
1066
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1053
1067
 
1068
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1054
1069
  if (scope.locals[name]) return [
1055
1070
  ...out,
1056
1071
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
1072
  ];
1058
1073
 
1074
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1059
1075
  if (globals[name]) return [
1060
1076
  ...out,
1061
1077
  [ Opcodes.global_set, globals[name + '#type'].idx ]
@@ -1064,6 +1080,15 @@ const setType = (scope, _name, type) => {
1064
1080
  // throw new Error('could not find var');
1065
1081
  };
1066
1082
 
1083
+ const getLastType = scope => {
1084
+ scope.gotLastType = true;
1085
+ return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1086
+ };
1087
+
1088
+ const setLastType = scope => {
1089
+ return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1090
+ };
1091
+
1067
1092
  const getNodeType = (scope, node) => {
1068
1093
  const inner = () => {
1069
1094
  if (node.type === 'Literal') {
@@ -1092,7 +1117,19 @@ const getNodeType = (scope, node) => {
1092
1117
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1093
1118
  if (internalConstrs[name]) return internalConstrs[name].type;
1094
1119
 
1095
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1120
+ // check if this is a prototype function
1121
+ // if so and there is only one impl (eg charCodeAt)
1122
+ // use that return type as that is the only possibility
1123
+ // (if non-matching type it would error out)
1124
+ if (name.startsWith('__')) {
1125
+ const spl = name.slice(2).split('_');
1126
+
1127
+ const func = spl[spl.length - 1];
1128
+ const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1129
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
+ }
1131
+
1132
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1096
1133
 
1097
1134
  // presume
1098
1135
  // todo: warn here?
@@ -1177,7 +1214,7 @@ const getNodeType = (scope, node) => {
1177
1214
  return TYPES.number;
1178
1215
  }
1179
1216
 
1180
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1217
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1181
1218
 
1182
1219
  // presume
1183
1220
  // todo: warn here?
@@ -1190,6 +1227,23 @@ const getNodeType = (scope, node) => {
1190
1227
  return ret;
1191
1228
  };
1192
1229
 
1230
+ const toString = (scope, wasm, type) => {
1231
+ const tmp = localTmp(scope, '#tostring_tmp');
1232
+ return [
1233
+ ...wasm,
1234
+ [ Opcodes.local_set, tmp ],
1235
+
1236
+ ...typeSwitch(scope, type, {
1237
+ [TYPES.string]: [
1238
+ [ Opcodes.local_get, tmp ]
1239
+ ],
1240
+ [TYPES.undefined]: [
1241
+ // [ Opcodes.]
1242
+ ]
1243
+ })
1244
+ ]
1245
+ };
1246
+
1193
1247
  const generateLiteral = (scope, decl, global, name) => {
1194
1248
  if (decl.value === null) return number(NULL);
1195
1249
 
@@ -1356,13 +1410,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1410
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1411
  out.push(
1358
1412
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1413
+ setLastType(scope)
1360
1414
  );
1361
1415
  } else if (countLeftover(out) === 0) {
1362
1416
  out.push(...number(UNDEFINED));
1363
1417
  out.push(
1364
1418
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1419
+ setLastType(scope)
1366
1420
  );
1367
1421
  }
1368
1422
 
@@ -1380,8 +1434,7 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1434
  if (name && name.startsWith('__')) {
1381
1435
  const spl = name.slice(2).split('_');
1382
1436
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1437
+ protoName = spl[spl.length - 1];
1385
1438
 
1386
1439
  target = { ...decl.callee };
1387
1440
  target.name = spl.slice(0, -1).join('_');
@@ -1407,12 +1460,11 @@ const generateCall = (scope, decl, _global, _name) => {
1407
1460
  Opcodes.i32_from_u,
1408
1461
 
1409
1462
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1463
+ setLastType(scope)
1411
1464
  ];
1412
1465
  }
1413
1466
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1467
+ protoName = decl.callee.property.name;
1416
1468
 
1417
1469
  target = decl.callee.object;
1418
1470
  }
@@ -1457,7 +1509,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1509
  ...RTArrayUtil.getLength(getPointer),
1458
1510
 
1459
1511
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1512
+ setLastType(scope)
1461
1513
  ];
1462
1514
  continue;
1463
1515
  }
@@ -1488,7 +1540,7 @@ const generateCall = (scope, decl, _global, _name) => {
1488
1540
  ...protoOut,
1489
1541
 
1490
1542
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1543
+ setLastType(scope),
1492
1544
  [ Opcodes.end ]
1493
1545
  ];
1494
1546
  }
@@ -1589,7 +1641,7 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1641
  // ...number(type, Valtype.i32),
1590
1642
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1643
  // );
1592
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1644
+ } else out.push(setLastType(scope));
1593
1645
 
1594
1646
  return out;
1595
1647
  };
@@ -1614,9 +1666,116 @@ const unhackName = name => {
1614
1666
  return name;
1615
1667
  };
1616
1668
 
1669
+ const knownType = (scope, type) => {
1670
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1671
+ return type[0][1];
1672
+ }
1673
+
1674
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1675
+ const idx = type[0][1];
1676
+
1677
+ // type idx = var idx + 1
1678
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1679
+ if (v.metadata?.type != null) return v.metadata.type;
1680
+ }
1681
+
1682
+ return null;
1683
+ };
1684
+
1685
+ const brTable = (input, bc, returns) => {
1686
+ const out = [];
1687
+ const keys = Object.keys(bc);
1688
+ const count = keys.length;
1689
+
1690
+ if (count === 1) {
1691
+ // return [
1692
+ // ...input,
1693
+ // ...bc[keys[0]]
1694
+ // ];
1695
+ return bc[keys[0]];
1696
+ }
1697
+
1698
+ if (count === 2) {
1699
+ // just use if else
1700
+ const other = keys.find(x => x !== 'default');
1701
+ return [
1702
+ ...input,
1703
+ ...number(other, Valtype.i32),
1704
+ [ Opcodes.i32_eq ],
1705
+ [ Opcodes.if, returns ],
1706
+ ...bc[other],
1707
+ [ Opcodes.else ],
1708
+ ...bc.default,
1709
+ [ Opcodes.end ]
1710
+ ];
1711
+ }
1712
+
1713
+ for (let i = 0; i < count; i++) {
1714
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1715
+ else out.push([ Opcodes.block, Blocktype.void ]);
1716
+ }
1717
+
1718
+ const nums = keys.filter(x => +x);
1719
+ const offset = Math.min(...nums);
1720
+ const max = Math.max(...nums);
1721
+
1722
+ const table = [];
1723
+ let br = 1;
1724
+
1725
+ for (let i = offset; i <= max; i++) {
1726
+ // if branch for this num, go to that block
1727
+ if (bc[i]) {
1728
+ table.push(br);
1729
+ br++;
1730
+ continue;
1731
+ }
1732
+
1733
+ // else default
1734
+ table.push(0);
1735
+ }
1736
+
1737
+ out.push(
1738
+ [ Opcodes.block, Blocktype.void ],
1739
+ ...input,
1740
+ ...(offset > 0 ? [
1741
+ ...number(offset, Valtype.i32),
1742
+ [ Opcodes.i32_sub ]
1743
+ ] : []),
1744
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1745
+ );
1746
+
1747
+ // if you can guess why we sort the wrong way and then reverse
1748
+ // (instead of just sorting the correct way)
1749
+ // dm me and if you are correct and the first person
1750
+ // I will somehow shout you out or something
1751
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1752
+
1753
+ br = count - 1;
1754
+ for (const x of orderedBc) {
1755
+ out.push(
1756
+ [ Opcodes.end ],
1757
+ ...bc[x],
1758
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1759
+ );
1760
+ br--;
1761
+ }
1762
+
1763
+ return [
1764
+ ...out,
1765
+ [ Opcodes.end, 'br table end' ]
1766
+ ];
1767
+ };
1768
+
1617
1769
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1770
+ const known = knownType(scope, type);
1771
+ if (known != null) {
1772
+ return bc[known] ?? bc.default;
1773
+ }
1619
1774
 
1775
+ if (process.argv.includes('-typeswitch-use-brtable'))
1776
+ return brTable(type, bc, returns);
1777
+
1778
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1620
1779
  const out = [
1621
1780
  ...type,
1622
1781
  [ Opcodes.local_set, tmp ],
@@ -1668,6 +1827,49 @@ const allocVar = (scope, name, global = false) => {
1668
1827
  return idx;
1669
1828
  };
1670
1829
 
1830
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1831
+ const target = global ? globals : scope.locals;
1832
+
1833
+ target[name].metadata ??= {};
1834
+ for (const x in metadata) {
1835
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1836
+ }
1837
+ };
1838
+
1839
+ const typeAnnoToPorfType = x => {
1840
+ if (TYPES[x]) return TYPES[x];
1841
+ if (TYPES['_' + x]) return TYPES['_' + x];
1842
+
1843
+ switch (x) {
1844
+ case 'i32':
1845
+ return TYPES.number;
1846
+ }
1847
+
1848
+ return null;
1849
+ };
1850
+
1851
+ const extractTypeAnnotation = decl => {
1852
+ let a = decl;
1853
+ while (a.typeAnnotation) a = a.typeAnnotation;
1854
+
1855
+ let type, elementType;
1856
+ if (a.typeName) {
1857
+ type = a.typeName.name;
1858
+ } else if (a.type.endsWith('Keyword')) {
1859
+ type = a.type.slice(2, -7).toLowerCase();
1860
+ } else if (a.type === 'TSArrayType') {
1861
+ type = 'array';
1862
+ elementType = extractTypeAnnotation(a.elementType).type;
1863
+ }
1864
+
1865
+ const typeName = type;
1866
+ type = typeAnnoToPorfType(type);
1867
+
1868
+ // if (decl.name) console.log(decl.name, { type, elementType });
1869
+
1870
+ return { type, typeName, elementType };
1871
+ };
1872
+
1671
1873
  const generateVar = (scope, decl) => {
1672
1874
  let out = [];
1673
1875
 
@@ -1695,6 +1897,11 @@ const generateVar = (scope, decl) => {
1695
1897
  }
1696
1898
 
1697
1899
  let idx = allocVar(scope, name, global);
1900
+
1901
+ if (typedInput && x.id.typeAnnotation) {
1902
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1903
+ }
1904
+
1698
1905
  if (x.init) {
1699
1906
  out = out.concat(generate(scope, x.init, global, name));
1700
1907
 
@@ -1858,7 +2065,7 @@ const generateAssign = (scope, decl) => {
1858
2065
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
2066
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
2067
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
2068
+ getLastType(scope),
1862
2069
  // hack: type is idx+1
1863
2070
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1864
2071
  ];
@@ -2025,7 +2232,7 @@ const generateConditional = (scope, decl) => {
2025
2232
  // note type
2026
2233
  out.push(
2027
2234
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2235
+ setLastType(scope)
2029
2236
  );
2030
2237
 
2031
2238
  out.push([ Opcodes.else ]);
@@ -2034,7 +2241,7 @@ const generateConditional = (scope, decl) => {
2034
2241
  // note type
2035
2242
  out.push(
2036
2243
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2244
+ setLastType(scope)
2038
2245
  );
2039
2246
 
2040
2247
  out.push([ Opcodes.end ]);
@@ -2478,7 +2685,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2478
2685
 
2479
2686
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
2687
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2688
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2482
2689
  if (pages.hasString) {
2483
2690
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
2691
  rawElements: new Array(1)
@@ -2505,7 +2712,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
2712
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
2713
 
2507
2714
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2715
+ setLastType(scope)
2509
2716
  ],
2510
2717
 
2511
2718
  [TYPES.string]: [
@@ -2537,7 +2744,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
2744
  ...number(newPointer),
2538
2745
 
2539
2746
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2747
+ setLastType(scope)
2541
2748
  ],
2542
2749
 
2543
2750
  default: [ [ Opcodes.unreachable ] ]
@@ -2586,7 +2793,7 @@ const generateFunc = (scope, decl) => {
2586
2793
  if (decl.generator) return todo('generator functions are not supported');
2587
2794
 
2588
2795
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
2796
+ const params = decl.params ?? [];
2590
2797
 
2591
2798
  // const innerScope = { ...scope };
2592
2799
  // TODO: share scope/locals between !!!
@@ -2600,7 +2807,11 @@ const generateFunc = (scope, decl) => {
2600
2807
  };
2601
2808
 
2602
2809
  for (let i = 0; i < params.length; i++) {
2603
- allocVar(innerScope, params[i], false);
2810
+ allocVar(innerScope, params[i].name, false);
2811
+
2812
+ if (typedInput && params[i].typeAnnotation) {
2813
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2814
+ }
2604
2815
  }
2605
2816
 
2606
2817
  let body = objectHack(decl.body);
@@ -2639,117 +2850,6 @@ const generateFunc = (scope, decl) => {
2639
2850
  );
2640
2851
  }
2641
2852
 
2642
- // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
2643
- let offset = 0, vecParams = 0;
2644
- for (let i = 0; i < params.length; i++) {
2645
- const name = params[i];
2646
- const local = func.locals[name];
2647
- if (local.type === Valtype.v128) {
2648
- vecParams++;
2649
-
2650
- /* wasm.unshift( // add v128 load for param
2651
- [ Opcodes.i32_const, 0 ],
2652
- [ ...Opcodes.v128_load, 0, i * 16 ],
2653
- [ Opcodes.local_set, local.idx ]
2654
- ); */
2655
-
2656
- // using params and replace_lane is noticably faster than just loading from memory (above) somehow
2657
-
2658
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2659
- const { vecType } = local;
2660
- let [ type, lanes ] = vecType.split('x');
2661
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2662
-
2663
- lanes = parseInt(lanes);
2664
- type = Valtype[type];
2665
-
2666
- const name = params[i]; // get original param name
2667
-
2668
- func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
2669
-
2670
- // update index of original local
2671
- // delete func.locals[name];
2672
-
2673
- // add new locals for params
2674
- for (let j = 0; j < lanes; j++) {
2675
- func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
2676
- }
2677
-
2678
- // prepend wasm to generate expected v128 locals
2679
- wasm.splice(i * 2 + offset * 2, 0,
2680
- ...i32x4(0, 0, 0, 0),
2681
- ...new Array(lanes).fill(0).flatMap((_, j) => [
2682
- [ Opcodes.local_get, offset + j ],
2683
- [ ...Opcodes[vecType + '_replace_lane'], j ]
2684
- ]),
2685
- [ Opcodes.local_set, i ]
2686
- );
2687
-
2688
- offset += lanes;
2689
-
2690
- // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
2691
- /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
2692
-
2693
- // add vec type index to hash name prefix for wrapper to know how to wrap
2694
- const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
2695
- const secondHash = func.name.slice(1).indexOf('#');
2696
- func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
2697
- }
2698
- }
2699
-
2700
- if (offset !== 0) {
2701
- // bump local indexes for all other locals after
2702
- for (const x in func.locals) {
2703
- const local = func.locals[x];
2704
- if (!local.vecParamAutogen) local.idx += offset;
2705
- }
2706
-
2707
- // bump local indexes in wasm local.get/set
2708
- for (let j = 0; j < wasm.length; j++) {
2709
- const inst = wasm[j];
2710
- if (j < offset * 2 + vecParams * 2) {
2711
- if (inst[0] === Opcodes.local_set) inst[1] += offset;
2712
- continue;
2713
- }
2714
-
2715
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
2716
- }
2717
- }
2718
-
2719
- // change v128 return into many <type> instead as unsupported return valtype
2720
- const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
2721
- if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
2722
- const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
2723
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2724
- const { vecType } = lastReturnLocal;
2725
- let [ type, lanes ] = vecType.split('x');
2726
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2727
-
2728
- lanes = parseInt(lanes);
2729
- type = Valtype[type];
2730
-
2731
- const vecIdx = lastReturnLocal.idx;
2732
-
2733
- const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
2734
- const tmpIdx = [];
2735
- for (let i = 0; i < lanes; i++) {
2736
- const idx = lastIdx + i + 1;
2737
- tmpIdx.push(idx);
2738
- func.locals[name + i] = { idx, type, vecReturnAutogen: true };
2739
- }
2740
-
2741
- wasm.splice(wasm.length - 1, 1,
2742
- ...new Array(lanes).fill(0).flatMap((_, i) => [
2743
- i === 0 ? null : [ Opcodes.local_get, vecIdx ],
2744
- [ ...Opcodes[vecType + '_extract_lane'], i ],
2745
- [ Opcodes.local_set, tmpIdx[i] ],
2746
- ].filter(x => x !== null)),
2747
- ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2748
- );
2749
-
2750
- func.returns = new Array(lanes).fill(type);
2751
- }
2752
-
2753
2853
  func.wasm = wasm;
2754
2854
 
2755
2855
  funcs.push(func);
@@ -116,13 +116,12 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
116
116
  return highlightAsm(out);
117
117
  };
118
118
 
119
- export const highlightAsm = asm =>
120
- asm
121
- .replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
122
- .replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
123
- .replace(/[^m](i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `${_[0]}\x1B[36m${_.slice(1)}\x1B[0m`)
124
- .replace(/(return_call|call|br_if|br|return|throw|rethrow)/g, _ => `\x1B[35m${_}\x1B[0m`)
125
- .replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
126
- .replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
127
- .replace(/ \-?[0-9\.]+/g, _ => ` \x1B[33m${_.slice(1)}\x1B[0m`)
128
- .replace(/ ;;.*$/gm, _ => `\x1B[90m${_.replaceAll(/\x1B\[[0-9]+m/g, '')}\x1B[0m`);
119
+ export const highlightAsm = asm => asm
120
+ .replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
121
+ .replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
122
+ .replace(/[^m](i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `${_[0]}\x1B[36m${_.slice(1)}\x1B[0m`)
123
+ .replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
124
+ .replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
125
+ .replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
126
+ .replace(/ \-?[0-9\.]+/g, _ => ` \x1B[33m${_.slice(1)}\x1B[0m`)
127
+ .replace(/ ;;.*$/gm, _ => `\x1B[90m${_.replaceAll(/\x1B\[[0-9]+m/g, '')}\x1B[0m`);
@@ -105,119 +105,5 @@ export const read_unsignedLEB128 = _input => {
105
105
  };
106
106
 
107
107
  // ieee 754 binary64
108
-
109
- // from https://github.com/feross/ieee754
110
- // BSD 3-Clause. Copyright 2008 Fair Oaks Labs, Inc. (https://github.com/feross/ieee754/blob/master/LICENSE)
111
- export const ieee754_binary64 = value => {
112
- return [...new Uint8Array(new Float64Array([ value ]).buffer)];
113
-
114
- let isLE = true, mLen = 52, nBytes = 8, offset = 0;
115
- let buffer = new Array(nBytes).fill(0);
116
-
117
- let e, m, c
118
- let eLen = (nBytes * 8) - mLen - 1
119
- const eMax = (1 << eLen) - 1
120
- const eBias = eMax >> 1
121
- const rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
122
- let i = isLE ? 0 : (nBytes - 1)
123
- const d = isLE ? 1 : -1
124
- const s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
125
-
126
- value = Math.abs(value)
127
-
128
- if (isNaN(value) || value === Infinity) {
129
- m = isNaN(value) ? 1 : 0
130
- e = eMax
131
- } else {
132
- e = Math.floor(Math.log(value) / Math.LN2)
133
- if (value * (c = Math.pow(2, -e)) < 1) {
134
- e--
135
- c *= 2
136
- }
137
- if (e + eBias >= 1) {
138
- value += rt / c
139
- } else {
140
- value += rt * Math.pow(2, 1 - eBias)
141
- }
142
- if (value * c >= 2) {
143
- e++
144
- c /= 2
145
- }
146
-
147
- if (e + eBias >= eMax) {
148
- m = 0
149
- e = eMax
150
- } else if (e + eBias >= 1) {
151
- m = ((value * c) - 1) * Math.pow(2, mLen)
152
- e = e + eBias
153
- } else {
154
- m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
155
- e = 0
156
- }
157
- }
158
-
159
- while (mLen >= 8) {
160
- buffer[offset + i] = m & 0xff
161
- i += d
162
- m /= 256
163
- mLen -= 8
164
- }
165
-
166
- e = (e << mLen) | m
167
- eLen += mLen
168
- while (eLen > 0) {
169
- buffer[offset + i] = e & 0xff
170
- i += d
171
- e /= 256
172
- eLen -= 8
173
- }
174
-
175
- buffer[offset + i - d] |= s * 128
176
-
177
- return buffer;
178
- };
179
-
180
- export const read_ieee754_binary64 = buffer => {
181
- return new Float64Array(new Uint8Array(buffer).buffer)[0];
182
-
183
- let isLE = true, mLen = 52, nBytes = 8, offset = 0;
184
-
185
- let e, m
186
- const eLen = (nBytes * 8) - mLen - 1
187
- const eMax = (1 << eLen) - 1
188
- const eBias = eMax >> 1
189
- let nBits = -7
190
- let i = isLE ? (nBytes - 1) : 0
191
- const d = isLE ? -1 : 1
192
- let s = buffer[offset + i]
193
-
194
- i += d
195
-
196
- e = s & ((1 << (-nBits)) - 1)
197
- s >>= (-nBits)
198
- nBits += eLen
199
- while (nBits > 0) {
200
- e = (e * 256) + buffer[offset + i]
201
- i += d
202
- nBits -= 8
203
- }
204
-
205
- m = e & ((1 << (-nBits)) - 1)
206
- e >>= (-nBits)
207
- nBits += mLen
208
- while (nBits > 0) {
209
- m = (m * 256) + buffer[offset + i]
210
- i += d
211
- nBits -= 8
212
- }
213
-
214
- if (e === 0) {
215
- e = 1 - eBias
216
- } else if (e === eMax) {
217
- return m ? NaN : ((s ? -1 : 1) * Infinity)
218
- } else {
219
- m = m + Math.pow(2, mLen)
220
- e = e - eBias
221
- }
222
- return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
223
- };
108
+ export const ieee754_binary64 = value => [...new Uint8Array(new Float64Array([ value ]).buffer)];
109
+ export const read_ieee754_binary64 = buffer => new Float64Array(new Uint8Array(buffer).buffer)[0];
package/compiler/index.js CHANGED
@@ -28,8 +28,8 @@ const logFuncs = (funcs, globals, exceptions) => {
28
28
 
29
29
  const getArg = name => process.argv.find(x => x.startsWith(`-${name}=`))?.slice(name.length + 2);
30
30
 
31
- const writeFileSync = (typeof process !== 'undefined' ? (await import('node:fs')).writeFileSync : undefined);
32
- const execSync = (typeof process !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
31
+ const writeFileSync = (typeof process?.version !== 'undefined' ? (await import('node:fs')).writeFileSync : undefined);
32
+ const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
33
33
 
34
34
  export default (code, flags) => {
35
35
  globalThis.optLog = process.argv.includes('-opt-log');
@@ -52,7 +52,7 @@ export default (code, flags) => {
52
52
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
53
53
 
54
54
  const t2 = performance.now();
55
- opt(funcs, globals, pages);
55
+ opt(funcs, globals, pages, tags);
56
56
  if (flags.includes('info')) console.log(`3. optimized code in ${(performance.now() - t2).toFixed(2)}ms`);
57
57
 
58
58
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
@@ -75,6 +75,7 @@ export default (code, flags) => {
75
75
 
76
76
  if (target === 'c') {
77
77
  const c = toc(out);
78
+ out.c = c;
78
79
 
79
80
  if (outFile) {
80
81
  writeFileSync(outFile, c);
@@ -82,7 +83,7 @@ export default (code, flags) => {
82
83
  console.log(c);
83
84
  }
84
85
 
85
- process.exit();
86
+ if (process.version) process.exit();
86
87
  }
87
88
 
88
89
  if (target === 'native') {
@@ -98,7 +99,7 @@ export default (code, flags) => {
98
99
  // obvious command escape is obvious
99
100
  execSync(args.join(' '), { stdio: 'inherit' });
100
101
 
101
- process.exit();
102
+ if (process.version) process.exit();
102
103
  }
103
104
 
104
105
  return out;
package/compiler/opt.js CHANGED
@@ -11,7 +11,7 @@ const performWasmOp = (op, a, b) => {
11
11
  }
12
12
  };
13
13
 
14
- export default (funcs, globals, pages) => {
14
+ export default (funcs, globals, pages, tags) => {
15
15
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
16
16
  if (optLevel === 0) return;
17
17
 
@@ -99,10 +99,14 @@ export default (funcs, globals, pages) => {
99
99
 
100
100
  if (process.argv.includes('-opt-inline-only')) return;
101
101
 
102
+ const tagUse = tags.reduce((acc, x) => { acc[x.idx] = 0; return acc; }, {});
103
+
102
104
  // wasm transform pass
103
105
  for (const f of funcs) {
104
106
  const wasm = f.wasm;
105
107
 
108
+ const lastType = f.locals['#last_type'];
109
+
106
110
  let runs = 2; // how many by default? add arg?
107
111
  while (runs > 0) {
108
112
  runs--;
@@ -125,6 +129,8 @@ export default (funcs, globals, pages) => {
125
129
  if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
126
130
  if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
127
131
 
132
+ if (inst[0] === Opcodes.throw) tagUse[inst[1]]++;
133
+
128
134
  if (inst[0] === Opcodes.block) {
129
135
  // remove unneeded blocks (no brs inside)
130
136
  // block
@@ -141,7 +147,7 @@ export default (funcs, globals, pages) => {
141
147
  depth--;
142
148
  if (depth <= 0) break;
143
149
  }
144
- if (op === Opcodes.br || op === Opcodes.br_if) {
150
+ if (op === Opcodes.br || op === Opcodes.br_if || op === Opcodes.br_table) {
145
151
  hasBranch = true;
146
152
  break;
147
153
  }
@@ -221,6 +227,7 @@ export default (funcs, globals, pages) => {
221
227
  }
222
228
 
223
229
  if (checks === 0) {
230
+ // todo: review indexes below
224
231
  wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
225
232
  wasm.splice(i - 1, 1); // remove this inst
226
233
 
@@ -231,6 +238,13 @@ export default (funcs, globals, pages) => {
231
238
  }
232
239
  }
233
240
 
241
+ // remove setting last type if it is never gotten
242
+ if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
243
+ // replace this inst with drop
244
+ wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
245
+ if (i > 0) i--;
246
+ }
247
+
234
248
  if (i < 1) continue;
235
249
  let lastInst = wasm[i - 1];
236
250
 
@@ -531,5 +545,12 @@ export default (funcs, globals, pages) => {
531
545
  }
532
546
  }
533
547
 
548
+ for (const x in tagUse) {
549
+ if (tagUse[x] === 0) {
550
+ const el = tags.find(y => y.idx === x);
551
+ tags.splice(tags.indexOf(el), 1);
552
+ }
553
+ }
554
+
534
555
  // return funcs;
535
556
  };
package/compiler/parse.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { log } from "./log.js";
2
+ // import { parse } from 'acorn';
2
3
 
3
4
  // deno compat
4
5
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -6,26 +7,48 @@ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
6
7
  globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
7
8
  }
8
9
 
9
- // import { parse } from 'acorn';
10
+ // should we try to support types (while parsing)
11
+ const types = process.argv.includes('-parse-types');
12
+ globalThis.typedInput = types && process.argv.includes('-opt-types');
10
13
 
11
14
  // todo: review which to use by default
12
- const parser = process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? 'acorn';
13
- const { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
14
-
15
15
  // supported parsers:
16
16
  // - acorn
17
17
  // - meriyah
18
18
  // - hermes-parser
19
19
  // - @babel/parser
20
20
 
21
- // should we try to support types (while parsing)
22
- const types = process.argv.includes('-types');
21
+ let parser, parse;
22
+ const loadParser = async (fallbackParser = 'acorn', forceParser) => {
23
+ parser = forceParser ?? process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? fallbackParser;
24
+ 0, { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
25
+ };
26
+ globalThis._porf_loadParser = loadParser;
27
+ await loadParser(types ? '@babel/parser' : undefined);
23
28
 
24
29
  if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parser', `passed -types with a parser (${parser}) which does not support`);
25
30
 
26
31
  export default (input, flags) => {
27
- return parse(input, {
32
+ const ast = parse(input, {
33
+ // acorn
28
34
  ecmaVersion: 'latest',
29
- sourceType: flags.includes('module') ? 'module' : 'script'
35
+
36
+ // meriyah
37
+ next: true,
38
+ module: flags.includes('module'),
39
+ webcompat: true,
40
+
41
+ // babel
42
+ plugins: types ? ['estree', 'typescript'] : ['estree'],
43
+
44
+ // multiple
45
+ sourceType: flags.includes('module') ? 'module' : 'script',
46
+ ranges: false,
47
+ tokens: false,
48
+ comments: false,
30
49
  });
50
+
51
+ if (ast.type === 'File') return ast.program;
52
+
53
+ return ast;
31
54
  };
@@ -150,7 +150,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
150
150
 
151
151
  if (typeCount !== 0) localDecl.push(encodeLocal(typeCount, lastType));
152
152
 
153
- return encodeVector([ ...encodeVector(localDecl), ...x.wasm.flat().filter(x => x <= 0xff), Opcodes.end ]);
153
+ return encodeVector([ ...encodeVector(localDecl), ...x.wasm.flat().filter(x => x != null && x <= 0xff), Opcodes.end ]);
154
154
  }))
155
155
  );
156
156
 
@@ -32,8 +32,6 @@ export const Opcodes = {
32
32
  throw: 0x08,
33
33
  rethrow: 0x09,
34
34
 
35
- return: 0x0F,
36
-
37
35
  call: 0x10,
38
36
  call_indirect: 0x11,
39
37
  return_call: 0x12,
@@ -42,7 +40,10 @@ export const Opcodes = {
42
40
  end: 0x0b,
43
41
  br: 0x0c,
44
42
  br_if: 0x0d,
43
+ br_table: 0x0e,
44
+ return: 0x0f,
45
45
  call: 0x10,
46
+
46
47
  drop: 0x1a,
47
48
 
48
49
  local_get: 0x20,
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
 
@@ -25,11 +25,11 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
25
25
  const times = [];
26
26
 
27
27
  const t1 = performance.now();
28
- const { wasm, funcs, globals, tags, exceptions, pages } = compile(source, flags);
28
+ const { wasm, funcs, globals, tags, exceptions, pages, c } = compile(source, flags);
29
29
 
30
30
  if (source.includes('export function')) flags.push('module');
31
31
 
32
- // fs.writeFileSync('out.wasm', Buffer.from(wasm));
32
+ fs.writeFileSync('out.wasm', Buffer.from(wasm));
33
33
 
34
34
  times.push(performance.now() - t1);
35
35
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
@@ -126,8 +126,8 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
126
126
  }
127
127
 
128
128
  if (flags.includes('decomp')) {
129
- return { exports, wasm, times, decomps: funcs.map(x => decompile(x.wasm, x.name, x.index, x.locals, x.params, x.returns, funcs, globals, exceptions)) };
129
+ return { exports, wasm, times, decomps: funcs.map(x => decompile(x.wasm, x.name, x.index, x.locals, x.params, x.returns, funcs, globals, exceptions)), c };
130
130
  }
131
131
 
132
- return { exports, wasm, times, pages };
132
+ return { exports, wasm, times, pages, c };
133
133
  };
@@ -0,0 +1 @@
1
+ {"traceEvents":[{"pid":13264,"tid":15096,"ts":95423117674,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeIsolate","dur":4531,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423122578,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeContext","dur":3271,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423130539,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeContext","dur":346,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423152512,"tts":0,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":4996680,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423152519,"tts":0,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":431,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423152969,"tts":0,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":4389296}},{"pid":13264,"tid":15096,"ts":95423160029,"tts":0,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":4924936,"type":"task"}},{"pid":13264,"tid":15096,"ts":95423160032,"tts":0,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":324,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423160367,"tts":0,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":4440256}},{"pid":13264,"tid":15096,"ts":95423175256,"tts":67346,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":6472768,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423175267,"tts":67356,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":299,"tdur":300,"args":{}},{"pid":13264,"tid":15096,"ts":95423175578,"tts":67667,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":5881080}},{"pid":13264,"tid":15096,"ts":95423188220,"tts":80241,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":6928320,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423188222,"tts":80243,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":363,"tdur":362,"args":{}},{"pid":13264,"tid":15096,"ts":95423188596,"tts":80616,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6372328}},{"pid":13264,"tid":15096,"ts":95423191843,"tts":83862,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8430312,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423191845,"tts":83864,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":258,"tdur":239,"args":{}},{"pid":13264,"tid":15096,"ts":95423192113,"tts":84113,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6741768}},{"pid":13264,"tid":15096,"ts":95423199261,"tts":91198,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8882104,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423199264,"tts":91200,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":256,"tdur":220,"args":{}},{"pid":13264,"tid":15096,"ts":95423199530,"tts":91430,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6991296}},{"pid":13264,"tid":15096,"ts":95423203033,"tts":94931,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":9582528,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423203035,"tts":94933,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":147,"tdur":128,"args":{}},{"pid":13264,"tid":15096,"ts":95423203191,"tts":95069,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7206184}},{"pid":13264,"tid":15096,"ts":95423207062,"tts":98940,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10032360,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423207063,"tts":98941,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":98,"tdur":98,"args":{}},{"pid":13264,"tid":15096,"ts":95423207169,"tts":99046,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7617064}},{"pid":13264,"tid":15096,"ts":95423210148,"tts":102008,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10442184,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423210149,"tts":102009,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":83,"tdur":82,"args":{}},{"pid":13264,"tid":15096,"ts":95423210241,"tts":102101,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7743728}},{"pid":13264,"tid":15096,"ts":95598120854,"tts":172740055,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10650144,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95598120861,"tts":172740060,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":188,"tdur":189,"args":{}},{"pid":13264,"tid":15096,"ts":95598121065,"tts":172740264,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":8016624}},{"pid":13264,"tid":15096,"ts":95707243189,"tts":280108308,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":20,"tdur":19,"args":{}},{"pid":13264,"tid":15096,"ts":95707243201,"tts":280108319,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":8,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244259,"tts":280109369,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":10,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244265,"tts":280109374,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":3,"tdur":3,"args":{}},{"pid":13264,"tid":15096,"ts":95707244376,"tts":280109452,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":9,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244381,"tts":280109456,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":3,"tdur":4,"args":{}},{"pid":13264,"tid":15096,"ts":95707245192,"tts":280110267,"ph":"X","cat":"v8","name":"V8.GCIncrementalMarkingStart","dur":214,"tdur":214,"args":{"epoch":11,"reason":"memory reducer"}},{"pid":13264,"tid":15096,"ts":95707245464,"tts":280110540,"ph":"X","cat":"v8","name":"V8.GCIncrementalMarking","dur":7,"tdur":6,"args":{"epoch":11}},{"pid":13264,"tid":15096,"ts":95707245559,"tts":280110634,"ph":"B","cat":"devtools.timeline,v8","name":"MajorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8366408,"type":"external finalize"}},{"pid":13264,"tid":15096,"ts":95707245561,"tts":280110635,"ph":"X","cat":"v8","name":"V8.GCFinalizeMCReduceMemory","dur":1914,"tdur":1617,"args":{}},{"pid":13264,"tid":15096,"ts":95707247497,"tts":280112273,"ph":"E","cat":"devtools.timeline,v8","name":"MajorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6173808}},{"pid":13264,"tid":15096,"ts":95423116966,"tts":0,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"Command Prompt - node --trace-event-categories v8 runner/index.js bench/bf.js"}},{"pid":13264,"tid":15096,"ts":95423116967,"tts":0,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"21.6.0"}},{"pid":13264,"tid":15096,"ts":95423116968,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":13264,"tid":15096,"ts":95423116978,"tts":0,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"21.6.0","v8":"11.8.172.17-node.19","uv":"1.47.0","zlib":"1.3.0.1-motley-40e35a7","brotli":"1.1.0","ares":"1.20.1","modules":"120","nghttp2":"1.58.0","napi":"9","llhttp":"9.1.3","uvwasi":"0.0.19","acorn":"8.11.3","simdjson":"3.6.3","simdutf":"4.0.8","ada":"2.7.4","undici":"5.28.2","cjs_module_lexer":"1.2.2","base64":"0.5.1","openssl":"3.0.12+quic","cldr":"44.0","icu":"74.1","tz":"2023c","unicode":"15.1","ngtcp2":"0.8.1","nghttp3":"0.7.0"},"arch":"x64","platform":"win32","release":{"name":"node"}}}},{"pid":13264,"tid":9152,"ts":95423117058,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":13264,"tid":12304,"ts":95423117145,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":11816,"ts":95423117184,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":5044,"ts":95423117206,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":4032,"ts":95423117236,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":15096,"ts":95423116966,"tts":0,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"Command Prompt - node --trace-event-categories v8 runner/index.js bench/bf.js"}},{"pid":13264,"tid":15096,"ts":95423116967,"tts":0,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"21.6.0"}},{"pid":13264,"tid":15096,"ts":95423116968,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":13264,"tid":15096,"ts":95423116978,"tts":0,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"21.6.0","v8":"11.8.172.17-node.19","uv":"1.47.0","zlib":"1.3.0.1-motley-40e35a7","brotli":"1.1.0","ares":"1.20.1","modules":"120","nghttp2":"1.58.0","napi":"9","llhttp":"9.1.3","uvwasi":"0.0.19","acorn":"8.11.3","simdjson":"3.6.3","simdutf":"4.0.8","ada":"2.7.4","undici":"5.28.2","cjs_module_lexer":"1.2.2","base64":"0.5.1","openssl":"3.0.12+quic","cldr":"44.0","icu":"74.1","tz":"2023c","unicode":"15.1","ngtcp2":"0.8.1","nghttp3":"0.7.0"},"arch":"x64","platform":"win32","release":{"name":"node"}}}},{"pid":13264,"tid":9152,"ts":95423117058,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":13264,"tid":12304,"ts":95423117145,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":11816,"ts":95423117184,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":5044,"ts":95423117206,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":4032,"ts":95423117236,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}}]}
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.2.0-c6c8c81",
4
+ "version": "0.2.0-e562242",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/repl.js CHANGED
@@ -45,9 +45,9 @@ let prev = '';
45
45
  const run = async (source, _context, _filename, callback, run = true) => {
46
46
  // hack: print "secret" before latest code ran to only enable printing for new code
47
47
 
48
- let toRun = prev + `;\nprint(-0x1337);\n` + source.trim();
48
+ let toRun = (prev ? (prev + `;\nprint(-0x1337);\n`) : '') + source.trim();
49
49
 
50
- let shouldPrint = false;
50
+ let shouldPrint = !prev;
51
51
  const { exports, wasm, pages } = await compile(toRun, [], {}, str => {
52
52
  if (shouldPrint) process.stdout.write(str);
53
53
  if (str === '-4919') shouldPrint = true;