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