porffor 0.1.1 → 0.2.0-c6c8c81
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 +184 -204
- package/compiler/2c.js +377 -0
- package/compiler/builtins/base64.js +91 -91
- package/compiler/builtins.js +19 -13
- package/compiler/codeGen.js +1313 -418
- package/compiler/decompile.js +35 -9
- package/compiler/embedding.js +9 -5
- package/compiler/encoding.js +8 -2
- package/compiler/index.js +55 -17
- package/compiler/log.js +15 -0
- package/compiler/opt.js +357 -258
- package/compiler/parse.js +24 -1
- package/compiler/prototype.js +263 -56
- package/compiler/sections.js +51 -8
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +20 -6
- package/package.json +6 -1
- package/porf.cmd +1 -1
- package/rhemyn/README.md +37 -0
- package/rhemyn/compile.js +214 -0
- package/rhemyn/parse.js +321 -0
- package/rhemyn/test/parse.js +59 -0
- package/runner/index.js +54 -31
- package/runner/info.js +37 -2
- package/runner/profile.js +1 -2
- package/runner/repl.js +13 -11
- package/runner/results.json +1 -0
- package/runner/transform.js +15 -36
- package/runner/version.js +10 -0
- package/CNAME +0 -1
- package/index.html +0 -1264
- package/logo.png +0 -0
- package/sw.js +0 -26
package/compiler/opt.js
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
import { Opcodes, Valtype } from "./wasmSpec.js";
|
2
2
|
import { number } from "./embedding.js";
|
3
|
-
|
4
|
-
|
5
|
-
if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
6
|
-
const textEncoder = new TextEncoder();
|
7
|
-
globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
|
8
|
-
}
|
3
|
+
import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
|
4
|
+
import { log } from "./log.js";
|
9
5
|
|
10
6
|
const performWasmOp = (op, a, b) => {
|
11
7
|
switch (op) {
|
@@ -15,12 +11,12 @@ const performWasmOp = (op, a, b) => {
|
|
15
11
|
}
|
16
12
|
};
|
17
13
|
|
18
|
-
export default (funcs, globals) => {
|
14
|
+
export default (funcs, globals, pages) => {
|
19
15
|
const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
|
20
16
|
if (optLevel === 0) return;
|
21
17
|
|
22
18
|
const tailCall = process.argv.includes('-tail-call');
|
23
|
-
if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
|
19
|
+
if (tailCall) log.warning('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
|
24
20
|
|
25
21
|
if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
|
26
22
|
// inline pass (very WIP)
|
@@ -95,7 +91,6 @@ export default (funcs, globals) => {
|
|
95
91
|
}
|
96
92
|
|
97
93
|
if (t.index > c.index) t.index--; // adjust index if after removed func
|
98
|
-
if (c.memory) t.memory = true;
|
99
94
|
}
|
100
95
|
|
101
96
|
funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
|
@@ -108,328 +103,432 @@ export default (funcs, globals) => {
|
|
108
103
|
for (const f of funcs) {
|
109
104
|
const wasm = f.wasm;
|
110
105
|
|
111
|
-
let
|
106
|
+
let runs = 2; // how many by default? add arg?
|
107
|
+
while (runs > 0) {
|
108
|
+
runs--;
|
112
109
|
|
113
|
-
|
114
|
-
|
115
|
-
getCount
|
116
|
-
|
117
|
-
|
110
|
+
let depth = [];
|
111
|
+
|
112
|
+
let getCount = {}, setCount = {};
|
113
|
+
for (const x in f.locals) {
|
114
|
+
getCount[f.locals[x].idx] = 0;
|
115
|
+
setCount[f.locals[x].idx] = 0;
|
116
|
+
}
|
118
117
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
118
|
+
// main pass
|
119
|
+
for (let i = 0; i < wasm.length; i++) {
|
120
|
+
let inst = wasm[i];
|
121
|
+
|
122
|
+
if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
|
123
|
+
if (inst[0] === Opcodes.end) depth.pop();
|
124
|
+
|
125
|
+
if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
|
126
|
+
if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
|
127
|
+
|
128
|
+
if (inst[0] === Opcodes.block) {
|
129
|
+
// remove unneeded blocks (no brs inside)
|
130
|
+
// block
|
131
|
+
// ...
|
132
|
+
// end
|
133
|
+
// -->
|
134
|
+
// ...
|
135
|
+
|
136
|
+
let hasBranch = false, j = i, depth = 0;
|
137
|
+
for (; j < wasm.length; j++) {
|
138
|
+
const op = wasm[j][0];
|
139
|
+
if (op === Opcodes.if || op === Opcodes.block || op === Opcodes.loop || op === Opcodes.try) depth++;
|
140
|
+
if (op === Opcodes.end) {
|
141
|
+
depth--;
|
142
|
+
if (depth <= 0) break;
|
143
|
+
}
|
144
|
+
if (op === Opcodes.br || op === Opcodes.br_if) {
|
145
|
+
hasBranch = true;
|
146
|
+
break;
|
147
|
+
}
|
144
148
|
}
|
145
|
-
|
146
|
-
|
147
|
-
|
149
|
+
|
150
|
+
if (!hasBranch) {
|
151
|
+
wasm.splice(i, 1); // remove this inst (block)
|
152
|
+
if (i > 0) i--;
|
153
|
+
inst = wasm[i];
|
154
|
+
|
155
|
+
wasm.splice(j - 1, 1); // remove end of this block
|
156
|
+
|
157
|
+
if (optLog) log('opt', `removed unneeded block in for loop`);
|
148
158
|
}
|
149
159
|
}
|
150
160
|
|
151
|
-
if (!
|
152
|
-
|
153
|
-
i
|
161
|
+
if (inst[inst.length - 1] === 'string_only' && !pages.hasString) {
|
162
|
+
// remove this inst
|
163
|
+
wasm.splice(i, 1);
|
164
|
+
if (i > 0) i--;
|
154
165
|
inst = wasm[i];
|
166
|
+
}
|
155
167
|
|
156
|
-
|
168
|
+
if (inst[inst.length - 1] === 'string_only|start' && !pages.hasString) {
|
169
|
+
let j = i;
|
170
|
+
for (; j < wasm.length; j++) {
|
171
|
+
const op = wasm[j];
|
172
|
+
if (op[op.length - 1] === 'string_only|end') break;
|
173
|
+
}
|
174
|
+
|
175
|
+
// remove section
|
176
|
+
wasm.splice(i, 1 + j - i);
|
157
177
|
|
158
|
-
if (
|
178
|
+
if (i > 0) i--;
|
179
|
+
inst = wasm[i];
|
159
180
|
}
|
160
|
-
}
|
161
181
|
|
162
|
-
|
163
|
-
|
182
|
+
if (inst[0] === Opcodes.if && typeof inst[2] === 'string') {
|
183
|
+
// remove unneeded typeswitch checks
|
184
|
+
|
185
|
+
const type = inst[2].split('|')[1];
|
186
|
+
let missing = false;
|
187
|
+
if (type === 'Array') missing = !pages.hasArray;
|
188
|
+
if (type === 'String') missing = !pages.hasString;
|
189
|
+
|
190
|
+
if (missing) {
|
191
|
+
let j = i, depth = 0;
|
192
|
+
for (; j < wasm.length; j++) {
|
193
|
+
const op = wasm[j][0];
|
194
|
+
if (op === Opcodes.if || op === Opcodes.block || op === Opcodes.loop || op === Opcodes.try) depth++;
|
195
|
+
if (op === Opcodes.end) {
|
196
|
+
depth--;
|
197
|
+
if (depth <= 0) break;
|
198
|
+
}
|
199
|
+
}
|
164
200
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
// local.get 0
|
169
|
-
// -->
|
170
|
-
// local.tee 0
|
201
|
+
wasm.splice(i - 3, 4 + j - i); // remove cond and this if
|
202
|
+
i -= 4;
|
203
|
+
inst = wasm[i];
|
171
204
|
|
172
|
-
|
173
|
-
|
205
|
+
if (optLog) log('opt', `removed unneeded typeswitch check`);
|
206
|
+
}
|
207
|
+
}
|
174
208
|
|
175
|
-
|
176
|
-
|
177
|
-
// if (optLog) log('opt', `consolidated set, get -> tee`);
|
178
|
-
continue;
|
179
|
-
}
|
209
|
+
if (inst[0] === Opcodes.end && inst[1] === 'TYPESWITCH_end') {
|
210
|
+
// remove unneeded entire typeswitch
|
180
211
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
212
|
+
let j = i - 1, depth = -1, checks = 0;
|
213
|
+
for (; j > 0; j--) {
|
214
|
+
const op = wasm[j][0];
|
215
|
+
if (op === Opcodes.if || op === Opcodes.block || op === Opcodes.loop || op === Opcodes.try) {
|
216
|
+
depth++;
|
217
|
+
if (depth === 0) break;
|
218
|
+
}
|
219
|
+
if (op === Opcodes.end) depth--;
|
220
|
+
if (wasm[j][2]?.startsWith?.('TYPESWITCH')) checks++;
|
221
|
+
}
|
187
222
|
|
188
|
-
|
223
|
+
if (checks === 0) {
|
224
|
+
wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
|
225
|
+
wasm.splice(i - 1, 1); // remove this inst
|
189
226
|
|
190
|
-
|
191
|
-
i -= 2;
|
192
|
-
continue;
|
193
|
-
}
|
227
|
+
if (optLog) log('opt', 'removed unneeded entire typeswitch');
|
194
228
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
// -->
|
200
|
-
// local.set 0
|
229
|
+
if (i > 0) i--;
|
230
|
+
continue;
|
231
|
+
}
|
232
|
+
}
|
201
233
|
|
202
|
-
|
234
|
+
if (i < 1) continue;
|
235
|
+
let lastInst = wasm[i - 1];
|
203
236
|
|
204
|
-
lastInst[0]
|
237
|
+
if (lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_set && inst[0] === Opcodes.local_get) {
|
238
|
+
// replace set, get -> tee (sets and returns)
|
239
|
+
// local.set 0
|
240
|
+
// local.get 0
|
241
|
+
// -->
|
242
|
+
// local.tee 0
|
205
243
|
|
206
|
-
|
207
|
-
|
208
|
-
continue;
|
209
|
-
}
|
244
|
+
lastInst[0] = Opcodes.local_tee; // replace last inst opcode (set -> tee)
|
245
|
+
wasm.splice(i, 1); // remove this inst (get)
|
210
246
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
// <nothing>>
|
247
|
+
getCount[inst[1]]--;
|
248
|
+
i--;
|
249
|
+
// if (optLog) log('opt', `consolidated set, get -> tee`);
|
250
|
+
continue;
|
251
|
+
}
|
217
252
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
253
|
+
if ((lastInst[0] === Opcodes.local_get || lastInst[0] === Opcodes.global_get) && inst[0] === Opcodes.drop) {
|
254
|
+
// replace get, drop -> nothing
|
255
|
+
// local.get 0
|
256
|
+
// drop
|
257
|
+
// -->
|
258
|
+
//
|
222
259
|
|
223
|
-
|
224
|
-
// replace const 0, eq -> eqz
|
225
|
-
// i32.const 0
|
226
|
-
// i32.eq
|
227
|
-
// -->
|
228
|
-
// i32.eqz
|
229
|
-
|
230
|
-
inst[0] = Opcodes.eqz[0][0]; // eq -> eqz
|
231
|
-
wasm.splice(i - 1, 1); // remove const 0
|
232
|
-
i--;
|
233
|
-
continue;
|
234
|
-
}
|
260
|
+
getCount[lastInst[1]]--;
|
235
261
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
// -->
|
241
|
-
// <nothing>
|
242
|
-
|
243
|
-
wasm.splice(i - 1, 2); // remove this inst and last
|
244
|
-
i -= 2;
|
245
|
-
// if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
246
|
-
continue;
|
247
|
-
}
|
262
|
+
wasm.splice(i - 1, 2); // remove this inst and last
|
263
|
+
i -= 2;
|
264
|
+
continue;
|
265
|
+
}
|
248
266
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
wasm.splice(i - 1, 2); // remove this inst and last
|
257
|
-
i -= 2;
|
258
|
-
// if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
259
|
-
continue;
|
260
|
-
}
|
267
|
+
if (lastInst[0] === Opcodes.local_tee && inst[0] === Opcodes.drop) {
|
268
|
+
// replace tee, drop -> set
|
269
|
+
// local.tee 0
|
270
|
+
// drop
|
271
|
+
// -->
|
272
|
+
// local.set 0
|
261
273
|
|
262
|
-
|
263
|
-
// replace call, return with tail calls (return_call)
|
264
|
-
// call X
|
265
|
-
// return
|
266
|
-
// -->
|
267
|
-
// return_call X
|
274
|
+
getCount[lastInst[1]]--;
|
268
275
|
|
269
|
-
|
276
|
+
lastInst[0] = Opcodes.local_set; // change last op
|
270
277
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
}
|
278
|
+
wasm.splice(i, 1); // remove this inst
|
279
|
+
i--;
|
280
|
+
continue;
|
281
|
+
}
|
276
282
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
283
|
+
if ((lastInst[0] === Opcodes.i32_const || lastInst[0] === Opcodes.i64_const || lastInst[0] === Opcodes.f64_const) && inst[0] === Opcodes.drop) {
|
284
|
+
// replace const, drop -> <nothing>
|
285
|
+
// i32.const 0
|
286
|
+
// drop
|
287
|
+
// -->
|
288
|
+
// <nothing>
|
289
|
+
|
290
|
+
wasm.splice(i - 1, 2); // remove these inst
|
291
|
+
i -= 2;
|
292
|
+
continue;
|
293
|
+
}
|
294
|
+
|
295
|
+
if (inst[0] === Opcodes.eq && lastInst[0] === Opcodes.const && lastInst[1] === 0 && valtype !== 'f64') {
|
296
|
+
// replace const 0, eq -> eqz
|
297
|
+
// i32.const 0
|
298
|
+
// i32.eq
|
299
|
+
// -->
|
300
|
+
// i32.eqz
|
301
|
+
|
302
|
+
inst[0] = Opcodes.eqz[0][0]; // eq -> eqz
|
303
|
+
wasm.splice(i - 1, 1); // remove const 0
|
304
|
+
i--;
|
305
|
+
continue;
|
306
|
+
}
|
307
|
+
|
308
|
+
if (inst[0] === Opcodes.i32_wrap_i64 && (lastInst[0] === Opcodes.i64_extend_i32_s || lastInst[0] === Opcodes.i64_extend_i32_u)) {
|
309
|
+
// remove unneeded i32 -> i64 -> i32
|
310
|
+
// i64.extend_i32_s
|
311
|
+
// i32.wrap_i64
|
312
|
+
// -->
|
313
|
+
// <nothing>
|
289
314
|
|
290
|
-
|
291
|
-
|
315
|
+
wasm.splice(i - 1, 2); // remove this inst and last
|
316
|
+
i -= 2;
|
317
|
+
// if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
318
|
+
continue;
|
319
|
+
}
|
320
|
+
|
321
|
+
if (inst[0] === Opcodes.i32_trunc_sat_f64_s[0] && (lastInst[0] === Opcodes.f64_convert_i32_u || lastInst[0] === Opcodes.f64_convert_i32_s)) {
|
322
|
+
// remove unneeded i32 -> f64 -> i32
|
323
|
+
// f64.convert_i32_s || f64.convert_i32_u
|
324
|
+
// i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
|
325
|
+
// -->
|
326
|
+
// <nothing>
|
327
|
+
|
328
|
+
wasm.splice(i - 1, 2); // remove this inst and last
|
329
|
+
i -= 2;
|
330
|
+
// if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
331
|
+
continue;
|
332
|
+
}
|
333
|
+
|
334
|
+
if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
|
335
|
+
// change const and immediate i32 convert to i32 const
|
336
|
+
// f64.const 0
|
337
|
+
// i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
|
338
|
+
// -->
|
339
|
+
// i32.const 0
|
340
|
+
|
341
|
+
wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
|
342
|
+
|
343
|
+
wasm.splice(i, 1); // remove this inst
|
344
|
+
i--;
|
345
|
+
if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
346
|
+
continue;
|
347
|
+
}
|
348
|
+
|
349
|
+
if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
|
350
|
+
// change i32 const and immediate convert to const (opposite way of previous)
|
351
|
+
// i32.const 0
|
352
|
+
// f64.convert_i32_s || f64.convert_i32_u
|
353
|
+
// -->
|
354
|
+
// f64.const 0
|
355
|
+
|
356
|
+
wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
|
357
|
+
|
358
|
+
wasm.splice(i, 1); // remove this inst
|
359
|
+
i--;
|
360
|
+
if (optLog) log('opt', `converted i32 const -> convert into const`);
|
361
|
+
continue;
|
362
|
+
}
|
363
|
+
|
364
|
+
if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
|
365
|
+
// replace call, return with tail calls (return_call)
|
366
|
+
// call X
|
367
|
+
// return
|
368
|
+
// -->
|
369
|
+
// return_call X
|
370
|
+
|
371
|
+
lastInst[0] = Opcodes.return_call; // change last inst return -> return_call
|
372
|
+
|
373
|
+
wasm.splice(i, 1); // remove this inst (return)
|
374
|
+
i--;
|
375
|
+
if (optLog) log('opt', `tail called return, call`);
|
376
|
+
continue;
|
377
|
+
}
|
378
|
+
|
379
|
+
if (false && i === wasm.length - 1 && inst[0] === Opcodes.return) {
|
380
|
+
// replace final return, end -> end (wasm has implicit return)
|
381
|
+
// return
|
382
|
+
// end
|
383
|
+
// -->
|
384
|
+
// end
|
292
385
|
|
293
|
-
|
294
|
-
|
295
|
-
|
386
|
+
wasm.splice(i, 1); // remove this inst (return)
|
387
|
+
i--;
|
388
|
+
// if (optLog) log('opt', `removed redundant return at end`);
|
389
|
+
continue;
|
390
|
+
}
|
391
|
+
|
392
|
+
// remove unneeded before get with update exprs (n++, etc) when value is unused
|
393
|
+
if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
|
296
394
|
// local.get 1
|
297
395
|
// local.get 1
|
298
396
|
// -->
|
299
397
|
// local.get 1
|
300
398
|
|
301
399
|
// remove drop at the end as well
|
302
|
-
if (wasm[i + 4][0] === Opcodes.drop)
|
303
|
-
wasm.splice(i + 4, 1);
|
304
|
-
}
|
400
|
+
if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
|
305
401
|
|
306
402
|
wasm.splice(i, 1); // remove this inst (second get)
|
307
403
|
i--;
|
308
404
|
continue;
|
309
405
|
}
|
310
|
-
}
|
311
406
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
407
|
+
if (i < 2) continue;
|
408
|
+
const lastLastInst = wasm[i - 2];
|
409
|
+
|
410
|
+
if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
|
411
|
+
// local.set x
|
412
|
+
// local.tee y
|
413
|
+
// local.get x
|
414
|
+
// -->
|
415
|
+
// <nothing>
|
416
|
+
|
417
|
+
wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
|
418
|
+
if (optLog) log('opt', `removed redundant inline param local handling`);
|
419
|
+
i -= 3;
|
420
|
+
continue;
|
421
|
+
}
|
323
422
|
}
|
324
|
-
}
|
325
423
|
|
326
|
-
|
424
|
+
if (optLevel < 2) continue;
|
327
425
|
|
328
|
-
|
426
|
+
if (optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
|
329
427
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
428
|
+
// remove unneeded var: remove pass
|
429
|
+
// locals only got once. we don't need to worry about sets/else as these are only candidates and we will check for matching set + get insts in wasm
|
430
|
+
let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
|
431
|
+
if (optLog) log('opt', `found unneeded locals candidates: ${unneededCandidates.join(', ')} (${unneededCandidates.length}/${Object.keys(getCount).length})`);
|
334
432
|
|
335
|
-
|
336
|
-
|
337
|
-
|
433
|
+
// note: disabled for now due to instability
|
434
|
+
if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
|
435
|
+
if (i < 1) continue;
|
338
436
|
|
339
|
-
|
340
|
-
|
437
|
+
const inst = wasm[i];
|
438
|
+
const lastInst = wasm[i - 1];
|
341
439
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
440
|
+
if (lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_set && inst[0] === Opcodes.local_get && unneededCandidates.includes(inst[1])) {
|
441
|
+
// local.set N
|
442
|
+
// local.get N
|
443
|
+
// -->
|
444
|
+
// <nothing>
|
347
445
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
446
|
+
wasm.splice(i - 1, 2); // remove insts
|
447
|
+
i -= 2;
|
448
|
+
delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
|
449
|
+
if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
|
450
|
+
}
|
353
451
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
452
|
+
if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
|
453
|
+
// local.tee N
|
454
|
+
// -->
|
455
|
+
// <nothing>
|
358
456
|
|
359
|
-
|
360
|
-
|
457
|
+
wasm.splice(i, 1); // remove inst
|
458
|
+
i--;
|
361
459
|
|
362
|
-
|
363
|
-
|
364
|
-
|
460
|
+
const localName = Object.keys(f.locals)[inst[1]];
|
461
|
+
const removedIdx = f.locals[localName].idx;
|
462
|
+
delete f.locals[localName]; // remove from locals
|
365
463
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
464
|
+
// fix locals index for locals after
|
465
|
+
for (const x in f.locals) {
|
466
|
+
const local = f.locals[x];
|
467
|
+
if (local.idx > removedIdx) local.idx--;
|
468
|
+
}
|
371
469
|
|
372
|
-
|
373
|
-
|
374
|
-
|
470
|
+
for (const inst of wasm) {
|
471
|
+
if ((inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) && inst[1] > removedIdx) inst[1]--;
|
472
|
+
}
|
375
473
|
|
376
|
-
|
377
|
-
|
474
|
+
unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
|
475
|
+
unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
|
378
476
|
|
379
|
-
|
477
|
+
if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
|
478
|
+
}
|
380
479
|
}
|
381
|
-
}
|
382
480
|
|
383
|
-
|
384
|
-
|
481
|
+
const useCount = {};
|
482
|
+
for (const x in f.locals) useCount[f.locals[x].idx] = 0;
|
385
483
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
484
|
+
// final pass
|
485
|
+
depth = [];
|
486
|
+
for (let i = 0; i < wasm.length; i++) {
|
487
|
+
let inst = wasm[i];
|
488
|
+
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) useCount[inst[1]]++;
|
391
489
|
|
392
|
-
|
393
|
-
|
490
|
+
if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
|
491
|
+
if (inst[0] === Opcodes.end) depth.pop();
|
394
492
|
|
395
|
-
|
396
|
-
|
397
|
-
|
493
|
+
if (i < 2) continue;
|
494
|
+
const lastInst = wasm[i - 1];
|
495
|
+
const lastLastInst = wasm[i - 2];
|
398
496
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
497
|
+
// todo: add more math ops
|
498
|
+
if (optLevel >= 3 && (inst[0] === Opcodes.add || inst[0] === Opcodes.sub || inst[0] === Opcodes.mul) && lastLastInst[0] === Opcodes.const && lastInst[0] === Opcodes.const) {
|
499
|
+
// inline const math ops
|
500
|
+
// i32.const a
|
501
|
+
// i32.const b
|
502
|
+
// i32.add
|
503
|
+
// -->
|
504
|
+
// i32.const a + b
|
407
505
|
|
408
|
-
|
409
|
-
|
506
|
+
// does not work with leb encoded
|
507
|
+
if (lastInst.length > 2 || lastLastInst.length > 2) continue;
|
410
508
|
|
411
|
-
|
412
|
-
|
509
|
+
let a = lastLastInst[1];
|
510
|
+
let b = lastInst[1];
|
413
511
|
|
414
|
-
|
415
|
-
|
512
|
+
const val = performWasmOp(inst[0], a, b);
|
513
|
+
if (optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
|
416
514
|
|
417
|
-
|
418
|
-
|
515
|
+
wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
|
516
|
+
i -= 2;
|
517
|
+
}
|
419
518
|
}
|
420
|
-
}
|
421
519
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
520
|
+
const localIdxs = Object.values(f.locals).map(x => x.idx);
|
521
|
+
// remove unused locals (cleanup)
|
522
|
+
for (const x in useCount) {
|
523
|
+
if (useCount[x] === 0) {
|
524
|
+
const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
|
525
|
+
if (optLog) log('opt', `removed internal local ${x} (${name})`);
|
526
|
+
delete f.locals[name];
|
527
|
+
}
|
429
528
|
}
|
430
|
-
}
|
431
529
|
|
432
|
-
|
530
|
+
if (optLog) log('opt', `final use counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${useCount[f.locals[x].idx]}`).join(', ')}`);
|
531
|
+
}
|
433
532
|
}
|
434
533
|
|
435
534
|
// return funcs;
|