/* * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.lir; import java.util.Arrays; import java.util.Comparator; import org.graalvm.compiler.asm.Assembler; import org.graalvm.compiler.asm.Label; import org.graalvm.compiler.core.common.calc.Condition; import org.graalvm.compiler.lir.asm.CompilationResultBuilder; import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaConstant; /** * This class encapsulates different strategies on how to generate code for switch instructions. * * The {@link #getBestStrategy(double[], JavaConstant[], LabelRef[])} method can be used to get * strategy with the smallest average effort (average number of comparisons until a decision is * reached). The strategy returned by this method will have its averageEffort set, while a strategy * constructed directly will not. */ public abstract class SwitchStrategy { private interface SwitchClosure { /** * Generates a conditional or unconditional jump. The jump will be unconditional if * condition is null. If defaultTarget is true, then the jump will go the the default. * * @param index Index of the value and the jump target (only used if defaultTarget == false) * @param condition The condition on which to jump (can be null) * @param defaultTarget true if the jump should go to the default target, false if index * should be used. */ void conditionalJump(int index, Condition condition, boolean defaultTarget); /** * Generates a conditional jump to the target with the specified index. The fall through * should go to the default target. * * @param index Index of the value and the jump target * @param condition The condition on which to jump * @param canFallThrough true if this is the last instruction in the switch statement, to * allow for fall-through optimizations. */ void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough); /** * Create a new label and generate a conditional jump to it. * * @param index Index of the value and the jump target * @param condition The condition on which to jump * @return a new Label */ Label conditionalJump(int index, Condition condition); /** * Binds a label returned by {@link #conditionalJump(int, Condition)}. */ void bind(Label label); /** * Return true iff the target of both indexes is the same. */ boolean isSameTarget(int index1, int index2); } /** * Backends can subclass this abstract class and generate code for switch strategies by * implementing the {@link #conditionalJump(int, Condition, Label)} method. */ public abstract static class BaseSwitchClosure implements SwitchClosure { private final CompilationResultBuilder crb; private final Assembler masm; private final LabelRef[] keyTargets; private final LabelRef defaultTarget; public BaseSwitchClosure(CompilationResultBuilder crb, Assembler masm, LabelRef[] keyTargets, LabelRef defaultTarget) { this.crb = crb; this.masm = masm; this.keyTargets = keyTargets; this.defaultTarget = defaultTarget; } /** * This method generates code for a comparison between the actual value and the constant at * the given index and a condition jump to target. */ protected abstract void conditionalJump(int index, Condition condition, Label target); @Override public void conditionalJump(int index, Condition condition, boolean targetDefault) { Label target = targetDefault ? defaultTarget.label() : keyTargets[index].label(); if (condition == null) { masm.jmp(target); } else { conditionalJump(index, condition, target); } } @Override public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { if (canFallThrough && crb.isSuccessorEdge(defaultTarget)) { conditionalJump(index, condition, keyTargets[index].label()); } else if (canFallThrough && crb.isSuccessorEdge(keyTargets[index])) { conditionalJump(index, condition.negate(), defaultTarget.label()); } else { conditionalJump(index, condition, keyTargets[index].label()); masm.jmp(defaultTarget.label()); } } @Override public Label conditionalJump(int index, Condition condition) { Label label = new Label(); conditionalJump(index, condition, label); return label; } @Override public void bind(Label label) { masm.bind(label); } @Override public boolean isSameTarget(int index1, int index2) { return keyTargets[index1] == keyTargets[index2]; } } /** * This closure is used internally to determine the average effort for a certain strategy on a * given switch instruction. */ private class EffortClosure implements SwitchClosure { private int defaultEffort; private int defaultCount; private final int[] keyEfforts = new int[keyProbabilities.length]; private final int[] keyCounts = new int[keyProbabilities.length]; private final LabelRef[] keyTargets; EffortClosure(LabelRef[] keyTargets) { this.keyTargets = keyTargets; } @Override public void conditionalJump(int index, Condition condition, boolean defaultTarget) { // nothing to do } @Override public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) { // nothing to do } @Override public Label conditionalJump(int index, Condition condition) { // nothing to do return null; } @Override public void bind(Label label) { // nothing to do } @Override public boolean isSameTarget(int index1, int index2) { return keyTargets[index1] == keyTargets[index2]; } public double getAverageEffort() { double defaultProbability = 1; double effort = 0; for (int i = 0; i < keyProbabilities.length; i++) { effort += keyEfforts[i] * keyProbabilities[i] / keyCounts[i]; defaultProbability -= keyProbabilities[i]; } return effort + defaultEffort * defaultProbability / defaultCount; } } public final double[] keyProbabilities; private double averageEffort = -1; private EffortClosure effortClosure; public SwitchStrategy(double[] keyProbabilities) { assert keyProbabilities.length >= 2; this.keyProbabilities = keyProbabilities; } public abstract Constant[] getKeyConstants(); public double getAverageEffort() { assert averageEffort >= 0 : "average effort was not calculated yet for this strategy"; return averageEffort; } /** * Tells the system that the given (inclusive) range of keys is reached after depth number of * comparisons, which is used to calculate the average effort. */ protected void registerEffort(int rangeStart, int rangeEnd, int depth) { if (effortClosure != null) { for (int i = rangeStart; i <= rangeEnd; i++) { effortClosure.keyEfforts[i] += depth; effortClosure.keyCounts[i]++; } } } /** * Tells the system that the default successor is reached after depth number of comparisons, * which is used to calculate average effort. */ protected void registerDefaultEffort(int depth) { if (effortClosure != null) { effortClosure.defaultEffort += depth; effortClosure.defaultCount++; } } @Override public String toString() { return getClass().getSimpleName() + "[avgEffort=" + averageEffort + "]"; } /** * This strategy orders the keys according to their probability and creates one equality * comparison per key. */ public static class SequentialStrategy extends SwitchStrategy { private final Integer[] indexes; private final Constant[] keyConstants; public SequentialStrategy(final double[] keyProbabilities, Constant[] keyConstants) { super(keyProbabilities); assert keyProbabilities.length == keyConstants.length; this.keyConstants = keyConstants; int keyCount = keyConstants.length; indexes = new Integer[keyCount]; for (int i = 0; i < keyCount; i++) { indexes[i] = i; } Arrays.sort(indexes, new Comparator() { @Override public int compare(Integer o1, Integer o2) { return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; } }); } @Override public Constant[] getKeyConstants() { return keyConstants; } @Override public void run(SwitchClosure closure) { for (int i = 0; i < keyConstants.length - 1; i++) { closure.conditionalJump(indexes[i], Condition.EQ, false); registerEffort(indexes[i], indexes[i], i + 1); } closure.conditionalJumpOrDefault(indexes[keyConstants.length - 1], Condition.EQ, true); registerEffort(indexes[keyConstants.length - 1], indexes[keyConstants.length - 1], keyConstants.length); registerDefaultEffort(keyConstants.length); } } /** * Base class for strategies that rely on primitive integer keys. */ private abstract static class PrimitiveStrategy extends SwitchStrategy { protected final JavaConstant[] keyConstants; protected PrimitiveStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { super(keyProbabilities); assert keyProbabilities.length == keyConstants.length; this.keyConstants = keyConstants; } @Override public JavaConstant[] getKeyConstants() { return keyConstants; } /** * Looks for the end of a stretch of key constants that are successive numbers and have the * same target. */ protected int getSliceEnd(SwitchClosure closure, int pos) { int slice = pos; while (slice < (keyConstants.length - 1) && keyConstants[slice + 1].asLong() == keyConstants[slice].asLong() + 1 && closure.isSameTarget(slice, slice + 1)) { slice++; } return slice; } } /** * This strategy divides the keys into ranges of successive keys with the same target and * creates comparisons for these ranges. */ public static class RangesStrategy extends PrimitiveStrategy { private final Integer[] indexes; public RangesStrategy(final double[] keyProbabilities, JavaConstant[] keyConstants) { super(keyProbabilities, keyConstants); int keyCount = keyConstants.length; indexes = new Integer[keyCount]; for (int i = 0; i < keyCount; i++) { indexes[i] = i; } Arrays.sort(indexes, new Comparator() { @Override public int compare(Integer o1, Integer o2) { return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0; } }); } @Override public void run(SwitchClosure closure) { int depth = 0; closure.conditionalJump(0, Condition.LT, true); registerDefaultEffort(++depth); int rangeStart = 0; int rangeEnd = getSliceEnd(closure, rangeStart); while (rangeEnd != keyConstants.length - 1) { if (rangeStart == rangeEnd) { closure.conditionalJump(rangeStart, Condition.EQ, false); registerEffort(rangeStart, rangeEnd, ++depth); } else { if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { closure.conditionalJump(rangeStart, Condition.LT, true); registerDefaultEffort(++depth); } closure.conditionalJump(rangeEnd, Condition.LE, false); registerEffort(rangeStart, rangeEnd, ++depth); } rangeStart = rangeEnd + 1; rangeEnd = getSliceEnd(closure, rangeStart); } if (rangeStart == rangeEnd) { closure.conditionalJumpOrDefault(rangeStart, Condition.EQ, true); registerEffort(rangeStart, rangeEnd, ++depth); registerDefaultEffort(depth); } else { if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) { closure.conditionalJump(rangeStart, Condition.LT, true); registerDefaultEffort(++depth); } closure.conditionalJumpOrDefault(rangeEnd, Condition.LE, true); registerEffort(rangeStart, rangeEnd, ++depth); registerDefaultEffort(depth); } } } /** * This strategy recursively subdivides the list of keys to create a binary search based on * probabilities. */ public static class BinaryStrategy extends PrimitiveStrategy { private static final double MIN_PROBABILITY = 0.00001; private final double[] probabilitySums; public BinaryStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) { super(keyProbabilities, keyConstants); probabilitySums = new double[keyProbabilities.length + 1]; double sum = 0; for (int i = 0; i < keyConstants.length; i++) { sum += Math.max(keyProbabilities[i], MIN_PROBABILITY); probabilitySums[i + 1] = sum; } } @Override public void run(SwitchClosure closure) { recurseBinarySwitch(closure, 0, keyConstants.length - 1, 0); } /** * Recursively generate a list of comparisons that always subdivides the keys in the given * (inclusive) range in the middle (in terms of probability, not index). If left is bigger * than zero, then we always know that the value is equal to or bigger than the left key. * This does not hold for the right key, as there may be a gap afterwards. */ private void recurseBinarySwitch(SwitchClosure closure, int left, int right, int startDepth) { assert startDepth < keyConstants.length * 3 : "runaway recursion in binary switch"; int depth = startDepth; boolean leftBorder = left == 0; boolean rightBorder = right == keyConstants.length - 1; if (left + 1 == right) { // only two possible values if (leftBorder || rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong() || keyConstants[left].asLong() + 1 != keyConstants[right].asLong()) { closure.conditionalJump(left, Condition.EQ, false); registerEffort(left, left, ++depth); closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); registerEffort(right, right, ++depth); registerDefaultEffort(depth); } else { // here we know that the value can only be one of these two keys in the range closure.conditionalJump(left, Condition.EQ, false); registerEffort(left, left, ++depth); closure.conditionalJump(right, null, false); registerEffort(right, right, depth); } return; } double probabilityStart = probabilitySums[left]; double probabilityMiddle = (probabilityStart + probabilitySums[right + 1]) / 2; assert probabilityStart >= probabilityStart; int middle = left; while (getSliceEnd(closure, middle + 1) < right && probabilitySums[getSliceEnd(closure, middle + 1)] < probabilityMiddle) { middle = getSliceEnd(closure, middle + 1); } middle = getSliceEnd(closure, middle); assert middle < keyConstants.length - 1; if (getSliceEnd(closure, left) == middle) { if (left == 0) { closure.conditionalJump(0, Condition.LT, true); registerDefaultEffort(++depth); } closure.conditionalJump(middle, Condition.LE, false); registerEffort(left, middle, ++depth); if (middle + 1 == right) { closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder); registerEffort(right, right, ++depth); registerDefaultEffort(depth); } else { if (keyConstants[middle].asLong() + 1 != keyConstants[middle + 1].asLong()) { closure.conditionalJump(middle + 1, Condition.LT, true); registerDefaultEffort(++depth); } if (getSliceEnd(closure, middle + 1) == right) { if (right == keyConstants.length - 1 || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { closure.conditionalJumpOrDefault(right, Condition.LE, rightBorder); registerEffort(middle + 1, right, ++depth); registerDefaultEffort(depth); } else { closure.conditionalJump(middle + 1, null, false); registerEffort(middle + 1, right, depth); } } else { recurseBinarySwitch(closure, middle + 1, right, depth); } } } else if (getSliceEnd(closure, middle + 1) == right) { if (rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) { closure.conditionalJump(right, Condition.GT, true); registerDefaultEffort(++depth); } closure.conditionalJump(middle + 1, Condition.GE, false); registerEffort(middle + 1, right, ++depth); recurseBinarySwitch(closure, left, middle, depth); } else { Label label = closure.conditionalJump(middle + 1, Condition.GE); depth++; recurseBinarySwitch(closure, left, middle, depth); closure.bind(label); recurseBinarySwitch(closure, middle + 1, right, depth); } } } public abstract void run(SwitchClosure closure); private static SwitchStrategy[] getStrategies(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { SwitchStrategy[] strategies = new SwitchStrategy[]{new SequentialStrategy(keyProbabilities, keyConstants), new RangesStrategy(keyProbabilities, keyConstants), new BinaryStrategy(keyProbabilities, keyConstants)}; for (SwitchStrategy strategy : strategies) { strategy.effortClosure = strategy.new EffortClosure(keyTargets); strategy.run(strategy.effortClosure); strategy.averageEffort = strategy.effortClosure.getAverageEffort(); strategy.effortClosure = null; } return strategies; } /** * Creates all switch strategies for the given switch, evaluates them (based on average effort) * and returns the best one. */ public static SwitchStrategy getBestStrategy(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) { SwitchStrategy[] strategies = getStrategies(keyProbabilities, keyConstants, keyTargets); double bestEffort = Integer.MAX_VALUE; SwitchStrategy bestStrategy = null; for (SwitchStrategy strategy : strategies) { if (strategy.getAverageEffort() < bestEffort) { bestEffort = strategy.getAverageEffort(); bestStrategy = strategy; } } return bestStrategy; } }