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