1 /*
   2  * Copyright (c) 2015, 2018, 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 
  24 /*
  25  * @test
  26  * @bug 8072909
  27  * @summary Test TimSort stack size on big arrays
  28  * @key intermittent
  29  * @library /test/lib
  30  * @modules java.management
  31  *          java.base/jdk.internal
  32  * @build TimSortStackSize2
  33  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  34  *                                sun.hotspot.WhiteBox$WhiteBoxPermission
  35  * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
  36  *     -XX:+WhiteBoxAPI TimSortStackSize2
  37  */
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.List;
  41 import java.util.function.Consumer;
  42 
  43 import jdk.test.lib.process.OutputAnalyzer;
  44 import jdk.test.lib.process.ProcessTools;
  45 import sun.hotspot.WhiteBox;
  46 
  47 public class TimSortStackSize2 {
  48 
  49     public static void main(String[] args) {
  50         if ( args == null || args.length == 0 ){
  51             startMeWithArgs();
  52         } else {
  53             doTestOfTwoTimSorts(Integer.parseInt(args[0]));
  54         }
  55     }
  56 
  57     private static void startMeWithArgs(){
  58         /*
  59          * big tests not for regular execution on all platforms:
  60          * run main/othervm -Xmx8g TimSortStackSize2 1073741824
  61          * run main/othervm -Xmx16g TimSortStackSize2 2147483644
  62          */
  63         try {
  64             Boolean compressedOops = WhiteBox.getWhiteBox()
  65                                              .getBooleanVMFlag("UseCompressedOops");
  66             long memory = (compressedOops == null || compressedOops) ? 385 : 770;
  67             final String xmsValue = "-Xms" +     memory + "m";
  68             final String xmxValue = "-Xmx" + 2 * memory + "m";
  69 
  70             System.out.printf("compressedOops: %s; Test will be started with \"%s %s\"%n",
  71                               compressedOops, xmsValue, xmxValue);
  72             OutputAnalyzer output = ProcessTools.executeTestJava(xmsValue,
  73                                                                  xmxValue,
  74                                                                  "TimSortStackSize2",
  75                                                                  "67108864");
  76             System.out.println(output.getOutput());
  77             output.shouldHaveExitValue(0);
  78         } catch (Exception e) {
  79             e.printStackTrace();
  80             throw new RuntimeException(e);
  81         }
  82     }
  83 
  84     private static void doTestOfTwoTimSorts(final int lengthOfTest){
  85         boolean passed = doTest("TimSort", lengthOfTest,
  86             (Integer [] a) -> Arrays.sort(a));
  87         passed = doTest("ComparableTimSort", lengthOfTest, (Integer [] a) ->
  88             Arrays.sort(a, (Object first, Object second) -> {
  89                 return ((Comparable<Object>)first).compareTo(second);
  90             }))
  91             && passed;
  92         if ( !passed ){
  93             throw new RuntimeException();
  94         }
  95     }
  96 
  97     private static boolean doTest(final String msg, final int lengthOfTest,
  98                                   final  Consumer<Integer[]> c){
  99         Integer [] a = null;
 100         try {
 101             a = new TimSortStackSize2(lengthOfTest).createArray();
 102             long begin = System.nanoTime();
 103             c.accept(a);
 104             long end = System.nanoTime();
 105             System.out.println(msg + " OK. Time: " + (end - begin) + "ns");
 106         } catch (ArrayIndexOutOfBoundsException e){
 107             System.out.println(msg + " broken:");
 108             e.printStackTrace();
 109             return false;
 110         } finally {
 111             a = null;
 112         }
 113         return true;
 114     }
 115 
 116     private static final int MIN_MERGE = 32;
 117     private final int minRun;
 118     private final int length;
 119     private final List<Long> runs = new ArrayList<Long>();
 120 
 121     public TimSortStackSize2(final int len) {
 122         this.length = len;
 123         minRun = minRunLength(len);
 124         fillRunsJDKWorstCase();
 125     }
 126 
 127     private static int minRunLength(int n) {
 128         assert n >= 0;
 129         int r = 0;      // Becomes 1 if any 1 bits are shifted off
 130         while (n >= MIN_MERGE) {
 131             r |= (n & 1);
 132             n >>= 1;
 133         }
 134         return n + r;
 135     }
 136 
 137     /**
 138      * Adds a sequence x_1, ..., x_n of run lengths to <code>runs</code> such that:<br>
 139      * 1. X = x_1 + ... + x_n <br>
 140      * 2. x_j >= minRun for all j <br>
 141      * 3. x_1 + ... + x_{j-2}  <  x_j  <  x_1 + ... + x_{j-1} for all j <br>
 142      * These conditions guarantee that TimSort merges all x_j's one by one
 143      * (resulting in X) using only merges on the second-to-last element.
 144      * @param X  The sum of the sequence that should be added to runs.
 145      */
 146     private void generateJDKWrongElem(long X) {
 147         for(long newTotal; X >= 2 * minRun + 1; X = newTotal) {
 148             //Default strategy
 149             newTotal = X / 2 + 1;
 150             //Specialized strategies
 151             if(3 * minRun + 3 <= X && X <= 4*minRun+1) {
 152                 // add x_1=MIN+1, x_2=MIN, x_3=X-newTotal  to runs
 153                 newTotal = 2 * minRun + 1;
 154             } else if (5 * minRun + 5 <= X && X <= 6 * minRun + 5) {
 155                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=X-newTotal  to runs
 156                 newTotal = 3 * minRun + 3;
 157             } else if (8 * minRun + 9 <= X && X <= 10 * minRun + 9) {
 158                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=2MIN+2, x_5=X-newTotal  to runs
 159                 newTotal = 5 * minRun + 5;
 160             } else if (13 * minRun + 15 <= X && X <= 16 * minRun + 17) {
 161                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=2MIN+2, x_5=3MIN+4, x_6=X-newTotal  to runs
 162                 newTotal = 8 * minRun + 9;
 163             }
 164             runs.add(0, X - newTotal);
 165         }
 166         runs.add(0, X);
 167     }
 168 
 169     /**
 170      * Fills <code>runs</code> with a sequence of run lengths of the form<br>
 171      * Y_n     x_{n,1}   x_{n,2}   ... x_{n,l_n} <br>
 172      * Y_{n-1} x_{n-1,1} x_{n-1,2} ... x_{n-1,l_{n-1}} <br>
 173      * ... <br>
 174      * Y_1     x_{1,1}   x_{1,2}   ... x_{1,l_1}<br>
 175      * The Y_i's are chosen to satisfy the invariant throughout execution,
 176      * but the x_{i,j}'s are merged (by <code>TimSort.mergeCollapse</code>)
 177      * into an X_i that violates the invariant.
 178      * X is the sum of all run lengths that will be added to <code>runs</code>.
 179      */
 180     private void fillRunsJDKWorstCase() {
 181         long runningTotal = 0;
 182         long Y = minRun + 4;
 183         long X = minRun;
 184 
 185         while (runningTotal + Y + X <= length) {
 186             runningTotal += X + Y;
 187             generateJDKWrongElem(X);
 188             runs.add(0, Y);
 189 
 190             // X_{i+1} = Y_i + x_{i,1} + 1, since runs.get(1) = x_{i,1}
 191             X = Y + runs.get(1) + 1;
 192 
 193             // Y_{i+1} = X_{i+1} + Y_i + 1
 194             Y += X + 1;
 195         }
 196 
 197         if (runningTotal + X <= length) {
 198             runningTotal += X;
 199             generateJDKWrongElem(X);
 200         }
 201 
 202         runs.add(length - runningTotal);
 203     }
 204 
 205     private Integer [] createArray() {
 206         Integer [] a = new Integer[length];
 207         Arrays.fill(a, 0);
 208         int endRun = -1;
 209         for (long len : runs) {
 210             a[endRun += len] = 1;
 211         }
 212         a[length - 1] = 0;
 213         return a;
 214     }
 215 
 216 }