forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathswitchrecognition.cpp
415 lines (364 loc) · 16.2 KB
/
switchrecognition.cpp
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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif
// We mainly rely on TryLowerSwitchToBitTest in these heuristics, but jump tables can be useful
// even without conversion to a bitmap test.
#define SWITCH_MAX_DISTANCE ((TARGET_POINTER_SIZE * BITS_PER_BYTE) - 1)
#define SWITCH_MIN_TESTS 3
//-----------------------------------------------------------------------------
// optSwitchRecognition: Optimize range check for `x == cns1 || x == cns2 || x == cns3 ...`
// pattern and convert it to Switch block (jump table) which is then *might* be converted
// to a bitmap test via TryLowerSwitchToBitTest.
// TODO: recognize general jump table patterns.
//
// Return Value:
// MODIFIED_EVERYTHING if the optimization was applied.
//
PhaseStatus Compiler::optSwitchRecognition()
{
// Limit to XARCH, ARM is already doing a great job with such comparisons using
// a series of ccmp instruction (see ifConvert phase).
#ifdef TARGET_XARCH
bool modified = false;
for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->Next())
{
// block->KindIs(BBJ_COND) check is for better throughput.
if (block->KindIs(BBJ_COND) && !block->isRunRarely() && optSwitchDetectAndConvert(block))
{
JITDUMP("Converted block " FMT_BB " to switch\n", block->bbNum)
modified = true;
}
}
if (modified)
{
fgRenumberBlocks();
return PhaseStatus::MODIFIED_EVERYTHING;
}
#endif
return PhaseStatus::MODIFIED_NOTHING;
}
//------------------------------------------------------------------------------
// IsConstantTestCondBlock : Does the given block represent a simple BBJ_COND
// constant test? e.g. JTRUE(EQ/NE(X, CNS)).
//
// Arguments:
// block - The block to check
// blockIfTrue - [out] The block that will be jumped to if X == CNS
// blockIfFalse - [out] The block that will be jumped to if X != CNS
// isReversed - [out] True if the condition is reversed (GT_NE)
// variableNode - [out] The variable node (X in the example above)
// cns - [out] The constant value (CNS in the example above)
//
// Return Value:
// True if the block represents a constant test, false otherwise
//
bool IsConstantTestCondBlock(const BasicBlock* block,
BasicBlock** blockIfTrue,
BasicBlock** blockIfFalse,
bool* isReversed,
GenTree** variableNode = nullptr,
ssize_t* cns = nullptr)
{
// NOTE: caller is expected to check that a block has multiple statements or not
if (block->KindIs(BBJ_COND) && (block->lastStmt() != nullptr) && !block->HasFlag(BBF_DONT_REMOVE))
{
const GenTree* rootNode = block->lastStmt()->GetRootNode();
assert(rootNode->OperIs(GT_JTRUE));
// It has to be JTRUE(GT_EQ or GT_NE)
if (rootNode->gtGetOp1()->OperIs(GT_EQ, GT_NE))
{
GenTree* op1 = rootNode->gtGetOp1()->gtGetOp1();
GenTree* op2 = rootNode->gtGetOp1()->gtGetOp2();
if (!varTypeIsIntOrI(op1) || !varTypeIsIntOrI(op2))
{
// Only TYP_INT and TYP_LONG are supported
return false;
}
// We're looking for "X EQ/NE CNS" or "CNS EQ/NE X" pattern
if (op1->IsCnsIntOrI() ^ op2->IsCnsIntOrI())
{
// TODO: relax this to support any side-effect free expression
if (!op1->OperIs(GT_LCL_VAR) && !op2->OperIs(GT_LCL_VAR))
{
return false;
}
*isReversed = rootNode->gtGetOp1()->OperIs(GT_NE);
*blockIfTrue = *isReversed ? block->GetFalseTarget() : block->GetTrueTarget();
*blockIfFalse = *isReversed ? block->GetTrueTarget() : block->GetFalseTarget();
if (block->FalseTargetIs(block) || block->TrueTargetIs(block))
{
// Ignoring weird cases like a condition jumping to itself
return false;
}
if ((variableNode != nullptr) && (cns != nullptr))
{
if (op1->IsCnsIntOrI())
{
*cns = op1->AsIntCon()->IconValue();
*variableNode = op2;
}
else
{
*cns = op2->AsIntCon()->IconValue();
*variableNode = op1;
}
}
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------------
// optSwitchDetectAndConvert : Try to detect a series of conditional blocks which
// can be converted into a switch (jump-table) construct. See optSwitchConvert
// for more details.
//
// Arguments:
// firstBlock - A block to start the search from
//
// Return Value:
// True if the conversion was successful, false otherwise
//
bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock)
{
assert(firstBlock->KindIs(BBJ_COND));
GenTree* variableNode = nullptr;
ssize_t cns = 0;
BasicBlock* blockIfTrue = nullptr;
BasicBlock* blockIfFalse = nullptr;
// The algorithm is simple - we check that the given block is a constant test block
// and then try to accumulate as many constant test blocks as possible. Once we hit
// a block that doesn't match the pattern, we start processing the accumulated blocks.
bool isReversed = false;
if (IsConstantTestCondBlock(firstBlock, &blockIfTrue, &blockIfFalse, &isReversed, &variableNode, &cns))
{
if (isReversed)
{
// First block uses NE - we don't support this yet. We currently expect all blocks to use EQ
// and allow NE for the last one (because it's what Roslyn usually emits).
// TODO: make it more flexible and support cases like "x != cns1 && x != cns2 && ..."
return false;
}
// No more than SWITCH_MAX_TABLE_SIZE blocks are allowed (arbitrary limit in this context)
int testValueIndex = 0;
ssize_t testValues[SWITCH_MAX_DISTANCE] = {};
testValues[testValueIndex++] = cns;
const BasicBlock* prevBlock = firstBlock;
// Now walk the next blocks and see if they are basically the same type of test
for (const BasicBlock* currBb = firstBlock->Next(); currBb != nullptr; currBb = currBb->Next())
{
GenTree* currVariableNode = nullptr;
ssize_t currCns = 0;
BasicBlock* currBlockIfTrue = nullptr;
BasicBlock* currBlockIfFalse = nullptr;
if (!currBb->hasSingleStmt())
{
// Only the first conditional block can have multiple statements.
// Stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
// Inspect secondary blocks
if (IsConstantTestCondBlock(currBb, &currBlockIfTrue, &currBlockIfFalse, &isReversed, &currVariableNode,
&currCns))
{
if (currBlockIfTrue != blockIfTrue)
{
// This blocks jumps to a different target, stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
if (!GenTree::Compare(currVariableNode, variableNode))
{
// A different variable node is used, stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
if (currBb->GetUniquePred(this) != prevBlock)
{
// Multiple preds in a secondary block, stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
if (!BasicBlock::sameEHRegion(prevBlock, currBb))
{
// Current block is in a different EH region, stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
// Ok we can work with that, add the test value to the list
testValues[testValueIndex++] = currCns;
if (testValueIndex == SWITCH_MAX_DISTANCE)
{
// Too many suitable tests found - stop and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
if (isReversed)
{
// We only support reversed test (GT_NE) for the last block.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
prevBlock = currBb;
}
else
{
// Current block is not a suitable test, stop searching and process what we already have.
return optSwitchConvert(firstBlock, testValueIndex, testValues, variableNode);
}
}
}
return false;
}
//------------------------------------------------------------------------------
// optSwitchConvert : Convert a series of conditional blocks into a switch block
// conditional blocks are blocks that have a single statement that is a GT_EQ
// or GT_NE node. The blocks are expected jump into the same target and test
// the same variable against different constants
//
// Arguments:
// firstBlock - First conditional block in the chain
// testsCount - Number of conditional blocks in the chain
// testValues - Array of constants that are tested against the variable
// nodeToTest - Variable node that is tested against the constants
//
// Return Value:
// True if the conversion was successful, false otherwise
//
bool Compiler::optSwitchConvert(BasicBlock* firstBlock, int testsCount, ssize_t* testValues, GenTree* nodeToTest)
{
assert(firstBlock->KindIs(BBJ_COND));
assert(!varTypeIsSmall(nodeToTest));
if (testsCount < SWITCH_MIN_TESTS)
{
// Early out - short chains.
return false;
}
static_assert_no_msg(SWITCH_MIN_TESTS > 0);
// Find max and min values in the testValues array
// At this point we have at least SWITCH_MIN_TESTS values in the array
ssize_t minValue = testValues[0];
ssize_t maxValue = testValues[0];
int testIdx = 0;
for (; testIdx < testsCount; testIdx++)
{
ssize_t testValue = testValues[testIdx];
if (testValue < 0)
{
// We don't support negative values
break;
}
const ssize_t newMinValue = min(minValue, testValue);
const ssize_t newMaxValue = max(maxValue, testValue);
assert(newMaxValue >= newMinValue);
if ((newMaxValue - newMinValue) > SWITCH_MAX_DISTANCE)
{
// Stop here, the distance between min and max is too big
break;
}
minValue = newMinValue;
maxValue = newMaxValue;
}
// testIdx is now representing the index of last good test value,
// Update testsCount as it's now potentially smaller than initially.
testsCount = testIdx;
if (testsCount < SWITCH_MIN_TESTS)
{
// Make sure we still have at least SWITCH_MIN_TESTS values after we filtered out some of them
return false;
}
// if MaxValue is less than SWITCH_MAX_DISTANCE then don't bother with SUB(val, minValue)
if (maxValue <= SWITCH_MAX_DISTANCE)
{
minValue = 0;
}
// Find the last block in the chain
const BasicBlock* lastBlock = firstBlock;
for (int i = 0; i < testsCount - 1; i++)
{
lastBlock = lastBlock->Next();
}
BasicBlock* blockIfTrue = nullptr;
BasicBlock* blockIfFalse = nullptr;
bool isReversed = false;
const bool isTest = IsConstantTestCondBlock(lastBlock, &blockIfTrue, &blockIfFalse, &isReversed);
assert(isTest);
// Convert firstBlock to a switch block
firstBlock->SetSwitch(new (this, CMK_BasicBlock) BBswtDesc);
firstBlock->bbCodeOffsEnd = lastBlock->bbCodeOffsEnd;
firstBlock->lastStmt()->GetRootNode()->ChangeOper(GT_SWITCH);
// The root node is now SUB(nodeToTest, minValue) if minValue != 0
GenTree* switchValue = gtCloneExpr(nodeToTest);
if (minValue != 0)
{
switchValue =
gtNewOperNode(GT_SUB, nodeToTest->TypeGet(), switchValue, gtNewIconNode(minValue, nodeToTest->TypeGet()));
}
firstBlock->lastStmt()->GetRootNode()->AsOp()->gtOp1 = switchValue;
gtSetStmtInfo(firstBlock->lastStmt());
fgSetStmtSeq(firstBlock->lastStmt());
gtUpdateStmtSideEffects(firstBlock->lastStmt());
// Unlink and remove the whole chain of conditional blocks
BasicBlock* blockToRemove = firstBlock->Next();
fgRemoveRefPred(blockToRemove, firstBlock);
while (!lastBlock->NextIs(blockToRemove))
{
blockToRemove = fgRemoveBlock(blockToRemove, true);
}
const auto jumpCount = static_cast<unsigned>(maxValue - minValue + 1);
assert((jumpCount > 0) && (jumpCount <= SWITCH_MAX_DISTANCE + 1));
const auto jmpTab = new (this, CMK_BasicBlock) BasicBlock*[jumpCount + 1 /*default case*/];
// Quirk: lastBlock's false target may have diverged from bbNext. If the false target is behind firstBlock,
// we may create a cycle in the BasicBlock list by setting firstBlock->bbNext to it.
// Add a new BBJ_ALWAYS to the false target to avoid this.
// (We only need this if the false target is behind firstBlock,
// but it's cheaper to just check if the false target has diverged)
// TODO-NoFallThrough: Revisit this quirk?
bool skipPredRemoval = false;
if (!lastBlock->FalseTargetIs(lastBlock->Next()))
{
if (isReversed)
{
assert(lastBlock->FalseTargetIs(blockIfTrue));
fgRemoveRefPred(blockIfTrue, firstBlock);
blockIfTrue = fgNewBBafter(BBJ_ALWAYS, firstBlock, true, blockIfTrue);
fgAddRefPred(blockIfTrue->GetTarget(), blockIfTrue);
skipPredRemoval = true;
}
else
{
assert(lastBlock->FalseTargetIs(blockIfFalse));
blockIfFalse = fgNewBBafter(BBJ_ALWAYS, firstBlock, true, blockIfFalse);
fgAddRefPred(blockIfFalse->GetTarget(), blockIfFalse);
}
}
fgHasSwitch = true;
firstBlock->GetSwitchTargets()->bbsCount = jumpCount + 1;
firstBlock->GetSwitchTargets()->bbsHasDefault = true;
firstBlock->GetSwitchTargets()->bbsDstTab = jmpTab;
firstBlock->SetNext(isReversed ? blockIfTrue : blockIfFalse);
// Splitting doesn't work well with jump-tables currently
opts.compProcedureSplitting = false;
// Compose a bit vector of all the values we have in the testValues array
// to quickly check if a value is in the array
ssize_t bitVector = 0;
for (testIdx = 0; testIdx < testsCount; testIdx++)
{
assert(testIdx <= (int)((sizeof(ssize_t) * BITS_PER_BYTE) - 1));
bitVector |= (ssize_t)(1ULL << static_cast<unsigned>((testValues[testIdx] - minValue)));
}
// Unlink blockIfTrue from firstBlock, we're going to link it again in the loop below.
if (!skipPredRemoval)
{
fgRemoveRefPred(blockIfTrue, firstBlock);
}
for (unsigned i = 0; i < jumpCount; i++)
{
// value exists in the testValues array (via bitVector) - 'true' case.
const bool isTrue = (bitVector & static_cast<ssize_t>(1ULL << i)) != 0;
jmpTab[i] = isTrue ? blockIfTrue : blockIfFalse;
fgAddRefPred(jmpTab[i], firstBlock);
}
// Link the 'default' case
jmpTab[jumpCount] = blockIfFalse;
fgAddRefPred(blockIfFalse, firstBlock);
return true;
}