1 /* 2 * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.lir; 24 25 import java.util.Arrays; 26 import java.util.Comparator; 27 28 import org.graalvm.compiler.asm.Assembler; 29 import org.graalvm.compiler.asm.Label; 30 import org.graalvm.compiler.core.common.calc.Condition; 31 import org.graalvm.compiler.lir.asm.CompilationResultBuilder; 32 33 import jdk.vm.ci.meta.Constant; 34 import jdk.vm.ci.meta.JavaConstant; 35 36 /** 37 * This class encapsulates different strategies on how to generate code for switch instructions. 38 * 39 * The {@link #getBestStrategy(double[], JavaConstant[], LabelRef[])} method can be used to get 40 * strategy with the smallest average effort (average number of comparisons until a decision is 41 * reached). The strategy returned by this method will have its averageEffort set, while a strategy 42 * constructed directly will not. 43 */ 44 public abstract class SwitchStrategy { 45 46 private interface SwitchClosure { 47 /** 48 * Generates a conditional or unconditional jump. The jump will be unconditional if 49 * condition is null. If defaultTarget is true, then the jump will go the the default. 50 * 51 * @param index Index of the value and the jump target (only used if defaultTarget == false) 52 * @param condition The condition on which to jump (can be null) 53 * @param defaultTarget true if the jump should go to the default target, false if index 54 * should be used. 55 */ 56 void conditionalJump(int index, Condition condition, boolean defaultTarget); 57 58 /** 59 * Generates a conditional jump to the target with the specified index. The fall through 60 * should go to the default target. 61 * 62 * @param index Index of the value and the jump target 63 * @param condition The condition on which to jump 64 * @param canFallThrough true if this is the last instruction in the switch statement, to 65 * allow for fall-through optimizations. 66 */ 67 void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough); 68 69 /** 70 * Create a new label and generate a conditional jump to it. 71 * 72 * @param index Index of the value and the jump target 73 * @param condition The condition on which to jump 74 * @return a new Label 75 */ 76 Label conditionalJump(int index, Condition condition); 77 78 /** 79 * Binds a label returned by {@link #conditionalJump(int, Condition)}. 80 */ 81 void bind(Label label); 82 83 /** 84 * Return true iff the target of both indexes is the same. 85 */ 86 boolean isSameTarget(int index1, int index2); 87 } 88 89 /** 90 * Backends can subclass this abstract class and generate code for switch strategies by 91 * implementing the {@link #conditionalJump(int, Condition, Label)} method. 92 */ 93 public abstract static class BaseSwitchClosure implements SwitchClosure { 94 95 private final CompilationResultBuilder crb; 96 private final Assembler masm; 97 private final LabelRef[] keyTargets; 98 private final LabelRef defaultTarget; 99 100 public BaseSwitchClosure(CompilationResultBuilder crb, Assembler masm, LabelRef[] keyTargets, LabelRef defaultTarget) { 101 this.crb = crb; 102 this.masm = masm; 103 this.keyTargets = keyTargets; 104 this.defaultTarget = defaultTarget; 105 } 106 107 /** 108 * This method generates code for a comparison between the actual value and the constant at 109 * the given index and a condition jump to target. 110 */ 111 protected abstract void conditionalJump(int index, Condition condition, Label target); 112 113 @Override 114 public void conditionalJump(int index, Condition condition, boolean targetDefault) { 115 Label target = targetDefault ? defaultTarget.label() : keyTargets[index].label(); 116 if (condition == null) { 117 masm.jmp(target); 118 } else { 119 conditionalJump(index, condition, target); 120 } 121 } 122 123 @Override 124 public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { 125 if (canFallThrough && crb.isSuccessorEdge(defaultTarget)) { 126 conditionalJump(index, condition, keyTargets[index].label()); 127 } else if (canFallThrough && crb.isSuccessorEdge(keyTargets[index])) { 128 conditionalJump(index, condition.negate(), defaultTarget.label()); 129 } else { 130 conditionalJump(index, condition, keyTargets[index].label()); 131 masm.jmp(defaultTarget.label()); 132 } 133 } 134 135 @Override 136 public Label conditionalJump(int index, Condition condition) { 137 Label label = new Label(); 138 conditionalJump(index, condition, label); 139 return label; 140 } 141 142 @Override 143 public void bind(Label label) { 144 masm.bind(label); 145 } 146 147 @Override 148 public boolean isSameTarget(int index1, int index2) { 149 return keyTargets[index1] == keyTargets[index2]; 150 } 151 152 } 153 154 /** 155 * This closure is used internally to determine the average effort for a certain strategy on a 156 * given switch instruction. 157 */ 158 private class EffortClosure implements SwitchClosure { 159 160 private int defaultEffort; 161 private int defaultCount; 162 private final int[] keyEfforts = new int[keyProbabilities.length]; 163 private final int[] keyCounts = new int[keyProbabilities.length]; 164 private final LabelRef[] keyTargets; 165 166 EffortClosure(LabelRef[] keyTargets) { 167 this.keyTargets = keyTargets; 168 } 169 170 @Override 171 public void conditionalJump(int index, Condition condition, boolean defaultTarget) { 172 // nothing to do 173 } 174 175 @Override 176 public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { 177 // nothing to do 178 } 179 180 @Override 181 public Label conditionalJump(int index, Condition condition) { 182 // nothing to do 183 return null; 184 } 185 186 @Override 187 public void bind(Label label) { 188 // nothing to do 189 } 190 191 @Override 192 public boolean isSameTarget(int index1, int index2) { 193 return keyTargets[index1] == keyTargets[index2]; 194 } 195 196 public double getAverageEffort() { 197 double defaultProbability = 1; 198 double effort = 0; 199 for (int i = 0; i < keyProbabilities.length; i++) { 200 effort += keyEfforts[i] * keyProbabilities[i] / keyCounts[i]; 201 defaultProbability -= keyProbabilities[i]; 202 } 203 return effort + defaultEffort * defaultProbability / defaultCount; 204 } 205 } 206 207 public final double[] keyProbabilities; 208 private double averageEffort = -1; 209 private EffortClosure effortClosure; 210 211 public SwitchStrategy(double[] keyProbabilities) { 212 assert keyProbabilities.length >= 2; 213 this.keyProbabilities = keyProbabilities; 214 } 215 216 public abstract Constant[] getKeyConstants(); 217 218 public double getAverageEffort() { 219 assert averageEffort >= 0 : "average effort was not calculated yet for this strategy"; 220 return averageEffort; 221 } 222 223 /** 224 * Tells the system that the given (inclusive) range of keys is reached after depth number of 225 * comparisons, which is used to calculate the average effort. 226 */ 227 protected void registerEffort(int rangeStart, int rangeEnd, int depth) { 228 if (effortClosure != null) { 229 for (int i = rangeStart; i <= rangeEnd; i++) { 230 effortClosure.keyEfforts[i] += depth; 231 effortClosure.keyCounts[i]++; 232 } 233 } 234 } 235 236 /** 237 * Tells the system that the default successor is reached after depth number of comparisons, 238 * which is used to calculate average effort. 239 */ 240 protected void registerDefaultEffort(int depth) { 241 if (effortClosure != null) { 242 effortClosure.defaultEffort += depth; 243 effortClosure.defaultCount++; 244 } 245 } 246 247 @Override 248 public String toString() { 249 return getClass().getSimpleName() + "[avgEffort=" + averageEffort + "]"; 250 } 251 252 /** 253 * This strategy orders the keys according to their probability and creates one equality 254 * comparison per key. 255 */ 256 public static class SequentialStrategy extends SwitchStrategy { 257 private final Integer[] indexes; 258 private final Constant[] keyConstants; 259 260 public SequentialStrategy(final double[] keyProbabilities, Constant[] keyConstants) { 261 super(keyProbabilities); 262 assert keyProbabilities.length == keyConstants.length; 263 264 this.keyConstants = keyConstants; 265 int keyCount = keyConstants.length; 266 indexes = new Integer[keyCount]; 267 for (int i = 0; i < keyCount; i++) { 268 indexes[i] = i; 269 } 270 Arrays.sort(indexes, new Comparator<Integer>() { 271 @Override 272 public int compare(Integer o1, Integer o2) { 273 return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; 274 } 275 }); 276 } 277 278 @Override 279 public Constant[] getKeyConstants() { 280 return keyConstants; 281 } 282 283 @Override 284 public void run(SwitchClosure closure) { 285 for (int i = 0; i < keyConstants.length - 1; i++) { 286 closure.conditionalJump(indexes[i], Condition.EQ, false); 287 registerEffort(indexes[i], indexes[i], i + 1); 288 } 289 closure.conditionalJumpOrDefault(indexes[keyConstants.length - 1], Condition.EQ, true); 290 registerEffort(indexes[keyConstants.length - 1], indexes[keyConstants.length - 1], keyConstants.length); 291 registerDefaultEffort(keyConstants.length); 292 } 293 } 294 295 /** 296 * Base class for strategies that rely on primitive integer keys. 297 */ 298 private abstract static class PrimitiveStrategy extends SwitchStrategy { 299 protected final JavaConstant[] keyConstants; 300 301 protected PrimitiveStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { 302 super(keyProbabilities); 303 assert keyProbabilities.length == keyConstants.length; 304 this.keyConstants = keyConstants; 305 } 306 307 @Override 308 public JavaConstant[] getKeyConstants() { 309 return keyConstants; 310 } 311 312 /** 313 * Looks for the end of a stretch of key constants that are successive numbers and have the 314 * same target. 315 */ 316 protected int getSliceEnd(SwitchClosure closure, int pos) { 317 int slice = pos; 318 while (slice < (keyConstants.length - 1) && keyConstants[slice + 1].asLong() == keyConstants[slice].asLong() + 1 && closure.isSameTarget(slice, slice + 1)) { 319 slice++; 320 } 321 return slice; 322 } 323 } 324 325 /** 326 * This strategy divides the keys into ranges of successive keys with the same target and 327 * creates comparisons for these ranges. 328 */ 329 public static class RangesStrategy extends PrimitiveStrategy { 330 private final Integer[] indexes; 331 332 public RangesStrategy(final double[] keyProbabilities, JavaConstant[] keyConstants) { 333 super(keyProbabilities, keyConstants); 334 335 int keyCount = keyConstants.length; 336 indexes = new Integer[keyCount]; 337 for (int i = 0; i < keyCount; i++) { 338 indexes[i] = i; 339 } 340 Arrays.sort(indexes, new Comparator<Integer>() { 341 @Override 342 public int compare(Integer o1, Integer o2) { 343 return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; 344 } 345 }); 346 } 347 348 @Override 349 public void run(SwitchClosure closure) { 350 int depth = 0; 351 closure.conditionalJump(0, Condition.LT, true); 352 registerDefaultEffort(++depth); 353 int rangeStart = 0; 354 int rangeEnd = getSliceEnd(closure, rangeStart); 355 while (rangeEnd != keyConstants.length - 1) { 356 if (rangeStart == rangeEnd) { 357 closure.conditionalJump(rangeStart, Condition.EQ, false); 358 registerEffort(rangeStart, rangeEnd, ++depth); 359 } else { 360 if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { 361 closure.conditionalJump(rangeStart, Condition.LT, true); 362 registerDefaultEffort(++depth); 363 } 364 closure.conditionalJump(rangeEnd, Condition.LE, false); 365 registerEffort(rangeStart, rangeEnd, ++depth); 366 } 367 rangeStart = rangeEnd + 1; 368 rangeEnd = getSliceEnd(closure, rangeStart); 369 } 370 if (rangeStart == rangeEnd) { 371 closure.conditionalJumpOrDefault(rangeStart, Condition.EQ, true); 372 registerEffort(rangeStart, rangeEnd, ++depth); 373 registerDefaultEffort(depth); 374 } else { 375 if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { 376 closure.conditionalJump(rangeStart, Condition.LT, true); 377 registerDefaultEffort(++depth); 378 } 379 closure.conditionalJumpOrDefault(rangeEnd, Condition.LE, true); 380 registerEffort(rangeStart, rangeEnd, ++depth); 381 registerDefaultEffort(depth); 382 } 383 } 384 } 385 386 /** 387 * This strategy recursively subdivides the list of keys to create a binary search based on 388 * probabilities. 389 */ 390 public static class BinaryStrategy extends PrimitiveStrategy { 391 392 private static final double MIN_PROBABILITY = 0.00001; 393 394 private final double[] probabilitySums; 395 396 public BinaryStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { 397 super(keyProbabilities, keyConstants); 398 probabilitySums = new double[keyProbabilities.length + 1]; 399 double sum = 0; 400 for (int i = 0; i < keyConstants.length; i++) { 401 sum += Math.max(keyProbabilities[i], MIN_PROBABILITY); 402 probabilitySums[i + 1] = sum; 403 } 404 } 405 406 @Override 407 public void run(SwitchClosure closure) { 408 recurseBinarySwitch(closure, 0, keyConstants.length - 1, 0); 409 } 410 411 /** 412 * Recursively generate a list of comparisons that always subdivides the keys in the given 413 * (inclusive) range in the middle (in terms of probability, not index). If left is bigger 414 * than zero, then we always know that the value is equal to or bigger than the left key. 415 * This does not hold for the right key, as there may be a gap afterwards. 416 */ 417 private void recurseBinarySwitch(SwitchClosure closure, int left, int right, int startDepth) { 418 assert startDepth < keyConstants.length * 3 : "runaway recursion in binary switch"; 419 int depth = startDepth; 420 boolean leftBorder = left == 0; 421 boolean rightBorder = right == keyConstants.length - 1; 422 423 if (left + 1 == right) { 424 // only two possible values 425 if (leftBorder || rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong() || keyConstants[left].asLong() + 1 != keyConstants[right].asLong()) { 426 closure.conditionalJump(left, Condition.EQ, false); 427 registerEffort(left, left, ++depth); 428 closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); 429 registerEffort(right, right, ++depth); 430 registerDefaultEffort(depth); 431 } else { 432 // here we know that the value can only be one of these two keys in the range 433 closure.conditionalJump(left, Condition.EQ, false); 434 registerEffort(left, left, ++depth); 435 closure.conditionalJump(right, null, false); 436 registerEffort(right, right, depth); 437 } 438 return; 439 } 440 double probabilityStart = probabilitySums[left]; 441 double probabilityMiddle = (probabilityStart + probabilitySums[right + 1]) / 2; 442 assert probabilityStart >= probabilityStart; 443 int middle = left; 444 while (getSliceEnd(closure, middle + 1) < right && probabilitySums[getSliceEnd(closure, middle + 1)] < probabilityMiddle) { 445 middle = getSliceEnd(closure, middle + 1); 446 } 447 middle = getSliceEnd(closure, middle); 448 assert middle < keyConstants.length - 1; 449 450 if (getSliceEnd(closure, left) == middle) { 451 if (left == 0) { 452 closure.conditionalJump(0, Condition.LT, true); 453 registerDefaultEffort(++depth); 454 } 455 closure.conditionalJump(middle, Condition.LE, false); 456 registerEffort(left, middle, ++depth); 457 458 if (middle + 1 == right) { 459 closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); 460 registerEffort(right, right, ++depth); 461 registerDefaultEffort(depth); 462 } else { 463 if (keyConstants[middle].asLong() + 1 != keyConstants[middle + 1].asLong()) { 464 closure.conditionalJump(middle + 1, Condition.LT, true); 465 registerDefaultEffort(++depth); 466 } 467 if (getSliceEnd(closure, middle + 1) == right) { 468 if (right == keyConstants.length - 1 || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { 469 closure.conditionalJumpOrDefault(right, Condition.LE, rightBorder); 470 registerEffort(middle + 1, right, ++depth); 471 registerDefaultEffort(depth); 472 } else { 473 closure.conditionalJump(middle + 1, null, false); 474 registerEffort(middle + 1, right, depth); 475 } 476 } else { 477 recurseBinarySwitch(closure, middle + 1, right, depth); 478 } 479 } 480 } else if (getSliceEnd(closure, middle + 1) == right) { 481 if (rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { 482 closure.conditionalJump(right, Condition.GT, true); 483 registerDefaultEffort(++depth); 484 } 485 closure.conditionalJump(middle + 1, Condition.GE, false); 486 registerEffort(middle + 1, right, ++depth); 487 recurseBinarySwitch(closure, left, middle, depth); 488 } else { 489 Label label = closure.conditionalJump(middle + 1, Condition.GE); 490 depth++; 491 recurseBinarySwitch(closure, left, middle, depth); 492 closure.bind(label); 493 recurseBinarySwitch(closure, middle + 1, right, depth); 494 } 495 } 496 } 497 498 public abstract void run(SwitchClosure closure); 499 500 private static SwitchStrategy[] getStrategies(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { 501 SwitchStrategy[] strategies = new SwitchStrategy[]{new SequentialStrategy(keyProbabilities, keyConstants), new RangesStrategy(keyProbabilities, keyConstants), 502 new BinaryStrategy(keyProbabilities, keyConstants)}; 503 for (SwitchStrategy strategy : strategies) { 504 strategy.effortClosure = strategy.new EffortClosure(keyTargets); 505 strategy.run(strategy.effortClosure); 506 strategy.averageEffort = strategy.effortClosure.getAverageEffort(); 507 strategy.effortClosure = null; 508 } 509 return strategies; 510 } 511 512 /** 513 * Creates all switch strategies for the given switch, evaluates them (based on average effort) 514 * and returns the best one. 515 */ 516 public static SwitchStrategy getBestStrategy(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { 517 SwitchStrategy[] strategies = getStrategies(keyProbabilities, keyConstants, keyTargets); 518 double bestEffort = Integer.MAX_VALUE; 519 SwitchStrategy bestStrategy = null; 520 for (SwitchStrategy strategy : strategies) { 521 if (strategy.getAverageEffort() < bestEffort) { 522 bestEffort = strategy.getAverageEffort(); 523 bestStrategy = strategy; 524 } 525 } 526 return bestStrategy; 527 } 528 }