/*
 * Copyright (c) 2019, 2021, 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
 * @requires !vm.graal.enabled & vm.opt.final.UseVtableBasedCHA == true
 * @modules java.base/jdk.internal.org.objectweb.asm
 *          java.base/jdk.internal.misc
 *          java.base/jdk.internal.vm.annotation
 * @library /test/lib /
 * @compile Utils.java
 * @build sun.hotspot.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
 *                   -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
 *                   -XX:CompileCommand=compileonly,*::m
 *                   -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
 *                   -XX:CompileCommand=compileonly,*::testHelper -XX:CompileCommand=inline,*::testHelper
 *                   -Xbatch -Xmixed -XX:+WhiteBoxAPI
 *                   -XX:-TieredCompilation
 *                      compiler.cha.StrengthReduceInterfaceCall
 *
 * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
 *                   -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
 *                   -XX:CompileCommand=compileonly,*::m
 *                   -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
 *                   -XX:CompileCommand=compileonly,*::testHelper -XX:CompileCommand=inline,*::testHelper
 *                   -Xbatch -Xmixed -XX:+WhiteBoxAPI
 *                   -XX:+TieredCompilation -XX:TieredStopAtLevel=1
 *                      compiler.cha.StrengthReduceInterfaceCall
 */
package compiler.cha;

import jdk.internal.vm.annotation.DontInline;

import static compiler.cha.Utils.*;

public class StrengthReduceInterfaceCall {
    public static void main(String[] args) {
        run(ObjectToString.class);
        run(ObjectHashCode.class);
        run(TwoLevelHierarchyLinear.class);
        run(ThreeLevelHierarchyLinear.class);
        run(ThreeLevelHierarchyAbstractVsDefault.class);
        run(ThreeLevelDefaultHierarchy.class);
        run(ThreeLevelDefaultHierarchy1.class);
        System.out.println("TEST PASSED");
    }

    public static class ObjectToString extends ATest<ObjectToString.I> {
        public ObjectToString() { super(I.class, C.class); }

        interface J           { String toString(); }
        interface I extends J {}

        static class C implements I {}

        interface K1 extends I {}
        interface K2 extends I { String toString(); } // K2.tS() ABSTRACT
        // interface K3 extends I { default String toString() { return "K3"; } // K2.tS() DEFAULT

        static class D implements I { public String toString() { return "D"; }}

        static class DJ1 implements J {}
        static class DJ2 implements J { public String toString() { return "DJ2"; }}

        @Override
        public Object test(I i) { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ }

        @TestCase
        public void testMono() {
            // 0. Trigger compilation of a monomorphic call site
            compile(monomophic()); // C1 <: C <: intf I <: intf J <: Object.toString()
            assertCompiled();

            // Dependency: none

            call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
            assertCompiled();
        }

        @TestCase
        public void testBi() {
            // 0. Trigger compilation of a bimorphic call site
            compile(bimorphic()); // C1 <: C <: intf I <: intf J <: Object.toString()
            assertCompiled();

            // Dependency: none

            call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
            assertCompiled();
        }

        @TestCase
        public void testMega() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C <: intf I <: intf J <: Object.toString()
            assertCompiled();

            // Dependency: none
            // compiler.cha.StrengthReduceInterfaceCall$ObjectToString::test (5 bytes)
            //     @ 1   compiler.cha.StrengthReduceInterfaceCall$ObjectToStringHelper::test (7 bytes)   inline (hot)
            //       @ 1   java.lang.Object::toString (36 bytes)   virtual call

            // No dependency - no invalidation
            repeat(100, () -> call(new C(){})); // Cn <: C <: intf I
            assertCompiled();

            initialize(K1.class,   // intf  K1             <: intf I <: intf J
                       K2.class,   // intf  K2.tS ABSTRACT <: intf I <: intf J
                       DJ1.class,  //      DJ1                       <: intf J
                       DJ2.class); //      DJ2.tS                    <: intf J
            assertCompiled();

            initialize(D.class); // D.tS <: intf I <: intf J
            assertCompiled();

