1 /*
   2  * Copyright (c) 2019, 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  * @requires !vm.graal.enabled
  27  * @modules java.base/jdk.internal.org.objectweb.asm
  28  *          java.base/jdk.internal.misc
  29  *          java.base/jdk.internal.vm.annotation
  30  * @library /test/lib /
  31  * @build sun.hotspot.WhiteBox
  32  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  33  *                                sun.hotspot.WhiteBox$WhiteBoxPermission
  34  *
  35  * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
  36  *                   -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
  37  *                   -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=compileonly,*::m -XX:CompileCommand=dontinline,*::test
  38  *                   -Xbatch -XX:+WhiteBoxAPI -Xmixed
  39  *                   -XX:-TieredCompilation
  40  *                      compiler.cha.StrengthReduceInterfaceCall
  41  *
  42  * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
  43  *                   -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
  44  *                   -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=compileonly,*::m -XX:CompileCommand=dontinline,*::test
  45  *                   -Xbatch -XX:+WhiteBoxAPI -Xmixed
  46  *                   -XX:+TieredCompilation -XX:TieredStopAtLevel=1
  47  *                      compiler.cha.StrengthReduceInterfaceCall
  48  */
  49 package compiler.cha;
  50 
  51 import jdk.internal.misc.Unsafe;
  52 import jdk.internal.org.objectweb.asm.ClassWriter;
  53 import jdk.internal.org.objectweb.asm.MethodVisitor;
  54 import jdk.internal.vm.annotation.DontInline;
  55 import sun.hotspot.WhiteBox;
  56 import sun.hotspot.code.NMethod;
  57 
  58 import java.io.IOException;
  59 import java.lang.annotation.Retention;
  60 import java.lang.annotation.RetentionPolicy;
  61 import java.lang.invoke.MethodHandle;
  62 import java.lang.invoke.MethodHandles;
  63 import java.lang.reflect.Method;
  64 import java.util.HashMap;
  65 import java.util.concurrent.Callable;
  66 
  67 import static jdk.test.lib.Asserts.*;
  68 import static jdk.internal.org.objectweb.asm.ClassWriter.*;
  69 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  70 
  71 public class StrengthReduceInterfaceCall {
  72     public static void main(String[] args) {
  73         run(ObjectToString.class);
  74         run(ObjectHashCode.class);
  75         run(TwoLevelHierarchyLinear.class);
  76         run(ThreeLevelHierarchyLinear.class);
  77         run(ThreeLevelHierarchyAbstractVsDefault.class);
  78         run(ThreeLevelDefaultHierarchy.class);
  79         run(ThreeLevelDefaultHierarchy1.class);
  80     }
  81 
  82     /* =========================================================== */
  83 
  84     interface Action {
  85         int run();
  86     }
  87 
  88     public static final Unsafe U = Unsafe.getUnsafe();
  89 
  90     interface Test<T> {
  91         void call(T o);
  92         T receiver(int id);
  93 
  94         default Runnable monomophic() {
  95             return () -> {
  96                 call(receiver(0)); // 100%
  97             };
  98         }
  99 
 100         default Runnable bimorphic() {
 101             return () -> {
 102                 call(receiver(0)); // 50%
 103                 call(receiver(1)); // 50%
 104             };
 105         }
 106 
 107         default Runnable polymorphic() {
 108             return () -> {
 109                 for (int i = 0; i < 23; i++) {
 110                     call(receiver(0)); // 92%
 111                 }
 112                 call(receiver(1)); // 4%
 113                 call(receiver(2)); // 4%
 114             };
 115         }
 116 
 117         default Runnable megamorphic() {
 118             return () -> {
 119                 call(receiver(0)); // 33%
 120                 call(receiver(1)); // 33%
 121                 call(receiver(2)); // 33%
 122             };
 123         }
 124 
 125         default void initialize(Class<?>... cs) {
 126             for (Class<?> c : cs) {
 127                 U.ensureClassInitialized(c);
 128             }
 129         }
 130 
 131         default void repeat(int cnt, Runnable r) {
 132             for (int i = 0; i < cnt; i++) {
 133                 r.run();
 134             }
 135         }
 136     }
 137 
 138     public static abstract class ATest<T> implements Test<T> {
 139         public static final WhiteBox WB = WhiteBox.getWhiteBox();
 140 
 141         public static final Object CORRECT = new Object();
 142         public static final Object WRONG   = new Object();
 143 
 144         final Method TEST;
 145         private final Class<T> declared;
 146         private final Class<?> receiver;
 147 
 148         private final HashMap<Integer, T> receivers = new HashMap<>();
 149 
 150         public ATest(Class<T> declared, Class<?> receiver) {
 151             this.declared = declared;
 152             this.receiver = receiver;
 153             TEST = compute(() -> this.getClass().getDeclaredMethod("test", declared));
 154         }
 155 
 156         @DontInline
 157         public abstract Object test(T i);
 158 
 159         public abstract void checkInvalidReceiver();
 160 
 161         public T receiver(int id) {
 162             return receivers.computeIfAbsent(id, (i -> {
 163                 try {
 164                     MyClassLoader cl = (MyClassLoader) receiver.getClassLoader();
 165                     Class<?> sub = cl.subclass(receiver, i);
 166                     return (T)sub.getDeclaredConstructor().newInstance();
 167                 } catch (Exception e) {
 168                     throw new Error(e);
 169                 }
 170             }));
 171         }
 172 
 173 
 174         public void compile(Runnable r) {
 175             while (!WB.isMethodCompiled(TEST)) {
 176                 for (int i = 0; i < 100; i++) {
 177                     r.run();
 178                 }
 179             }
 180             assertCompiled(); // record nmethod info
 181         }
 182 
 183         private NMethod prevNM = null;
 184 
 185         public void assertNotCompiled() {
 186             NMethod curNM = NMethod.get(TEST, false);
 187             assertTrue(prevNM != null); // was previously compiled
 188             assertTrue(curNM == null || prevNM.compile_id != curNM.compile_id); // either no nmethod present or recompiled
 189             prevNM = curNM; // update nmethod info
 190         }
 191 
 192         public void assertCompiled() {
 193             NMethod curNM = NMethod.get(TEST, false);
 194             assertTrue(curNM != null); // nmethod is present
 195             assertTrue(prevNM == null || prevNM.compile_id == curNM.compile_id); // no recompilations if nmethod present
 196             prevNM = curNM; // update nmethod info
 197         }
 198 
 199         @Override
 200         public void call(T i) {
 201             assertTrue(test(i) != WRONG);
 202         }
 203     }
 204 
 205     @Retention(value = RetentionPolicy.RUNTIME)
 206     public @interface TestCase {}
 207 
 208     static void run(Class<?> test) {
 209         try {
 210             for (Method m : test.getDeclaredMethods()) {
 211                 if (m.isAnnotationPresent(TestCase.class)) {
 212                     System.out.println(m.toString());
 213                     ClassLoader cl = new MyClassLoader(test);
 214                     Class<?> c = cl.loadClass(test.getName());
 215                     c.getMethod(m.getName()).invoke(c.getDeclaredConstructor().newInstance());
 216                 }
 217             }
 218         } catch (Exception e) {
 219             throw new Error(e);
 220         }
 221     }
 222 
 223     static class ObjectToStringHelper {
 224         static Object test(Object o) {
 225             throw new Error("not used");
 226         }
 227     }
 228     static class ObjectHashCodeHelper {
 229         static int test(Object o) {
 230         throw new Error("not used");
 231     }
 232     }
 233 
 234     static final class MyClassLoader extends ClassLoader {
 235         private final Class<?> test;
 236 
 237         MyClassLoader(Class<?> test) {
 238             this.test = test;
 239         }
 240 
 241         static String intl(String s) {
 242             return s.replace('.', '/');
 243         }
 244 
 245         Class<?> subclass(Class<?> c, int id) {
 246             String name = c.getName() + id;
 247             Class<?> sub = findLoadedClass(name);
 248             if (sub == null) {
 249                 ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
 250                 cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, intl(c.getName()), null);
 251 
 252                 { // Default constructor: <init>()V
 253                     MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 254                     mv.visitCode();
 255                     mv.visitVarInsn(ALOAD, 0);
 256                     mv.visitMethodInsn(INVOKESPECIAL, intl(c.getName()), "<init>", "()V", false);
 257                     mv.visitInsn(RETURN);
 258                     mv.visitMaxs(0, 0);
 259                     mv.visitEnd();
 260                 }
 261 
 262                 byte[] classFile = cw.toByteArray();
 263                 return defineClass(name, classFile, 0, classFile.length);
 264             }
 265             return sub;
 266         }
 267 
 268         protected Class<?> loadClass(String name, boolean resolve)
 269                 throws ClassNotFoundException
 270         {
 271             // First, check if the class has already been loaded
 272             Class<?> c = findLoadedClass(name);
 273             if (c == null) {
 274                 try {
 275                     c = getParent().loadClass(name);
 276                     if (name.endsWith("ObjectToStringHelper")) {
 277                         ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
 278                         cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
 279 
 280                         {
 281                             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
 282                             mv.visitCode();
 283                             mv.visitVarInsn(ALOAD, 0);
 284                             mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "toString", "()Ljava/lang/String;", true);
 285                             mv.visitInsn(ARETURN);
 286                             mv.visitMaxs(0, 0);
 287                             mv.visitEnd();
 288                         }
 289 
 290                         byte[] classFile = cw.toByteArray();
 291                         return defineClass(name, classFile, 0, classFile.length);
 292                     } else if (name.endsWith("ObjectHashCodeHelper")) {
 293                         ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
 294                         cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
 295 
 296                         {
 297                             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)I", null, null);
 298                             mv.visitCode();
 299                             mv.visitVarInsn(ALOAD, 0);
 300                             mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "hashCode", "()I", true);
 301                             mv.visitInsn(IRETURN);
 302                             mv.visitMaxs(0, 0);
 303                             mv.visitEnd();
 304                         }
 305 
 306                         byte[] classFile = cw.toByteArray();
 307                         return defineClass(name, classFile, 0, classFile.length);
 308                     } else if (c == test || name.startsWith(test.getName())) {
 309                         try {
 310                             String path = name.replace('.', '/') + ".class";
 311                             byte[] classFile = getParent().getResourceAsStream(path).readAllBytes();
 312                             return defineClass(name, classFile, 0, classFile.length);
 313                         } catch (IOException e) {
 314                             throw new Error(e);
 315                         }
 316                     }
 317                 } catch (ClassNotFoundException e) {
 318                     // ClassNotFoundException thrown if class not found
 319                     // from the non-null parent class loader
 320                 }
 321 
 322                 if (c == null) {
 323                     // If still not found, then invoke findClass in order
 324                     // to find the class.
 325                     c = findClass(name);
 326                 }
 327             }
 328             if (resolve) {
 329                 resolveClass(c);
 330             }
 331             return c;
 332         }
 333     }
 334 
 335     public interface RunnableWithException {
 336         void run() throws Throwable;
 337     }
 338 
 339     public static void shouldThrow(Class<? extends Throwable> expectedException, RunnableWithException r) {
 340         try {
 341             r.run();
 342             throw new AssertionError("Exception not thrown: " + expectedException.getName());
 343         } catch(Throwable e) {
 344             if (expectedException == e.getClass()) {
 345                 // success: proper exception is thrown
 346             } else {
 347                 throw new Error(expectedException.getName() + " is expected", e);
 348             }
 349         }
 350     }
 351 
 352     public static MethodHandle unsafeCastMH(Class<?> cls) {
 353         try {
 354             MethodHandle mh = MethodHandles.identity(Object.class);
 355             return MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(cls));
 356         } catch (Throwable e) {
 357             throw new Error(e);
 358         }
 359     }
 360 
 361     static <T> T compute(Callable<T> c) {
 362         try {
 363             return c.call();
 364         } catch (Exception e) {
 365             throw new Error(e);
 366         }
 367     }
 368 }