porffor 0.2.0-6fd3f05 → 0.2.0-8f0a405

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
@@ -131,6 +131,14 @@ No particular order and no guarentees, just what could happen soon™
131
131
  - Smarter inline selection (snapshots?)
132
132
  - Remove const ifs (`if (true)`, etc)
133
133
  - Use type(script) information to remove unneeded typechecker code
134
+ - Cool proposals
135
+ - [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
136
+ - [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
137
+ - [Array Equality](https://github.com/tc39/proposal-array-equality)
138
+ - [Declarations in Conditionals](https://github.com/tc39/proposal-Declarations-in-Conditionals)
139
+ - [Seeded Pseudo-Random Numbers](https://github.com/tc39/proposal-seeded-random)
140
+ - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
141
+ - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
134
142
 
135
143
  ## Performance
136
144
  *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.
@@ -180,7 +188,7 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
180
188
  - `wasmSpec.js`: "enums"/info from wasm spec
181
189
  - `wrap.js`: wrapper for compiler which instantiates and produces nice exports
182
190
 
183
- - `runner`: contains utils for running js with the compiler
191
+ - `runner`: contains utils for running JS with the compiler
184
192
  - `index.js`: the main file, you probably want to use this
185
193
  - `info.js`: runs with extra info printed
186
194
  - `repl.js`: basic repl (uses `node:repl`)
@@ -212,11 +220,14 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
212
220
  - `-target=native` only:
213
221
  - `-compiler=clang` to set compiler binary (path/name) to use to compile
214
222
  - `-cO=O3` to set compiler opt argument
223
+ - `-parser=acorn|@babel/parser|meriyah|hermes-parser` (default: `acorn`) to set which parser to use
224
+ - `-parse-types` to enable parsing type annotations/typescript. if `-parser` is unset, changes default to `@babel/parser`. does not type check
225
+ - `-opt-types` to perform optimizations using type annotations as compiler hints. does not type check
215
226
  - `-valtype=i32|i64|f64` (default: `f64`) to set valtype
216
227
  - `-O0` to disable opt
217
228
  - `-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)
229
+ - `-O2` to enable advanced opt (inlining). unstable
230
+ - `-O3` to enable advanceder opt (precompute const math). unstable
220
231
  - `-no-run` to not run wasm output, just compile
221
232
  - `-opt-log` to log some opts
222
233
  - `-code-log` to log some codegen (you probably want `-funcs`)
@@ -230,20 +241,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
230
241
  - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
231
242
 
232
243
  ## 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).
244
+ 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
245
 
235
246
  ## Isn't this the same as AssemblyScript/other Wasm langs?
236
247
  No. they are not alike at all internally and have very different goals/ideals:
237
248
  - 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
249
+ - Porffor primarily consumes JS
250
+ - Porffor is written in pure JS and compiles itself, not using Binaryen/etc
240
251
  - (Also I didn't know it existed when I started this, lol)
241
252
 
242
253
  ## FAQ
243
254
 
244
255
  ### 1. Why the name?
245
256
  `purple` in Welsh is `porffor`. Why purple?
246
- - No other js engine is purple colored
257
+ - No other JS engine is purple colored
247
258
  - Purple is pretty cool
248
259
  - Purple apparently represents "ambition", which is.. one word to describe this project
249
260
  - The hard to speak name is also the noise your brain makes in reaction to this idea!
package/compiler/2c.js CHANGED
@@ -124,7 +124,7 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
124
124
  const returns = f.returns.length > 0;
125
125
 
126
126
  const shouldInline = f.internal;
127
- 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`;
128
128
 
129
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);
130
130
  for (const x of localKeys) {
@@ -367,6 +367,11 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
367
367
  line(`return ${vals.pop()}`);
368
368
  }
369
369
 
370
+ if (f.name === 'main') {
371
+ out += '\n';
372
+ line(`return 0`);
373
+ }
374
+
370
375
  out += '}\n\n';
371
376
  }
372
377
 
@@ -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?
@@ -1356,13 +1393,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1393
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1394
  out.push(
1358
1395
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1396
+ setLastType(scope)
1360
1397
  );
1361
1398
  } else if (countLeftover(out) === 0) {
1362
1399
  out.push(...number(UNDEFINED));
1363
1400
  out.push(
1364
1401
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1402
+ setLastType(scope)
1366
1403
  );
1367
1404
  }
1368
1405
 
@@ -1380,8 +1417,7 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1417
  if (name && name.startsWith('__')) {
1381
1418
  const spl = name.slice(2).split('_');
1382
1419
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1420
+ protoName = spl[spl.length - 1];
1385
1421
 
1386
1422
  target = { ...decl.callee };
1387
1423
  target.name = spl.slice(0, -1).join('_');
@@ -1407,12 +1443,11 @@ const generateCall = (scope, decl, _global, _name) => {
1407
1443
  Opcodes.i32_from_u,
1408
1444
 
1409
1445
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1446
+ setLastType(scope)
1411
1447
  ];
1412
1448
  }
1413
1449
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1450
+ protoName = decl.callee.property.name;
1416
1451
 
1417
1452
  target = decl.callee.object;
1418
1453
  }
@@ -1457,7 +1492,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1492
  ...RTArrayUtil.getLength(getPointer),
1458
1493
 
1459
1494
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1495
+ setLastType(scope)
1461
1496
  ];
