/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.runtime;

import org.jikesrvm.ArchitectureSpecific.CodeArray;
import org.jikesrvm.ArchitectureSpecific.DynamicLinkerHelper;
import org.jikesrvm.VM;
import org.jikesrvm.Constants;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.MethodReference;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.vmmagic.pragma.DynamicBridge;
import org.vmmagic.pragma.Entrypoint;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;

/**
 * Implement lazy compilation.
 */
@DynamicBridge
public class DynamicLinker implements Constants {

  /**
   * Resolve, compile if necessary, and invoke a method.
   *  Taken:    nothing (calling context is implicit)
   *  Returned: does not return (method dispatch table is updated and method is executed)
   */
  @Entrypoint
  static void lazyMethodInvoker() {
    DynamicLink dl = DL_Helper.resolveDynamicInvocation();
    RVMMethod targMethod = DL_Helper.resolveMethodRef(dl);
    DL_Helper.compileMethod(dl, targMethod);
    CodeArray code = targMethod.getCurrentEntryCodeArray();
    Magic.dynamicBridgeTo(code);                   // restore parameters and invoke
    if (VM.VerifyAssertions) VM._assert(NOT_REACHED);  // does not return here
  }

  /**
   * Report unimplemented native method error.
   *  Taken:    nothing (calling context is implicit)
   *  Returned: does not return (throws UnsatisfiedLinkError)
   */
  @Entrypoint
  static void unimplementedNativeMethod() {
    DynamicLink dl = DL_Helper.resolveDynamicInvocation();
    RVMMethod targMethod = DL_Helper.resolveMethodRef(dl);
    throw new UnsatisfiedLinkError(targMethod.toString());
  }

  /**
   * Report a magic SysCall has been mistakenly invoked
   */
  @Entrypoint
  static void sysCallMethod() {
    DynamicLink dl = DL_Helper.resolveDynamicInvocation();
    RVMMethod targMethod = DL_Helper.resolveMethodRef(dl);
    throw new UnsatisfiedLinkError(targMethod.toString() + " which is a SysCall");
  }

  /**
   * Helper class that does the real work of resolving method references
   * and compiling a lazy method invocation.  In separate class so
   * that it doesn't implement DynamicBridge magic.
   */
  private static class DL_Helper {

    /**
     * Discover method reference to be invoked via dynamic bridge.
     *
     * Taken:       nothing (call stack is examined to find invocation site)
     * Returned:    DynamicLink that describes call site.
     */
    @NoInline
    static DynamicLink resolveDynamicInvocation() {

      // find call site
      //
      VM.disableGC();
      Address callingFrame = Magic.getCallerFramePointer(Magic.getFramePointer());
      Address returnAddress = Magic.getReturnAddress(callingFrame);
      callingFrame = Magic.getCallerFramePointer(callingFrame);
      int callingCompiledMethodId = Magic.getCompiledMethodID(callingFrame);
      CompiledMethod callingCompiledMethod = CompiledMethods.getCompiledMethod(callingCompiledMethodId);
      Offset callingInstructionOffset = callingCompiledMethod.getInstructionOffset(returnAddress);
      VM.enableGC();

      // obtain symbolic method reference
      //
      DynamicLink dynamicLink = new DynamicLink();
      callingCompiledMethod.getDynamicLink(dynamicLink, callingInstructionOffset);

      return dynamicLink;
    }

    /**
     * Resolve method ref into appropriate RVMMethod
     *
     * Taken:       DynamicLink that describes call site.
     * Returned:    RVMMethod that should be invoked.
     */
    @NoInline
    static RVMMethod resolveMethodRef(DynamicLink dynamicLink) {
      // resolve symbolic method reference into actual method
      //
      MethodReference methodRef = dynamicLink.methodRef();
      if (dynamicLink.isInvokeSpecial()) {
        return methodRef.resolveInvokeSpecial();
      } else if (dynamicLink.isInvokeStatic()) {
        return methodRef.resolve();
      } else {
        // invokevirtual or invokeinterface
        VM.disableGC();
        Object targetObject = DynamicLinkerHelper.getReceiverObject();
        VM.enableGC();
        RVMClass targetClass = Magic.getObjectType(targetObject).asClass();
        RVMMethod targetMethod = targetClass.findVirtualMethod(methodRef.getName(), methodRef.getDescriptor());
        if (targetMethod == null) {
          throw new IncompatibleClassChangeError(targetClass.getDescriptor().classNameFromDescriptor());
        }
        return targetMethod;
      }
    }

    /**
     * Compile (if necessary) targetMethod and patch the appropriate disaptch tables
     * @param targetMethod the RVMMethod to compile (if not already compiled)
     */
    @NoInline
    static void compileMethod(DynamicLink dynamicLink, RVMMethod targetMethod) {

      RVMClass targetClass = targetMethod.getDeclaringClass();

      // if necessary, compile method
      //
      if (!targetMethod.isCompiled()) {
        targetMethod.compile();

        // If targetMethod is a virtual method, then eagerly patch tib of declaring class.
        // (we need to do this to get the method test used by opt to work with lazy compilation).
        if (!(targetMethod.isObjectInitializer() || targetMethod.isStatic())) {
          targetClass.updateTIBEntry(targetMethod);
        }
      }

      // patch appropriate dispatch table
      //
      if (targetMethod.isObjectInitializer() || targetMethod.isStatic()) {
        targetClass.updateJTOCEntry(targetMethod);
      } else if (dynamicLink.isInvokeSpecial()) {
        targetClass.updateTIBEntry(targetMethod);
      } else {
        VM.disableGC();
        Object targetObject = DynamicLinkerHelper.getReceiverObject();
        VM.enableGC();
        RVMClass recvClass = (RVMClass) Magic.getObjectType(targetObject);
        recvClass.updateTIBEntry(targetMethod);
      }
    }
  }
}
