/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

public class Main {

  /*
   * Ensure an inlined static invoke explicitly triggers the
   * initialization check of the called method's declaring class, and
   * that the corresponding load class instruction does not get
   * removed before register allocation & code generation.
   */

  // CHECK-START: void Main.invokeStaticInlined() builder (after)
  // CHECK-DAG:     [[LoadClass:l\d+]]    LoadClass
  // CHECK-DAG:     [[ClinitCheck:l\d+]]  ClinitCheck [ [[LoadClass]] ]
  // CHECK-DAG:                           InvokeStaticOrDirect [ [[ClinitCheck]] ]

  // CHECK-START: void Main.invokeStaticInlined() inliner (after)
  // CHECK-DAG:     [[LoadClass:l\d+]]    LoadClass
  // CHECK-DAG:     [[ClinitCheck:l\d+]]  ClinitCheck [ [[LoadClass]] ]

  // CHECK-START: void Main.invokeStaticInlined() inliner (after)
  // CHECK-NOT:                           InvokeStaticOrDirect

  // The following checks ensure the clinit check instruction added by
  // the builder is pruned by the PrepareForRegisterAllocation, while
  // the load class instruction is preserved.  As the control flow
  // graph is not dumped after (nor before) this step, we check the
  // CFG as it is before the next pass (liveness analysis) instead.

  // CHECK-START: void Main.invokeStaticInlined() liveness (before)
  // CHECK-DAG:                           LoadClass

  // CHECK-START: void Main.invokeStaticInlined() liveness (before)
  // CHECK-NOT:                           ClinitCheck
  // CHECK-NOT:                           InvokeStaticOrDirect

  static void invokeStaticInlined() {
    ClassWithClinit1.$opt$inline$StaticMethod();
  }

  static class ClassWithClinit1 {
    static {
      System.out.println("Main$ClassWithClinit1's static initializer");
    }

    static void $opt$inline$StaticMethod() {
    }
  }

  /*
   * Ensure a non-inlined static invoke eventually has an implicit
   * initialization check of the called method's declaring class.
   */

  // CHECK-START: void Main.invokeStaticNotInlined() builder (after)
  // CHECK-DAG:     [[LoadClass:l\d+]]    LoadClass
  // CHECK-DAG:     [[ClinitCheck:l\d+]]  ClinitCheck [ [[LoadClass]] ]
  // CHECK-DAG:                           InvokeStaticOrDirect [ [[ClinitCheck]] ]

  // CHECK-START: void Main.invokeStaticNotInlined() inliner (after)
  // CHECK-DAG:     [[LoadClass:l\d+]]    LoadClass
  // CHECK-DAG:     [[ClinitCheck:l\d+]]  ClinitCheck [ [[LoadClass]] ]
  // CHECK-DAG:                           InvokeStaticOrDirect [ [[ClinitCheck]] ]

  // The following checks ensure the clinit check and load class
  // instructions added by the builder are pruned by the
  // PrepareForRegisterAllocation.  As the control flow graph is not
  // dumped after (nor before) this step, we check the CFG as it is
  // before the next pass (liveness analysis) instead.

  // CHECK-START: void Main.invokeStaticNotInlined() liveness (before)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main.invokeStaticNotInlined() liveness (before)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  static void invokeStaticNotInlined() {
    ClassWithClinit2.staticMethod();
  }

  static class ClassWithClinit2 {
    static {
      System.out.println("Main$ClassWithClinit2's static initializer");
    }

    static boolean doThrow = false;

    static void staticMethod() {
      if (doThrow) {
        // Try defeating inlining.
        throw new Error();
      }
    }
  }

  /*
   * Ensure an inlined call to a static method whose declaring class
   * is statically known to have been initialized does not require an
   * explicit clinit check.
   */

  // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() inliner (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck
  // CHECK-NOT:                           InvokeStaticOrDirect

  static class ClassWithClinit3 {
    static void invokeStaticInlined() {
      // The invocation of invokeStaticInlined triggers the
      // initialization of ClassWithClinit3, meaning that the
      // hereinbelow call to $opt$inline$StaticMethod does not need a
      // clinit check.
      $opt$inline$StaticMethod();
    }

    static {
      System.out.println("Main$ClassWithClinit3's static initializer");
    }

    static void $opt$inline$StaticMethod() {
    }
  }

  /*
   * Ensure an non-inlined call to a static method whose declaring
   * class is statically known to have been initialized does not
   * require an explicit clinit check.
   */

  // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  static class ClassWithClinit4 {
    static void invokeStaticNotInlined() {
      // The invocation of invokeStaticNotInlined triggers the
      // initialization of ClassWithClinit4, meaning that the
      // hereinbelow call to staticMethod does not need a clinit
      // check.
      staticMethod();
    }

    static {
      System.out.println("Main$ClassWithClinit4's static initializer");
    }

    static boolean doThrow = false;

    static void staticMethod() {
      if (doThrow) {
        // Try defeating inlining.
        throw new Error();
      }
    }
  }

  /*
   * Ensure an inlined call to a static method whose declaring class
   * is a super class of the caller's class does not require an
   * explicit clinit check.
   */

  // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() inliner (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck
  // CHECK-NOT:                           InvokeStaticOrDirect

  static class ClassWithClinit5 {
    static void $opt$inline$StaticMethod() {
    }

    static {
      System.out.println("Main$ClassWithClinit5's static initializer");
    }
  }

  static class SubClassOfClassWithClinit5 extends ClassWithClinit5 {
    static void invokeStaticInlined() {
      ClassWithClinit5.$opt$inline$StaticMethod();
    }
  }

  /*
   * Ensure an non-inlined call to a static method whose declaring
   * class is a super class of the caller's class does not require an
   * explicit clinit check.
   */

  // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after)
  // CHECK-DAG:                           InvokeStaticOrDirect

  // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after)
  // CHECK-NOT:                           LoadClass
  // CHECK-NOT:                           ClinitCheck

  static class ClassWithClinit6 {
    static boolean doThrow = false;

    static void staticMethod() {
      if (doThrow) {
        // Try defeating inlining.
        throw new Error();
      }
    }

    static {
      System.out.println("Main$ClassWithClinit6's static initializer");
    }
  }

  static class SubClassOfClassWithClinit6 extends ClassWithClinit6 {
    static void invokeStaticNotInlined() {
      ClassWithClinit6.staticMethod();
    }
  }

  // TODO: Add a test for the case of a static method whose declaring
  // class type index is not available (i.e. when `storage_index`
  // equals `DexFile::kDexNoIndex` in
  // art::HGraphBuilder::BuildInvoke).

  public static void main(String[] args) {
    invokeStaticInlined();
    invokeStaticNotInlined();
    ClassWithClinit3.invokeStaticInlined();
    ClassWithClinit4.invokeStaticNotInlined();
    SubClassOfClassWithClinit5.invokeStaticInlined();
    SubClassOfClassWithClinit6.invokeStaticNotInlined();
  }
}