1462
1497
  continue;
1463
1498
  }
@@ -1488,7 +1523,7 @@ const generateCall = (scope, decl, _global, _name) => {
1488
1523
  ...protoOut,
1489
1524
 
1490
1525
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1526
+ setLastType(scope),
1492
1527
  [ Opcodes.end ]
1493
1528
  ];
1494
1529
  }
@@ -1589,7 +1624,7 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1624
  // ...number(type, Valtype.i32),
1590
1625
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1626
  // );
1592
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1627
+ } else out.push(setLastType(scope));
1593
1628
 
1594
1629
  return out;
1595
1630
  };
@@ -1614,7 +1649,28 @@ const unhackName = name => {
1614
1649
  return name;
1615
1650
  };
1616
1651
 
1652
+ const knownType = (scope, type) => {
1653
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1654
+ return type[0][1];
1655
+ }
1656
+
1657
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1658
+ const idx = type[0][1];
1659
+
1660
+ // type idx = var idx + 1
1661
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1662
+ if (v.metadata?.type != null) return v.metadata.type;
1663
+ }
1664
+
1665
+ return null;
1666
+ };
1667
+
1617
1668
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1669
+ const known = knownType(scope, type);
1670
+ if (known != null) {
1671
+ return bc[known] ?? bc.default;
1672
+ }
1673
+
1618
1674
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1619
1675
 
