porffor 0.0.0-44bc2d8 → 0.0.0-48403fd

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