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/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,432 @@ export default (funcs, globals) => {
108
103
  for (const f of funcs) {
109
104
  const wasm = f.wasm;
110
105
 
111
- let depth = [];
106
+ let runs = 2; // how many by default? add arg?
107
+ while (runs > 0) {
108
+ runs--;
112
109
 
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
- }
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
- // 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;
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
- if (op === Opcodes.br) {
146
- hasBranch = true;
147
- break;
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 (!hasBranch) {
152
- wasm.splice(i, 1); // remove this inst (block)
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
- wasm.splice(j - 1, 1); // remove end of this block
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 (optLog) log('opt', `removed unneeded block in for loop`);
178
+ if (i > 0) i--;
179
+ inst = wasm[i];
159
180
  }
160
- }
161
181
 
162
- if (i < 1) continue;
163
- let lastInst = wasm[i - 1];
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
- 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
201
+ wasm.splice(i - 3, 4 + j - i); // remove cond and this if
202
+ i -= 4;
203
+ inst = wasm[i];
171
204
 
172
- lastInst[0] = Opcodes.local_tee; // replace last inst opcode (set -> tee)
173
- wasm.splice(i, 1); // remove this inst (get)
205
+ if (optLog) log('opt', `removed unneeded typeswitch check`);
206
+ }
207
+ }
174
208
 
175
- getCount[inst[1]]--;
176
- i--;
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
- 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
- //
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
- getCount[lastInst[1]]--;
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
- wasm.splice(i - 1, 2); // remove this inst and last
191
- i -= 2;
192
- continue;
193
- }
227
+ if (optLog) log('opt', 'removed unneeded entire typeswitch');
194
228
 
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
229
+ if (i > 0) i--;
230
+ continue;
231
+ }
232
+ }
201
233
 
202
- getCount[lastInst[1]]--;
234
+ if (i < 1) continue;
235
+ let lastInst = wasm[i - 1];
203
236
 
204
- lastInst[0] = Opcodes.local_set; // change last op
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
- wasm.splice(i, 1); // remove this inst
207
- i--;
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
- 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>>
247
+ getCount[inst[1]]--;
248
+ i--;
249
+ // if (optLog) log('opt', `consolidated set, get -> tee`);
250
+ continue;
251
+ }
217
252
 
218
- wasm.splice(i - 1, 2); // remove this inst
219
- i -= 2;
220
- continue;
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
- 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
- }
260
+ getCount[lastInst[1]]--;
235
261
 
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
- }
262
+ wasm.splice(i - 1, 2); // remove this inst and last
263
+ i -= 2;
264
+ continue;
265
+ }
248
266
 
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
- }
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
- 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
274
+ getCount[lastInst[1]]--;
268
275
 
269
- lastInst[0] = Opcodes.return_call; // change last inst return -> return_call
276
+ lastInst[0] = Opcodes.local_set; // change last op
270
277
 
271
- wasm.splice(i, 1); // remove this inst (return)
272
- i--;
273
- if (optLog) log('opt', `tail called return, call`);
274
- continue;
275
- }
278
+ wasm.splice(i, 1); // remove this inst
279
+ i--;
280
+ continue;
281
+ }
276
282
 
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
- }
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
- if (i < 2) continue;
291
- const lastLastInst = wasm[i - 2];
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
- 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) {
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
- 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;
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
- if (optLevel < 2) continue;
424
+ if (optLevel < 2) continue;
327
425
 
328
- if (optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
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
- // 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})`);
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
- // 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;
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
- const inst = wasm[i];
340
- const lastInst = wasm[i - 1];
437
+ const inst = wasm[i];
438
+ const lastInst = wasm[i - 1];
341
439
 
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>
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
- 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
- }
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
- if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
355
- // local.tee N
356
- // -->
357
- // <nothing>
452
+ if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
453
+ // local.tee N
454
+ // -->
455
+ // <nothing>
358
456
 
359
- wasm.splice(i, 1); // remove inst
360
- i--;
457
+ wasm.splice(i, 1); // remove inst
458
+ i--;
361
459
 
362
- const localName = Object.keys(f.locals)[inst[1]];
363
- const removedIdx = f.locals[localName].idx;
364
- delete f.locals[localName]; // remove from locals
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
- // 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
- }
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
- 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
- }
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
- unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
377
- unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
474
+ unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
475
+ unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
378
476
 
379
- if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
477
+ if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
478
+ }
380
479
  }
381
- }
382
480
 
383
- const useCount = {};
384
- for (const x in f.locals) useCount[f.locals[x].idx] = 0;
481
+ const useCount = {};
482
+ for (const x in f.locals) useCount[f.locals[x].idx] = 0;
385
483
 
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]]++;
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
- 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();
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
- if (i < 2) continue;
396
- const lastInst = wasm[i - 1];
397
- const lastLastInst = wasm[i - 2];
493
+ if (i < 2) continue;
494
+ const lastInst = wasm[i - 1];
495
+ const lastLastInst = wasm[i - 2];
398
496
 
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
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
- // does not work with leb encoded
409
- if (lastInst.length > 2 || lastLastInst.length > 2) continue;
506
+ // does not work with leb encoded
507
+ if (lastInst.length > 2 || lastLastInst.length > 2) continue;
410
508
 
411
- let a = lastLastInst[1];
412
- let b = lastInst[1];
509
+ let a = lastLastInst[1];
510
+ let b = lastInst[1];
413
511
 
414
- const val = performWasmOp(inst[0], a, b);
415
- if (optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
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
- wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
418
- i -= 2;
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
- 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];
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
- if (optLog) log('opt', `final use counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${useCount[f.locals[x].idx]}`).join(', ')}`);
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;