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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/LICM.h"
#include "jit/IonAnalysis.h"
#include "jit/JitSpewer.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
using namespace js;
using namespace js::jit;
// There are two constants which control whether a loop is LICM'd or is left
// unchanged. For rationale see comment in jit::LICM() below.
//
// A bit of quick profiling with the wasm Embenchen suite on x64 shows that
// the threshold pair (100,25) has either no effect or gives a small net
// reduction in memory traffic, compared to unconstrained LICMing. Halving
// them to (50,12) gives a small overall increase in memory traffic,
// suggesting it excludes too many loops from LICM. Doubling them to (200,50)
// gives a win that is even smaller than (100,25), hence (100,25) seems the
// best choice.
//
// If a loop has more than this number of basic blocks in its body, it won't
// be LICM'd.
static constexpr size_t LargestAllowedLoop = 100;
// If a loop contains an MTableSwitch instruction that has more than this many
// successors, it won't be LICM'd.
static constexpr size_t LargestAllowedTableSwitch = 25;
// Test whether any instruction in the loop possiblyCalls().
static bool LoopContainsPossibleCall(MIRGraph& graph, MBasicBlock* header,
MBasicBlock* backedge) {
for (auto i(graph.rpoBegin(header));; ++i) {
MOZ_ASSERT(i != graph.rpoEnd(),
"Reached end of graph searching for blocks in loop");
MBasicBlock* block = *i;
if (!block->isMarked()) {
continue;
}
for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;
++insIter) {
MInstruction* ins = *insIter;
if (ins->possiblyCalls()) {
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM, " Possible call found at %s%u", ins->opName(),
ins->id());
#endif
return true;
}
}
if (block == backedge) {
break;
}
}
return false;
}
// Tests whether any instruction in the loop is a table-switch with more than
// `LargestAllowedTableSwitch` successors. If it returns true, it also
// returns the actual number of successors of the instruction in question,
// although that is used only for statistics/debug printing.
static bool LoopContainsBigTableSwitch(MIRGraph& graph, MBasicBlock* header,
/*OUT*/ size_t* numSuccessors) {
MBasicBlock* backedge = header->backedge();
for (auto i(graph.rpoBegin(header));; ++i) {
MOZ_ASSERT(i != graph.rpoEnd(),
"Reached end of graph searching for blocks in loop");
MBasicBlock* block = *i;
if (!block->isMarked()) {
continue;
}
for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;
++insIter) {
MInstruction* ins = *insIter;
if (ins->isTableSwitch() &&
ins->toTableSwitch()->numSuccessors() > LargestAllowedTableSwitch) {
*numSuccessors = ins->toTableSwitch()->numSuccessors();
return true;
}
}
if (block == backedge) {
break;
}
}
return false;
}
// When a nested loop has no exits back into what would be its parent loop,
// MarkLoopBlocks on the parent loop doesn't mark the blocks of the nested
// loop, since they technically aren't part of the loop. However, AliasAnalysis
// currently does consider such nested loops to be part of their parent
// loops. Consequently, we can't use IsInLoop on dependency() values; we must
// test whether a dependency() is *before* the loop, even if it is not
// technically in the loop.
static bool IsBeforeLoop(MDefinition* ins, MBasicBlock* header) {
return ins->block()->id() < header->id();
}
// Test whether the given instruction is inside the loop (and thus not
// loop-invariant).
static bool IsInLoop(MDefinition* ins) { return ins->block()->isMarked(); }
// Test whether the given instruction is cheap and not worth hoisting unless
// one of its users will be hoisted as well.
static bool RequiresHoistedUse(const MDefinition* ins, bool hasCalls) {
if (ins->isBox()) {
MOZ_ASSERT(!ins->toBox()->input()->isBox(),
"Box of a box could lead to unbounded recursion");
return true;
}
// Integer constants are usually cheap and aren't worth hoisting on their
// own, in general. Floating-point constants typically are worth hoisting,
// unless they'll end up being spilled (eg. due to a call).
if (ins->isConstant() && (!IsFloatingPointType(ins->type()) || hasCalls)) {
return true;
}
return false;
}
// Test whether the given instruction has any operands defined within the loop.
static bool HasOperandInLoop(MInstruction* ins, bool hasCalls) {
// An instruction is only loop invariant if it and all of its operands can
// be safely hoisted into the loop preheader.
for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
MDefinition* op = ins->getOperand(i);
if (!IsInLoop(op)) {
continue;
}
if (RequiresHoistedUse(op, hasCalls)) {
// Recursively test for loop invariance. Note that the recursion is
// bounded because we require RequiresHoistedUse to be set at each
// level.
if (!HasOperandInLoop(op->toInstruction(), hasCalls)) {
continue;
}
}
return true;
}
return false;
}
// Test whether the given instruction is hoistable, ignoring memory
// dependencies.
static bool IsHoistableIgnoringDependency(MInstruction* ins, bool hasCalls) {
return ins->isMovable() && !ins->isEffectful() &&
!HasOperandInLoop(ins, hasCalls);
}
// Test whether the given instruction has a memory dependency inside the loop.
static bool HasDependencyInLoop(MInstruction* ins, MBasicBlock* header) {
// Don't hoist if this instruction depends on a store inside the loop.
if (MDefinition* dep = ins->dependency()) {
return !IsBeforeLoop(dep, header);
}
return false;
}
// Test whether the given instruction is hoistable.
static bool IsHoistable(MInstruction* ins, MBasicBlock* header, bool hasCalls) {
return IsHoistableIgnoringDependency(ins, hasCalls) &&
!HasDependencyInLoop(ins, header);
}
// In preparation for hoisting an instruction, hoist any of its operands which
// were too cheap to hoist on their own.
static void MoveDeferredOperands(MInstruction* ins, MInstruction* hoistPoint,
bool hasCalls) {
// If any of our operands were waiting for a user to be hoisted, make a note
// to hoist them.
for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
MDefinition* op = ins->getOperand(i);
if (!IsInLoop(op)) {
continue;
}
MOZ_ASSERT(RequiresHoistedUse(op, hasCalls),
"Deferred loop-invariant operand is not cheap");
MInstruction* opIns = op->toInstruction();
// Recursively move the operands. Note that the recursion is bounded
// because we require RequiresHoistedUse to be set at each level.
MoveDeferredOperands(opIns, hoistPoint, hasCalls);
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM,
" Hoisting %s%u (now that a user will be hoisted)",
opIns->opName(), opIns->id());
#endif
opIns->block()->moveBefore(hoistPoint, opIns);
opIns->setBailoutKind(BailoutKind::LICM);
}
}
static void VisitLoopBlock(MBasicBlock* block, MBasicBlock* header,
MInstruction* hoistPoint, bool hasCalls) {
for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;) {
MInstruction* ins = *insIter++;
if (!IsHoistable(ins, header, hasCalls)) {
#ifdef JS_JITSPEW
if (IsHoistableIgnoringDependency(ins, hasCalls)) {
JitSpew(JitSpew_LICM,
" %s%u isn't hoistable due to dependency on %s%u",
ins->opName(), ins->id(), ins->dependency()->opName(),
ins->dependency()->id());
}
#endif
continue;
}
// Don't hoist a cheap constant if it doesn't enable us to hoist one of
// its uses. We want those instructions as close as possible to their
// use, to minimize register pressure.
if (RequiresHoistedUse(ins, hasCalls)) {
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM, " %s%u will be hoisted only if its users are",
ins->opName(), ins->id());
#endif
continue;
}
// Hoist operands which were too cheap to hoist on their own.
MoveDeferredOperands(ins, hoistPoint, hasCalls);
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM, " Hoisting %s%u", ins->opName(), ins->id());
#endif
// Move the instruction to the hoistPoint.
block->moveBefore(hoistPoint, ins);
ins->setBailoutKind(BailoutKind::LICM);
}
}
static void VisitLoop(MIRGraph& graph, MBasicBlock* header) {
MInstruction* hoistPoint = header->loopPredecessor()->lastIns();
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM, " Visiting loop with header block%u, hoisting to %s%u",
header->id(), hoistPoint->opName(), hoistPoint->id());
#endif
MBasicBlock* backedge = header->backedge();
// This indicates whether the loop contains calls or other things which
// clobber most or all floating-point registers. In such loops,
// floating-point constants should not be hoisted unless it enables further
// hoisting.
bool hasCalls = LoopContainsPossibleCall(graph, header, backedge);
for (auto i(graph.rpoBegin(header));; ++i) {
MOZ_ASSERT(i != graph.rpoEnd(),
"Reached end of graph searching for blocks in loop");
MBasicBlock* block = *i;
if (!block->isMarked()) {
continue;
}
#ifdef JS_JITSPEW
JitSpew(JitSpew_LICM, " Visiting block%u", block->id());
#endif
VisitLoopBlock(block, header, hoistPoint, hasCalls);
if (block == backedge) {
break;
}
}
}
bool jit::LICM(MIRGenerator* mir, MIRGraph& graph) {
JitSpew(JitSpew_LICM, "Beginning LICM pass");
// Iterate in RPO to visit outer loops before inner loops. We'd hoist the
// same things either way, but outer first means we do a little less work.
for (auto i(graph.rpoBegin()), e(graph.rpoEnd()); i != e; ++i) {
MBasicBlock* header = *i;
if (!header->isLoopHeader()) {
continue;
}
bool canOsr;
size_t numBlocks = MarkLoopBlocks(graph, header, &canOsr);
if (numBlocks == 0) {
JitSpew(JitSpew_LICM,
" Skipping loop with header block%u -- contains zero blocks",
header->id());
continue;
}
// There are various reasons why we might choose not to LICM a given loop:
//
// (a) Hoisting out of a loop that has an entry from the OSR block in
// addition to its normal entry is tricky. In theory we could clone
// the instruction and insert phis. In practice we don't bother.
//
// (b) If the loop contains a large number of blocks, we play safe and
// punt, in order to reduce the risk of creating excessive register
// pressure by hoisting lots of values out of the loop. In a larger
// loop there's more likely to be duplication of invariant expressions
// within the loop body, and that duplication will be GVN'd but only
// within the scope of the loop body, so there's less loss from not
// lifting them out of the loop entirely.
//
// (c) If the loop contains a multiway switch with many successors, there
// could be paths with low probabilities, from which LICMing will be a
// net loss, especially if a large number of values are hoisted out.
// See bug 1708381 for a spectacular example and bug 1712078 for
// further discussion.
//
// It's preferable to perform test (c) only if (a) and (b) pass since (c)
// is more expensive to determine -- requiring a visit to all the MIR
// nodes -- than (a) or (b), which only involve visiting all blocks.
bool doVisit = true;
if (canOsr) {
JitSpew(JitSpew_LICM, " Skipping loop with header block%u due to OSR",
header->id());
doVisit = false;
} else if (numBlocks > LargestAllowedLoop) {
JitSpew(JitSpew_LICM,
" Skipping loop with header block%u "
"due to too many blocks (%u > thresh %u)",
header->id(), (uint32_t)numBlocks, (uint32_t)LargestAllowedLoop);
doVisit = false;
} else {
size_t switchSize = 0;
if (LoopContainsBigTableSwitch(graph, header, &switchSize)) {
JitSpew(JitSpew_LICM,
" Skipping loop with header block%u "
"due to oversize tableswitch (%u > thresh %u)",
header->id(), (uint32_t)switchSize,
(uint32_t)LargestAllowedTableSwitch);
doVisit = false;
}
}
if (doVisit) {
VisitLoop(graph, header);
}
UnmarkLoopBlocks(graph, header);
if (mir->shouldCancel("LICM (main loop)")) {
return false;
}
}
return true;
}
|