1620
1676
  const out = [
@@ -1668,6 +1724,49 @@ const allocVar = (scope, name, global = false) => {
1668
1724
  return idx;
1669
1725
  };
1670
1726
 
1727
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1728
+ const target = global ? globals : scope.locals;
1729
+
1730
+ target[name].metadata ??= {};
1731
+ for (const x in metadata) {
1732
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1733
+ }
1734
+ };
1735
+
1736
+ const typeAnnoToPorfType = x => {
1737
+ if (TYPES[x]) return TYPES[x];
1738
+ if (TYPES['_' + x]) return TYPES['_' + x];
1739
+
1740
+ switch (x) {
1741
+ case 'i32':
1742
+ return TYPES.number;
1743
+ }
1744
+
1745
+ return null;
1746
+ };
1747
+
1748
+ const extractTypeAnnotation = decl => {
1749
+ let a = decl;
1750
+ while (a.typeAnnotation) a = a.typeAnnotation;
1751
+
1752
+ let type, elementType;
1753
+ if (a.typeName) {
1754
+ type = a.typeName.name;
1755
+ } else if (a.type.endsWith('Keyword')) {
1756
+ type = a.type.slice(2, -7).toLowerCase();
1757
+ } else if (a.type === 'TSArrayType') {
1758
+ type = 'array';
1759
+ elementType = extractTypeAnnotation(a.elementType).type;
1760
+ }
1761
+
1762
+ const typeName = type;
1763
+ type = typeAnnoToPorfType(type);
1764
+
1765
+ // if (decl.name) console.log(decl.name, { type, elementType });
1766
+
1767
+ return { type, typeName, elementType };
1768
+ };
1769
+
1671
1770
  const generateVar = (scope, decl) => {
1672
1771
  let out = [];
1673
1772
 
@@ -1695,6 +1794,11 @@ const generateVar = (scope, decl) => {
1695
1794
  }
1696
1795
 
1697
1796
  let idx = allocVar(scope, name, global);
1797
+
1798
+ if (typedInput && x.id.typeAnnotation) {
1799
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1800
+ }
1801
+
1698
1802
  if (x.init) {
1699
1803
  out = out.concat(generate(scope, x.init, global, name));
1700
1804
 
@@ -1858,7 +1962,7 @@ const generateAssign = (scope, decl) => {
1858
1962
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
1963
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
1964
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
1965
+ getLastType(scope),
1862
1966
  // hack: type is idx+1
1863
1967
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1864
1968
  ];
@@ -2025,7 +2129,7 @@ const generateConditional = (scope, decl) => {
2025
2129
  // note type
2026
2130
  out.push(
2027
2131
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2132
+ setLastType(scope)
2029
2133
  );
2030
2134
 
2031
2135
  out.push([ Opcodes.else ]);
@@ -2034,7 +2138,7 @@ const generateConditional = (scope, decl) => {
2034
2138
  // note type
2035
2139
  out.push(
2036
2140
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2141
+ setLastType(scope)
2038
2142
  );
2039
2143
 
2040
2144
  out.push([ Opcodes.end ]);
@@ -2478,7 +2582,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2478
2582
 
2479
2583
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
2584
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2585
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2482
2586
  if (pages.hasString) {
2483
2587
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
2588
  rawElements: new Array(1)
@@ -2505,7 +2609,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
2609
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
2610
 
2507
2611
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2612
+ setLastType(scope)
2509
2613
  ],
2510
2614
 
2511
2615
  [TYPES.string]: [
@@ -2537,7 +2641,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
2641
  ...number(newPointer),
2538
2642
 
2539
2643
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2644
+ setLastType(scope)
2541
2645
  ],
2542
2646
 
2543
2647
  default: [ [ Opcodes.unreachable ] ]
@@ -2586,7 +2690,7 @@ const generateFunc = (scope, decl) => {
2586
2690
  if (decl.generator) return todo('generator functions are not supported');
2587
2691
 
2588
2692
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
2693
+ const params = decl.params ?? [];
2590
2694
 
2591
2695
  // const innerScope = { ...scope };
2592
2696
  // TODO: share scope/locals between !!!
@@ -2600,7 +2704,11 @@ const generateFunc = (scope, decl) => {
2600
2704
  };
2601
2705
 
2602
2706
  for (let i = 0; i < params.length; i++) {
2603
- allocVar(innerScope, params[i], false);
2707
+ allocVar(innerScope, params[i].name, false);
2708
+
2709
+ if (typedInput && params[i].typeAnnotation) {
2710
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2711
+ }
2604
2712
  }
2605
2713
 
2606
2714
  let body = objectHack(decl.body);
@@ -2639,117 +2747,6 @@ const generateFunc = (scope, decl) => {
2639
2747
  );
2640
2748
  }
2641
2749
 
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
2750
  func.wasm = wasm;
2754
2751
 
2755
2752
  funcs.push(func);
package/compiler/opt.js CHANGED
@@ -103,6 +103,8 @@ export default (funcs, globals, pages) => {
103
103
  for (const f of funcs) {
104
104
  const wasm = f.wasm;
105
105
 
106
+ const lastType = f.locals['#last_type'];
107
+
106
108
  let runs = 2; // how many by default? add arg?
107
109
  while (runs > 0) {
108
110
  runs--;
@@ -221,6 +223,7 @@ export default (funcs, globals, pages) => {
221
223
  }
222
224
 
223
225
  if (checks === 0) {
226
+ // todo: review indexes below
224
227
  wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
225
228
  wasm.splice(i - 1, 1); // remove this inst
226
229
 
@@ -231,6 +234,13 @@ export default (funcs, globals, pages) => {
231
234
  }
232
235
  }
233
236
 
237
+ // remove setting last type if it is never gotten
238
+ if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
239
+ // replace this inst with drop
240
+ wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
241
+ if (i > 0) i--;
242
+ }
243
+
234
244
  if (i < 1) continue;
235
245
  let lastInst = wasm[i - 1];
236
246
 
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,16 +7,9 @@ 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
-
11
- let parser, parse;
12
-
13
- const loadParser = async () => {
14
- parser = process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? 'acorn';
15
- 0, { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
16
- };
17
- globalThis._porf_loadParser = loadParser;
18
- await loadParser();
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');
19
13
 
20
14
  // todo: review which to use by default
21
15
  // supported parsers:
@@ -24,8 +18,13 @@ await loadParser();
24
18
  // - hermes-parser
25
19
  // - @babel/parser
26
20
 
27
- // should we try to support types (while parsing)
28
- 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);
29
28
 
30
29
  if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parser', `passed -types with a parser (${parser}) which does not support`);
31
30
 
@@ -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
 
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-6fd3f05",
4
+ "version": "0.2.0-8f0a405",
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;
package/tmp.c DELETED
@@ -1,69 +0,0 @@
1
-
2
- #include <stdio.h>
3
-
4
- struct ReturnValue {
5
- double value;
6
- long type;
7
- };
8
-
9
- double sum = 0;
10
- long sumdtype = 0;
11
- double counter = 0;
12
- long counterdtype = 0;
13
-
14
- double inline f64_f(double x, double y) {
15
- return (x - ((int)(x / y) * y));
16
- }
17
-
18
- struct ReturnValue isPrime(double number, long numberdtype) {
19
- double i = 0;
20
- long idtype = 0;
21
- double __tmpop_left = 0;
22
- double __tmpop_right = 0;
23
- long compare_left_pointer = 0;
24
- long compare_left_length = 0;
25
- long compare_right_pointer = 0;
26
- long compare_right_length = 0;
27
- long compare_index = 0;
28
- long compare_index_end = 0;
29
-
30
- if (number < 2e+0) {
31
- return (struct ReturnValue){ 1, 0e+0 };
32
- }
33
- i = 2e+0;
34
- idtype = 0;
35
- while (i < number) {
36
- if (f64_f(number, i) == 0e+0) {
37
- return (struct ReturnValue){ 1, 0e+0 };
38
- }
39
- i = i + 1e+0;
40
- }
41
- return (struct ReturnValue){ 1, 1e+0 };
42
- }
43
-
44
- double inline __console_log(double x) {
45
- printf("%f\n", x);
46
- printf("%c", (int)(1e+1));
47
- }
48
-
49
- int main() {
50
- long dlast_type = 0;
51
- double elogicinner_tmp = 0;
52
- long dtypeswitch_tmp = 0;
53
-
54
- sum = 0e+0;
55
- sumdtype = 0;
56
- counter = 0e+0;
57
- counterdtype = 0;
58
- while (counter <= 1e+5) {
59
- const struct ReturnValue _ = isPrime(counter, counterdtype);
60
- dlast_type = _.type;
61
- if ((unsigned long)(elogicinner_tmp = _.value) == 1e+0) {
62
- sum = sum + counter;
63
- sumdtype = 0;
64
- }
65
- counter = counter + 1e+0;
66
- }
67
- __console_log(sum);
68
- }
69
-