porffor 0.2.0-a759814 → 0.2.0-c597461
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 +20 -9
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +262 -42
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +14 -3
- package/compiler/prototype.js +171 -16
- package/compiler/sections.js +1 -1
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +98 -8
- package/package.json +1 -1
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
|
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`)
|
@@ -201,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
|
|
201
209
|
- `test262`: test262 runner and utils
|
202
210
|
|
203
211
|
## Usecases
|
204
|
-
Basically none (other than giving people headaches). Potential ideas
|
212
|
+
Basically none right now (other than giving people headaches). Potential ideas:
|
213
|
+
- Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
|
214
|
+
- Compiling JS to native binaries. This is still very early, [`2c`](#2c) is not that good yet :(
|
215
|
+
- More in future probably?
|
205
216
|
|
206
217
|
## Usage
|
207
|
-
Basically nothing will work :). See files in `test` for examples.
|
218
|
+
Basically nothing will work :). See files in `test` and `bench` for examples.
|
208
219
|
|
209
220
|
1. Clone repo
|
210
221
|
2. `npm install`
|
@@ -241,20 +252,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
|
|
241
252
|
- `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
|
242
253
|
|
243
254
|
## VSCode extension
|
244
|
-
There is a vscode extension in `porffor-for-vscode` which tweaks
|
255
|
+
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
256
|
|
246
257
|
## Isn't this the same as AssemblyScript/other Wasm langs?
|
247
258
|
No. they are not alike at all internally and have very different goals/ideals:
|
248
259
|
- Porffor is made as a generic JS engine, not for Wasm stuff specifically
|
249
|
-
- Porffor
|
250
|
-
- Porffor is
|
260
|
+
- Porffor primarily consumes JS
|
261
|
+
- Porffor is written in pure JS and compiles itself, not using Binaryen/etc
|
251
262
|
- (Also I didn't know it existed when I started this, lol)
|
252
263
|
|
253
264
|
## FAQ
|
254
265
|
|
255
266
|
### 1. Why the name?
|
256
267
|
`purple` in Welsh is `porffor`. Why purple?
|
257
|
-
- No other
|
268
|
+
- No other JS engine is purple colored
|
258
269
|
- Purple is pretty cool
|
259
270
|
- Purple apparently represents "ambition", which is.. one word to describe this project
|
260
271
|
- 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";
|
@@ -55,7 +55,7 @@ const todo = msg => {
|
|
55
55
|
};
|
56
56
|
|
57
57
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
58
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
59
|
switch (decl.type) {
|
60
60
|
case 'BinaryExpression':
|
61
61
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -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':
|
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
86
|
return generateExp(scope, decl);
|
82
87
|
|
83
88
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
90
|
|
86
91
|
case 'NewExpression':
|
87
92
|
return generateNew(scope, decl, global, name);
|
@@ -680,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
685
|
[ Opcodes.i32_eqz ], */
|
681
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
687
|
],
|
688
|
+
[TYPES._bytestring]: [ // duplicate of string
|
689
|
+
[ Opcodes.local_get, tmp ],
|
690
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
|
+
|
692
|
+
// get length
|
693
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
694
|
+
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
683
697
|
default: def
|
684
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
699
|
];
|
@@ -707,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
707
721
|
[ Opcodes.i32_eqz ],
|
708
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
723
|
],
|
724
|
+
[TYPES._bytestring]: [ // duplicate of string
|
725
|
+
[ Opcodes.local_get, tmp ],
|
726
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
|
+
|
728
|
+
// get length
|
729
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
730
|
+
|
731
|
+
// if length == 0
|
732
|
+
[ Opcodes.i32_eqz ],
|
733
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
734
|
+
],
|
710
735
|
default: [
|
711
736
|
// if value == 0
|
712
737
|
[ Opcodes.local_get, tmp ],
|
@@ -947,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
972
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
973
|
}
|
949
974
|
|
975
|
+
if (typeof wasm === 'function') {
|
976
|
+
const scope = {
|
977
|
+
name,
|
978
|
+
params,
|
979
|
+
locals,
|
980
|
+
returns,
|
981
|
+
localInd: allLocals.length,
|
982
|
+
};
|
983
|
+
|
984
|
+
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
985
|
+
}
|
986
|
+
|
950
987
|
let baseGlobalIdx, i = 0;
|
951
988
|
for (const type of globalTypes) {
|
952
989
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1064,8 @@ const TYPES = {
|
|
1027
1064
|
|
1028
1065
|
// these are not "typeof" types but tracked internally
|
1029
1066
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1031
1069
|
};
|
1032
1070
|
|
1033
1071
|
const TYPE_NAMES = {
|
@@ -1041,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1041
1079
|
[TYPES.bigint]: 'BigInt',
|
1042
1080
|
|
1043
1081
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1084
|
};
|
1046
1085
|
|
1047
1086
|
const getType = (scope, _name) => {
|
@@ -1094,6 +1133,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1133
|
if (node.type === 'Literal') {
|
1095
1134
|
if (node.regex) return TYPES._regexp;
|
1096
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1097
1138
|
return TYPES[typeof node.value];
|
1098
1139
|
}
|
1099
1140
|
|
@@ -1107,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1148
|
|
1108
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1150
|
const name = node.callee.name;
|
1151
|
+
if (!name) {
|
1152
|
+
// iife
|
1153
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1154
|
+
|
1155
|
+
// presume
|
1156
|
+
// todo: warn here?
|
1157
|
+
return TYPES.number;
|
1158
|
+
}
|
1159
|
+
|
1110
1160
|
const func = funcs.find(x => x.name === name);
|
1111
1161
|
|
1112
1162
|
if (func) {
|
@@ -1201,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1205
1255
|
|
1206
1256
|
return TYPES.number;
|
1207
1257
|
}
|
@@ -1227,6 +1277,23 @@ const getNodeType = (scope, node) => {
|
|
1227
1277
|
return ret;
|
1228
1278
|
};
|
1229
1279
|
|
1280
|
+
const toString = (scope, wasm, type) => {
|
1281
|
+
const tmp = localTmp(scope, '#tostring_tmp');
|
1282
|
+
return [
|
1283
|
+
...wasm,
|
1284
|
+
[ Opcodes.local_set, tmp ],
|
1285
|
+
|
1286
|
+
...typeSwitch(scope, type, {
|
1287
|
+
[TYPES.string]: [
|
1288
|
+
[ Opcodes.local_get, tmp ]
|
1289
|
+
],
|
1290
|
+
[TYPES.undefined]: [
|
1291
|
+
// [ Opcodes.]
|
1292
|
+
]
|
1293
|
+
})
|
1294
|
+
]
|
1295
|
+
};
|
1296
|
+
|
1230
1297
|
const generateLiteral = (scope, decl, global, name) => {
|
1231
1298
|
if (decl.value === null) return number(NULL);
|
1232
1299
|
|
@@ -1244,16 +1311,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1311
|
return number(decl.value ? 1 : 0);
|
1245
1312
|
|
1246
1313
|
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];
|
1314
|
+
return makeString(scope, decl.value, global, name);
|
1257
1315
|
|
1258
1316
|
default:
|
1259
1317
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1274,9 +1332,9 @@ const countLeftover = wasm => {
|
|
1274
1332
|
|
1275
1333
|
if (depth === 0)
|
1276
1334
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1277
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1335
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1278
1336
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1279
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1337
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1338
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1339
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1340
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1360,7 @@ const disposeLeftover = wasm => {
|
|
1302
1360
|
const generateExp = (scope, decl) => {
|
1303
1361
|
const expression = decl.expression;
|
1304
1362
|
|
1305
|
-
const out = generate(scope, expression);
|
1363
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1364
|
disposeLeftover(out);
|
1307
1365
|
|
1308
1366
|
return out;
|
@@ -1360,7 +1418,7 @@ const RTArrayUtil = {
|
|
1360
1418
|
]
|
1361
1419
|
};
|
1362
1420
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1421
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1422
|
/* const callee = decl.callee;
|
1365
1423
|
const args = decl.arguments;
|
1366
1424
|
|
@@ -1479,10 +1537,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1537
|
// use local for cached i32 length as commonly used
|
1480
1538
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1539
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1540
|
|
1484
1541
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1542
|
|
1543
|
+
const rawPointer = [
|
1544
|
+
...generate(scope, target),
|
1545
|
+
Opcodes.i32_to_u
|
1546
|
+
];
|
1547
|
+
|
1548
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1549
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1550
|
+
|
1551
|
+
let allOptUnused = true;
|
1486
1552
|
let lengthI32CacheUsed = false;
|
1487
1553
|
const protoBC = {};
|
1488
1554
|
for (const x in protoCands) {
|
@@ -1502,6 +1568,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1568
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1569
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1570
|
|
1571
|
+
let optUnused = false;
|
1505
1572
|
const protoOut = protoFunc(getPointer, {
|
1506
1573
|
getCachedI32: () => {
|
1507
1574
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1583,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1583
|
return makeArray(scope, {
|
1517
1584
|
rawElements: new Array(length)
|
1518
1585
|
}, _global, _name, true, itemType);
|
1586
|
+
}, () => {
|
1587
|
+
optUnused = true;
|
1588
|
+
return unusedValue;
|
1519
1589
|
});
|
1520
1590
|
|
1591
|
+
if (!optUnused) allOptUnused = false;
|
1592
|
+
|
1521
1593
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1594
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1595
|
...protoOut,
|
1524
1596
|
|
1525
1597
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1600,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1600
|
];
|
1529
1601
|
}
|
1530
1602
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1603
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1604
|
|
1534
|
-
|
1535
|
-
|
1605
|
+
return [
|
1606
|
+
...(usePointerCache ? [
|
1607
|
+
...rawPointer,
|
1608
|
+
[ Opcodes.local_set, pointerLocal ],
|
1609
|
+
] : []),
|
1536
1610
|
|
1537
1611
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1612
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1618,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1618
|
|
1545
1619
|
// TODO: error better
|
1546
1620
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1621
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1622
|
];
|
1549
1623
|
}
|
1550
1624
|
}
|
@@ -1591,7 +1665,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1665
|
const func = funcs.find(x => x.index === idx);
|
1592
1666
|
|
1593
1667
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1668
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1669
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1670
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1671
|
|
1596
1672
|
let args = decl.arguments;
|
1597
1673
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1685,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1685
|
let out = [];
|
1610
1686
|
for (const arg of args) {
|
1611
1687
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1688
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1689
|
}
|
1614
1690
|
|
1615
1691
|
out.push([ Opcodes.call, idx ]);
|
1616
1692
|
|
1617
|
-
if (!
|
1693
|
+
if (!typedReturn) {
|
1618
1694
|
// let type;
|
1619
1695
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1696
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1741,102 @@ const knownType = (scope, type) => {
|
|
1665
1741
|
return null;
|
1666
1742
|
};
|
1667
1743
|
|
1744
|
+
const brTable = (input, bc, returns) => {
|
1745
|
+
const out = [];
|
1746
|
+
const keys = Object.keys(bc);
|
1747
|
+
const count = keys.length;
|
1748
|
+
|
1749
|
+
if (count === 1) {
|
1750
|
+
// return [
|
1751
|
+
// ...input,
|
1752
|
+
// ...bc[keys[0]]
|
1753
|
+
// ];
|
1754
|
+
return bc[keys[0]];
|
1755
|
+
}
|
1756
|
+
|
1757
|
+
if (count === 2) {
|
1758
|
+
// just use if else
|
1759
|
+
const other = keys.find(x => x !== 'default');
|
1760
|
+
return [
|
1761
|
+
...input,
|
1762
|
+
...number(other, Valtype.i32),
|
1763
|
+
[ Opcodes.i32_eq ],
|
1764
|
+
[ Opcodes.if, returns ],
|
1765
|
+
...bc[other],
|
1766
|
+
[ Opcodes.else ],
|
1767
|
+
...bc.default,
|
1768
|
+
[ Opcodes.end ]
|
1769
|
+
];
|
1770
|
+
}
|
1771
|
+
|
1772
|
+
for (let i = 0; i < count; i++) {
|
1773
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1774
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1775
|
+
}
|
1776
|
+
|
1777
|
+
const nums = keys.filter(x => +x);
|
1778
|
+
const offset = Math.min(...nums);
|
1779
|
+
const max = Math.max(...nums);
|
1780
|
+
|
1781
|
+
const table = [];
|
1782
|
+
let br = 1;
|
1783
|
+
|
1784
|
+
for (let i = offset; i <= max; i++) {
|
1785
|
+
// if branch for this num, go to that block
|
1786
|
+
if (bc[i]) {
|
1787
|
+
table.push(br);
|
1788
|
+
br++;
|
1789
|
+
continue;
|
1790
|
+
}
|
1791
|
+
|
1792
|
+
// else default
|
1793
|
+
table.push(0);
|
1794
|
+
}
|
1795
|
+
|
1796
|
+
out.push(
|
1797
|
+
[ Opcodes.block, Blocktype.void ],
|
1798
|
+
...input,
|
1799
|
+
...(offset > 0 ? [
|
1800
|
+
...number(offset, Valtype.i32),
|
1801
|
+
[ Opcodes.i32_sub ]
|
1802
|
+
] : []),
|
1803
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1804
|
+
);
|
1805
|
+
|
1806
|
+
// if you can guess why we sort the wrong way and then reverse
|
1807
|
+
// (instead of just sorting the correct way)
|
1808
|
+
// dm me and if you are correct and the first person
|
1809
|
+
// I will somehow shout you out or something
|
1810
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1811
|
+
|
1812
|
+
br = count - 1;
|
1813
|
+
for (const x of orderedBc) {
|
1814
|
+
out.push(
|
1815
|
+
[ Opcodes.end ],
|
1816
|
+
...bc[x],
|
1817
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1818
|
+
);
|
1819
|
+
br--;
|
1820
|
+
}
|
1821
|
+
|
1822
|
+
return [
|
1823
|
+
...out,
|
1824
|
+
[ Opcodes.end, 'br table end' ]
|
1825
|
+
];
|
1826
|
+
};
|
1827
|
+
|
1668
1828
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1829
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1830
|
+
|
1669
1831
|
const known = knownType(scope, type);
|
1670
1832
|
if (known != null) {
|
1671
1833
|
return bc[known] ?? bc.default;
|
1672
1834
|
}
|
1673
1835
|
|
1674
|
-
|
1836
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1837
|
+
return brTable(type, bc, returns);
|
1675
1838
|
|
1839
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1840
|
const out = [
|
1677
1841
|
...type,
|
1678
1842
|
[ Opcodes.local_set, tmp ],
|
@@ -1794,6 +1958,11 @@ const generateVar = (scope, decl) => {
|
|
1794
1958
|
}
|
1795
1959
|
|
1796
1960
|
let idx = allocVar(scope, name, global);
|
1961
|
+
|
1962
|
+
if (typedInput && x.id.typeAnnotation) {
|
1963
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1964
|
+
}
|
1965
|
+
|
1797
1966
|
if (x.init) {
|
1798
1967
|
out = out.concat(generate(scope, x.init, global, name));
|
1799
1968
|
|
@@ -1803,10 +1972,6 @@ const generateVar = (scope, decl) => {
|
|
1803
1972
|
|
1804
1973
|
// hack: this follows spec properly but is mostly unneeded 😅
|
1805
1974
|
// 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
1975
|
}
|
1811
1976
|
|
1812
1977
|
return out;
|
@@ -2053,6 +2218,8 @@ const generateUnary = (scope, decl) => {
|
|
2053
2218
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2054
2219
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2055
2220
|
|
2221
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2222
|
+
|
2056
2223
|
// object and internal types
|
2057
2224
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2058
2225
|
});
|
@@ -2241,6 +2408,7 @@ const generateForOf = (scope, decl) => {
|
|
2241
2408
|
}
|
2242
2409
|
|
2243
2410
|
// set type for local
|
2411
|
+
// todo: optimize away counter and use end pointer
|
2244
2412
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2245
2413
|
[TYPES._array]: [
|
2246
2414
|
...setType(scope, leftName, TYPES.number),
|
@@ -2459,7 +2627,8 @@ const StoreOps = {
|
|
2459
2627
|
f64: Opcodes.f64_store,
|
2460
2628
|
|
2461
2629
|
// expects i32 input!
|
2462
|
-
|
2630
|
+
i8: Opcodes.i32_store8,
|
2631
|
+
i16: Opcodes.i32_store16,
|
2463
2632
|
};
|
2464
2633
|
|
2465
2634
|
let data = [];
|
@@ -2478,6 +2647,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2478
2647
|
}
|
2479
2648
|
};
|
2480
2649
|
|
2650
|
+
const getAllocType = itemType => {
|
2651
|
+
switch (itemType) {
|
2652
|
+
case 'i8': return 'bytestring';
|
2653
|
+
case 'i16': return 'string';
|
2654
|
+
|
2655
|
+
default: return 'array';
|
2656
|
+
}
|
2657
|
+
};
|
2658
|
+
|
2481
2659
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2482
2660
|
const out = [];
|
2483
2661
|
|
@@ -2487,7 +2665,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2487
2665
|
|
2488
2666
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2489
2667
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2490
|
-
arrays.set(name, allocPage(`${itemType
|
2668
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2491
2669
|
}
|
2492
2670
|
|
2493
2671
|
const pointer = arrays.get(name);
|
@@ -2533,7 +2711,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2533
2711
|
out.push(
|
2534
2712
|
...number(0, Valtype.i32),
|
2535
2713
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2536
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2714
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2537
2715
|
);
|
2538
2716
|
}
|
2539
2717
|
|
@@ -2543,15 +2721,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2543
2721
|
return [ out, pointer ];
|
2544
2722
|
};
|
2545
2723
|
|
2724
|
+
const byteStringable = str => {
|
2725
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2726
|
+
|
2727
|
+
for (let i = 0; i < str.length; i++) {
|
2728
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2729
|
+
}
|
2730
|
+
|
2731
|
+
return true;
|
2732
|
+
};
|
2733
|
+
|
2546
2734
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2547
2735
|
const rawElements = new Array(str.length);
|
2736
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2548
2737
|
for (let i = 0; i < str.length; i++) {
|
2549
|
-
|
2738
|
+
const c = str.charCodeAt(i);
|
2739
|
+
rawElements[i] = c;
|
2740
|
+
|
2741
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2550
2742
|
}
|
2551
2743
|
|
2552
2744
|
return makeArray(scope, {
|
2553
2745
|
rawElements
|
2554
|
-
}, global, name, false, 'i16')[0];
|
2746
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2555
2747
|
};
|
2556
2748
|
|
2557
2749
|
let arrays = new Map();
|
@@ -2581,7 +2773,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2581
2773
|
|
2582
2774
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2583
2775
|
// hack: this is naughty and will break things!
|
2584
|
-
let newOut = number(0,
|
2776
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2585
2777
|
if (pages.hasString) {
|
2586
2778
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2587
2779
|
rawElements: new Array(1)
|
@@ -2642,6 +2834,34 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2642
2834
|
...number(TYPES.string, Valtype.i32),
|
2643
2835
|
setLastType(scope)
|
2644
2836
|
],
|
2837
|
+
[TYPES._bytestring]: [
|
2838
|
+
// setup new/out array
|
2839
|
+
...newOut,
|
2840
|
+
[ Opcodes.drop ],
|
2841
|
+
|
2842
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2843
|
+
|
2844
|
+
...generate(scope, decl.property),
|
2845
|
+
Opcodes.i32_to_u,
|
2846
|
+
|
2847
|
+
...(aotPointer ? [] : [
|
2848
|
+
...generate(scope, decl.object),
|
2849
|
+
Opcodes.i32_to_u,
|
2850
|
+
[ Opcodes.i32_add ]
|
2851
|
+
]),
|
2852
|
+
|
2853
|
+
// load current string ind {arg}
|
2854
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2855
|
+
|
2856
|
+
// store to new string ind 0
|
2857
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2858
|
+
|
2859
|
+
// return new string (page)
|
2860
|
+
...number(newPointer),
|
2861
|
+
|
2862
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2863
|
+
setLastType(scope)
|
2864
|
+
],
|
2645
2865
|
|
2646
2866
|
default: [ [ Opcodes.unreachable ] ]
|
2647
2867
|
});
|
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/prototype.js
CHANGED
@@ -16,7 +16,8 @@ const TYPES = {
|
|
16
16
|
|
17
17
|
// these are not "typeof" types but tracked internally
|
18
18
|
_array: 0x10,
|
19
|
-
_regexp: 0x11
|
19
|
+
_regexp: 0x11,
|
20
|
+
_bytestring: 0x12
|
20
21
|
};
|
21
22
|
|
22
23
|
// todo: turn these into built-ins once arrays and these become less hacky
|
@@ -71,7 +72,7 @@ export const PrototypeFuncs = function() {
|
|
71
72
|
],
|
72
73
|
|
73
74
|
// todo: only for 1 argument
|
74
|
-
push: (pointer, length, wNewMember) => [
|
75
|
+
push: (pointer, length, wNewMember, _1, _2, _3, unusedValue) => [
|
75
76
|
// get memory offset of array at last index (length)
|
76
77
|
...length.getCachedI32(),
|
77
78
|
...number(ValtypeSize[valtype], Valtype.i32),
|
@@ -92,22 +93,28 @@ export const PrototypeFuncs = function() {
|
|
92
93
|
...number(1, Valtype.i32),
|
93
94
|
[ Opcodes.i32_add ],
|
94
95
|
|
95
|
-
...
|
96
|
-
|
96
|
+
...(unusedValue() ? [] : [
|
97
|
+
...length.setCachedI32(),
|
98
|
+
...length.getCachedI32(),
|
99
|
+
])
|
97
100
|
]),
|
98
101
|
|
99
|
-
...
|
100
|
-
|
102
|
+
...(unusedValue() ? [] : [
|
103
|
+
...length.getCachedI32(),
|
104
|
+
Opcodes.i32_from_u
|
105
|
+
])
|
101
106
|
|
102
107
|
// ...length.get()
|
103
108
|
],
|
104
109
|
|
105
|
-
pop: (pointer, length) => [
|
110
|
+
pop: (pointer, length, _1, _2, _3, _4, unusedValue) => [
|
106
111
|
// if length == 0, noop
|
107
112
|
...length.getCachedI32(),
|
108
113
|
[ Opcodes.i32_eqz ],
|
109
114
|
[ Opcodes.if, Blocktype.void ],
|
110
|
-
...
|
115
|
+
...(unusedValue() ? [] : [
|
116
|
+
...number(UNDEFINED),
|
117
|
+
]),
|
111
118
|
[ Opcodes.br, 1 ],
|
112
119
|
[ Opcodes.end ],
|
113
120
|
|
@@ -119,19 +126,23 @@ export const PrototypeFuncs = function() {
|
|
119
126
|
...number(1, Valtype.i32),
|
120
127
|
[ Opcodes.i32_sub ],
|
121
128
|
|
122
|
-
...
|
123
|
-
|
129
|
+
...(unusedValue() ? [] : [
|
130
|
+
...length.setCachedI32(),
|
131
|
+
...length.getCachedI32(),
|
132
|
+
])
|
124
133
|
]),
|
125
134
|
|
126
135
|
// load last element
|
127
|
-
...
|
128
|
-
|
129
|
-
|
136
|
+
...(unusedValue() ? [] : [
|
137
|
+
...length.getCachedI32(),
|
138
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
139
|
+
[ Opcodes.i32_mul ],
|
130
140
|
|
131
|
-
|
132
|
-
|
141
|
+
...pointer,
|
142
|
+
[ Opcodes.i32_add ],
|
133
143
|
|
134
|
-
|
144
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
145
|
+
])
|
135
146
|
],
|
136
147
|
|
137
148
|
shift: (pointer, length) => [
|
@@ -472,8 +483,152 @@ export const PrototypeFuncs = function() {
|
|
472
483
|
this[TYPES.string].at.returnType = TYPES.string;
|
473
484
|
this[TYPES.string].charAt.returnType = TYPES.string;
|
474
485
|
this[TYPES.string].charCodeAt.local = Valtype.i32;
|
486
|
+
this[TYPES.string].charCodeAt.noPointerCache = zeroChecks.charcodeat;
|
475
487
|
|
476
488
|
this[TYPES.string].isWellFormed.local = Valtype.i32;
|
477
489
|
this[TYPES.string].isWellFormed.local2 = Valtype.i32;
|
478
490
|
this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
|
491
|
+
|
492
|
+
if (process.argv.includes('-bytestring')) {
|
493
|
+
this[TYPES._bytestring] = {
|
494
|
+
at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
|
495
|
+
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
496
|
+
|
497
|
+
return [
|
498
|
+
// setup new/out array
|
499
|
+
...newOut,
|
500
|
+
[ Opcodes.drop ],
|
501
|
+
|
502
|
+
...number(0, Valtype.i32), // base 0 for store later
|
503
|
+
|
504
|
+
...wIndex,
|
505
|
+
Opcodes.i32_to_u,
|
506
|
+
[ Opcodes.local_tee, iTmp ],
|
507
|
+
|
508
|
+
// if index < 0: access index + array length
|
509
|
+
...number(0, Valtype.i32),
|
510
|
+
[ Opcodes.i32_lt_s ],
|
511
|
+
[ Opcodes.if, Blocktype.void ],
|
512
|
+
[ Opcodes.local_get, iTmp ],
|
513
|
+
...length.getCachedI32(),
|
514
|
+
[ Opcodes.i32_add ],
|
515
|
+
[ Opcodes.local_set, iTmp ],
|
516
|
+
[ Opcodes.end ],
|
517
|
+
|
518
|
+
// if still < 0 or >= length: return undefined
|
519
|
+
[ Opcodes.local_get, iTmp ],
|
520
|
+
...number(0, Valtype.i32),
|
521
|
+
[ Opcodes.i32_lt_s ],
|
522
|
+
|
523
|
+
[ Opcodes.local_get, iTmp ],
|
524
|
+
...length.getCachedI32(),
|
525
|
+
[ Opcodes.i32_ge_s ],
|
526
|
+
[ Opcodes.i32_or ],
|
527
|
+
|
528
|
+
[ Opcodes.if, Blocktype.void ],
|
529
|
+
...number(UNDEFINED),
|
530
|
+
[ Opcodes.br, 1 ],
|
531
|
+
[ Opcodes.end ],
|
532
|
+
|
533
|
+
[ Opcodes.local_get, iTmp ],
|
534
|
+
|
535
|
+
...pointer,
|
536
|
+
[ Opcodes.i32_add ],
|
537
|
+
|
538
|
+
// load current string ind {arg}
|
539
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
540
|
+
|
541
|
+
// store to new string ind 0
|
542
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
543
|
+
|
544
|
+
// return new string (pointer)
|
545
|
+
...number(newPointer)
|
546
|
+
];
|
547
|
+
},
|
548
|
+
|
549
|
+
// todo: out of bounds properly
|
550
|
+
charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
|
551
|
+
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
552
|
+
|
553
|
+
return [
|
554
|
+
// setup new/out array
|
555
|
+
...newOut,
|
556
|
+
[ Opcodes.drop ],
|
557
|
+
|
558
|
+
...number(0, Valtype.i32), // base 0 for store later
|
559
|
+
|
560
|
+
...wIndex,
|
561
|
+
|
562
|
+
Opcodes.i32_to,
|
563
|
+
|
564
|
+
...pointer,
|
565
|
+
[ Opcodes.i32_add ],
|
566
|
+
|
567
|
+
// load current string ind {arg}
|
568
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
569
|
+
|
570
|
+
// store to new string ind 0
|
571
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
572
|
+
|
573
|
+
// return new string (page)
|
574
|
+
...number(newPointer)
|
575
|
+
];
|
576
|
+
},
|
577
|
+
|
578
|
+
charCodeAt: (pointer, length, wIndex, iTmp) => {
|
579
|
+
return [
|
580
|
+
...wIndex,
|
581
|
+
Opcodes.i32_to,
|
582
|
+
|
583
|
+
...(zeroChecks.charcodeat ? [] : [
|
584
|
+
[ Opcodes.local_set, iTmp ],
|
585
|
+
|
586
|
+
// index < 0
|
587
|
+
...(noUnlikelyChecks ? [] : [
|
588
|
+
[ Opcodes.local_get, iTmp ],
|
589
|
+
...number(0, Valtype.i32),
|
590
|
+
[ Opcodes.i32_lt_s ],
|
591
|
+
]),
|
592
|
+
|
593
|
+
// index >= length
|
594
|
+
[ Opcodes.local_get, iTmp ],
|
595
|
+
...length.getCachedI32(),
|
596
|
+
[ Opcodes.i32_ge_s ],
|
597
|
+
|
598
|
+
...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
|
599
|
+
[ Opcodes.if, Blocktype.void ],
|
600
|
+
...number(NaN),
|
601
|
+
[ Opcodes.br, 1 ],
|
602
|
+
[ Opcodes.end ],
|
603
|
+
|
604
|
+
[ Opcodes.local_get, iTmp ],
|
605
|
+
]),
|
606
|
+
|
607
|
+
...pointer,
|
608
|
+
[ Opcodes.i32_add ],
|
609
|
+
|
610
|
+
// load current string ind {arg}
|
611
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
612
|
+
Opcodes.i32_from_u
|
613
|
+
];
|
614
|
+
},
|
615
|
+
|
616
|
+
isWellFormed: () => {
|
617
|
+
return [
|
618
|
+
// we know it must be true as it is a bytestring lol
|
619
|
+
...number(1)
|
620
|
+
]
|
621
|
+
}
|
622
|
+
};
|
623
|
+
|
624
|
+
this[TYPES._bytestring].at.local = Valtype.i32;
|
625
|
+
this[TYPES._bytestring].at.returnType = TYPES._bytestring;
|
626
|
+
this[TYPES._bytestring].charAt.returnType = TYPES._bytestring;
|
627
|
+
this[TYPES._bytestring].charCodeAt.local = Valtype.i32;
|
628
|
+
this[TYPES._bytestring].charCodeAt.noPointerCache = zeroChecks.charcodeat;
|
629
|
+
|
630
|
+
this[TYPES._bytestring].isWellFormed.local = Valtype.i32;
|
631
|
+
this[TYPES._bytestring].isWellFormed.local2 = Valtype.i32;
|
632
|
+
this[TYPES._bytestring].isWellFormed.returnType = TYPES.boolean;
|
633
|
+
}
|
479
634
|
};
|
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,
|
@@ -56,9 +57,12 @@ export const Opcodes = {
|
|
56
57
|
i64_load: 0x29,
|
57
58
|
f64_load: 0x2b,
|
58
59
|
|
60
|
+
i32_load8_s: 0x2c,
|
61
|
+
i32_load8_u: 0x2d,
|
59
62
|
i32_load16_s: 0x2e,
|
60
63
|
i32_load16_u: 0x2f,
|
61
64
|
|
65
|
+
i32_store8: 0x3a,
|
62
66
|
i32_store16: 0x3b,
|
63
67
|
|
64
68
|
i32_store: 0x36,
|
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`;
|
@@ -18,7 +19,8 @@ const TYPES = {
|
|
18
19
|
|
19
20
|
// internal
|
20
21
|
[internalTypeBase]: '_array',
|
21
|
-
[internalTypeBase + 1]: '_regexp'
|
22
|
+
[internalTypeBase + 1]: '_regexp',
|
23
|
+
[internalTypeBase + 2]: '_bytestring'
|
22
24
|
};
|
23
25
|
|
24
26
|
export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
|
@@ -35,14 +37,95 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
35
37
|
if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
|
36
38
|
|
37
39
|
const t2 = performance.now();
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
|
41
|
+
let instance;
|
42
|
+
try {
|
43
|
+
0, { instance } = await WebAssembly.instantiate(wasm, {
|
44
|
+
'': {
|
45
|
+
p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
|
46
|
+
c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
|
47
|
+
t: _ => performance.now(),
|
48
|
+
...customImports
|
49
|
+
}
|
50
|
+
});
|
51
|
+
} catch (e) {
|
52
|
+
const funcInd = parseInt(e.message.match(/function #([0-9]+) /)[1]);
|
53
|
+
const blobOffset = parseInt(e.message.split('@')[1]);
|
54
|
+
|
55
|
+
// convert blob offset -> function wasm offset.
|
56
|
+
// this is not good code and is somewhat duplicated
|
57
|
+
// I just want it to work for debugging, I don't care about perf/yes
|
58
|
+
|
59
|
+
const func = funcs.find(x => x.index === funcInd);
|
60
|
+
const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
|
61
|
+
|
62
|
+
let localDecl = [], typeCount = 0, lastType;
|
63
|
+
for (let i = 0; i < locals.length; i++) {
|
64
|
+
const local = locals[i];
|
65
|
+
if (i !== 0 && local.type !== lastType) {
|
66
|
+
localDecl.push(encodeLocal(typeCount, lastType));
|
67
|
+
typeCount = 0;
|
68
|
+
}
|
69
|
+
|
70
|
+
typeCount++;
|
71
|
+
lastType = local.type;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (typeCount !== 0) localDecl.push(encodeLocal(typeCount, lastType));
|
75
|
+
|
76
|
+
const toFind = encodeVector(localDecl).concat(func.wasm.flat().filter(x => x != null && x <= 0xff).slice(0, 40));
|
77
|
+
|
78
|
+
let i = 0;
|
79
|
+
for (; i < wasm.length; i++) {
|
80
|
+
let mismatch = false;
|
81
|
+
for (let j = 0; j < toFind.length; j++) {
|
82
|
+
if (wasm[i + j] !== toFind[j]) {
|
83
|
+
mismatch = true;
|
84
|
+
break;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
if (!mismatch) break;
|
89
|
+
}
|
90
|
+
|
91
|
+
if (i === wasm.length) throw e;
|
92
|
+
|
93
|
+
const offset = (blobOffset - i) + encodeVector(localDecl).length;
|
94
|
+
|
95
|
+
let cumLen = 0;
|
96
|
+
i = 0;
|
97
|
+
for (; i < func.wasm.length; i++) {
|
98
|
+
cumLen += func.wasm[i].filter(x => x != null && x <= 0xff).length;
|
99
|
+
if (cumLen === offset) break;
|
100
|
+
}
|
101
|
+
|
102
|
+
if (cumLen !== offset) throw e;
|
103
|
+
|
104
|
+
i -= 1;
|
105
|
+
|
106
|
+
console.log(`\x1B[35m\x1B[1mporffor backtrace\u001b[0m`);
|
107
|
+
|
108
|
+
console.log('\x1B[4m' + func.name + '\x1B[0m');
|
109
|
+
|
110
|
+
const surrounding = 6;
|
111
|
+
|
112
|
+
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');
|
113
|
+
|
114
|
+
const noAnsi = s => s.replace(/\u001b\[[0-9]+m/g, '');
|
115
|
+
let longest = 0;
|
116
|
+
for (let j = 0; j < decomp.length; j++) {
|
117
|
+
longest = Math.max(longest, noAnsi(decomp[j]).length);
|
44
118
|
}
|
45
|
-
|
119
|
+
|
120
|
+
const middle = Math.floor(decomp.length / 2);
|
121
|
+
decomp[middle] = `\x1B[47m\x1B[30m${noAnsi(decomp[middle])}${'\u00a0'.repeat(longest - noAnsi(decomp[middle]).length)}\x1B[0m`;
|
122
|
+
|
123
|
+
console.log('\x1B[90m...\x1B[0m');
|
124
|
+
console.log(decomp.join('\n'));
|
125
|
+
console.log('\x1B[90m...\x1B[0m\n');
|
126
|
+
|
127
|
+
throw e;
|
128
|
+
}
|
46
129
|
|
47
130
|
times.push(performance.now() - t2);
|
48
131
|
if (flags.includes('info')) console.log(`instantiated in ${times[1].toFixed(2)}ms`);
|
@@ -95,6 +178,13 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
95
178
|
return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
96
179
|
}
|
97
180
|
|
181
|
+
case '_bytestring': {
|
182
|
+
const pointer = ret;
|
183
|
+
const length = new Int32Array(memory.buffer, pointer, 1);
|
184
|
+
|
185
|
+
return Array.from(new Uint8Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
186
|
+
}
|
187
|
+
|
98
188
|
case 'function': {
|
99
189
|
// wasm func index, including all imports
|
100
190
|
const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
|
package/package.json
CHANGED