11
11
#define SWITCH_MAX_DISTANCE ((TARGET_POINTER_SIZE * BITS_PER_BYTE) - 1 )
12
12
#define SWITCH_MIN_TESTS 3
13
13
14
+ // This is a heuristics based value tuned for optimal performance
15
+ #define CONVERT_SWITCH_TO_CCMP_MIN_TEST 5
16
+
14
17
// -----------------------------------------------------------------------------
15
18
// optRecognizeAndOptimizeSwitchJumps: Optimize range check for `x == cns1 || x == cns2 || x == cns3 ...`
16
19
// pattern and convert it to a BBJ_SWITCH block (jump table), which then *might* be converted
@@ -160,18 +163,22 @@ bool IsConstantTestCondBlock(const BasicBlock* block,
160
163
// testingForConversion - Test if its likely a switch conversion will happen.
161
164
// Used to prevent a pessimization when optimizing for conditional chaining.
162
165
// Done in this function to prevent maintaining the check in two places.
166
+ // ccmpVec - BitVec to use to track all the nodes participating in a single switch
163
167
//
164
168
// Return Value:
165
169
// True if the conversion was successful, false otherwise
166
170
//
167
- bool Compiler::optSwitchDetectAndConvert (BasicBlock* firstBlock, bool testingForConversion)
171
+ bool Compiler::optSwitchDetectAndConvert (BasicBlock* firstBlock, bool testingForConversion, BitVec* ccmpVec )
168
172
{
169
173
assert (firstBlock->KindIs (BBJ_COND));
170
174
171
- GenTree* variableNode = nullptr ;
172
- ssize_t cns = 0 ;
173
- BasicBlock* trueTarget = nullptr ;
174
- BasicBlock* falseTarget = nullptr ;
175
+ GenTree* variableNode = nullptr ;
176
+ ssize_t cns = 0 ;
177
+ BasicBlock* trueTarget = nullptr ;
178
+ BasicBlock* falseTarget = nullptr ;
179
+ int testValueIndex = 0 ;
180
+ ssize_t testValues[SWITCH_MAX_DISTANCE] = {};
181
+ weight_t falseLikelihood = firstBlock->GetFalseEdge ()->getLikelihood ();
175
182
176
183
// The algorithm is simple - we check that the given block is a constant test block
177
184
// and then try to accumulate as many constant test blocks as possible. Once we hit
@@ -186,17 +193,30 @@ bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock, bool testingFor
186
193
// TODO: make it more flexible and support cases like "x != cns1 && x != cns2 && ..."
187
194
return false ;
188
195
}
196
+ if (testingForConversion)
197
+ {
198
+ assert (ccmpVec != nullptr );
199
+ BitVecTraits ccmpTraits (fgBBNumMax + 1 , this );
200
+ if (BitVecOps::IsMember (&ccmpTraits, *ccmpVec, firstBlock->bbNum ))
201
+ {
202
+ BitVecOps::RemoveElemD (&ccmpTraits, *ccmpVec, firstBlock->bbNum );
203
+ return true ;
204
+ }
205
+ else
206
+ {
207
+ BitVecOps::ClearD (&ccmpTraits, *ccmpVec);
208
+ }
209
+ }
189
210
190
211
// No more than SWITCH_MAX_TABLE_SIZE blocks are allowed (arbitrary limit in this context)
191
- int testValueIndex = 0 ;
192
- ssize_t testValues[SWITCH_MAX_DISTANCE] = {};
193
- testValues[testValueIndex] = cns;
212
+ testValueIndex = 0 ;
213
+ testValues[testValueIndex] = cns;
194
214
testValueIndex++;
195
215
196
216
// Track likelihood of reaching the false block
197
217
//
198
- weight_t falseLikelihood = firstBlock->GetFalseEdge ()->getLikelihood ();
199
- const BasicBlock* prevBlock = firstBlock;
218
+ falseLikelihood = firstBlock->GetFalseEdge ()->getLikelihood ();
219
+ const BasicBlock* prevBlock = firstBlock;
200
220
201
221
// Now walk the chain of test blocks, and see if they are basically the same type of test
202
222
for (BasicBlock *currBb = falseTarget, *currFalseTarget; currBb != nullptr ; currBb = currFalseTarget)
@@ -209,8 +229,8 @@ bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock, bool testingFor
209
229
{
210
230
// Only the first conditional block can have multiple statements.
211
231
// Stop searching and process what we already have.
212
- return !testingForConversion &&
213
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
232
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
233
+ testingForConversion, ccmpVec );
214
234
}
215
235
216
236
// Inspect secondary blocks
@@ -220,29 +240,29 @@ bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock, bool testingFor
220
240
if (currTrueTarget != trueTarget)
221
241
{
222
242
// This blocks jumps to a different target, stop searching and process what we already have.
223
- return !testingForConversion &&
224
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
243
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
244
+ testingForConversion, ccmpVec );
225
245
}
226
246
227
247
if (!GenTree::Compare (currVariableNode, variableNode->gtEffectiveVal ()))
228
248
{
229
249
// A different variable node is used, stop searching and process what we already have.
230
- return !testingForConversion &&
231
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
250
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
251
+ testingForConversion, ccmpVec );
232
252
}
233
253
234
254
if (currBb->GetUniquePred (this ) != prevBlock)
235
255
{
236
256
// Multiple preds in a secondary block, stop searching and process what we already have.
237
- return !testingForConversion &&
238
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
257
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
258
+ testingForConversion, ccmpVec );
239
259
}
240
260
241
261
if (!BasicBlock::sameEHRegion (prevBlock, currBb))
242
262
{
243
263
// Current block is in a different EH region, stop searching and process what we already have.
244
- return !testingForConversion &&
245
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
264
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
265
+ testingForConversion, ccmpVec );
246
266
}
247
267
248
268
// Ok we can work with that, add the test value to the list
@@ -252,27 +272,23 @@ bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock, bool testingFor
252
272
if (testValueIndex == SWITCH_MAX_DISTANCE)
253
273
{
254
274
// Too many suitable tests found - stop and process what we already have.
255
- return !testingForConversion &&
256
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
275
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
276
+ testingForConversion, ccmpVec );
257
277
}
258
278
259
279
if (isReversed)
260
280
{
261
281
// We only support reversed test (GT_NE) for the last block.
262
- return !testingForConversion &&
263
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
282
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
283
+ testingForConversion, ccmpVec );
264
284
}
265
-
266
- if (testingForConversion)
267
- return true ;
268
-
269
285
prevBlock = currBb;
270
286
}
271
287
else
272
288
{
273
289
// Current block is not a suitable test, stop searching and process what we already have.
274
- return !testingForConversion &&
275
- optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode );
290
+ return optSwitchConvert (firstBlock, testValueIndex, testValues, falseLikelihood, variableNode,
291
+ testingForConversion, ccmpVec );
276
292
}
277
293
}
278
294
}
@@ -292,16 +308,30 @@ bool Compiler::optSwitchDetectAndConvert(BasicBlock* firstBlock, bool testingFor
292
308
// testValues - Array of constants that are tested against the variable
293
309
// falseLikelihood - Likelihood of control flow reaching the false block
294
310
// nodeToTest - Variable node that is tested against the constants
311
+ // testingForConversion - Test if its likely a switch conversion will happen.
312
+ // Used to prevent a pessimization when optimizing for conditional chaining.
313
+ // Done in this function to prevent maintaining the check in two places.
314
+ // ccmpVec - BitVec to use to track all the nodes participating in a single switch
295
315
//
296
316
// Return Value:
297
317
// True if the conversion was successful, false otherwise
298
318
//
299
- bool Compiler::optSwitchConvert (
300
- BasicBlock* firstBlock, int testsCount, ssize_t * testValues, weight_t falseLikelihood, GenTree* nodeToTest)
319
+ bool Compiler::optSwitchConvert (BasicBlock* firstBlock,
320
+ int testsCount,
321
+ ssize_t * testValues,
322
+ weight_t falseLikelihood,
323
+ GenTree* nodeToTest,
324
+ bool testingForConversion,
325
+ BitVec* ccmpVec)
301
326
{
302
327
assert (firstBlock->KindIs (BBJ_COND));
303
328
assert (!varTypeIsSmall (nodeToTest));
304
329
330
+ if (testingForConversion && (testsCount < CONVERT_SWITCH_TO_CCMP_MIN_TEST))
331
+ {
332
+ return false ;
333
+ }
334
+
305
335
if (testsCount < SWITCH_MIN_TESTS)
306
336
{
307
337
// Early out - short chains.
@@ -376,6 +406,20 @@ bool Compiler::optSwitchConvert(
376
406
FlowEdge* const trueEdge = firstBlock->GetTrueEdge ();
377
407
FlowEdge* const falseEdge = firstBlock->GetFalseEdge ();
378
408
409
+ if (testingForConversion)
410
+ {
411
+ assert (ccmpVec != nullptr );
412
+ BitVecTraits ccmpTraits (fgBBNumMax + 1 , this );
413
+ // Return if we are just checking for a possibility of a switch convert and not actually making the conversion
414
+ // to switch here.
415
+ BasicBlock* iterBlock = firstBlock;
416
+ for (int i = 0 ; i < testsCount; i++)
417
+ {
418
+ BitVecOps::AddElemD (&ccmpTraits, *ccmpVec, iterBlock->bbNum );
419
+ iterBlock = iterBlock->GetFalseTarget ();
420
+ }
421
+ return true ;
422
+ }
379
423
// Convert firstBlock to a switch block
380
424
const unsigned jumpCount = static_cast <unsigned >(maxValue - minValue + 1 );
381
425
assert ((jumpCount > 0 ) && (jumpCount <= SWITCH_MAX_DISTANCE + 1 ));
0 commit comments