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/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
- // deno compat
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
- let depth = [];
106
+ const lastType = f.locals['#last_type'];
112
107
 
113
- let getCount = {}, setCount = {};
114
- for (const x in f.locals) {
115
- getCount[f.locals[x].idx] = 0;
116
- setCount[f.locals[x].idx] = 0;
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
- // main pass
120
- for (let i = 0; i < wasm.length; i++) {
121
- let inst = wasm[i];
122
-
123
- if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
124
- if (inst[0] === Opcodes.end) depth.pop();
125
-
126
- if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
127
- if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
128
-
129
- if (inst[0] === Opcodes.block) {
130
- // remove unneeded blocks (no brs inside)
131
- // block
132
- // ...
133
- // end
134
- // -->
135
- // ...
136
-
137
- let hasBranch = false, j = i, depth = 0;
138
- for (; j < wasm.length; j++) {
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
- if (op === Opcodes.br) {
146
- hasBranch = true;
147
- break;
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 (!hasBranch) {
152
- wasm.splice(i, 1); // remove this inst (block)
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
- wasm.splice(j - 1, 1); // remove end of this block
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 (optLog) log('opt', `removed unneeded block in for loop`);
180
+ if (i > 0) i--;
181
+ inst = wasm[i];
159
182
  }
160
- }
161
183
 
162
- if (i < 1) continue;
163
- let lastInst = wasm[i - 1];
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
- if (lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_set && inst[0] === Opcodes.local_get) {
166
- // replace set, get -> tee (sets and returns)
167
- // local.set 0
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
- lastInst[0] = Opcodes.local_tee; // replace last inst opcode (set -> tee)
173
- wasm.splice(i, 1); // remove this inst (get)
207
+ if (optLog) log('opt', `removed unneeded typeswitch check`);
208
+ }
209
+ }
174
210
 
175
- getCount[inst[1]]--;
176
- i--;
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
- if ((lastInst[0] === Opcodes.local_get || lastInst[0] === Opcodes.global_get) && inst[0] === Opcodes.drop) {
182
- // replace get, drop -> nothing
183
- // local.get 0
184
- // drop
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
- getCount[lastInst[1]]--;
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
- wasm.splice(i - 1, 2); // remove this inst and last
191
- i -= 2;
192
- continue;
193
- }
230
+ if (optLog) log('opt', 'removed unneeded entire typeswitch');
194
231
 
195
- if (lastInst[0] === Opcodes.local_tee && inst[0] === Opcodes.drop) {
196
- // replace tee, drop -> set
197
- // local.tee 0
198
- // drop
199
- // -->
200
- // local.set 0
232
+ if (i > 0) i--;
233
+ continue;
234
+ }
235
+ }
201
236
 
202
- getCount[lastInst[1]]--;
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
- lastInst[0] = Opcodes.local_set; // change last op
244
+ if (i < 1) continue;
245
+ let lastInst = wasm[i - 1];
205
246
 
206
- wasm.splice(i, 1); // remove this inst
207
- i--;
208
- continue;
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
- if ((lastInst[0] === Opcodes.i32_const || lastInst[0] === Opcodes.i64_const || lastInst[0] === Opcodes.f64_const) && inst[0] === Opcodes.drop) {
212
- // replace const, drop -> <nothing>
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
- wasm.splice(i - 1, 2); // remove this inst
219
- i -= 2;
220
- continue;
221
- }
257
+ getCount[inst[1]]--;
258
+ i--;
259
+ // if (optLog) log('opt', `consolidated set, get -> tee`);
260
+ continue;
261
+ }
222
262
 
223
- if (inst[0] === Opcodes.eq && lastInst[0] === Opcodes.const && lastInst[1] === 0 && valtype !== 'f64') {
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
- }
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
- if (inst[0] === Opcodes.i32_wrap_i64 && (lastInst[0] === Opcodes.i64_extend_i32_s || lastInst[0] === Opcodes.i64_extend_i32_u)) {
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
- if (inst[0] === Opcodes.i32_trunc_sat_f64_s[0] && (lastInst[0] === Opcodes.f64_convert_i32_u || lastInst[0] === Opcodes.f64_convert_i32_s)) {
250
- // remove unneeded i32 -> f64 -> i32
251
- // f64.convert_i32_s || f64.convert_i32_u
252
- // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
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
- if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
- // replace call, return with tail calls (return_call)
264
- // call X
265
- // return
266
- // -->
267
- // return_call X
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
- lastInst[0] = Opcodes.return_call; // change last inst return -> return_call
284
+ getCount[lastInst[1]]--;
270
285
 
271
- wasm.splice(i, 1); // remove this inst (return)
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
- if (false && i === wasm.length - 1 && inst[0] === Opcodes.return) {
278
- // replace final return, end -> end (wasm has implicit return)
279
- // return
280
- // end
281
- // -->
282
- // end
283
-
284
- wasm.splice(i, 1); // remove this inst (return)
285
- i--;
286
- // if (optLog) log('opt', `removed redundant return at end`);
287
- continue;
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
- if (i < 2) continue;
291
- const lastLastInst = wasm[i - 2];
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
- if (depth.length === 2) {
294
- // hack to remove unneeded before get in for loops with (...; i++)
295
- if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
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
- if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
313
- // local.set x
314
- // local.tee y
315
- // local.get x
316
- // -->
317
- // <nothing>
318
-
319
- wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
320
- if (optLog) log('opt', `removed redundant inline param local handling`);
321
- i -= 3;
322
- continue;
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
- if (optLevel < 2) continue;
434
+ if (optLevel < 2) continue;
327
435
 
328
- if (optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
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
- // remove unneeded var: remove pass
331
- // 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
332
- let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
333
- if (optLog) log('opt', `found unneeded locals candidates: ${unneededCandidates.join(', ')} (${unneededCandidates.length}/${Object.keys(getCount).length})`);
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
- // note: disabled for now due to instability
336
- if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
337
- if (i < 1) continue;
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
- const inst = wasm[i];
340
- const lastInst = wasm[i - 1];
447
+ const inst = wasm[i];
448
+ const lastInst = wasm[i - 1];
341
449
 
342
- if (lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_set && inst[0] === Opcodes.local_get && unneededCandidates.includes(inst[1])) {
343
- // local.set N
344
- // local.get N
345
- // -->
346
- // <nothing>
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
- wasm.splice(i - 1, 2); // remove insts
349
- i -= 2;
350
- delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
351
- if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
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
- if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
355
- // local.tee N
356
- // -->
357
- // <nothing>
462
+ if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
463
+ // local.tee N
464
+ // -->
465
+ // <nothing>
358
466
 
359
- wasm.splice(i, 1); // remove inst
360
- i--;
467
+ wasm.splice(i, 1); // remove inst
468
+ i--;
361
469
 
362
- const localName = Object.keys(f.locals)[inst[1]];
363
- const removedIdx = f.locals[localName].idx;
364
- delete f.locals[localName]; // remove from locals
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
- // fix locals index for locals after
367
- for (const x in f.locals) {
368
- const local = f.locals[x];
369
- if (local.idx > removedIdx) local.idx--;
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
- for (const inst of wasm) {
373
- if ((inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) && inst[1] > removedIdx) inst[1]--;
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
- unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
377
- unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
484
+ unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
485
+ unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
378
486
 
379
- if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
487
+ if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
488
+ }
380
489
  }
381
- }
382
490
 
383
- const useCount = {};
384
- for (const x in f.locals) useCount[f.locals[x].idx] = 0;
491
+ const useCount = {};
492
+ for (const x in f.locals) useCount[f.locals[x].idx] = 0;
385
493
 
386
- // final pass
387
- depth = [];
388
- for (let i = 0; i < wasm.length; i++) {
389
- let inst = wasm[i];
390
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) useCount[inst[1]]++;
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
- if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
393
- if (inst[0] === Opcodes.end) depth.pop();
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
- if (i < 2) continue;
396
- const lastInst = wasm[i - 1];
397
- const lastLastInst = wasm[i - 2];
503
+ if (i < 2) continue;
504
+ const lastInst = wasm[i - 1];
505
+ const lastLastInst = wasm[i - 2];
398
506
 
399
- // todo: add more math ops
400
- if (optLevel >= 3 && (inst[0] === Opcodes.add || inst[0] === Opcodes.sub || inst[0] === Opcodes.mul) && lastLastInst[0] === Opcodes.const && lastInst[0] === Opcodes.const) {
401
- // inline const math ops
402
- // i32.const a
403
- // i32.const b
404
- // i32.add
405
- // -->
406
- // i32.const a + b
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
- // does not work with leb encoded
409
- if (lastInst.length > 2 || lastLastInst.length > 2) continue;
516
+ // does not work with leb encoded
517
+ if (lastInst.length > 2 || lastLastInst.length > 2) continue;
410
518
 
411
- let a = lastLastInst[1];
412
- let b = lastInst[1];
519
+ let a = lastLastInst[1];
520
+ let b = lastInst[1];
413
521
 
414
- const val = performWasmOp(inst[0], a, b);
415
- if (optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
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
- wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
418
- i -= 2;
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
- const localIdxs = Object.values(f.locals).map(x => x.idx);
423
- // remove unused locals (cleanup)
424
- for (const x in useCount) {
425
- if (useCount[x] === 0) {
426
- const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
427
- if (optLog) log('opt', `removed internal local ${x} (${name})`);
428
- delete f.locals[name];
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
- if (optLog) log('opt', `final use counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${useCount[f.locals[x].idx]}`).join(', ')}`);
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;