-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoptimizer.js
More file actions
125 lines (109 loc) · 4.15 KB
/
Copy pathoptimizer.js
File metadata and controls
125 lines (109 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class Optimizer {
constructor() {
this.eliminatedIndices = [];
}
optimize(instructions) {
this.eliminatedIndices = [];
let current = [...instructions];
let changed = true;
let passes = 0;
while (changed && passes < 10) {
changed = false;
passes++;
const result = this.eliminateDeadCode(current);
if (result.removed > 0) {
changed = true;
current = result.instructions;
}
}
return current;
}
// returns { instructions, removed }
eliminateDeadCode(instructions) {
const hasControlFlow = instructions.some(i =>
['label','goto','if_false_goto','if_goto'].includes(i.type)
);
const keep = [];
let removed = 0;
for (let i = 0; i < instructions.length; i++) {
const instr = instructions[i];
if (this.isEliminable(instr) && instr.result) {
const used = hasControlFlow
? this.isUsedAnywhere(instr.result, i, instructions)
: this.isUsedAfterLinear(instr.result, i, instructions);
if (!used) {
instr._dead = true;
this.eliminatedIndices.push(i);
removed++;
continue;
}
}
// remove unused declarations
if (instr.type === 'declare') {
if (!this.isUsedAnywhere(instr.result, -1, instructions)) {
instr._dead = true;
this.eliminatedIndices.push(i);
removed++;
continue;
}
}
keep.push(instr);
}
return { instructions: keep, removed };
}
// can this instruction be eliminated if its result is unused?
isEliminable(instr) {
// never eliminate these side-effect instructions
const nonEliminable = [
'label','goto','if_false_goto','if_goto',
'call','param','return',
'func_begin','func_end',
'break','continue'
];
if (nonEliminable.includes(instr.type)) return false;
return instr.result != null;
}
// for code with control flow: check if var is used anywhere (conservative)
isUsedAnywhere(varName, defIndex, instructions) {
for (let i = 0; i < instructions.length; i++) {
if (i === defIndex) continue;
if (this.instrUsesVar(instructions[i], varName)) return true;
}
return false;
}
// for straight-line code: check forward until redefinition
isUsedAfterLinear(varName, defIndex, instructions) {
for (let i = defIndex + 1; i < instructions.length; i++) {
const instr = instructions[i];
if (this.instrUsesVar(instr, varName)) return true;
// If redefined and not self-referencing, stop
if (instr.result === varName && instr.arg1 !== varName && instr.arg2 !== varName) {
return false;
}
}
return false;
}
// does this instruction reference varName as an operand?
instrUsesVar(instr, varName) {
if (instr.arg1 === varName) return true;
if (instr.arg2 === varName) return true;
if (instr.type === 'return' && instr.arg1 === varName) return true;
if ((instr.type === 'if_false_goto' || instr.type === 'if_goto') && instr.arg1 === varName) return true;
return false;
}
// run a diff between original and optimized to find dead lines
static diff(original, optimized) {
const optimizedSet = new Set();
for (const instr of optimized) {
optimizedSet.add(JSON.stringify(instr));
}
const deadLines = [];
for (let i = 0; i < original.length; i++) {
const key = JSON.stringify(original[i]);
if (!optimizedSet.has(key)) {
deadLines.push(i);
}
}
return deadLines;
}
}