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