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