001/* 002 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.lir; 024 025import java.util.*; 026 027import jdk.internal.jvmci.meta.*; 028 029import com.oracle.graal.asm.*; 030import com.oracle.graal.compiler.common.calc.*; 031import com.oracle.graal.lir.asm.*; 032 033/** 034 * This class encapsulates different strategies on how to generate code for switch instructions. 035 * 036 * The {@link #getBestStrategy(double[], JavaConstant[], LabelRef[])} method can be used to get 037 * strategy with the smallest average effort (average number of comparisons until a decision is 038 * reached). The strategy returned by this method will have its averageEffort set, while a strategy 039 * constructed directly will not. 040 */ 041public abstract class SwitchStrategy { 042 043 private interface SwitchClosure { 044 /** 045 * Generates a conditional or unconditional jump. The jump will be unconditional if 046 * condition is null. If defaultTarget is true, then the jump will go the the default. 047 * 048 * @param index Index of the value and the jump target (only used if defaultTarget == false) 049 * @param condition The condition on which to jump (can be null) 050 * @param defaultTarget true if the jump should go to the default target, false if index 051 * should be used. 052 */ 053 void conditionalJump(int index, Condition condition, boolean defaultTarget); 054 055 /** 056 * Generates a conditional jump to the target with the specified index. The fall through 057 * should go to the default target. 058 * 059 * @param index Index of the value and the jump target 060 * @param condition The condition on which to jump 061 * @param canFallThrough true if this is the last instruction in the switch statement, to 062 * allow for fall-through optimizations. 063 */ 064 void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough); 065 066 /** 067 * Create a new label and generate a conditional jump to it. 068 * 069 * @param index Index of the value and the jump target 070 * @param condition The condition on which to jump 071 * @return a new Label 072 */ 073 Label conditionalJump(int index, Condition condition); 074 075 /** 076 * Binds a label returned by {@link #conditionalJump(int, Condition)}. 077 */ 078 void bind(Label label); 079 080 /** 081 * Return true iff the target of both indexes is the same. 082 */ 083 boolean isSameTarget(int index1, int index2); 084 } 085 086 /** 087 * Backends can subclass this abstract class and generate code for switch strategies by 088 * implementing the {@link #conditionalJump(int, Condition, Label)} method. 089 */ 090 public abstract static class BaseSwitchClosure implements SwitchClosure { 091 092 private final CompilationResultBuilder crb; 093 private final Assembler masm; 094 private final LabelRef[] keyTargets; 095 private final LabelRef defaultTarget; 096 097 public BaseSwitchClosure(CompilationResultBuilder crb, Assembler masm, LabelRef[] keyTargets, LabelRef defaultTarget) { 098 this.crb = crb; 099 this.masm = masm; 100 this.keyTargets = keyTargets; 101 this.defaultTarget = defaultTarget; 102 } 103 104 /** 105 * This method generates code for a comparison between the actual value and the constant at 106 * the given index and a condition jump to target. 107 */ 108 protected abstract void conditionalJump(int index, Condition condition, Label target); 109 110 public void conditionalJump(int index, Condition condition, boolean targetDefault) { 111 Label target = targetDefault ? defaultTarget.label() : keyTargets[index].label(); 112 if (condition == null) { 113 masm.jmp(target); 114 } else { 115 conditionalJump(index, condition, target); 116 } 117 } 118 119 public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { 120 if (canFallThrough && crb.isSuccessorEdge(defaultTarget)) { 121 conditionalJump(index, condition, keyTargets[index].label()); 122 } else if (canFallThrough && crb.isSuccessorEdge(keyTargets[index])) { 123 conditionalJump(index, condition.negate(), defaultTarget.label()); 124 } else { 125 conditionalJump(index, condition, keyTargets[index].label()); 126 masm.jmp(defaultTarget.label()); 127 } 128 } 129 130 public Label conditionalJump(int index, Condition condition) { 131 Label label = new Label(); 132 conditionalJump(index, condition, label); 133 return label; 134 } 135 136 public void bind(Label label) { 137 masm.bind(label); 138 } 139 140 public boolean isSameTarget(int index1, int index2) { 141 return keyTargets[index1] == keyTargets[index2]; 142 } 143 144 } 145 146 /** 147 * This closure is used internally to determine the average effort for a certain strategy on a 148 * given switch instruction. 149 */ 150 private class EffortClosure implements SwitchClosure { 151 152 private int defaultEffort; 153 private int defaultCount; 154 private final int[] keyEfforts = new int[keyConstants.length]; 155 private final int[] keyCounts = new int[keyConstants.length]; 156 private final LabelRef[] keyTargets; 157 158 public EffortClosure(LabelRef[] keyTargets) { 159 this.keyTargets = keyTargets; 160 } 161 162 public void conditionalJump(int index, Condition condition, boolean defaultTarget) { 163 // nothing to do 164 } 165 166 public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { 167 // nothing to do 168 } 169 170 public Label conditionalJump(int index, Condition condition) { 171 // nothing to do 172 return null; 173 } 174 175 public void bind(Label label) { 176 // nothing to do 177 } 178 179 public boolean isSameTarget(int index1, int index2) { 180 return keyTargets[index1] == keyTargets[index2]; 181 } 182 183 public double getAverageEffort() { 184 double defaultProbability = 1; 185 double effort = 0; 186 for (int i = 0; i < keyConstants.length; i++) { 187 effort += keyEfforts[i] * keyProbabilities[i] / keyCounts[i]; 188 defaultProbability -= keyProbabilities[i]; 189 } 190 return effort + defaultEffort * defaultProbability / defaultCount; 191 } 192 } 193 194 public final double[] keyProbabilities; 195 public final JavaConstant[] keyConstants; 196 private double averageEffort = -1; 197 private EffortClosure effortClosure; 198 199 public SwitchStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { 200 assert keyConstants.length == keyProbabilities.length && keyConstants.length >= 2; 201 this.keyProbabilities = keyProbabilities; 202 this.keyConstants = keyConstants; 203 } 204 205 public double getAverageEffort() { 206 assert averageEffort >= 0 : "average effort was not calculated yet for this strategy"; 207 return averageEffort; 208 } 209 210 /** 211 * Looks for the end of a stretch of key constants that are successive numbers and have the same 212 * target. 213 */ 214 protected int getSliceEnd(SwitchClosure closure, int pos) { 215 int slice = pos; 216 while (slice < (keyConstants.length - 1) && keyConstants[slice + 1].asLong() == keyConstants[slice].asLong() + 1 && closure.isSameTarget(slice, slice + 1)) { 217 slice++; 218 } 219 return slice; 220 } 221 222 /** 223 * Tells the system that the given (inclusive) range of keys is reached after depth number of 224 * comparisons, which is used to calculate the average effort. 225 */ 226 protected void registerEffort(int rangeStart, int rangeEnd, int depth) { 227 if (effortClosure != null) { 228 for (int i = rangeStart; i <= rangeEnd; i++) { 229 effortClosure.keyEfforts[i] += depth; 230 effortClosure.keyCounts[i]++; 231 } 232 } 233 } 234 235 /** 236 * Tells the system that the default successor is reached after depth number of comparisons, 237 * which is used to calculate average effort. 238 */ 239 protected void registerDefaultEffort(int depth) { 240 if (effortClosure != null) { 241 effortClosure.defaultEffort += depth; 242 effortClosure.defaultCount++; 243 } 244 } 245 246 @Override 247 public String toString() { 248 return getClass().getSimpleName() + "[avgEffort=" + averageEffort + "]"; 249 } 250 251 /** 252 * This strategy orders the keys according to their probability and creates one equality 253 * comparison per key. 254 */ 255 public static class SequentialStrategy extends SwitchStrategy { 256 private final Integer[] indexes; 257 258 public SequentialStrategy(final double[] keyProbabilities, JavaConstant[] keyConstants) { 259 super(keyProbabilities, keyConstants); 260 261 int keyCount = keyConstants.length; 262 indexes = new Integer[keyCount]; 263 for (int i = 0; i < keyCount; i++) { 264 indexes[i] = i; 265 } 266 Arrays.sort(indexes, new Comparator<Integer>() { 267 @Override 268 public int compare(Integer o1, Integer o2) { 269 return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; 270 } 271 }); 272 } 273 274 @Override 275 public void run(SwitchClosure closure) { 276 for (int i = 0; i < keyConstants.length - 1; i++) { 277 closure.conditionalJump(indexes[i], Condition.EQ, false); 278 registerEffort(indexes[i], indexes[i], i + 1); 279 } 280 closure.conditionalJumpOrDefault(indexes[keyConstants.length - 1], Condition.EQ, true); 281 registerEffort(indexes[keyConstants.length - 1], indexes[keyConstants.length - 1], keyConstants.length); 282 registerDefaultEffort(keyConstants.length); 283 } 284 } 285 286 /** 287 * This strategy divides the keys into ranges of successive keys with the same target and 288 * creates comparisons for these ranges. 289 */ 290 public static class RangesStrategy extends SwitchStrategy { 291 private final Integer[] indexes; 292 293 public RangesStrategy(final double[] keyProbabilities, JavaConstant[] keyConstants) { 294 super(keyProbabilities, keyConstants); 295 296 int keyCount = keyConstants.length; 297 indexes = new Integer[keyCount]; 298 for (int i = 0; i < keyCount; i++) { 299 indexes[i] = i; 300 } 301 Arrays.sort(indexes, new Comparator<Integer>() { 302 @Override 303 public int compare(Integer o1, Integer o2) { 304 return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; 305 } 306 }); 307 } 308 309 @Override 310 public void run(SwitchClosure closure) { 311 int depth = 0; 312 closure.conditionalJump(0, Condition.LT, true); 313 registerDefaultEffort(++depth); 314 int rangeStart = 0; 315 int rangeEnd = getSliceEnd(closure, rangeStart); 316 while (rangeEnd != keyConstants.length - 1) { 317 if (rangeStart == rangeEnd) { 318 closure.conditionalJump(rangeStart, Condition.EQ, false); 319 registerEffort(rangeStart, rangeEnd, ++depth); 320 } else { 321 if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { 322 closure.conditionalJump(rangeStart, Condition.LT, true); 323 registerDefaultEffort(++depth); 324 } 325 closure.conditionalJump(rangeEnd, Condition.LE, false); 326 registerEffort(rangeStart, rangeEnd, ++depth); 327 } 328 rangeStart = rangeEnd + 1; 329 rangeEnd = getSliceEnd(closure, rangeStart); 330 } 331 if (rangeStart == rangeEnd) { 332 closure.conditionalJumpOrDefault(rangeStart, Condition.EQ, true); 333 registerEffort(rangeStart, rangeEnd, ++depth); 334 registerDefaultEffort(depth); 335 } else { 336 if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { 337 closure.conditionalJump(rangeStart, Condition.LT, true); 338 registerDefaultEffort(++depth); 339 } 340 closure.conditionalJumpOrDefault(rangeEnd, Condition.LE, true); 341 registerEffort(rangeStart, rangeEnd, ++depth); 342 registerDefaultEffort(depth); 343 } 344 } 345 } 346 347 /** 348 * This strategy recursively subdivides the list of keys to create a binary search based on 349 * probabilities. 350 */ 351 public static class BinaryStrategy extends SwitchStrategy { 352 353 private static final double MIN_PROBABILITY = 0.00001; 354 355 private final double[] probabilitySums; 356 357 public BinaryStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { 358 super(keyProbabilities, keyConstants); 359 probabilitySums = new double[keyProbabilities.length + 1]; 360 double sum = 0; 361 for (int i = 0; i < keyConstants.length; i++) { 362 sum += Math.max(keyProbabilities[i], MIN_PROBABILITY); 363 probabilitySums[i + 1] = sum; 364 } 365 } 366 367 @Override 368 public void run(SwitchClosure closure) { 369 recurseBinarySwitch(closure, 0, keyConstants.length - 1, 0); 370 } 371 372 /** 373 * Recursively generate a list of comparisons that always subdivides the keys in the given 374 * (inclusive) range in the middle (in terms of probability, not index). If left is bigger 375 * than zero, then we always know that the value is equal to or bigger than the left key. 376 * This does not hold for the right key, as there may be a gap afterwards. 377 */ 378 private void recurseBinarySwitch(SwitchClosure closure, int left, int right, int startDepth) { 379 assert startDepth < keyConstants.length * 3 : "runaway recursion in binary switch"; 380 int depth = startDepth; 381 boolean leftBorder = left == 0; 382 boolean rightBorder = right == keyConstants.length - 1; 383 384 if (left + 1 == right) { 385 // only two possible values 386 if (leftBorder || rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong() || keyConstants[left].asLong() + 1 != keyConstants[right].asLong()) { 387 closure.conditionalJump(left, Condition.EQ, false); 388 registerEffort(left, left, ++depth); 389 closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); 390 registerEffort(right, right, ++depth); 391 registerDefaultEffort(depth); 392 } else { 393 // here we know that the value can only be one of these two keys in the range 394 closure.conditionalJump(left, Condition.EQ, false); 395 registerEffort(left, left, ++depth); 396 closure.conditionalJump(right, null, false); 397 registerEffort(right, right, depth); 398 } 399 return; 400 } 401 double probabilityStart = probabilitySums[left]; 402 double probabilityMiddle = (probabilityStart + probabilitySums[right + 1]) / 2; 403 assert probabilityStart >= probabilityStart; 404 int middle = left; 405 while (getSliceEnd(closure, middle + 1) < right && probabilitySums[getSliceEnd(closure, middle + 1)] < probabilityMiddle) { 406 middle = getSliceEnd(closure, middle + 1); 407 } 408 middle = getSliceEnd(closure, middle); 409 assert middle < keyConstants.length - 1; 410 411 if (getSliceEnd(closure, left) == middle) { 412 if (left == 0) { 413 closure.conditionalJump(0, Condition.LT, true); 414 registerDefaultEffort(++depth); 415 } 416 closure.conditionalJump(middle, Condition.LE, false); 417 registerEffort(left, middle, ++depth); 418 419 if (middle + 1 == right) { 420 closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); 421 registerEffort(right, right, ++depth); 422 registerDefaultEffort(depth); 423 } else { 424 if (keyConstants[middle].asLong() + 1 != keyConstants[middle + 1].asLong()) { 425 closure.conditionalJump(middle + 1, Condition.LT, true); 426 registerDefaultEffort(++depth); 427 } 428 if (getSliceEnd(closure, middle + 1) == right) { 429 if (right == keyConstants.length - 1 || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { 430 closure.conditionalJumpOrDefault(right, Condition.LE, rightBorder); 431 registerEffort(middle + 1, right, ++depth); 432 registerDefaultEffort(depth); 433 } else { 434 closure.conditionalJump(middle + 1, null, false); 435 registerEffort(middle + 1, right, depth); 436 } 437 } else { 438 recurseBinarySwitch(closure, middle + 1, right, depth); 439 } 440 } 441 } else if (getSliceEnd(closure, middle + 1) == right) { 442 if (rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { 443 closure.conditionalJump(right, Condition.GT, true); 444 registerDefaultEffort(++depth); 445 } 446 closure.conditionalJump(middle + 1, Condition.GE, false); 447 registerEffort(middle + 1, right, ++depth); 448 recurseBinarySwitch(closure, left, middle, depth); 449 } else { 450 Label label = closure.conditionalJump(middle + 1, Condition.GE); 451 depth++; 452 recurseBinarySwitch(closure, left, middle, depth); 453 closure.bind(label); 454 recurseBinarySwitch(closure, middle + 1, right, depth); 455 } 456 } 457 } 458 459 public abstract void run(SwitchClosure closure); 460 461 private static SwitchStrategy[] getStrategies(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { 462 SwitchStrategy[] strategies = new SwitchStrategy[]{new SequentialStrategy(keyProbabilities, keyConstants), new RangesStrategy(keyProbabilities, keyConstants), 463 new BinaryStrategy(keyProbabilities, keyConstants)}; 464 for (SwitchStrategy strategy : strategies) { 465 strategy.effortClosure = strategy.new EffortClosure(keyTargets); 466 strategy.run(strategy.effortClosure); 467 strategy.averageEffort = strategy.effortClosure.getAverageEffort(); 468 strategy.effortClosure = null; 469 } 470 return strategies; 471 } 472 473 /** 474 * Creates all switch strategies for the given switch, evaluates them (based on average effort) 475 * and returns the best one. 476 */ 477 public static SwitchStrategy getBestStrategy(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { 478 SwitchStrategy[] strategies = getStrategies(keyProbabilities, keyConstants, keyTargets); 479 double bestEffort = Integer.MAX_VALUE; 480 SwitchStrategy bestStrategy = null; 481 for (SwitchStrategy strategy : strategies) { 482 if (strategy.getAverageEffort() < bestEffort) { 483 bestEffort = strategy.getAverageEffort(); 484 bestStrategy = strategy; 485 } 486 } 487 return bestStrategy; 488 } 489}