            call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
            assertCompiled();
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class ObjectHashCode extends ATest<ObjectHashCode.I> {
        public ObjectHashCode() { super(I.class, C.class); }

        interface J {}
        interface I extends J {}

        static class C implements I {}

        interface K1 extends I {}
        interface K2 extends I { int hashCode(); } // K2.hC() ABSTRACT
        // interface K3 extends I { default int hashCode() { return CORRECT; } // K2.hC() DEFAULT

        static class D implements I { public int hashCode() { return super.hashCode(); }}

        static class DJ1 implements J {}
        static class DJ2 implements J { public int hashCode() { return super.hashCode(); }}

        @Override
        public Object test(I i) {
            return ObjectHashCodeHelper.testHelper(i); /* invokeinterface I.hashCode() */
        }

        @TestCase
        public void testMono() {
            // 0. Trigger compilation of a monomorphic call site
            compile(monomophic()); // C1 <: C <: intf I <: intf J <: Object.hashCode()
            assertCompiled();

            // Dependency: none

            call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
            assertCompiled();
        }

        @TestCase
        public void testBi() {
            // 0. Trigger compilation of a bimorphic call site
            compile(bimorphic()); // C1 <: C <: intf I <: intf J <: Object.toString()
            assertCompiled();

            // Dependency: none

            call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
            assertCompiled();
        }

        @TestCase
        public void testMega() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C <: intf I <: intf J <: Object.hashCode()
            assertCompiled();

            // Dependency: none

            // No dependency - no invalidation
            repeat(100, () -> call(new C(){})); // Cn <: C <: intf I
            assertCompiled();

            initialize(K1.class,   // intf  K1             <: intf I <: intf J
                       K2.class,   // intf  K2.hC ABSTRACT <: intf I <: intf J
                       DJ1.class,  //      DJ1                       <: intf J
                       DJ2.class); //      DJ2.hC                    <: intf J
            assertCompiled();

            initialize(D.class); // D.hC <: intf I <: intf J
            assertCompiled();

            call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
            assertCompiled();
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class TwoLevelHierarchyLinear extends ATest<TwoLevelHierarchyLinear.I> {
        public TwoLevelHierarchyLinear() { super(I.class, C.class); }

        interface J { default Object m() { return WRONG; } }

        interface I extends J { Object m(); }
        static class C implements I { public Object m() { return CORRECT; }}

        interface K1 extends I {}
        interface K2 extends I { Object m(); }
        interface K3 extends I { default Object m() { return WRONG;   }}
        interface K4 extends I { default Object m() { return CORRECT; }}

        static class D implements I { public Object m() { return WRONG;   }}

        static class DJ1 implements J {}
        static class DJ2 implements J { public Object m() { return WRONG; }}

        @DontInline
        public Object test(I i) {
            return i.m();
        }

        @TestCase
        public void testMega1() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m ABSTRACT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check is preserved

            // 1. No deoptimization/invalidation on not-yet-seen receiver
            repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            // 2. No dependency invalidation on class loading of unrelated classes: different context
            initialize(K1.class,   // intf  K1            <: intf I.m ABSTRACT <: intf J.m DEFAULT
                       K2.class,   // intf  K2.m ABSTRACT <: intf I.m ABSTRACT <: intf J.m DEFAULT
                       DJ1.class,  //      DJ1                                 <: intf J.m DEFAULT
                       DJ2.class); //      DJ2.m                               <: intf J.m DEFAULT
            assertCompiled();

            // 3. Dependency invalidation on D <: I
            initialize(D.class); // D.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertNotCompiled();

            // 4. Recompilation: no inlining, no dependencies
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        @TestCase
        public void testMega2() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No invalidation: interfaces don't participate in CHA.
            initialize(K3.class); // intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            // 2. Dependency invalidation on K3n <: I with concrete method.
            call(new K3() { public Object m() { return CORRECT; }}); // K3n.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m ABSTRACT
            assertNotCompiled();

            // 3. Recompilation: no inlining, no dependencies
            compile(megamorphic());
            call(new K3() { public Object m() { return CORRECT; }}); // K3n.m <: intf K3.m DEFAULT  <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        @TestCase
        public void testMega3() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No invalidation: interfaces don't participate in CHA.
            initialize(K4.class); // intf K4.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            // 2. Dependency invalidation on K4n <: I with default method.
            call(new K4() { /* default method K4.m */ }); // K4n <: intf K4.m DEFAULT <: intf I.m ABSTRACT <: intf J.m ABSTRACT
            assertNotCompiled();

            // 3. Recompilation: no inlining, no dependencies
            compile(megamorphic());
            call(new K4() { /* default method K4.m */  }); // K4n <: intf K3.m DEFAULT  <: intf I.m ABSTRACT <: intf J.m DEFAULT
            assertCompiled();

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class ThreeLevelHierarchyLinear extends ATest<ThreeLevelHierarchyLinear.I> {
        public ThreeLevelHierarchyLinear() { super(I.class, C.class); }

        interface J           { Object m(); }
        interface I extends J {}

        interface K1 extends I {}
        interface K2 extends I { Object m(); }
        interface K3 extends I { default Object m() { return WRONG;   }}
        interface K4 extends I { default Object m() { return CORRECT; }}

        static class C  implements I { public Object m() { return CORRECT; }}

        static class DI implements I { public Object m() { return WRONG;   }}
        static class DJ implements J { public Object m() { return WRONG;   }}

        @DontInline
        public Object test(I i) {
            return i.m(); // I <: J.m ABSTRACT
        }

        @TestCase
        public void testMega1() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No deoptimization/invalidation on not-yet-seen receiver
            repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I
            assertCompiled(); // No deopt on not-yet-seen receiver

            // 2. No dependency invalidation: different context
            initialize(DJ.class,  //      DJ.m                    <: intf J.m ABSTRACT
                       K1.class,  // intf K1            <: intf I <: intf J.m ABSTRACT
                       K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT
            assertCompiled();

            // 3. Dependency invalidation: DI.m <: I
            initialize(DI.class); //      DI.m          <: intf I <: intf J.m ABSTRACT
            assertNotCompiled();

            // 4. Recompilation w/o a dependency
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled(); // no dependency

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        @TestCase
        public void testMega2() {
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // No invalidation: interfaces don't participate in CHA.
            initialize(K3.class); // intf K3.m DEFAULT <: intf I;
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            call(new K3() { public Object m() { return CORRECT; }}); // Kn.m <: K3.m DEFAULT <: intf I <: intf J.m ABSTRACT
            assertNotCompiled();

            // Recompilation w/o a dependency
            compile(megamorphic());
            // Dependency: none
            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
            call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();
        }

        @TestCase
        public void testMega3() {
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // No invalidation: interfaces don't participate in CHA.
            initialize(K4.class); // intf K3.m DEFAULT <: intf I;
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            call(new K4() { /* default method K4.m */ }); // K4n <: K4.m DEFAULT <: intf I <: intf J.m ABSTRACT
            assertNotCompiled();

            // Recompilation w/o a dependency
            compile(megamorphic());
            // Dependency: none
            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
            call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() { public Object m() { return WRONG; }}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class ThreeLevelHierarchyAbstractVsDefault extends ATest<ThreeLevelHierarchyAbstractVsDefault.I> {
        public ThreeLevelHierarchyAbstractVsDefault() { super(I.class, C.class); }

        interface J1                { default Object m() { return WRONG; } } // intf J1.m DEFAULT
        interface J2 extends J1     { Object m(); }                          // intf J2.m ABSTRACT <: intf J1
        interface I  extends J1, J2 {}                                       // intf  I.m OVERPASS <: intf J1,J2

        static class C  implements I { public Object m() { return CORRECT; }}

        @DontInline
        public Object test(I i) {
            return i.m(); // intf I.m OVERPASS
        }

        static class DI implements I { public Object m() { return WRONG;   }}

        static class DJ11 implements J1 {}
        static class DJ12 implements J1 { public Object m() { return WRONG; }}

        static class DJ2 implements J2 { public Object m() { return WRONG;   }}

        interface K11 extends J1 {}
        interface K12 extends J1 { Object m(); }
        interface K13 extends J1 { default Object m() { return WRONG; }}
        interface K21 extends J2 {}
        interface K22 extends J2 { Object m(); }
        interface K23 extends J2 { default Object m() { return WRONG; }}


        public void testMega1() {
            // 0. Trigger compilation of megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No deopt/invalidation on not-yet-seen receiver
            repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
            assertCompiled();

            // 2. No dependency invalidation: different context
            initialize(K11.class, K12.class, K13.class,
                       K21.class, K22.class, K23.class);

            // 3. Dependency invalidation: Cn.m <: C <: I
            call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
            assertNotCompiled();

            // 4. Recompilation w/o a dependency
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }});
            assertCompiled(); // no inlining

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        public void testMega2() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic());
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No dependency invalidation: different context
            initialize(DJ11.class,
                       DJ12.class,
                       DJ2.class);
            assertCompiled();

            // 2. Dependency invalidation: DI.m <: I
            initialize(DI.class);
            assertNotCompiled();

            // 3. Recompilation w/o a dependency
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }});
            assertCompiled(); // no inlining

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J1() {}); // super interface
                test(j);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J2() { public Object m() { return WRONG; }}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class ThreeLevelDefaultHierarchy extends ATest<ThreeLevelDefaultHierarchy.I> {
        public ThreeLevelDefaultHierarchy() { super(I.class, C.class); }

        interface J           { default Object m() { return WRONG; }}
        interface I extends J {}

        static class C  implements I { public Object m() { return CORRECT; }}

        interface K1 extends I {}
        interface K2 extends I { Object m(); }
        interface K3 extends J { default Object m() { return WRONG; }}

        static class DI implements I { public Object m() { return WRONG; }}
        static class DJ implements J { public Object m() { return WRONG; }}
        static class DK3 implements K3 {}

        @DontInline
        public Object test(I i) {
            return i.m();
        }

        @TestCase
        public void testMega() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No deoptimization/invalidation on not-yet-seen receiver
            repeat(100, () -> call(new C() {}));
            assertCompiled();

            // 2. No dependency invalidation
            initialize(DJ.class,    //      DJ.m                               <: intf J.m ABSTRACT
                       K1.class,   // intf  K1            <: intf I            <: intf J.m ABSTRACT
                       K2.class,   // intf  K2.m ABSTRACT <: intf I            <: intf J.m ABSTRACT
                       DK3.class); //      DK3.m          <: intf K3.m DEFAULT <: intf J.m ABSTRACT
            assertCompiled();

            // 3. Dependency invalidation
            initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT
            assertNotCompiled();

            // 4. Recompilation w/o a dependency
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }});
            assertCompiled(); // no inlining
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }

    public static class ThreeLevelDefaultHierarchy1 extends ATest<ThreeLevelDefaultHierarchy1.I> {
        public ThreeLevelDefaultHierarchy1() { super(I.class, C.class); }

        interface J1                { Object m();}
        interface J2 extends J1     { default Object m() { return WRONG; }  }
        interface I  extends J1, J2 {}

        static class C  implements I { public Object m() { return CORRECT; }}

        interface K1 extends I {}
        interface K2 extends I { Object m(); }
        interface K3 extends I { default Object m() { return WRONG; }}

        static class DI implements I { public Object m() { return WRONG; }}
        static class DJ1 implements J1 { public Object m() { return WRONG; }}
        static class DJ2 implements J2 { public Object m() { return WRONG; }}

        @DontInline
        public Object test(I i) {
            return i.m();
        }

        @TestCase
        public void testMega() {
            // 0. Trigger compilation of a megamorphic call site
            compile(megamorphic());
            assertCompiled();

            // Dependency: type = unique_concrete_method, context = I, method = C.m

            checkInvalidReceiver(); // ensure proper type check on receiver is preserved

            // 1. No deoptimization/invalidation on not-yet-seen receiver
            repeat(100, () -> call(new C() {}));
            assertCompiled();

            // 2. No dependency invalidation
            initialize(DJ1.class,
                       DJ2.class,
                       K1.class,
                       K2.class,
                       K3.class);
            assertCompiled();

            // 3. Dependency invalidation
            initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT
            assertNotCompiled();

            // 4. Recompilation w/o a dependency
            compile(megamorphic());
            call(new C() { public Object m() { return CORRECT; }});
            assertCompiled(); // no inlining
        }

        @Override
        public void checkInvalidReceiver() {
            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
                test(o);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J1() { public Object m() { return WRONG; } }); // super interface
                test(j);
            });
            assertCompiled();

            shouldThrow(IncompatibleClassChangeError.class, () -> {
                I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J2() {}); // super interface
                test(j);
            });
            assertCompiled();
        }
    }
}