porffor 0.2.0-9ca9aed → 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 +29 -10
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +263 -155
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +23 -2
- package/compiler/parse.js +11 -12
- package/compiler/sections.js +1 -1
- package/compiler/wasmSpec.js +3 -2
- package/compiler/wrap.js +89 -7
- package/package.json +1 -1
- package/runner/repl.js +2 -2
- package/tmp.c +0 -71
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,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
|
-
-
|
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
|
+
- Inlining investigation
|
147
|
+
- Self hosted testing?
|
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
|
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
|
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
|
239
|
-
- Porffor is
|
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
|
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/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':
|
@@ -214,6 +219,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
214
219
|
}
|
215
220
|
|
216
221
|
default:
|
222
|
+
if (decl.type.startsWith('TS')) {
|
223
|
+
// ignore typescript nodes
|
224
|
+
return [];
|
225
|
+
}
|
226
|
+
|
217
227
|
return todo(`no generation for ${decl.type}!`);
|
218
228
|
}
|
219
229
|
};
|
@@ -360,12 +370,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
370
|
...right,
|
361
371
|
// note type
|
362
372
|
...rightType,
|
363
|
-
|
373
|
+
setLastType(scope),
|
364
374
|
[ Opcodes.else ],
|
365
375
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
376
|
// note type
|
367
377
|
...leftType,
|
368
|
-
|
378
|
+
setLastType(scope),
|
369
379
|
[ Opcodes.end ],
|
370
380
|
Opcodes.i32_from
|
371
381
|
];
|
@@ -379,12 +389,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
389
|
...right,
|
380
390
|
// note type
|
381
391
|
...rightType,
|
382
|
-
|
392
|
+
setLastType(scope),
|
383
393
|
[ Opcodes.else ],
|
384
394
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
395
|
// note type
|
386
396
|
...leftType,
|
387
|
-
|
397
|
+
setLastType(scope),
|
388
398
|
[ Opcodes.end ]
|
389
399
|
];
|
390
400
|
};
|
@@ -852,7 +862,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
852
862
|
|
853
863
|
let tmpLeft, tmpRight;
|
854
864
|
// if equal op, check if strings for compareStrings
|
855
|
-
if (op === '===' || op === '==' || op === '!==' || op === '!=') {
|
865
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
866
|
+
const knownLeft = knownType(scope, leftType);
|
867
|
+
const knownRight = knownType(scope, rightType);
|
868
|
+
|
869
|
+
// todo: intelligent partial skip later
|
870
|
+
// if neither known are string, stop this madness
|
871
|
+
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
872
|
+
return;
|
873
|
+
}
|
874
|
+
|
856
875
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
876
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
877
|
|
@@ -902,7 +921,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
902
921
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
903
922
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
904
923
|
// }
|
905
|
-
}
|
924
|
+
})();
|
906
925
|
|
907
926
|
return finalise([
|
908
927
|
...left,
|
@@ -933,6 +952,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
952
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
953
|
}
|
935
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
|
+
|
936
967
|
let baseGlobalIdx, i = 0;
|
937
968
|
for (const type of globalTypes) {
|
938
969
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1013,18 +1044,7 @@ const TYPES = {
|
|
1013
1044
|
|
1014
1045
|
// these are not "typeof" types but tracked internally
|
1015
1046
|
_array: 0x10,
|
1016
|
-
_regexp: 0x11
|
1017
|
-
|
1018
|
-
// typed arrays
|
1019
|
-
_int8array: 0x20,
|
1020
|
-
_uint8array: 0x21,
|
1021
|
-
_uint8clampedarray: 0x22,
|
1022
|
-
_int16array: 0x23,
|
1023
|
-
_uint16array: 0x24,
|
1024
|
-
_int32array: 0x25,
|
1025
|
-
_uint32array: 0x26,
|
1026
|
-
_float32array: 0x27,
|
1027
|
-
_float64array: 0x28,
|
1047
|
+
_regexp: 0x11
|
1028
1048
|
};
|
1029
1049
|
|
1030
1050
|
const TYPE_NAMES = {
|
@@ -1062,11 +1082,13 @@ const setType = (scope, _name, type) => {
|
|
1062
1082
|
|
1063
1083
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1064
1084
|
|
1085
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1065
1086
|
if (scope.locals[name]) return [
|
1066
1087
|
...out,
|
1067
1088
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1068
1089
|
];
|
1069
1090
|
|
1091
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1070
1092
|
if (globals[name]) return [
|
1071
1093
|
...out,
|
1072
1094
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
@@ -1075,6 +1097,15 @@ const setType = (scope, _name, type) => {
|
|
1075
1097
|
// throw new Error('could not find var');
|
1076
1098
|
};
|
1077
1099
|
|
1100
|
+
const getLastType = scope => {
|
1101
|
+
scope.gotLastType = true;
|
1102
|
+
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1103
|
+
};
|
1104
|
+
|
1105
|
+
const setLastType = scope => {
|
1106
|
+
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1107
|
+
};
|
1108
|
+
|
1078
1109
|
const getNodeType = (scope, node) => {
|
1079
1110
|
const inner = () => {
|
1080
1111
|
if (node.type === 'Literal') {
|
@@ -1103,7 +1134,19 @@ const getNodeType = (scope, node) => {
|
|
1103
1134
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1104
1135
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1105
1136
|
|
1106
|
-
|
1137
|
+
// check if this is a prototype function
|
1138
|
+
// if so and there is only one impl (eg charCodeAt)
|
1139
|
+
// use that return type as that is the only possibility
|
1140
|
+
// (if non-matching type it would error out)
|
1141
|
+
if (name.startsWith('__')) {
|
1142
|
+
const spl = name.slice(2).split('_');
|
1143
|
+
|
1144
|
+
const func = spl[spl.length - 1];
|
1145
|
+
const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
|
1146
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1147
|
+
}
|
1148
|
+
|
1149
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1107
1150
|
|
1108
1151
|
// presume
|
1109
1152
|
// todo: warn here?
|
@@ -1188,7 +1231,7 @@ const getNodeType = (scope, node) => {
|
|
1188
1231
|
return TYPES.number;
|
1189
1232
|
}
|
1190
1233
|
|
1191
|
-
if (scope.locals['#last_type']) return [
|
1234
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1192
1235
|
|
1193
1236
|
// presume
|
1194
1237
|
// todo: warn here?
|
@@ -1201,6 +1244,23 @@ const getNodeType = (scope, node) => {
|
|
1201
1244
|
return ret;
|
1202
1245
|
};
|
1203
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
|
+
|
1204
1264
|
const generateLiteral = (scope, decl, global, name) => {
|
1205
1265
|
if (decl.value === null) return number(NULL);
|
1206
1266
|
|
@@ -1367,13 +1427,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1367
1427
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1368
1428
|
out.push(
|
1369
1429
|
...getNodeType(scope, finalStatement),
|
1370
|
-
|
1430
|
+
setLastType(scope)
|
1371
1431
|
);
|
1372
1432
|
} else if (countLeftover(out) === 0) {
|
1373
1433
|
out.push(...number(UNDEFINED));
|
1374
1434
|
out.push(
|
1375
1435
|
...number(TYPES.undefined, Valtype.i32),
|
1376
|
-
|
1436
|
+
setLastType(scope)
|
1377
1437
|
);
|
1378
1438
|
}
|
1379
1439
|
|
@@ -1391,8 +1451,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1391
1451
|
if (name && name.startsWith('__')) {
|
1392
1452
|
const spl = name.slice(2).split('_');
|
1393
1453
|
|
1394
|
-
|
1395
|
-
protoName = func;
|
1454
|
+
protoName = spl[spl.length - 1];
|
1396
1455
|
|
1397
1456
|
target = { ...decl.callee };
|
1398
1457
|
target.name = spl.slice(0, -1).join('_');
|
@@ -1418,12 +1477,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1418
1477
|
Opcodes.i32_from_u,
|
1419
1478
|
|
1420
1479
|
...number(TYPES.boolean, Valtype.i32),
|
1421
|
-
|
1480
|
+
setLastType(scope)
|
1422
1481
|
];
|
1423
1482
|
}
|
1424
1483
|
|
1425
|
-
|
1426
|
-
protoName = func;
|
1484
|
+
protoName = decl.callee.property.name;
|
1427
1485
|
|
1428
1486
|
target = decl.callee.object;
|
1429
1487
|
}
|
@@ -1468,7 +1526,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1468
1526
|
...RTArrayUtil.getLength(getPointer),
|
1469
1527
|
|
1470
1528
|
...number(TYPES.number, Valtype.i32),
|
1471
|
-
|
1529
|
+
setLastType(scope)
|
1472
1530
|
];
|
1473
1531
|
continue;
|
1474
1532
|
}
|
@@ -1499,7 +1557,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1499
1557
|
...protoOut,
|
1500
1558
|
|
1501
1559
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1502
|
-
|
1560
|
+
setLastType(scope),
|
1503
1561
|
[ Opcodes.end ]
|
1504
1562
|
];
|
1505
1563
|
}
|
@@ -1567,7 +1625,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1567
1625
|
const func = funcs.find(x => x.index === idx);
|
1568
1626
|
|
1569
1627
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1570
|
-
const
|
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);
|
1571
1631
|
|
1572
1632
|
let args = decl.arguments;
|
1573
1633
|
if (func && args.length < paramCount) {
|
@@ -1585,12 +1645,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1585
1645
|
let out = [];
|
1586
1646
|
for (const arg of args) {
|
1587
1647
|
out = out.concat(generate(scope, arg));
|
1588
|
-
if (
|
1648
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1589
1649
|
}
|
1590
1650
|
|
1591
1651
|
out.push([ Opcodes.call, idx ]);
|
1592
1652
|
|
1593
|
-
if (!
|
1653
|
+
if (!typedReturn) {
|
1594
1654
|
// let type;
|
1595
1655
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1596
1656
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1600,7 +1660,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1660
|
// ...number(type, Valtype.i32),
|
1601
1661
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1602
1662
|
// );
|
1603
|
-
} else out.push(
|
1663
|
+
} else out.push(setLastType(scope));
|
1604
1664
|
|
1605
1665
|
return out;
|
1606
1666
|
};
|
@@ -1625,9 +1685,116 @@ const unhackName = name => {
|
|
1625
1685
|
return name;
|
1626
1686
|
};
|
1627
1687
|
|
1688
|
+
const knownType = (scope, type) => {
|
1689
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1690
|
+
return type[0][1];
|
1691
|
+
}
|
1692
|
+
|
1693
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1694
|
+
const idx = type[0][1];
|
1695
|
+
|
1696
|
+
// type idx = var idx + 1
|
1697
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1698
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1699
|
+
}
|
1700
|
+
|
1701
|
+
return null;
|
1702
|
+
};
|
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
|
+
|
1628
1788
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1629
|
-
const
|
1789
|
+
const known = knownType(scope, type);
|
1790
|
+
if (known != null) {
|
1791
|
+
return bc[known] ?? bc.default;
|
1792
|
+
}
|
1793
|
+
|
1794
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1795
|
+
return brTable(type, bc, returns);
|
1630
1796
|
|
1797
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1631
1798
|
const out = [
|
1632
1799
|
...type,
|
1633
1800
|
[ Opcodes.local_set, tmp ],
|
@@ -1679,6 +1846,49 @@ const allocVar = (scope, name, global = false) => {
|
|
1679
1846
|
return idx;
|
1680
1847
|
};
|
1681
1848
|
|
1849
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
1850
|
+
const target = global ? globals : scope.locals;
|
1851
|
+
|
1852
|
+
target[name].metadata ??= {};
|
1853
|
+
for (const x in metadata) {
|
1854
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
1855
|
+
}
|
1856
|
+
};
|
1857
|
+
|
1858
|
+
const typeAnnoToPorfType = x => {
|
1859
|
+
if (TYPES[x]) return TYPES[x];
|
1860
|
+
if (TYPES['_' + x]) return TYPES['_' + x];
|
1861
|
+
|
1862
|
+
switch (x) {
|
1863
|
+
case 'i32':
|
1864
|
+
return TYPES.number;
|
1865
|
+
}
|
1866
|
+
|
1867
|
+
return null;
|
1868
|
+
};
|
1869
|
+
|
1870
|
+
const extractTypeAnnotation = decl => {
|
1871
|
+
let a = decl;
|
1872
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
1873
|
+
|
1874
|
+
let type, elementType;
|
1875
|
+
if (a.typeName) {
|
1876
|
+
type = a.typeName.name;
|
1877
|
+
} else if (a.type.endsWith('Keyword')) {
|
1878
|
+
type = a.type.slice(2, -7).toLowerCase();
|
1879
|
+
} else if (a.type === 'TSArrayType') {
|
1880
|
+
type = 'array';
|
1881
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
1882
|
+
}
|
1883
|
+
|
1884
|
+
const typeName = type;
|
1885
|
+
type = typeAnnoToPorfType(type);
|
1886
|
+
|
1887
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
1888
|
+
|
1889
|
+
return { type, typeName, elementType };
|
1890
|
+
};
|
1891
|
+
|
1682
1892
|
const generateVar = (scope, decl) => {
|
1683
1893
|
let out = [];
|
1684
1894
|
|
@@ -1706,6 +1916,11 @@ const generateVar = (scope, decl) => {
|
|
1706
1916
|
}
|
1707
1917
|
|
1708
1918
|
let idx = allocVar(scope, name, global);
|
1919
|
+
|
1920
|
+
if (typedInput && x.id.typeAnnotation) {
|
1921
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1922
|
+
}
|
1923
|
+
|
1709
1924
|
if (x.init) {
|
1710
1925
|
out = out.concat(generate(scope, x.init, global, name));
|
1711
1926
|
|
@@ -1869,7 +2084,7 @@ const generateAssign = (scope, decl) => {
|
|
1869
2084
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1870
2085
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
2086
|
|
1872
|
-
|
2087
|
+
getLastType(scope),
|
1873
2088
|
// hack: type is idx+1
|
1874
2089
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1875
2090
|
];
|
@@ -2036,7 +2251,7 @@ const generateConditional = (scope, decl) => {
|
|
2036
2251
|
// note type
|
2037
2252
|
out.push(
|
2038
2253
|
...getNodeType(scope, decl.consequent),
|
2039
|
-
|
2254
|
+
setLastType(scope)
|
2040
2255
|
);
|
2041
2256
|
|
2042
2257
|
out.push([ Opcodes.else ]);
|
@@ -2045,7 +2260,7 @@ const generateConditional = (scope, decl) => {
|
|
2045
2260
|
// note type
|
2046
2261
|
out.push(
|
2047
2262
|
...getNodeType(scope, decl.alternate),
|
2048
|
-
|
2263
|
+
setLastType(scope)
|
2049
2264
|
);
|
2050
2265
|
|
2051
2266
|
out.push([ Opcodes.end ]);
|
@@ -2489,7 +2704,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2489
2704
|
|
2490
2705
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2491
2706
|
// hack: this is naughty and will break things!
|
2492
|
-
let newOut = number(0,
|
2707
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2493
2708
|
if (pages.hasString) {
|
2494
2709
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2495
2710
|
rawElements: new Array(1)
|
@@ -2516,7 +2731,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2516
2731
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2517
2732
|
|
2518
2733
|
...number(TYPES.number, Valtype.i32),
|
2519
|
-
|
2734
|
+
setLastType(scope)
|
2520
2735
|
],
|
2521
2736
|
|
2522
2737
|
[TYPES.string]: [
|
@@ -2548,7 +2763,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2548
2763
|
...number(newPointer),
|
2549
2764
|
|
2550
2765
|
...number(TYPES.string, Valtype.i32),
|
2551
|
-
|
2766
|
+
setLastType(scope)
|
2552
2767
|
],
|
2553
2768
|
|
2554
2769
|
default: [ [ Opcodes.unreachable ] ]
|
@@ -2597,7 +2812,7 @@ const generateFunc = (scope, decl) => {
|
|
2597
2812
|
if (decl.generator) return todo('generator functions are not supported');
|
2598
2813
|
|
2599
2814
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2600
|
-
const params = decl.params
|
2815
|
+
const params = decl.params ?? [];
|
2601
2816
|
|
2602
2817
|
// const innerScope = { ...scope };
|
2603
2818
|
// TODO: share scope/locals between !!!
|
@@ -2611,7 +2826,11 @@ const generateFunc = (scope, decl) => {
|
|
2611
2826
|
};
|
2612
2827
|
|
2613
2828
|
for (let i = 0; i < params.length; i++) {
|
2614
|
-
allocVar(innerScope, params[i], false);
|
2829
|
+
allocVar(innerScope, params[i].name, false);
|
2830
|
+
|
2831
|
+
if (typedInput && params[i].typeAnnotation) {
|
2832
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2833
|
+
}
|
2615
2834
|
}
|
2616
2835
|
|
2617
2836
|
let body = objectHack(decl.body);
|
@@ -2650,117 +2869,6 @@ const generateFunc = (scope, decl) => {
|
|
2650
2869
|
);
|
2651
2870
|
}
|
2652
2871
|
|
2653
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2654
|
-
let offset = 0, vecParams = 0;
|
2655
|
-
for (let i = 0; i < params.length; i++) {
|
2656
|
-
const name = params[i];
|
2657
|
-
const local = func.locals[name];
|
2658
|
-
if (local.type === Valtype.v128) {
|
2659
|
-
vecParams++;
|
2660
|
-
|
2661
|
-
/* wasm.unshift( // add v128 load for param
|
2662
|
-
[ Opcodes.i32_const, 0 ],
|
2663
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2664
|
-
[ Opcodes.local_set, local.idx ]
|
2665
|
-
); */
|
2666
|
-
|
2667
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2668
|
-
|
2669
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2670
|
-
const { vecType } = local;
|
2671
|
-
let [ type, lanes ] = vecType.split('x');
|
2672
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2673
|
-
|
2674
|
-
lanes = parseInt(lanes);
|
2675
|
-
type = Valtype[type];
|
2676
|
-
|
2677
|
-
const name = params[i]; // get original param name
|
2678
|
-
|
2679
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2680
|
-
|
2681
|
-
// update index of original local
|
2682
|
-
// delete func.locals[name];
|
2683
|
-
|
2684
|
-
// add new locals for params
|
2685
|
-
for (let j = 0; j < lanes; j++) {
|
2686
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2687
|
-
}
|
2688
|
-
|
2689
|
-
// prepend wasm to generate expected v128 locals
|
2690
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2691
|
-
...i32x4(0, 0, 0, 0),
|
2692
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2693
|
-
[ Opcodes.local_get, offset + j ],
|
2694
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2695
|
-
]),
|
2696
|
-
[ Opcodes.local_set, i ]
|
2697
|
-
);
|
2698
|
-
|
2699
|
-
offset += lanes;
|
2700
|
-
|
2701
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2702
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2703
|
-
|
2704
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2705
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2706
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2707
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2708
|
-
}
|
2709
|
-
}
|
2710
|
-
|
2711
|
-
if (offset !== 0) {
|
2712
|
-
// bump local indexes for all other locals after
|
2713
|
-
for (const x in func.locals) {
|
2714
|
-
const local = func.locals[x];
|
2715
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2716
|
-
}
|
2717
|
-
|
2718
|
-
// bump local indexes in wasm local.get/set
|
2719
|
-
for (let j = 0; j < wasm.length; j++) {
|
2720
|
-
const inst = wasm[j];
|
2721
|
-
if (j < offset * 2 + vecParams * 2) {
|
2722
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2723
|
-
continue;
|
2724
|
-
}
|
2725
|
-
|
2726
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2727
|
-
}
|
2728
|
-
}
|
2729
|
-
|
2730
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2731
|
-
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]);
|
2732
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2733
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2734
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2735
|
-
const { vecType } = lastReturnLocal;
|
2736
|
-
let [ type, lanes ] = vecType.split('x');
|
2737
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2738
|
-
|
2739
|
-
lanes = parseInt(lanes);
|
2740
|
-
type = Valtype[type];
|
2741
|
-
|
2742
|
-
const vecIdx = lastReturnLocal.idx;
|
2743
|
-
|
2744
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2745
|
-
const tmpIdx = [];
|
2746
|
-
for (let i = 0; i < lanes; i++) {
|
2747
|
-
const idx = lastIdx + i + 1;
|
2748
|
-
tmpIdx.push(idx);
|
2749
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2750
|
-
}
|
2751
|
-
|
2752
|
-
wasm.splice(wasm.length - 1, 1,
|
2753
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2754
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2755
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2756
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2757
|
-
].filter(x => x !== null)),
|
2758
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2759
|
-
);
|
2760
|
-
|
2761
|
-
func.returns = new Array(lanes).fill(type);
|
2762
|
-
}
|
2763
|
-
|
2764
2872
|
func.wasm = wasm;
|
2765
2873
|
|
2766
2874
|
funcs.push(func);
|
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,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,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
|
-
//
|
10
|
-
|
11
|
-
|
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
|
-
|
28
|
-
const
|
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
|
|
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
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 =
|
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,71 +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
|
-
return 0;
|
70
|
-
}
|
71
|
-
|