1 /*
   2  * Copyright (c) 2009, 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.nodes.extended;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Arrays;
  27 import java.util.HashMap;
  28 import java.util.List;
  29 import java.util.Map;
  30 
  31 import org.graalvm.compiler.core.common.spi.ConstantFieldProvider;
  32 import org.graalvm.compiler.core.common.type.IntegerStamp;
  33 import org.graalvm.compiler.core.common.type.PrimitiveStamp;
  34 import org.graalvm.compiler.graph.NodeClass;
  35 import org.graalvm.compiler.graph.spi.Simplifiable;
  36 import org.graalvm.compiler.graph.spi.SimplifierTool;
  37 import org.graalvm.compiler.nodeinfo.NodeInfo;
  38 import org.graalvm.compiler.nodes.AbstractBeginNode;
  39 import org.graalvm.compiler.nodes.ConstantNode;
  40 import org.graalvm.compiler.nodes.FixedGuardNode;
  41 import org.graalvm.compiler.nodes.FixedWithNextNode;
  42 import org.graalvm.compiler.nodes.LogicNode;
  43 import org.graalvm.compiler.nodes.ValueNode;
  44 import org.graalvm.compiler.nodes.calc.IntegerBelowNode;
  45 import org.graalvm.compiler.nodes.java.LoadIndexedNode;
  46 import org.graalvm.compiler.nodes.spi.LIRLowerable;
  47 import org.graalvm.compiler.nodes.spi.NodeLIRBuilderTool;
  48 import org.graalvm.compiler.nodes.util.GraphUtil;
  49 
  50 import jdk.vm.ci.meta.DeoptimizationAction;
  51 import jdk.vm.ci.meta.DeoptimizationReason;
  52 import jdk.vm.ci.meta.JavaConstant;
  53 import jdk.vm.ci.meta.JavaKind;
  54 
  55 /**
  56  * The {@code IntegerSwitchNode} represents a switch on integer keys, with a sorted array of key
  57  * values. The actual implementation of the switch will be decided by the backend.
  58  */
  59 @NodeInfo
  60 public final class IntegerSwitchNode extends SwitchNode implements LIRLowerable, Simplifiable {
  61     public static final NodeClass<IntegerSwitchNode> TYPE = NodeClass.create(IntegerSwitchNode.class);
  62 
  63     protected final int[] keys;
  64 
  65     public IntegerSwitchNode(ValueNode value, AbstractBeginNode[] successors, int[] keys, double[] keyProbabilities, int[] keySuccessors) {
  66         super(TYPE, value, successors, keySuccessors, keyProbabilities);
  67         assert keySuccessors.length == keys.length + 1;
  68         assert keySuccessors.length == keyProbabilities.length;
  69         this.keys = keys;
  70         assert value.stamp() instanceof PrimitiveStamp && value.stamp().getStackKind().isNumericInteger();
  71         assert assertSorted();
  72     }
  73 
  74     private boolean assertSorted() {
  75         for (int i = 1; i < keys.length; i++) {
  76             assert keys[i - 1] < keys[i];
  77         }
  78         return true;
  79     }
  80 
  81     public IntegerSwitchNode(ValueNode value, int successorCount, int[] keys, double[] keyProbabilities, int[] keySuccessors) {
  82         this(value, new AbstractBeginNode[successorCount], keys, keyProbabilities, keySuccessors);
  83     }
  84 
  85     @Override
  86     public boolean isSorted() {
  87         return true;
  88     }
  89 
  90     /**
  91      * Gets the key at the specified index.
  92      *
  93      * @param i the index
  94      * @return the key at that index
  95      */
  96     @Override
  97     public JavaConstant keyAt(int i) {
  98         return JavaConstant.forInt(keys[i]);
  99     }
 100 
 101     @Override
 102     public int keyCount() {
 103         return keys.length;
 104     }
 105 
 106     @Override
 107     public boolean equalKeys(SwitchNode switchNode) {
 108         if (!(switchNode instanceof IntegerSwitchNode)) {
 109             return false;
 110         }
 111         IntegerSwitchNode other = (IntegerSwitchNode) switchNode;
 112         return Arrays.equals(keys, other.keys);
 113     }
 114 
 115     @Override
 116     public void generate(NodeLIRBuilderTool gen) {
 117         gen.emitSwitch(this);
 118     }
 119 
 120     public AbstractBeginNode successorAtKey(int key) {
 121         return blockSuccessor(successorIndexAtKey(key));
 122     }
 123 
 124     public int successorIndexAtKey(int key) {
 125         for (int i = 0; i < keyCount(); i++) {
 126             if (keys[i] == key) {
 127                 return keySuccessorIndex(i);
 128             }
 129         }
 130         return keySuccessorIndex(keyCount());
 131     }
 132 
 133     @Override
 134     public void simplify(SimplifierTool tool) {
 135         if (blockSuccessorCount() == 1) {
 136             tool.addToWorkList(defaultSuccessor());
 137             graph().removeSplitPropagate(this, defaultSuccessor());
 138         } else if (value() instanceof ConstantNode) {
 139             killOtherSuccessors(tool, successorIndexAtKey(value().asJavaConstant().asInt()));
 140         } else if (tryOptimizeEnumSwitch(tool)) {
 141             return;
 142         } else if (tryRemoveUnreachableKeys(tool)) {
 143             return;
 144         }
 145     }
 146 
 147     static final class KeyData {
 148         final int key;
 149         final double keyProbability;
 150         final int keySuccessor;
 151 
 152         KeyData(int key, double keyProbability, int keySuccessor) {
 153             this.key = key;
 154             this.keyProbability = keyProbability;
 155             this.keySuccessor = keySuccessor;
 156         }
 157     }
 158 
 159     /**
 160      * Remove unreachable keys from the switch based on the stamp of the value, i.e., based on the
 161      * known range of the switch value.
 162      */
 163     private boolean tryRemoveUnreachableKeys(SimplifierTool tool) {
 164         if (!(value().stamp() instanceof IntegerStamp)) {
 165             return false;
 166         }
 167         IntegerStamp integerStamp = (IntegerStamp) value().stamp();
 168         if (integerStamp.isUnrestricted()) {
 169             return false;
 170         }
 171 
 172         List<KeyData> newKeyDatas = new ArrayList<>(keys.length);
 173         ArrayList<AbstractBeginNode> newSuccessors = new ArrayList<>(blockSuccessorCount());
 174         for (int i = 0; i < keys.length; i++) {
 175             if (integerStamp.contains(keys[i])) {
 176                 newKeyDatas.add(new KeyData(keys[i], keyProbabilities[i], addNewSuccessor(keySuccessor(i), newSuccessors)));
 177             }
 178         }
 179 
 180         if (newKeyDatas.size() == keys.length) {
 181             /* All keys are reachable. */
 182             return false;
 183 
 184         } else if (newKeyDatas.size() == 0) {
 185             tool.addToWorkList(defaultSuccessor());
 186             graph().removeSplitPropagate(this, defaultSuccessor());
 187             return true;
 188 
 189         } else {
 190             int newDefaultSuccessor = addNewSuccessor(defaultSuccessor(), newSuccessors);
 191             double newDefaultProbability = keyProbabilities[keyProbabilities.length - 1];
 192             doReplace(tool, value(), newKeyDatas, newSuccessors, newDefaultSuccessor, newDefaultProbability);
 193             return true;
 194         }
 195     }
 196 
 197     /**
 198      * For switch statements on enum values, the Java compiler has to generate complicated code:
 199      * because {@link Enum#ordinal()} can change when recompiling an enum, it cannot be used
 200      * directly as the value that is switched on. An intermediate int[] array, which is initialized
 201      * once at run time based on the actual {@link Enum#ordinal()} values, is used.
 202      *
 203      * The {@link ConstantFieldProvider} of Graal already detects the int[] arrays and marks them as
 204      * {@link ConstantNode#isDefaultStable() stable}, i.e., the array elements are constant. The
 205      * code in this method detects array loads from such a stable array and re-wires the switch to
 206      * use the keys from the array elements, so that the array load is unnecessary.
 207      */
 208     private boolean tryOptimizeEnumSwitch(SimplifierTool tool) {
 209         if (!(value() instanceof LoadIndexedNode)) {
 210             /* Not the switch pattern we are looking for. */
 211             return false;
 212         }
 213         LoadIndexedNode loadIndexed = (LoadIndexedNode) value();
 214         if (loadIndexed.usages().count() > 1) {
 215             /*
 216              * The array load is necessary for other reasons too, so there is no benefit optimizing
 217              * the switch.
 218              */
 219             return false;
 220         }
 221         assert loadIndexed.usages().first() == this;
 222 
 223         ValueNode newValue = loadIndexed.index();
 224         JavaConstant arrayConstant = loadIndexed.array().asJavaConstant();
 225         if (arrayConstant == null || ((ConstantNode) loadIndexed.array()).getStableDimension() != 1 || !((ConstantNode) loadIndexed.array()).isDefaultStable()) {
 226             /*
 227              * The array is a constant that we can optimize. We require the array elements to be
 228              * constant too, since we put them as literal constants into the switch keys.
 229              */
 230             return false;
 231         }
 232 
 233         Integer optionalArrayLength = tool.getConstantReflection().readArrayLength(arrayConstant);
 234         if (optionalArrayLength == null) {
 235             /* Loading a constant value can be denied by the VM. */
 236             return false;
 237         }
 238         int arrayLength = optionalArrayLength;
 239 
 240         Map<Integer, List<Integer>> reverseArrayMapping = new HashMap<>();
 241         for (int i = 0; i < arrayLength; i++) {
 242             JavaConstant elementConstant = tool.getConstantReflection().readArrayElement(arrayConstant, i);
 243             if (elementConstant == null || elementConstant.getJavaKind() != JavaKind.Int) {
 244                 /* Loading a constant value can be denied by the VM. */
 245                 return false;
 246             }
 247             int element = elementConstant.asInt();
 248 
 249             /*
 250              * The value loaded from the array is the old switch key, the index into the array is
 251              * the new switch key. We build a mapping from the old switch key to new keys.
 252              */
 253             reverseArrayMapping.computeIfAbsent(element, e -> new ArrayList<>()).add(i);
 254         }
 255 
 256         /* Build high-level representation of new switch keys. */
 257         List<KeyData> newKeyDatas = new ArrayList<>(arrayLength);
 258         ArrayList<AbstractBeginNode> newSuccessors = new ArrayList<>(blockSuccessorCount());
 259         for (int i = 0; i < keys.length; i++) {
 260             List<Integer> newKeys = reverseArrayMapping.get(keys[i]);
 261             if (newKeys == null || newKeys.size() == 0) {
 262                 /* The switch case is unreachable, we can ignore it. */
 263                 continue;
 264             }
 265 
 266             /*
 267              * We do not have detailed profiling information about the individual new keys, so we
 268              * have to assume they split the probability of the old key.
 269              */
 270             double newKeyProbability = keyProbabilities[i] / newKeys.size();
 271             int newKeySuccessor = addNewSuccessor(keySuccessor(i), newSuccessors);
 272 
 273             for (int newKey : newKeys) {
 274                 newKeyDatas.add(new KeyData(newKey, newKeyProbability, newKeySuccessor));
 275             }
 276         }
 277 
 278         int newDefaultSuccessor = addNewSuccessor(defaultSuccessor(), newSuccessors);
 279         double newDefaultProbability = keyProbabilities[keyProbabilities.length - 1];
 280 
 281         /*
 282          * We remove the array load, but we still need to preserve exception semantics by keeping
 283          * the bounds check. Fortunately the array length is a constant.
 284          */
 285         LogicNode boundsCheck = graph().unique(new IntegerBelowNode(newValue, ConstantNode.forInt(arrayLength, graph())));
 286         graph().addBeforeFixed(this, graph().add(new FixedGuardNode(boundsCheck, DeoptimizationReason.BoundsCheckException, DeoptimizationAction.InvalidateReprofile)));
 287 
 288         /*
 289          * Build the low-level representation of the new switch keys and replace ourself with a new
 290          * node.
 291          */
 292         doReplace(tool, newValue, newKeyDatas, newSuccessors, newDefaultSuccessor, newDefaultProbability);
 293 
 294         /* The array load is now unnecessary. */
 295         assert loadIndexed.hasNoUsages();
 296         GraphUtil.removeFixedWithUnusedInputs(loadIndexed);
 297 
 298         return true;
 299     }
 300 
 301     private static int addNewSuccessor(AbstractBeginNode newSuccessor, ArrayList<AbstractBeginNode> newSuccessors) {
 302         int index = newSuccessors.indexOf(newSuccessor);
 303         if (index == -1) {
 304             index = newSuccessors.size();
 305             newSuccessors.add(newSuccessor);
 306         }
 307         return index;
 308     }
 309 
 310     private void doReplace(SimplifierTool tool, ValueNode newValue, List<KeyData> newKeyDatas, ArrayList<AbstractBeginNode> newSuccessors, int newDefaultSuccessor, double newDefaultProbability) {
 311         /* Sort the new keys (invariant of the IntegerSwitchNode). */
 312         newKeyDatas.sort((k1, k2) -> k1.key - k2.key);
 313 
 314         /* Create the final data arrays. */
 315         int newKeyCount = newKeyDatas.size();
 316         int[] newKeys = new int[newKeyCount];
 317         double[] newKeyProbabilities = new double[newKeyCount + 1];
 318         int[] newKeySuccessors = new int[newKeyCount + 1];
 319 
 320         for (int i = 0; i < newKeyCount; i++) {
 321             KeyData keyData = newKeyDatas.get(i);
 322             newKeys[i] = keyData.key;
 323             newKeyProbabilities[i] = keyData.keyProbability;
 324             newKeySuccessors[i] = keyData.keySuccessor;
 325         }
 326 
 327         newKeySuccessors[newKeyCount] = newDefaultSuccessor;
 328         newKeyProbabilities[newKeyCount] = newDefaultProbability;
 329 
 330         /* Normalize new probabilities so that they sum up to 1. */
 331         double totalProbability = 0;
 332         for (double probability : newKeyProbabilities) {
 333             totalProbability += probability;
 334         }
 335         if (totalProbability > 0) {
 336             for (int i = 0; i < newKeyProbabilities.length; i++) {
 337                 newKeyProbabilities[i] /= totalProbability;
 338             }
 339         } else {
 340             for (int i = 0; i < newKeyProbabilities.length; i++) {
 341                 newKeyProbabilities[i] = 1.0 / newKeyProbabilities.length;
 342             }
 343         }
 344 
 345         /* Remove dead successors. */
 346         for (int i = 0; i < blockSuccessorCount(); i++) {
 347             AbstractBeginNode successor = blockSuccessor(i);
 348             if (!newSuccessors.contains(successor)) {
 349                 tool.deleteBranch(successor);
 350             }
 351             setBlockSuccessor(i, null);
 352         }
 353 
 354         /* Create the new switch node and replace ourself with it. */
 355         AbstractBeginNode[] successorsArray = newSuccessors.toArray(new AbstractBeginNode[newSuccessors.size()]);
 356         SwitchNode newSwitch = graph().add(new IntegerSwitchNode(newValue, successorsArray, newKeys, newKeyProbabilities, newKeySuccessors));
 357         ((FixedWithNextNode) predecessor()).setNext(newSwitch);
 358         GraphUtil.killWithUnusedFloatingInputs(this);
 359     }
 360 }