UNPKG

7.66 kBJavaScriptView Raw
1'use strict';
2
3var pipeline = require('json-pipeline');
4var frontier = require('dominance-frontier');
5var BitField = require('bitfield.js');
6
7function Scheduler(input) {
8 this.input = input;
9 this.output = pipeline.create('dominance');
10
11 // Mapping: input node to output block
12 this.blocks = new Array(this.input.nodes.length);
13 for (var i = 0; i < this.blocks.length; i++)
14 this.blocks[i] = null;
15
16 this.nodes = new Array(this.input.nodes.length);
17 for (var i = 0; i < this.nodes.length; i++)
18 this.nodes[i] = null;
19
20 this.visited = new BitField(this.input.nodes.length);
21
22 // Queue of instructions to schedule
23 this.scheduleQueue = [];
24}
25module.exports = Scheduler;
26
27Scheduler.create = function create(input) {
28 return new Scheduler(input);
29};
30
31Scheduler.prototype.run = function run() {
32 // Construct CFG out of control dependencies
33 this.constructCFG();
34
35 // Create dominance checks
36 frontier.create(this.output).compute();
37
38 // Schedule nodes into blocks
39 for (var i = 0; i < this.scheduleQueue.length; i++)
40 this.scheduleEarly(this.scheduleQueue[i]);
41
42 this.visited.wipe();
43 // Schedule control and their uses as late as possible
44 for (var i = 0; i < this.scheduleQueue.length; i++)
45 this.scheduleLate(this.scheduleQueue[i]);
46
47 // Schedule the rest
48 for (var i = 0; i < this.input.nodes.length; i++) {
49 var node = this.input.nodes[i];
50
51 // Pick-up only live blocks
52 if (this.blocks[node.index] !== null)
53 this.scheduleLate(node);
54 }
55
56 // Place nodes
57 for (var i = 0; i < this.scheduleQueue.length; i++)
58 this.place(this.scheduleQueue[i]);
59
60 // Move jump nodes to the end of the blocks
61 this.moveControl();
62
63 return this.output;
64};
65
66Scheduler.prototype.constructCFG = function constructCFG() {
67 var queue = [ this.input.nodes[0] ];
68 var visited = this.visited;
69 while (queue.length !== 0) {
70 var node = queue.pop();
71
72 // Visit nodes only once
73 if (!visited.set(node.index))
74 continue;
75
76 // Create parent blocks
77 var block = this.getBlock(node.index);
78
79 // Queue control nodes
80 this.queueControl(block, node);
81
82 // Get `jump` or `if` instruction
83 var last = this.getLastIndirection(node);
84
85 // No indirection
86 if (last === null)
87 continue;
88
89 // Merge point
90 if (last.opcode === 'region' || last.opcode === 'start') {
91 this.output.setCurrentBlock(block);
92 this.output.addControl('jump');
93
94 block.jump(this.getBlock(last.index));
95 queue.push(last);
96 continue;
97 }
98
99 // Queue and link block successors
100 for (var i = 0; i < last.controlUses.length; i += 2) {
101 var child = last.controlUses[i];
102
103 block.jump(this.getBlock(child.index));
104 queue.push(child);
105 }
106 }
107
108 return queue;
109};
110
111Scheduler.prototype.getBlock = function getBlock(index) {
112 if (this.blocks[index] !== null)
113 return this.blocks[index];
114
115 var block = this.output.block();
116 this.blocks[index] = block;
117 return block;
118};
119
120Scheduler.prototype.getLastIndirection = function getLastIndirection(node) {
121 for (var i = 0; i < node.controlUses.length; i += 2) {
122 var use = node.controlUses[i];
123 if (use.opcode === 'if' || use.opcode === 'jump')
124 return use;
125 if (use.opcode === 'region' || use.opcode === 'start')
126 return use;
127 }
128 return null;
129};
130
131Scheduler.prototype.queueControl = function queueControl(block, node) {
132 for (var i = 0; i < node.controlUses.length; i += 2) {
133 var use = node.controlUses[i];
134 if (use.opcode === 'region' || use.opcode === 'start')
135 continue;
136
137 // Process inputs of control node
138 this.scheduleQueue.push(use);
139
140 // Pin control nodes
141 this.blocks[use.index] = block;
142 }
143};
144
145Scheduler.prototype.scheduleEarly = function scheduleEarly(node) {
146 var block = this.blocks[node.index];
147
148 // Select highest block by default
149 if (block === null)
150 block = this.output.blocks[0];
151
152 for (var i = 0; i < node.inputs.length; i++) {
153 var input = node.inputs[i];
154 if (this.blocks[input.index] === null)
155 this.scheduleEarly(input);
156
157 var inputBlock = this.blocks[input.index];
158 if (block.dominates(inputBlock))
159 block = inputBlock;
160 }
161
162 // Node was pinned, it can't move anymore
163 if (this.blocks[node.index] !== null)
164 return;
165
166 this.blocks[node.index] = block;
167};
168
169Scheduler.prototype.scheduleLate = function scheduleLate(node) {
170 if (!this.visited.set(node.index))
171 return;
172
173 for (var i = 0; i < node.uses.length; i += 2) {
174 var use = node.uses[i];
175 var isPinned = use.control.length !== 0;
176 if (isPinned)
177 continue;
178
179 this.scheduleLate(use);
180 }
181
182 // Pinned nodes don't move
183 if (node.control.length !== 0)
184 return;
185
186 // Find lowest common ancestor in dominator tree
187 var lca = null;
188 for (var i = 0; i < node.uses.length; i += 2) {
189 var use = node.uses[i];
190 var index = node.uses[i + 1];
191
192 // Use appropriate phi's control input
193 if (use.opcode === 'ssa:phi') {
194 var controlBlock = this.blocks[use.control[0].index];
195 lca = this.findLCA(lca, controlBlock.predecessors[index]);
196 } else {
197 lca = this.findLCA(lca, this.blocks[use.index]);
198 }
199 }
200
201 // TODO(indutny): move out of loops
202 if (lca === null)
203 return;
204
205 var best = lca;
206 var worst = this.blocks[node.index];
207 while (lca !== worst.parent && lca !== null) {
208 if (lca.loopDepth < best.loopDepth)
209 best = lca;
210 lca = lca.parent;
211 }
212 this.blocks[node.index] = best;
213};
214
215Scheduler.prototype.findLCA = function LCA(lca, block) {
216 if (lca === null)
217 return block;
218
219 // Get on the same depth level
220 while (lca.dominanceDepth > block.dominanceDepth)
221 lca = lca.parent;
222 while (lca.dominanceDepth < block.dominanceDepth)
223 block = block.parent;
224
225 // Go up until match
226 while (lca !== block) {
227 lca = lca.parent;
228 block = block.parent;
229 }
230
231 return lca;
232};
233
234Scheduler.prototype.place = function place(node) {
235 // Already placed
236 if (this.nodes[node.index] !== null)
237 return;
238
239 var current = this.output.create(node.opcode);
240 this.nodes[node.index] = current;
241
242 // Place inputs
243 if (node.opcode !== 'ssa:phi')
244 for (var j = 0; j < node.inputs.length; j++)
245 this.place(node.inputs[j]);
246
247 var block = this.blocks[node.index];
248 block.add(current);
249
250 // Place phi inputs after placing phi: they are either in other blocks,
251 // or represent the value from the loop's back edge
252 if (node.opcode === 'ssa:phi')
253 for (var j = 0; j < node.inputs.length; j++)
254 this.place(node.inputs[j]);
255
256 // Assign literals
257 for (var j = 0; j < node.literals.length; j++)
258 current.addLiteral(node.literals[j]);
259
260 for (var j = 0; j < node.inputs.length; j++)
261 current.addInput(this.nodes[node.inputs[j].index]);
262
263 if (node.control.length === 1) {
264 current.setControl(this.blocks[node.control[0].index]);
265 } else if (node.control.length === 2) {
266 current.setControl(this.blocks[node.control[0].index],
267 this.blocks[node.control[1].index]);
268 }
269};
270
271Scheduler.prototype.moveControl = function moveControl() {
272 for (var i = 0; i < this.output.blocks.length; i++) {
273 var block = this.output.blocks[i];
274
275 for (var j = block.nodes.length - 1; j >= 0; j--) {
276 var node = block.nodes[j];
277 if (node.opcode !== 'if' &&
278 node.opcode !== 'return' &&
279 node.opcode !== 'jump') {
280 continue;
281 }
282
283 block.remove(j);
284 block.add(node);
285
286 // Only one jump per block
287 break;
288 }
289
290 // Now as the control is the last instruction - link blocks together
291 var last = block.getLastControl();
292 for (var j = 0; j < block.successors.length; j++)
293 block.successors[j].setControl(last);
294 }
295};