porffor 0.2.0-a759814 → 0.2.0-aea77ff

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
@@ -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,10 @@ 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
134
137
  - Cool proposals
135
138
  - [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
136
139
  - [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
@@ -139,6 +142,9 @@ No particular order and no guarentees, just what could happen soon™
139
142
  - [Seeded Pseudo-Random Numbers](https://github.com/tc39/proposal-seeded-random)
140
143
  - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
141
144
  - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
+ - Posts
146
+ - Inlining investigation
147
+ - Self hosted testing?
142
148
 
143
149
  ## Performance
144
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.
@@ -165,10 +171,12 @@ Mostly for reducing size. I do not really care about compiler perf/time as long
165
171
  - Remove unneeded blocks (no `br`s inside)
166
172
  - Remove unused imports
167
173
  - Use data segments for initing arrays/strings
174
+ - (Likely more not documented yet, todo)
168
175
 
169
176
  ### Wasm module
170
177
  - Type cache/index (no repeated types)
171
178
  - No main func if empty (and other exports)
179
+ - No tags if unused/optimized out
172
180
 
173
181
  ## Test262
174
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.
@@ -188,7 +196,7 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
188
196
  - `wasmSpec.js`: "enums"/info from wasm spec
189
197
  - `wrap.js`: wrapper for compiler which instantiates and produces nice exports
190
198
 
191
- - `runner`: contains utils for running js with the compiler
199
+ - `runner`: contains utils for running JS with the compiler
192
200
  - `index.js`: the main file, you probably want to use this
193
201
  - `info.js`: runs with extra info printed
194
202
  - `repl.js`: basic repl (uses `node:repl`)
@@ -241,20 +249,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
241
249
  - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
242
250
 
243
251
  ## VSCode extension
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).
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).
245
253
 
246
254
  ## Isn't this the same as AssemblyScript/other Wasm langs?
247
255
  No. they are not alike at all internally and have very different goals/ideals:
248
256
  - Porffor is made as a generic JS engine, not for Wasm stuff specifically
249
- - Porffor takes in JS, not a different language or typescript
250
- - 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
251
259
  - (Also I didn't know it existed when I started this, lol)
252
260
 
253
261
  ## FAQ
254
262
 
255
263
  ### 1. Why the name?
256
264
  `purple` in Welsh is `porffor`. Why purple?
257
- - No other js engine is purple colored
265
+ - No other JS engine is purple colored
258
266
  - Purple is pretty cool
259
267
  - Purple apparently represents "ambition", which is.. one word to describe this project
260
268
  - The hard to speak name is also the noise your brain makes in reaction to this idea!
@@ -1,4 +1,4 @@
1
- import { Blocktype, Opcodes, Valtype } from "./wasmSpec.js";
1
+ import { Blocktype, Opcodes, Valtype, ValtypeSize } from "./wasmSpec.js";
2
2
  import { number, i32x4 } from "./embedding.js";
3
3
 
4
4
  export const importedFuncs = [
@@ -29,6 +29,21 @@ for (let i = 0; i < importedFuncs.length; i++) {
29
29
 
30
30
  const char = c => number(c.charCodeAt(0));
31
31
 
32
+ const printStaticStr = str => {
33
+ const out = [];
34
+
35
+ for (let i = 0; i < str.length; i++) {
36
+ out.push(
37
+ // ...number(str.charCodeAt(i)),
38
+ ...number(str.charCodeAt(i), Valtype.i32),
39
+ Opcodes.i32_from_u,
40
+ [ Opcodes.call, importedFuncs.printChar ]
41
+ );
42
+ }
43
+
44
+ return out;
45
+ };
46
+
32
47
  // todo: somehow diff between these (undefined != null) while remaining falsey in wasm as a number value
33
48
  export const UNDEFINED = 0;
34
49
  export const NULL = 0;
@@ -187,12 +202,125 @@ export const BuiltinFuncs = function() {
187
202
 
188
203
 
189
204
  this.__console_log = {
190
- params: [ valtypeBinary ],
191
- locals: [],
205
+ params: [ valtypeBinary, Valtype.i32 ],
206
+ typedParams: true,
207
+ locals: [ Valtype.i32, Valtype.i32 ],
192
208
  returns: [],
193
- wasm: [
194
- [ Opcodes.local_get, 0 ],
195
- [ Opcodes.call, importedFuncs.print ],
209
+ wasm: (scope, { TYPES, typeSwitch }) => [
210
+ ...typeSwitch(scope, [ [ Opcodes.local_get, 1 ] ], {
211
+ [TYPES.number]: [
212
+ [ Opcodes.local_get, 0 ],
213
+ [ Opcodes.call, importedFuncs.print ],
214
+ ],
215
+ [TYPES.boolean]: [
216
+ [ Opcodes.local_get, 0 ],
217
+ Opcodes.i32_to_u,
218
+ [ Opcodes.if, Blocktype.void ],
219
+ ...printStaticStr('true'),
220
+ [ Opcodes.else ],
221
+ ...printStaticStr('false'),
222
+ [ Opcodes.end ]
223
+ ],
224
+ [TYPES.string]: [
225
+ // simply print a string :))
226
+ // cache input pointer as i32
227
+ [ Opcodes.local_get, 0 ],
228
+ Opcodes.i32_to_u,
229
+ [ Opcodes.local_tee, 2 ],
230
+
231
+ // make end pointer
232
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
233
+ ...number(ValtypeSize.i16, Valtype.i32),
234
+ [ Opcodes.i32_mul ],
235
+
236
+ [ Opcodes.local_get, 2 ],
237
+ [ Opcodes.i32_add ],
238
+ [ Opcodes.local_set, 3 ],
239
+
240
+ [ Opcodes.loop, Blocktype.void ],
241
+
242
+ // print current char
243
+ [ Opcodes.local_get, 2 ],
244
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
245
+ Opcodes.i32_from_u,
246
+ [ Opcodes.call, importedFuncs.printChar ],
247
+
248
+ // increment pointer by sizeof i16
249
+ [ Opcodes.local_get, 2 ],
250
+ ...number(ValtypeSize.i16, Valtype.i32),
251
+ [ Opcodes.i32_add ],
252
+ [ Opcodes.local_tee, 2 ],
253
+
254
+ // if pointer != end pointer, loop
255
+ [ Opcodes.local_get, 3 ],
256
+ [ Opcodes.i32_ne ],
257
+ [ Opcodes.br_if, 0 ],
258
+
259
+ [ Opcodes.end ]
260
+ ],
261
+ [TYPES._array]: [
262
+ ...printStaticStr('[ '),
263
+
264
+ // cache input pointer as i32
265
+ [ Opcodes.local_get, 0 ],
266
+ Opcodes.i32_to_u,
267
+ [ Opcodes.local_tee, 2 ],
268
+
269
+ // make end pointer
270
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
271
+ ...number(ValtypeSize[valtype], Valtype.i32),
272
+ [ Opcodes.i32_mul ],
273
+
274
+ [ Opcodes.local_get, 2 ],
275
+ [ Opcodes.i32_add ],
276
+ [ Opcodes.local_set, 3 ],
277
+
278
+ [ Opcodes.loop, Blocktype.void ],
279
+
280
+ // print current char
281
+ [ Opcodes.local_get, 2 ],
282
+ [ Opcodes.load, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
283
+ [ Opcodes.call, importedFuncs.print ],
284
+
285
+ // increment pointer by sizeof valtype
286
+ [ Opcodes.local_get, 2 ],
287
+ ...number(ValtypeSize[valtype], Valtype.i32),
288
+ [ Opcodes.i32_add ],
289
+ [ Opcodes.local_tee, 2 ],
290
+
291
+ // if pointer != end pointer, print separator and loop
292
+ [ Opcodes.local_get, 3 ],
293
+ [ Opcodes.i32_ne ],
294
+ [ Opcodes.if, Blocktype.void ],
295
+ ...printStaticStr(', '),
296
+ [ Opcodes.br, 1 ],
297
+ [ Opcodes.end ],
298
+
299
+ [ Opcodes.end ],
300
+
301
+ ...printStaticStr(' ]'),
302
+ ],
303
+ [TYPES.undefined]: [
304
+ ...printStaticStr('undefined')
305
+ ],
306
+ [TYPES.function]: [
307
+ ...printStaticStr('function () {}')
308
+ ],
309
+ [TYPES.object]: [
310
+ [ Opcodes.local_get, 0 ],
311
+ Opcodes.i32_to_u,
312
+ [ Opcodes.if, Blocktype.void ],
313
+ ...printStaticStr('{}'),
314
+ [ Opcodes.else ],
315
+ ...printStaticStr('null'),
316
+ [ Opcodes.end ]
317
+ ],
318
+ default: [
319
+ [ Opcodes.local_get, 0 ],
320
+ [ Opcodes.call, importedFuncs.print ],
321
+ ]
322
+ }, Blocktype.void),
323
+
196
324
  ...char('\n'),
197
325
  [ Opcodes.call, importedFuncs.printChar ]
198
326
  ]
@@ -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";
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
68
 
69
69
  case 'ArrowFunctionExpression':
70
70
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
71
+ const func = generateFunc(scope, decl);
72
+
73
+ if (decl.type.endsWith('Expression')) {
74
+ return number(func.index);
75
+ }
76
+
72
77
  return [];
73
78
 
74
79
  case 'BlockStatement':
@@ -947,6 +952,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
952
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
953
  }
949
954
 
955
+ if (typeof wasm === 'function') {
956
+ const scope = {
957
+ name,
958
+ params,
959
+ locals,
960
+ returns,
961
+ localInd: allLocals.length,
962
+ };
963
+
964
+ wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
965
+ }
966
+
950
967
  let baseGlobalIdx, i = 0;
951
968
  for (const type of globalTypes) {
952
969
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1227,6 +1244,23 @@ const getNodeType = (scope, node) => {
1227
1244
  return ret;
1228
1245
  };
1229
1246
 
1247
+ const toString = (scope, wasm, type) => {
1248
+ const tmp = localTmp(scope, '#tostring_tmp');
1249
+ return [
1250
+ ...wasm,
1251
+ [ Opcodes.local_set, tmp ],
1252
+
1253
+ ...typeSwitch(scope, type, {
1254
+ [TYPES.string]: [
1255
+ [ Opcodes.local_get, tmp ]
1256
+ ],
1257
+ [TYPES.undefined]: [
1258
+ // [ Opcodes.]
1259
+ ]
1260
+ })
1261
+ ]
1262
+ };
1263
+
1230
1264
  const generateLiteral = (scope, decl, global, name) => {
1231
1265
  if (decl.value === null) return number(NULL);
1232
1266
 
@@ -1591,7 +1625,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1625
  const func = funcs.find(x => x.index === idx);
1592
1626
 
1593
1627
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1628
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1629
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1630
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1631
 
1596
1632
  let args = decl.arguments;
1597
1633
  if (func && args.length < paramCount) {
@@ -1609,12 +1645,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1645
  let out = [];
1610
1646
  for (const arg of args) {
1611
1647
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1648
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1649
  }
1614
1650
 
1615
1651
  out.push([ Opcodes.call, idx ]);
1616
1652
 
1617
- if (!userFunc) {
1653
+ if (!typedReturn) {
1618
1654
  // let type;
1619
1655
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1656
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1701,100 @@ const knownType = (scope, type) => {
1665
1701
  return null;
1666
1702
  };
1667
1703
 
1704
+ const brTable = (input, bc, returns) => {
1705
+ const out = [];
1706
+ const keys = Object.keys(bc);
1707
+ const count = keys.length;
1708
+
1709
+ if (count === 1) {
1710
+ // return [
1711
+ // ...input,
1712
+ // ...bc[keys[0]]
1713
+ // ];
1714
+ return bc[keys[0]];
1715
+ }
1716
+
1717
+ if (count === 2) {
1718
+ // just use if else
1719
+ const other = keys.find(x => x !== 'default');
1720
+ return [
1721
+ ...input,
1722
+ ...number(other, Valtype.i32),
1723
+ [ Opcodes.i32_eq ],
1724
+ [ Opcodes.if, returns ],
1725
+ ...bc[other],
1726
+ [ Opcodes.else ],
1727
+ ...bc.default,
1728
+ [ Opcodes.end ]
1729
+ ];
1730
+ }
1731
+
1732
+ for (let i = 0; i < count; i++) {
1733
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1734
+ else out.push([ Opcodes.block, Blocktype.void ]);
1735
+ }
1736
+
1737
+ const nums = keys.filter(x => +x);
1738
+ const offset = Math.min(...nums);
1739
+ const max = Math.max(...nums);
1740
+
1741
+ const table = [];
1742
+ let br = 1;
1743
+
1744
+ for (let i = offset; i <= max; i++) {
1745
+ // if branch for this num, go to that block
1746
+ if (bc[i]) {
1747
+ table.push(br);
1748
+ br++;
1749
+ continue;
1750
+ }
1751
+
1752
+ // else default
1753
+ table.push(0);
1754
+ }
1755
+
1756
+ out.push(
1757
+ [ Opcodes.block, Blocktype.void ],
1758
+ ...input,
1759
+ ...(offset > 0 ? [
1760
+ ...number(offset, Valtype.i32),
1761
+ [ Opcodes.i32_sub ]
1762
+ ] : []),
1763
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1764
+ );
1765
+
1766
+ // if you can guess why we sort the wrong way and then reverse
1767
+ // (instead of just sorting the correct way)
1768
+ // dm me and if you are correct and the first person
1769
+ // I will somehow shout you out or something
1770
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1771
+
1772
+ br = count - 1;
1773
+ for (const x of orderedBc) {
1774
+ out.push(
1775
+ [ Opcodes.end ],
1776
+ ...bc[x],
1777
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1778
+ );
1779
+ br--;
1780
+ }
1781
+
1782
+ return [
1783
+ ...out,
1784
+ [ Opcodes.end, 'br table end' ]
1785
+ ];
1786
+ };
1787
+
1668
1788
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1669
1789
  const known = knownType(scope, type);
1670
1790
  if (known != null) {
1671
1791
  return bc[known] ?? bc.default;
1672
1792
  }
1673
1793
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1794
+ if (process.argv.includes('-typeswitch-use-brtable'))
1795
+ return brTable(type, bc, returns);
1675
1796
 
1797
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1798
  const out = [
1677
1799
  ...type,
1678
1800
  [ Opcodes.local_set, tmp ],
@@ -1794,6 +1916,11 @@ const generateVar = (scope, decl) => {
1794
1916
  }
1795
1917
 
1796
1918
  let idx = allocVar(scope, name, global);
1919
+
1920
+ if (typedInput && x.id.typeAnnotation) {
1921
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1922
+ }
1923
+
1797
1924
  if (x.init) {
1798
1925
  out = out.concat(generate(scope, x.init, global, name));
1799
1926
 
@@ -1803,10 +1930,6 @@ const generateVar = (scope, decl) => {
1803
1930
 
1804
1931
  // hack: this follows spec properly but is mostly unneeded 😅
1805
1932
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1806
-
1807
- if (typedInput && x.id.typeAnnotation) {
1808
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1809
- }
1810
1933
  }
1811
1934
 
1812
1935
  return out;
@@ -2581,7 +2704,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2581
2704
 
2582
2705
  // // todo: we should only do this for strings but we don't know at compile-time :(
2583
2706
  // hack: this is naughty and will break things!
2584
- let newOut = number(0, Valtype.f64), newPointer = -1;
2707
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2585
2708
  if (pages.hasString) {
2586
2709
  0, [ newOut, newPointer ] = makeArray(scope, {
2587
2710
  rawElements: new Array(1)
@@ -15,7 +15,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
15
15
  if (name) out += `${makeSignature(params, returns)} ;; $${name} (${ind})\n`;
16
16
 
17
17
  const justLocals = Object.values(locals).sort((a, b) => a.idx - b.idx).slice(params.length);
18
- if (justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
18
+ if (name && justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
19
19
 
20
20
  let i = -1, lastInst;
21
21
  let byte = 0;
@@ -32,7 +32,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
32
32
  inst = [ [ inst[0], inst[1] ], ...inst.slice(2) ];
33
33
  }
34
34
 
35
- if (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all) depth--;
35
+ if (depth > 0 && (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all)) depth--;
36
36
 
37
37
  out += ' '.repeat(Math.max(0, depth * 2));
38
38
 
@@ -119,7 +119,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
119
119
  export const highlightAsm = asm => asm
120
120
  .replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
121
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`)
122
+ .replace(/(i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `\x1B[36m${_}\x1B[0m`)
123
123
  .replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
124
124
  .replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
125
125
  .replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
package/compiler/index.js CHANGED
@@ -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);
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,6 +99,8 @@ 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;
@@ -127,6 +129,8 @@ export default (funcs, globals, pages) => {
127
129
  if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
128
130
  if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
129
131
 
132
+ if (inst[0] === Opcodes.throw) tagUse[inst[1]]++;
133
+
130
134
  if (inst[0] === Opcodes.block) {
131
135
  // remove unneeded blocks (no brs inside)
132
136
  // block
@@ -143,7 +147,7 @@ export default (funcs, globals, pages) => {
143
147
  depth--;
144
148
  if (depth <= 0) break;
145
149
  }
146
- if (op === Opcodes.br || op === Opcodes.br_if) {
150
+ if (op === Opcodes.br || op === Opcodes.br_if || op === Opcodes.br_table) {
147
151
  hasBranch = true;
148
152
  break;
149
153
  }
@@ -235,7 +239,7 @@ export default (funcs, globals, pages) => {
235
239
  }
236
240
 
237
241
  // remove setting last type if it is never gotten
238
- if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType.idx) {
242
+ if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
239
243
  // replace this inst with drop
240
244
  wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
241
245
  if (i > 0) i--;
@@ -541,5 +545,12 @@ export default (funcs, globals, pages) => {
541
545
  }
542
546
  }
543
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
+
544
555
  // return funcs;
545
556
  };
@@ -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,5 +1,6 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
+ import { encodeVector, encodeLocal } from './encoding.js';
3
4
  // import fs from 'node:fs';
4
5
 
5
6
  const bold = x => `\u001b[1m${x}\u001b[0m`;
@@ -35,14 +36,95 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
35
36
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
36
37
 
37
38
  const t2 = performance.now();
38
- const { instance } = await WebAssembly.instantiate(wasm, {
39
- '': {
40
- p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
41
- c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
42
- t: _ => performance.now(),
43
- ...customImports
39
+
40
+ let instance;
41
+ try {
42
+ 0, { instance } = await WebAssembly.instantiate(wasm, {
43
+ '': {
44
+ p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
45
+ c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
46
+ t: _ => performance.now(),
47
+ ...customImports
48
+ }
49
+ });
50
+ } catch (e) {
51
+ const funcInd = parseInt(e.message.match(/function #([0-9]+) /)[1]);
52
+ const blobOffset = parseInt(e.message.split('@')[1]);
53
+
54
+ // convert blob offset -> function wasm offset.
55
+ // this is not good code and is somewhat duplicated
56
+ // I just want it to work for debugging, I don't care about perf/yes
57
+
58
+ const func = funcs.find(x => x.index === funcInd);
59
+ const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
60
+
61
+ let localDecl = [], typeCount = 0, lastType;
62
+ for (let i = 0; i < locals.length; i++) {
63
+ const local = locals[i];
64
+ if (i !== 0 && local.type !== lastType) {
65
+ localDecl.push(encodeLocal(typeCount, lastType));
66
+ typeCount = 0;
67
+ }
68
+
69
+ typeCount++;
70
+ lastType = local.type;
71
+ }
72
+
73
+ if (typeCount !== 0) localDecl.push(encodeLocal(typeCount, lastType));
74
+
75
+ const toFind = encodeVector(localDecl).concat(func.wasm.flat().filter(x => x != null && x <= 0xff).slice(0, 40));
76
+
77
+ let i = 0;
78
+ for (; i < wasm.length; i++) {
79
+ let mismatch = false;
80
+ for (let j = 0; j < toFind.length; j++) {
81
+ if (wasm[i + j] !== toFind[j]) {
82
+ mismatch = true;
83
+ break;
84
+ }
85
+ }
86
+
87
+ if (!mismatch) break;
44
88
  }
45
- });
89
+
90
+ if (i === wasm.length) throw e;
91
+
92
+ const offset = (blobOffset - i) + encodeVector(localDecl).length;
93
+
94
+ let cumLen = 0;
95
+ i = 0;
96
+ for (; i < func.wasm.length; i++) {
97
+ cumLen += func.wasm[i].filter(x => x != null && x <= 0xff).length;
98
+ if (cumLen === offset) break;
99
+ }
100
+
101
+ if (cumLen !== offset) throw e;
102
+
103
+ i -= 1;
104
+
105
+ console.log(`\x1B[35m\x1B[1mporffor backtrace\u001b[0m`);
106
+
107
+ console.log('\x1B[4m' + func.name + '\x1B[0m');
108
+
109
+ const surrounding = 6;
110
+
111
+ const decomp = decompile(func.wasm.slice(i - surrounding, i + surrounding + 1), '', 0, func.locals, func.params, func.returns, funcs, globals, exceptions).slice(0, -1).split('\n');
112
+
113
+ const noAnsi = s => s.replace(/\u001b\[[0-9]+m/g, '');
114
+ let longest = 0;
115
+ for (let j = 0; j < decomp.length; j++) {
116
+ longest = Math.max(longest, noAnsi(decomp[j]).length);
117
+ }
118
+
119
+ const middle = Math.floor(decomp.length / 2);
120
+ decomp[middle] = `\x1B[47m\x1B[30m${noAnsi(decomp[middle])}${'\u00a0'.repeat(longest - noAnsi(decomp[middle]).length)}\x1B[0m`;
121
+
122
+ console.log('\x1B[90m...\x1B[0m');
123
+ console.log(decomp.join('\n'));
124
+ console.log('\x1B[90m...\x1B[0m\n');
125
+
126
+ throw e;
127
+ }
46
128
 
47
129
  times.push(performance.now() - t2);
48
130
  if (flags.includes('info')) console.log(`instantiated in ${times[1].toFixed(2)}ms`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.2.0-a759814",
4
+ "version": "0.2.0-aea77ff",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {