1/*
2 * Copyright (c) 2013, 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 */
23package org.graalvm.compiler.lir;
24
25import java.util.Arrays;
26import java.util.Comparator;
27
28import org.graalvm.compiler.asm.Assembler;
29import org.graalvm.compiler.asm.Label;
30import org.graalvm.compiler.core.common.calc.Condition;
31import org.graalvm.compiler.lir.asm.CompilationResultBuilder;
32
33import jdk.vm.ci.meta.Constant;
34import jdk.vm.ci.meta.JavaConstant;
35
36/**
37 * This class encapsulates different strategies on how to generate code for switch instructions.
38 *
39 * The {@link #getBestStrategy(double[], JavaConstant[], LabelRef[])} method can be used to get
40 * strategy with the smallest average effort (average number of comparisons until a decision is
41 * reached). The strategy returned by this method will have its averageEffort set, while a strategy
42 * constructed directly will not.
43 */
44public abstract class SwitchStrategy {
45
46    private interface SwitchClosure {
47        /**
48         * Generates a conditional or unconditional jump. The jump will be unconditional if
49         * condition is null. If defaultTarget is true, then the jump will go the the default.
50         *
51         * @param index Index of the value and the jump target (only used if defaultTarget == false)
52         * @param condition The condition on which to jump (can be null)
53         * @param defaultTarget true if the jump should go to the default target, false if index
54         *            should be used.
55         */
56        void conditionalJump(int index, Condition condition, boolean defaultTarget);
57
58        /**
59         * Generates a conditional jump to the target with the specified index. The fall through
60         * should go to the default target.
61         *
62         * @param index Index of the value and the jump target
63         * @param condition The condition on which to jump
64         * @param canFallThrough true if this is the last instruction in the switch statement, to
65         *            allow for fall-through optimizations.
66         */
67        void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough);
68
69        /**
70         * Create a new label and generate a conditional jump to it.
71         *
72         * @param index Index of the value and the jump target
73         * @param condition The condition on which to jump
74         * @return a new Label
75         */
76        Label conditionalJump(int index, Condition condition);
77
78        /**
79         * Binds a label returned by {@link #conditionalJump(int, Condition)}.
80         */
81        void bind(Label label);
82
83        /**
84         * Return true iff the target of both indexes is the same.
85         */
86        boolean isSameTarget(int index1, int index2);
87    }
88
89    /**
90     * Backends can subclass this abstract class and generate code for switch strategies by
91     * implementing the {@link #conditionalJump(int, Condition, Label)} method.
92     */
93    public abstract static class BaseSwitchClosure implements SwitchClosure {
94
95        private final CompilationResultBuilder crb;
96        private final Assembler masm;
97        private final LabelRef[] keyTargets;
98        private final LabelRef defaultTarget;
99
100        public BaseSwitchClosure(CompilationResultBuilder crb, Assembler masm, LabelRef[] keyTargets, LabelRef defaultTarget) {
101            this.crb = crb;
102            this.masm = masm;
103            this.keyTargets = keyTargets;
104            this.defaultTarget = defaultTarget;
105        }
106
107        /**
108         * This method generates code for a comparison between the actual value and the constant at
109         * the given index and a condition jump to target.
110         */
111        protected abstract void conditionalJump(int index, Condition condition, Label target);
112
113        @Override
114        public void conditionalJump(int index, Condition condition, boolean targetDefault) {
115            Label target = targetDefault ? defaultTarget.label() : keyTargets[index].label();
116            if (condition == null) {
117                masm.jmp(target);
118            } else {
119                conditionalJump(index, condition, target);
120            }
121        }
122
123        @Override
124        public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) {
125            if (canFallThrough && crb.isSuccessorEdge(defaultTarget)) {
126                conditionalJump(index, condition, keyTargets[index].label());
127            } else if (canFallThrough && crb.isSuccessorEdge(keyTargets[index])) {
128                conditionalJump(index, condition.negate(), defaultTarget.label());
129            } else {
130                conditionalJump(index, condition, keyTargets[index].label());
131                masm.jmp(defaultTarget.label());
132            }
133        }
134
135        @Override
136        public Label conditionalJump(int index, Condition condition) {
137            Label label = new Label();
138            conditionalJump(index, condition, label);
139            return label;
140        }
141
142        @Override
143        public void bind(Label label) {
144            masm.bind(label);
145        }
146
147        @Override
148        public boolean isSameTarget(int index1, int index2) {
149            return keyTargets[index1] == keyTargets[index2];
150        }
151
152    }
153
154    /**
155     * This closure is used internally to determine the average effort for a certain strategy on a
156     * given switch instruction.
157     */
158    private class EffortClosure implements SwitchClosure {
159
160        private int defaultEffort;
161        private int defaultCount;
162        private final int[] keyEfforts = new int[keyProbabilities.length];
163        private final int[] keyCounts = new int[keyProbabilities.length];
164        private final LabelRef[] keyTargets;
165
166        EffortClosure(LabelRef[] keyTargets) {
167            this.keyTargets = keyTargets;
168        }
169
170        @Override
171        public void conditionalJump(int index, Condition condition, boolean defaultTarget) {
172            // nothing to do
173        }
174
175        @Override
176        public void conditionalJumpOrDefault(int index, Condition condition, boolean canFallThrough) {
177            // nothing to do
178        }
179
180        @Override
181        public Label conditionalJump(int index, Condition condition) {
182            // nothing to do
183            return null;
184        }
185
186        @Override
187        public void bind(Label label) {
188            // nothing to do
189        }
190
191        @Override
192        public boolean isSameTarget(int index1, int index2) {
193            return keyTargets[index1] == keyTargets[index2];
194        }
195
196        public double getAverageEffort() {
197            double defaultProbability = 1;
198            double effort = 0;
199            for (int i = 0; i < keyProbabilities.length; i++) {
200                effort += keyEfforts[i] * keyProbabilities[i] / keyCounts[i];
201                defaultProbability -= keyProbabilities[i];
202            }
203            return effort + defaultEffort * defaultProbability / defaultCount;
204        }
205    }
206
207    public final double[] keyProbabilities;
208    private double averageEffort = -1;
209    private EffortClosure effortClosure;
210
211    public SwitchStrategy(double[] keyProbabilities) {
212        assert keyProbabilities.length >= 2;
213        this.keyProbabilities = keyProbabilities;
214    }
215
216    public abstract Constant[] getKeyConstants();
217
218    public double getAverageEffort() {
219        assert averageEffort >= 0 : "average effort was not calculated yet for this strategy";
220        return averageEffort;
221    }
222
223    /**
224     * Tells the system that the given (inclusive) range of keys is reached after depth number of
225     * comparisons, which is used to calculate the average effort.
226     */
227    protected void registerEffort(int rangeStart, int rangeEnd, int depth) {
228        if (effortClosure != null) {
229            for (int i = rangeStart; i <= rangeEnd; i++) {
230                effortClosure.keyEfforts[i] += depth;
231                effortClosure.keyCounts[i]++;
232            }
233        }
234    }
235
236    /**
237     * Tells the system that the default successor is reached after depth number of comparisons,
238     * which is used to calculate average effort.
239     */
240    protected void registerDefaultEffort(int depth) {
241        if (effortClosure != null) {
242            effortClosure.defaultEffort += depth;
243            effortClosure.defaultCount++;
244        }
245    }
246
247    @Override
248    public String toString() {
249        return getClass().getSimpleName() + "[avgEffort=" + averageEffort + "]";
250    }
251
252    /**
253     * This strategy orders the keys according to their probability and creates one equality
254     * comparison per key.
255     */
256    public static class SequentialStrategy extends SwitchStrategy {
257        private final Integer[] indexes;
258        private final Constant[] keyConstants;
259
260        public SequentialStrategy(final double[] keyProbabilities, Constant[] keyConstants) {
261            super(keyProbabilities);
262            assert keyProbabilities.length == keyConstants.length;
263
264            this.keyConstants = keyConstants;
265            int keyCount = keyConstants.length;
266            indexes = new Integer[keyCount];
267            for (int i = 0; i < keyCount; i++) {
268                indexes[i] = i;
269            }
270            Arrays.sort(indexes, new Comparator<Integer>() {
271                @Override
272                public int compare(Integer o1, Integer o2) {
273                    return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0;
274                }
275            });
276        }
277
278        @Override
279        public Constant[] getKeyConstants() {
280            return keyConstants;
281        }
282
283        @Override
284        public void run(SwitchClosure closure) {
285            for (int i = 0; i < keyConstants.length - 1; i++) {
286                closure.conditionalJump(indexes[i], Condition.EQ, false);
287                registerEffort(indexes[i], indexes[i], i + 1);
288            }
289            closure.conditionalJumpOrDefault(indexes[keyConstants.length - 1], Condition.EQ, true);
290            registerEffort(indexes[keyConstants.length - 1], indexes[keyConstants.length - 1], keyConstants.length);
291            registerDefaultEffort(keyConstants.length);
292        }
293    }
294
295    /**
296     * Base class for strategies that rely on primitive integer keys.
297     */
298    private abstract static class PrimitiveStrategy extends SwitchStrategy {
299        protected final JavaConstant[] keyConstants;
300
301        protected PrimitiveStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) {
302            super(keyProbabilities);
303            assert keyProbabilities.length == keyConstants.length;
304            this.keyConstants = keyConstants;
305        }
306
307        @Override
308        public JavaConstant[] getKeyConstants() {
309            return keyConstants;
310        }
311
312        /**
313         * Looks for the end of a stretch of key constants that are successive numbers and have the
314         * same target.
315         */
316        protected int getSliceEnd(SwitchClosure closure, int pos) {
317            int slice = pos;
318            while (slice < (keyConstants.length - 1) && keyConstants[slice + 1].asLong() == keyConstants[slice].asLong() + 1 && closure.isSameTarget(slice, slice + 1)) {
319                slice++;
320            }
321            return slice;
322        }
323    }
324
325    /**
326     * This strategy divides the keys into ranges of successive keys with the same target and
327     * creates comparisons for these ranges.
328     */
329    public static class RangesStrategy extends PrimitiveStrategy {
330        private final Integer[] indexes;
331
332        public RangesStrategy(final double[] keyProbabilities, JavaConstant[] keyConstants) {
333            super(keyProbabilities, keyConstants);
334
335            int keyCount = keyConstants.length;
336            indexes = new Integer[keyCount];
337            for (int i = 0; i < keyCount; i++) {
338                indexes[i] = i;
339            }
340            Arrays.sort(indexes, new Comparator<Integer>() {
341                @Override
342                public int compare(Integer o1, Integer o2) {
343                    return keyProbabilities[o1] < keyProbabilities[o2] ? 1 : keyProbabilities[o1] > keyProbabilities[o2] ? -1 : 0;
344                }
345            });
346        }
347
348        @Override
349        public void run(SwitchClosure closure) {
350            int depth = 0;
351            closure.conditionalJump(0, Condition.LT, true);
352            registerDefaultEffort(++depth);
353            int rangeStart = 0;
354            int rangeEnd = getSliceEnd(closure, rangeStart);
355            while (rangeEnd != keyConstants.length - 1) {
356                if (rangeStart == rangeEnd) {
357                    closure.conditionalJump(rangeStart, Condition.EQ, false);
358                    registerEffort(rangeStart, rangeEnd, ++depth);
359                } else {
360                    if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) {
361                        closure.conditionalJump(rangeStart, Condition.LT, true);
362                        registerDefaultEffort(++depth);
363                    }
364                    closure.conditionalJump(rangeEnd, Condition.LE, false);
365                    registerEffort(rangeStart, rangeEnd, ++depth);
366                }
367                rangeStart = rangeEnd + 1;
368                rangeEnd = getSliceEnd(closure, rangeStart);
369            }
370            if (rangeStart == rangeEnd) {
371                closure.conditionalJumpOrDefault(rangeStart, Condition.EQ, true);
372                registerEffort(rangeStart, rangeEnd, ++depth);
373                registerDefaultEffort(depth);
374            } else {
375                if (rangeStart == 0 || keyConstants[rangeStart - 1].asLong() + 1 != keyConstants[rangeStart].asLong()) {
376                    closure.conditionalJump(rangeStart, Condition.LT, true);
377                    registerDefaultEffort(++depth);
378                }
379                closure.conditionalJumpOrDefault(rangeEnd, Condition.LE, true);
380                registerEffort(rangeStart, rangeEnd, ++depth);
381                registerDefaultEffort(depth);
382            }
383        }
384    }
385
386    /**
387     * This strategy recursively subdivides the list of keys to create a binary search based on
388     * probabilities.
389     */
390    public static class BinaryStrategy extends PrimitiveStrategy {
391
392        private static final double MIN_PROBABILITY = 0.00001;
393
394        private final double[] probabilitySums;
395
396        public BinaryStrategy(double[] keyProbabilities, JavaConstant[] keyConstants) {
397            super(keyProbabilities, keyConstants);
398            probabilitySums = new double[keyProbabilities.length + 1];
399            double sum = 0;
400            for (int i = 0; i < keyConstants.length; i++) {
401                sum += Math.max(keyProbabilities[i], MIN_PROBABILITY);
402                probabilitySums[i + 1] = sum;
403            }
404        }
405
406        @Override
407        public void run(SwitchClosure closure) {
408            recurseBinarySwitch(closure, 0, keyConstants.length - 1, 0);
409        }
410
411        /**
412         * Recursively generate a list of comparisons that always subdivides the keys in the given
413         * (inclusive) range in the middle (in terms of probability, not index). If left is bigger
414         * than zero, then we always know that the value is equal to or bigger than the left key.
415         * This does not hold for the right key, as there may be a gap afterwards.
416         */
417        private void recurseBinarySwitch(SwitchClosure closure, int left, int right, int startDepth) {
418            assert startDepth < keyConstants.length * 3 : "runaway recursion in binary switch";
419            int depth = startDepth;
420            boolean leftBorder = left == 0;
421            boolean rightBorder = right == keyConstants.length - 1;
422
423            if (left + 1 == right) {
424                // only two possible values
425                if (leftBorder || rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong() || keyConstants[left].asLong() + 1 != keyConstants[right].asLong()) {
426                    closure.conditionalJump(left, Condition.EQ, false);
427                    registerEffort(left, left, ++depth);
428                    closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder);
429                    registerEffort(right, right, ++depth);
430                    registerDefaultEffort(depth);
431                } else {
432                    // here we know that the value can only be one of these two keys in the range
433                    closure.conditionalJump(left, Condition.EQ, false);
434                    registerEffort(left, left, ++depth);
435                    closure.conditionalJump(right, null, false);
436                    registerEffort(right, right, depth);
437                }
438                return;
439            }
440            double probabilityStart = probabilitySums[left];
441            double probabilityMiddle = (probabilityStart + probabilitySums[right + 1]) / 2;
442            assert probabilityStart >= probabilityStart;
443            int middle = left;
444            while (getSliceEnd(closure, middle + 1) < right && probabilitySums[getSliceEnd(closure, middle + 1)] < probabilityMiddle) {
445                middle = getSliceEnd(closure, middle + 1);
446            }
447            middle = getSliceEnd(closure, middle);
448            assert middle < keyConstants.length - 1;
449
450            if (getSliceEnd(closure, left) == middle) {
451                if (left == 0) {
452                    closure.conditionalJump(0, Condition.LT, true);
453                    registerDefaultEffort(++depth);
454                }
455                closure.conditionalJump(middle, Condition.LE, false);
456                registerEffort(left, middle, ++depth);
457
458                if (middle + 1 == right) {
459                    closure.conditionalJumpOrDefault(right, Condition.EQ, rightBorder);
460                    registerEffort(right, right, ++depth);
461                    registerDefaultEffort(depth);
462                } else {
463                    if (keyConstants[middle].asLong() + 1 != keyConstants[middle + 1].asLong()) {
464                        closure.conditionalJump(middle + 1, Condition.LT, true);
465                        registerDefaultEffort(++depth);
466                    }
467                    if (getSliceEnd(closure, middle + 1) == right) {
468                        if (right == keyConstants.length - 1 || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) {
469                            closure.conditionalJumpOrDefault(right, Condition.LE, rightBorder);
470                            registerEffort(middle + 1, right, ++depth);
471                            registerDefaultEffort(depth);
472                        } else {
473                            closure.conditionalJump(middle + 1, null, false);
474                            registerEffort(middle + 1, right, depth);
475                        }
476                    } else {
477                        recurseBinarySwitch(closure, middle + 1, right, depth);
478                    }
479                }
480            } else if (getSliceEnd(closure, middle + 1) == right) {
481                if (rightBorder || keyConstants[right].asLong() + 1 != keyConstants[right + 1].asLong()) {
482                    closure.conditionalJump(right, Condition.GT, true);
483                    registerDefaultEffort(++depth);
484                }
485                closure.conditionalJump(middle + 1, Condition.GE, false);
486                registerEffort(middle + 1, right, ++depth);
487                recurseBinarySwitch(closure, left, middle, depth);
488            } else {
489                Label label = closure.conditionalJump(middle + 1, Condition.GE);
490                depth++;
491                recurseBinarySwitch(closure, left, middle, depth);
492                closure.bind(label);
493                recurseBinarySwitch(closure, middle + 1, right, depth);
494            }
495        }
496    }
497
498    public abstract void run(SwitchClosure closure);
499
500    private static SwitchStrategy[] getStrategies(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) {
501        SwitchStrategy[] strategies = new SwitchStrategy[]{new SequentialStrategy(keyProbabilities, keyConstants), new RangesStrategy(keyProbabilities, keyConstants),
502                        new BinaryStrategy(keyProbabilities, keyConstants)};
503        for (SwitchStrategy strategy : strategies) {
504            strategy.effortClosure = strategy.new EffortClosure(keyTargets);
505            strategy.run(strategy.effortClosure);
506            strategy.averageEffort = strategy.effortClosure.getAverageEffort();
507            strategy.effortClosure = null;
508        }
509        return strategies;
510    }
511
512    /**
513     * Creates all switch strategies for the given switch, evaluates them (based on average effort)
514     * and returns the best one.
515     */
516    public static SwitchStrategy getBestStrategy(double[] keyProbabilities, JavaConstant[] keyConstants, LabelRef[] keyTargets) {
517        SwitchStrategy[] strategies = getStrategies(keyProbabilities, keyConstants, keyTargets);
518        double bestEffort = Integer.MAX_VALUE;
519        SwitchStrategy bestStrategy = null;
520        for (SwitchStrategy strategy : strategies) {
521            if (strategy.getAverageEffort() < bestEffort) {
522                bestEffort = strategy.getAverageEffort();
523                bestStrategy = strategy;
524            }
525        }
526        return bestStrategy;
527    }
528}
529