porffor 0.2.0-09999e8 → 0.2.0-29c477a
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 +21 -10
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +137 -22
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +14 -3
- package/compiler/sections.js +1 -1
- package/compiler/wasmSpec.js +3 -2
- package/compiler/wrap.js +89 -7
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Porffor <sup><sub>/ˈpɔrfɔr/ *(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
|
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
|
-
-
|
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
|
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`)
|
@@ -220,11 +228,14 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
|
|
220
228
|
- `-target=native` only:
|
221
229
|
- `-compiler=clang` to set compiler binary (path/name) to use to compile
|
222
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
|
223
234
|
- `-valtype=i32|i64|f64` (default: `f64`) to set valtype
|
224
235
|
- `-O0` to disable opt
|
225
236
|
- `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
|
226
|
-
- `-O2` to enable advanced opt (inlining)
|
227
|
-
- `-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
|
228
239
|
- `-no-run` to not run wasm output, just compile
|
229
240
|
- `-opt-log` to log some opts
|
230
241
|
- `-code-log` to log some codegen (you probably want `-funcs`)
|
@@ -238,20 +249,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
|
|
238
249
|
- `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
|
239
250
|
|
240
251
|
## VSCode extension
|
241
|
-
There is a vscode extension in `porffor-for-vscode` which tweaks
|
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).
|
242
253
|
|
243
254
|
## Isn't this the same as AssemblyScript/other Wasm langs?
|
244
255
|
No. they are not alike at all internally and have very different goals/ideals:
|
245
256
|
- Porffor is made as a generic JS engine, not for Wasm stuff specifically
|
246
|
-
- Porffor
|
247
|
-
- Porffor is
|
257
|
+
- Porffor primarily consumes JS
|
258
|
+
- Porffor is written in pure JS and compiles itself, not using Binaryen/etc
|
248
259
|
- (Also I didn't know it existed when I started this, lol)
|
249
260
|
|
250
261
|
## FAQ
|
251
262
|
|
252
263
|
### 1. Why the name?
|
253
264
|
`purple` in Welsh is `porffor`. Why purple?
|
254
|
-
- No other
|
265
|
+
- No other JS engine is purple colored
|
255
266
|
- Purple is pretty cool
|
256
267
|
- Purple apparently represents "ambition", which is.. one word to describe this project
|
257
268
|
- The hard to speak name is also the noise your brain makes in reaction to this idea!
|
package/compiler/builtins.js
CHANGED
@@ -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
|
-
|
205
|
+
params: [ valtypeBinary, Valtype.i32 ],
|
206
|
+
typedParams: true,
|
207
|
+
locals: [ Valtype.i32, Valtype.i32 ],
|
192
208
|
returns: [],
|
193
|
-
wasm: [
|
194
|
-
[ Opcodes.local_get,
|
195
|
-
|
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
|
]
|
package/compiler/codeGen.js
CHANGED
@@ -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
|
|
@@ -1244,16 +1278,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1278
|
return number(decl.value ? 1 : 0);
|
1245
1279
|
|
1246
1280
|
case 'string':
|
1247
|
-
|
1248
|
-
const rawElements = new Array(str.length);
|
1249
|
-
let j = 0;
|
1250
|
-
for (let i = 0; i < str.length; i++) {
|
1251
|
-
rawElements[i] = str.charCodeAt(i);
|
1252
|
-
}
|
1253
|
-
|
1254
|
-
return makeArray(scope, {
|
1255
|
-
rawElements
|
1256
|
-
}, global, name, false, 'i16')[0];
|
1281
|
+
return makeString(scope, decl.value, global, name);
|
1257
1282
|
|
1258
1283
|
default:
|
1259
1284
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1591,7 +1616,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1616
|
const func = funcs.find(x => x.index === idx);
|
1592
1617
|
|
1593
1618
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1619
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1620
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1621
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1622
|
|
1596
1623
|
let args = decl.arguments;
|
1597
1624
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1636,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1636
|
let out = [];
|
1610
1637
|
for (const arg of args) {
|
1611
1638
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1639
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1640
|
}
|
1614
1641
|
|
1615
1642
|
out.push([ Opcodes.call, idx ]);
|
1616
1643
|
|
1617
|
-
if (!
|
1644
|
+
if (!typedReturn) {
|
1618
1645
|
// let type;
|
1619
1646
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1647
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1692,100 @@ const knownType = (scope, type) => {
|
|
1665
1692
|
return null;
|
1666
1693
|
};
|
1667
1694
|
|
1695
|
+
const brTable = (input, bc, returns) => {
|
1696
|
+
const out = [];
|
1697
|
+
const keys = Object.keys(bc);
|
1698
|
+
const count = keys.length;
|
1699
|
+
|
1700
|
+
if (count === 1) {
|
1701
|
+
// return [
|
1702
|
+
// ...input,
|
1703
|
+
// ...bc[keys[0]]
|
1704
|
+
// ];
|
1705
|
+
return bc[keys[0]];
|
1706
|
+
}
|
1707
|
+
|
1708
|
+
if (count === 2) {
|
1709
|
+
// just use if else
|
1710
|
+
const other = keys.find(x => x !== 'default');
|
1711
|
+
return [
|
1712
|
+
...input,
|
1713
|
+
...number(other, Valtype.i32),
|
1714
|
+
[ Opcodes.i32_eq ],
|
1715
|
+
[ Opcodes.if, returns ],
|
1716
|
+
...bc[other],
|
1717
|
+
[ Opcodes.else ],
|
1718
|
+
...bc.default,
|
1719
|
+
[ Opcodes.end ]
|
1720
|
+
];
|
1721
|
+
}
|
1722
|
+
|
1723
|
+
for (let i = 0; i < count; i++) {
|
1724
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1725
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1726
|
+
}
|
1727
|
+
|
1728
|
+
const nums = keys.filter(x => +x);
|
1729
|
+
const offset = Math.min(...nums);
|
1730
|
+
const max = Math.max(...nums);
|
1731
|
+
|
1732
|
+
const table = [];
|
1733
|
+
let br = 1;
|
1734
|
+
|
1735
|
+
for (let i = offset; i <= max; i++) {
|
1736
|
+
// if branch for this num, go to that block
|
1737
|
+
if (bc[i]) {
|
1738
|
+
table.push(br);
|
1739
|
+
br++;
|
1740
|
+
continue;
|
1741
|
+
}
|
1742
|
+
|
1743
|
+
// else default
|
1744
|
+
table.push(0);
|
1745
|
+
}
|
1746
|
+
|
1747
|
+
out.push(
|
1748
|
+
[ Opcodes.block, Blocktype.void ],
|
1749
|
+
...input,
|
1750
|
+
...(offset > 0 ? [
|
1751
|
+
...number(offset, Valtype.i32),
|
1752
|
+
[ Opcodes.i32_sub ]
|
1753
|
+
] : []),
|
1754
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1755
|
+
);
|
1756
|
+
|
1757
|
+
// if you can guess why we sort the wrong way and then reverse
|
1758
|
+
// (instead of just sorting the correct way)
|
1759
|
+
// dm me and if you are correct and the first person
|
1760
|
+
// I will somehow shout you out or something
|
1761
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1762
|
+
|
1763
|
+
br = count - 1;
|
1764
|
+
for (const x of orderedBc) {
|
1765
|
+
out.push(
|
1766
|
+
[ Opcodes.end ],
|
1767
|
+
...bc[x],
|
1768
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1769
|
+
);
|
1770
|
+
br--;
|
1771
|
+
}
|
1772
|
+
|
1773
|
+
return [
|
1774
|
+
...out,
|
1775
|
+
[ Opcodes.end, 'br table end' ]
|
1776
|
+
];
|
1777
|
+
};
|
1778
|
+
|
1668
1779
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1669
1780
|
const known = knownType(scope, type);
|
1670
1781
|
if (known != null) {
|
1671
1782
|
return bc[known] ?? bc.default;
|
1672
1783
|
}
|
1673
1784
|
|
1674
|
-
|
1785
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1786
|
+
return brTable(type, bc, returns);
|
1675
1787
|
|
1788
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1789
|
const out = [
|
1677
1790
|
...type,
|
1678
1791
|
[ Opcodes.local_set, tmp ],
|
@@ -1759,11 +1872,12 @@ const extractTypeAnnotation = decl => {
|
|
1759
1872
|
elementType = extractTypeAnnotation(a.elementType).type;
|
1760
1873
|
}
|
1761
1874
|
|
1875
|
+
const typeName = type;
|
1762
1876
|
type = typeAnnoToPorfType(type);
|
1763
1877
|
|
1764
1878
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1765
1879
|
|
1766
|
-
return { type, elementType };
|
1880
|
+
return { type, typeName, elementType };
|
1767
1881
|
};
|
1768
1882
|
|
1769
1883
|
const generateVar = (scope, decl) => {
|
@@ -1793,6 +1907,11 @@ const generateVar = (scope, decl) => {
|
|
1793
1907
|
}
|
1794
1908
|
|
1795
1909
|
let idx = allocVar(scope, name, global);
|
1910
|
+
|
1911
|
+
if (typedInput && x.id.typeAnnotation) {
|
1912
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1913
|
+
}
|
1914
|
+
|
1796
1915
|
if (x.init) {
|
1797
1916
|
out = out.concat(generate(scope, x.init, global, name));
|
1798
1917
|
|
@@ -1802,10 +1921,6 @@ const generateVar = (scope, decl) => {
|
|
1802
1921
|
|
1803
1922
|
// hack: this follows spec properly but is mostly unneeded 😅
|
1804
1923
|
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1805
|
-
|
1806
|
-
if (typedInput && x.id.typeAnnotation) {
|
1807
|
-
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1808
|
-
}
|
1809
1924
|
}
|
1810
1925
|
|
1811
1926
|
return out;
|
@@ -2580,7 +2695,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2580
2695
|
|
2581
2696
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2582
2697
|
// hack: this is naughty and will break things!
|
2583
|
-
let newOut = number(0,
|
2698
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2584
2699
|
if (pages.hasString) {
|
2585
2700
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2586
2701
|
rawElements: new Array(1)
|
package/compiler/decompile.js
CHANGED
@@ -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(/
|
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
|
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
|
};
|
package/compiler/sections.js
CHANGED
@@ -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/compiler/wasmSpec.js
CHANGED
@@ -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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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