--- /dev/null 2017-02-14 20:36:24.000000000 +0300 +++ new/test/compiler/cha/StrengthReduceInterfaceCall.java 2017-02-14 20:36:24.000000000 +0300 @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules java.base/jdk.internal.org.objectweb.asm + * java.base/jdk.internal.misc + * java.base/jdk.internal.vm.annotation + * @library /test/lib / + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+PrintCompilation -XX:+PrintInlining + * -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:CompileCommand=dontinline,*::test + * -XX:-TieredCompilation + * compiler.cha.StrengthReduceInterfaceCall + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+PrintCompilation -XX:+PrintInlining + * -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:CompileCommand=dontinline,*::test + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * compiler.cha.StrengthReduceInterfaceCall + */ +package compiler.cha; + +import jdk.internal.misc.Unsafe; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.vm.annotation.DontInline; +import sun.hotspot.WhiteBox; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.concurrent.Callable; + +import static jdk.test.lib.Asserts.*; +import static jdk.internal.org.objectweb.asm.ClassWriter.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +public class StrengthReduceInterfaceCall { + public static void main(String[] args) throws Exception { + run(OneLevelHierarchy.class); + run(ThreeLevelHierarchy.class); + run(ThreeLevelDefaultHierarchy.class); + run(ThreeLevelHierarchy1.class); + } + + public static class ThreeLevelHierarchy1 extends ATest { + public ThreeLevelHierarchy1() { super(I.class, C.class); } + + interface J1 { default int m() { return WRONG; } } + interface J2 extends J1 { int m(); } + interface I extends J1, J2 {} + + static class C implements I { public int m() { return CORRECT; }} + + @DontInline + public int test(I i) { + return i.m(); // I.m OVERPASS + } + + static class DI implements I { public int m() { return WRONG; }} + + static class DJ11 implements J1 {} + static class DJ12 implements J1 { public int m() { return WRONG; }} + + static class DJ2 implements J2 { public int m() { return WRONG; }} + + @TestCase + public void test1() { + compile(megamorphic()); + + // 1. No deopt on not-yet-seen receiver + repeat(10, () -> call(new C() {})); + assertCompiled(); + + call(new C() { public int m() { return CORRECT; }}); + assertNotCompiled(); + + compile(megamorphic()); + call(new C() { public int m() { return CORRECT; }}); + assertCompiled(); // no inlining + } + + @TestCase + public void test2() { + compile(megamorphic()); + + // Dependency context is I + initialize(DJ11.class, DJ11.class, DJ2.class); + assertCompiled(); + + initialize(DI.class); + assertNotCompiled(); + + compile(megamorphic()); + call(new C() { public int m() { return CORRECT; }}); + assertCompiled(); // no inlining + } + } + + public static class ThreeLevelHierarchy extends ATest { + public ThreeLevelHierarchy() { super(I.class, C.class); } + + interface J { int m(); } + interface I extends J {} + + interface K1 extends I {} + interface K2 extends I { int m(); } + interface K3 extends I { default int m() { return WRONG; }} + + static class C implements I { public int m() { return CORRECT; }} + + static class DI implements I { public int m() { return WRONG; }} + static class DJ implements J { public int m() { return WRONG; }} + + @DontInline + public int test(I i) { + return i.m(); // J.m ABSTRACT + } + + @TestCase + public void test1() { + compile(megamorphic()); + + // No deopt on not-yet-seen receiver + repeat(10, () -> call(new C(){})); + assertCompiled(); + + // nmethod dependency context is I + initialize(DJ.class, K1.class, K2.class); + assertCompiled(); + + // Dependency invalidation: DI <: I + initialize(DI.class /*, K3.class*/); + assertNotCompiled(); + + // Recompilation w/o a dependency + compile(megamorphic()); + call(new C() { public int m() { return CORRECT; }}); + assertCompiled(); + } + + @TestCase + public void test2() { + compile(megamorphic()); + + // Dependency invalidation + // FIXME: default methods in sub-interfaces shouldn't be taken into account by CHA + initialize(K3.class); + assertNotCompiled(); + } + } + + public static class ThreeLevelDefaultHierarchy extends ATest { + public ThreeLevelDefaultHierarchy() { super(I.class, C.class); } + + interface J { default int m() { return WRONG; }} + interface I extends J {} + + static class C implements I { public int m() { return CORRECT; }} + + interface K1 extends I {} + interface K2 extends I { int m(); } + interface K3 extends I { default int m() { return WRONG; }} + + static class DI implements I { public int m() { return WRONG; }} + static class DJ implements J { public int m() { return WRONG; }} + + @DontInline + public int test(I i) { + return i.m(); // no inlining since J.m is a default method + } + + @TestCase + public void test1() { + compile(megamorphic()); + + // No deopt on not-yet-seen receiver + repeat(10, () -> call(new C() {})); + + // no dependency, no inlining + initialize(DJ.class, DI.class, K1.class, K2.class, K3.class); + assertCompiled(); + } + } + + public static class ThreeLevelDefault1Hierarchy extends ATest { + public ThreeLevelDefault1Hierarchy() { super(I.class, C.class); } + + interface J1 { int m();} + interface J2 extends J1 { default int m() { return WRONG; } } + interface I extends J1, J2 {} + + static class C implements I { public int m() { return CORRECT; }} + + interface K1 extends I {} + interface K2 extends I { int m(); } + interface K3 extends I { default int m() { return WRONG; }} + + static class DI implements I { public int m() { return WRONG; }} + static class DJ1 implements J1 { public int m() { return WRONG; }} + static class DJ2 implements J2 { public int m() { return WRONG; }} + + @DontInline + public int test(I i) { + return i.m(); // no inlining since J.m is a default method + } + + @TestCase + public void test1() { + compile(megamorphic()); + + // No deopt on not-yet-seen receiver + repeat(10, () -> call(new C() {})); + + // no dependency, no inlining + // CHA doesn't support default methods yet. + initialize(DJ1.class, DJ2.class, DI.class, K1.class, K2.class, K3.class); + assertCompiled(); + } + } + + public static class OneLevelHierarchy extends ATest { + public OneLevelHierarchy() { super(I.class, C.class); } + + interface J { default int m() { return WRONG; } } + + interface I extends J { int m(); } + static class C implements I { public int m() { return CORRECT; }} + + interface K1 extends I {} + interface K2 extends I { int m(); } + interface K3 extends I { default int m() { return WRONG; }} + + static class D implements I { public int m() { return WRONG; }} + + static class DJ1 implements J {} + static class DJ2 implements J { public int m() { return WRONG; }} + + @DontInline + public int test(I i) { + return i.m(); + } + + @TestCase + public void test1() { + compile(megamorphic()); + + // No deopt on not-yet-seen receiver + repeat(10, () -> call(new C(){})); + assertCompiled(); + + // No dependency invalidation + initialize(K1.class, K2.class, DJ1.class, DJ2.class); + assertCompiled(); + + // Dependency invalidation + initialize(D.class/*, K3.class*/); + assertNotCompiled(); + + // Recompilation: no inlining, no dependencies + compile(megamorphic()); + call(new C() { public int m() { return CORRECT; }}); + assertCompiled(); + } + + @TestCase + public void test2() { + compile(megamorphic()); + + // Dependency invalidation + initialize(K3.class); + assertNotCompiled(); + + // Recompilation: still inlines + // FIXME: no default method support in CHA yet + compile(megamorphic()); + call(new K3() { public int m() { return CORRECT; }}); + assertNotCompiled(); + + // Recompilation: no inlining, no dependencies + compile(megamorphic()); + + call(new K2() { public int m() { return CORRECT; }}); + call(new K3() { public int m() { return CORRECT; }}); + call(new K3() {}); + call(new C() { public int m() { return CORRECT; }}); +// assertCompiled(); // No CHA happened + + + } + } + + /* =========================================================== */ + + static T compute(Callable c) { + try { + return c.call(); + } catch (Exception e) { + throw new Error(e); + } + } + + interface Action { + int run(); + } + + public static final Unsafe U = Unsafe.getUnsafe(); + + interface Test { + boolean isCompiled(); + void assertNotCompiled(); + void assertCompiled(); + +// void compile(Action a); + + void call(T o); + T receiver(int id); + + default Runnable monomophic() { + return () -> { + call(receiver(0)); // 100% + }; + } + + default Runnable bimorphic() { + return () -> { + call(receiver(0)); // 50% + call(receiver(1)); // 50% + }; + } + + default Runnable polymorphic() { + return () -> { + for (int i = 0; i < 23; i++) { + call(receiver(0)); // 92% + } + call(receiver(1)); // 4% + call(receiver(2)); // 4% + }; + } + + default Runnable megamorphic() { + return () -> { + call(receiver(0)); // 33% + call(receiver(1)); // 33% + call(receiver(2)); // 33% + }; + } + + default void compile(Runnable r) { + assertNotCompiled(); + while(!isCompiled()) { + r.run(); + } + assertCompiled(); + } + default void load(Class... c) { /*already loaded*/ } + default void initialize(Class... cs) { + for (Class c : cs) { + U.ensureClassInitialized(c); + } + } + + default void repeat(int cnt, Runnable r) { + for (int i = 0; i < cnt; i++) { + r.run(); + } + } + } + + public static abstract class ATest implements Test { + public static final WhiteBox WB = WhiteBox.getWhiteBox(); + + static final int CORRECT = 1; + static final int WRONG = 1; + + final Method TEST; + private final Class declared; + private final Class receiver; + + private final HashMap receivers = new HashMap<>(); + + public ATest(Class declared, Class receiver) { + this.declared = declared; + this.receiver = receiver; + TEST = compute(() -> this.getClass().getDeclaredMethod("test", declared)); + } + + @DontInline + public abstract int test(T i); + + public T receiver(int id) { + return receivers.computeIfAbsent(id, (i -> { + try { + MyClassLoader cl = (MyClassLoader) receiver.getClassLoader(); + Class sub = cl.subclass(receiver, i); + return (T)sub.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new Error(e); + } + })); + } + + @Override + public boolean isCompiled() { return WB.isMethodCompiled(TEST); } + + @Override + public void assertNotCompiled() { assertFalse(isCompiled()); } + + @Override + public void assertCompiled() { assertTrue(isCompiled()); } + + @Override + public void call(T i) { + assertTrue(test(i) == CORRECT); + } + } + + @Retention(value = RetentionPolicy.RUNTIME) + public @interface TestCase {} + + static void run(Class test) { + try { + for (Method m : test.getDeclaredMethods()) { + if (m.isAnnotationPresent(TestCase.class)) { + System.out.println(m.toString()); + ClassLoader cl = new MyClassLoader(test); + Class c = cl.loadClass(test.getName()); + c.getMethod(m.getName()).invoke(c.getDeclaredConstructor().newInstance()); + } + } + } catch (Exception e) { + throw new Error(e); + } + } + + static final class MyClassLoader extends ClassLoader { + private final Class test; + + MyClassLoader(Class test) { + this.test = test; + } + + static String intl(String s) { + return s.replace('.', '/'); + } + + Class subclass(Class c, int id) { + String name = c.getName() + id; + Class sub = findLoadedClass(name); + if (sub == null) { + ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); + cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, intl(c.getName()), null); + + { // Default constructor: ()V + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, intl(c.getName()), "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + byte[] classFile = cw.toByteArray(); + return defineClass(name, classFile, 0, classFile.length); + } + return sub; + } + + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + // First, check if the class has already been loaded + Class c = findLoadedClass(name); + if (c == null) { + try { + c = getParent().loadClass(name); + if (c == test || name.startsWith(test.getName())) { + try { + String path = name.replace('.', '/') + ".class"; + byte[] classFile = getParent().getResourceAsStream(path).readAllBytes(); + return defineClass(name, classFile, 0, classFile.length); + } catch (IOException e) { + throw new Error(e); + } + } + } catch (ClassNotFoundException e) { + // ClassNotFoundException thrown if class not found + // from the non-null parent class loader + } + + if (c == null) { + // If still not found, then invoke findClass in order + // to find the class. + c = findClass(name); + } + } + if (resolve) { + resolveClass(c); + } + return c; + } + } +}