/*
 * Copyright (c) 2013, 2018, 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 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.
/** * 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 default.
Params:
  • index – Index of the value and the jump target (only used if defaultTarget == false)
  • condition – The condition on which to jump (can be null)
  • defaultTarget – true if the jump should go to the default target, false if index should be used.
/** * 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 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.
Params:
  • index – Index of the value and the jump target
  • condition – The condition on which to jump
  • canFallThrough – true if this is the last instruction in the switch statement, to allow for fall-through optimizations.
/** * 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.
Params:
  • index – Index of the value and the jump target
  • condition – The condition on which to jump
Returns:a new Label
/** * 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 conditionalJump(int, 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.
/** * 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 conditionalJump(int, Condition, Label) method.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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<Integer>() { @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.
/** * 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.
/** * 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.
/** * 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<Integer>() { @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.
/** * 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.
/** * 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 probabilityMiddle >= 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.
/** * 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; } }