/*
 *  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.classloader;

import org.jikesrvm.ArchitectureSpecific.CodeArray;
import org.jikesrvm.ArchitectureSpecific.InterfaceMethodConflictResolver;
import org.jikesrvm.VM;
import org.jikesrvm.SizeConstants;
import org.jikesrvm.mm.mminterface.MemoryManager;
import org.jikesrvm.objectmodel.IMT;
import org.jikesrvm.objectmodel.ITable;
import org.jikesrvm.objectmodel.ITableArray;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.objectmodel.TIB;
import org.jikesrvm.objectmodel.TIBLayoutConstants;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.RuntimeEntrypoints;
import org.vmmagic.pragma.Entrypoint;

/**
 * Runtime system mechanisms and data structures to implement interface invocation.
 *
 * We support two mechanisms:
 * <pre>
 *   IMT-based (Alpern, Cocchi, Fink, Grove, and Lieber OOPSLA'01).
 *   ITable-based (searched at dispatch time with 1 entry move-to-front cache(
  * </pre>
 */
public class InterfaceInvocation implements TIBLayoutConstants, SizeConstants {

  /*
   * PART I: runtime routines to implement the invokeinterface bytecode.
   *         these routines are called from the generated code
   *         as part of the interface invocation sequence.
   */

  /**
   * Resolve an interface method call.
   * This routine is never called by the IMT-based dispatching code.
   * It is only called for directly indexed ITables when the table
   * index was unknown at compile time (ie the target Interface was not loaded).
   *
   * @param target object to which interface method is to be applied
   * @param mid id of the MemberReference for the target interface method.
   * @return machine code corresponding to desired interface method
   */
  @Entrypoint
  public static CodeArray invokeInterface(Object target, int mid) throws IncompatibleClassChangeError {

    MethodReference mref = MemberReference.getMemberRef(mid).asMethodReference();
    RVMMethod sought = mref.resolveInterfaceMethod();
    RVMClass I = sought.getDeclaringClass();
    RVMClass C = Magic.getObjectType(target).asClass();
    if (VM.BuildForITableInterfaceInvocation) {
      TIB tib = C.getTypeInformationBlock();
      ITable iTable = findITable(tib, I.getInterfaceId());
      return iTable.getCode(getITableIndex(I, mref.getName(), mref.getDescriptor()));
    } else {
      if (!RuntimeEntrypoints.isAssignableWith(I, C)) throw new IncompatibleClassChangeError();
      RVMMethod found = C.findVirtualMethod(sought.getName(), sought.getDescriptor());
      if (found == null) throw new IncompatibleClassChangeError();
      return found.getCurrentEntryCodeArray();
    }
  }

  /**
   * Return a reference to the itable for a given class, interface pair
   * We might not have created the iTable yet, in which case we will do that and then return it.
   *
   * @param tib the TIB for the class
   * @param id interface id of the interface sought (NOT dictionary id!!)
   * @return iTable for desired interface
   */
  @Entrypoint
  public static ITable findITable(TIB tib, int id) throws IncompatibleClassChangeError {
    ITableArray iTables = tib.getITableArray();
    // Search for the right ITable
    RVMType I = RVMClass.getInterface(id);
    if (iTables != null) {
      // check the cache at slot 0
      ITable iTable = iTables.get(0);
      if (iTable.isFor(I)) {
        return iTable; // cache hit :)
      }

      // cache miss :(
      // Have to search the 'real' entries for the iTable
      for (int i = 1; i < iTables.length(); i++) {
        iTable = iTables.get(i);
        if (iTable.isFor(I)) {
          // found it; update cache
          iTables.set(0, iTable);
          return iTable;
        }
      }
    }

    // Didn't find the itable, so we don't yet know if
    // the class implements the interface. :(((
    // Therefore, we need to establish that and then
    // look for the iTable again.
    RVMClass C = (RVMClass) tib.getType();
    if (!RuntimeEntrypoints.isAssignableWith(I, C)) throw new IncompatibleClassChangeError();
    synchronized (C) {
      installITable(C, (RVMClass) I);
    }
    ITable iTable = findITable(tib, id);
    if (VM.VerifyAssertions) VM._assert(iTable != null);
    return iTable;
  }

  /**
   * <code>mid</code> is the dictionary id of an interface method we are trying to invoke
   * <code>RHStib</code> is the TIB of an object on which we are attempting to invoke it.
   *
   * We were unable to resolve the member reference at compile time.
   * Therefore we must resolve it now and then call invokeinterfaceImplementsTest
   * with the right LHSclass.
   *
   * @param mid     Dictionary id of the {@link MemberReference} for the target interface method.
   * @param rhsObject  The object on which we are attempting to invoke the interface method
   */
  @Entrypoint
  public static void unresolvedInvokeinterfaceImplementsTest(int mid, Object rhsObject)
      throws IncompatibleClassChangeError {
    RVMMethod sought = MemberReference.getMemberRef(mid).asMethodReference().resolveInterfaceMethod();
    RVMClass LHSclass = sought.getDeclaringClass();
    if (!LHSclass.isResolved()) {
      LHSclass.resolve();
    }
    /* If the object is not null, ensure that it implements the interface.
     * If it is null, then we return to our caller and let them raise the
     * null pointer exception when they attempt to get the object's TIB so
     * they can actually make the interface call.
     */
    if (rhsObject != null) {
      TIB RHStib = ObjectModel.getTIB(rhsObject);
      if (LHSclass.isInterface() && DynamicTypeCheck.instanceOfInterface(LHSclass, RHStib)) return;
      // Raise an IncompatibleClassChangeError.
      throw new IncompatibleClassChangeError();
    }
  }

  /*
  * PART II: Code to initialize the interface dispatching data structures.
  *          Called during the instantiate step of class loading.
  *          Preconditions:
  *            (1) the caller has the lock on the RVMClass object
  *                whose data structures and being initialized.
  *            (2) the VMT for the class contains valid code.
  */

  /**
   * Main entrypoint called from RVMClass.instantiate to
   * initialize the interface dispatching data structures for
   * the given class.
   *
   * @param klass the RVMClass to initialize the dispatch structures for.
   */
  public static void initializeDispatchStructures(RVMClass klass) {
    // if klass is abstract, we'll never use the dispatching structures.
    if (klass.isAbstract()) return;
    RVMClass[] interfaces = klass.getAllImplementedInterfaces();
    if (interfaces.length != 0) {
      if (VM.BuildForIMTInterfaceInvocation) {
        IMTDict d = buildIMTDict(klass, interfaces);
        populateIMT(klass, d);
      }
    }
  }

  /**
   * Build up a description of the IMT contents for the given class.
   * NOTE: this structure is only used during class loading, so
   *       we don't have to worry about making it space efficient.
   *
   * @param klass the RVMClass whose IMT we are going to build.
   * @return an IMTDict that describes the IMT we need to build for the class.
   */
  private static IMTDict buildIMTDict(RVMClass klass, RVMClass[] interfaces) {
    IMTDict d = new IMTDict(klass);
    for (RVMClass i : interfaces) {
      RVMMethod[] interfaceMethods = i.getDeclaredMethods();
      for (RVMMethod im : interfaceMethods) {
        if (im.isClassInitializer()) continue;
        if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract());
        InterfaceMethodSignature sig = InterfaceMethodSignature.findOrCreate(im.getMemberRef());
        RVMMethod vm = klass.findVirtualMethod(im.getName(), im.getDescriptor());
        // NOTE: if there is some error condition, then we are playing a dirty trick and
        //       pretending that a static method of RuntimeEntrypoints is a virtual method.
        //       Since the methods in question take no arguments, we can get away with this.
        if (vm == null || vm.isAbstract()) {
          vm = Entrypoints.raiseAbstractMethodError;
        } else if (!vm.isPublic()) {
          vm = Entrypoints.raiseIllegalAccessError;
        }
        d.addElement(sig, vm);
      }
    }
    return d;
  }

  /**
   * Populate an indirect IMT for C using the IMTDict d
   */
  private static void populateIMT(RVMClass klass, IMTDict d) {
    TIB tib = klass.getTypeInformationBlock();
    IMT IMT = MemoryManager.newIMT();
    klass.setIMT(IMT);
    d.populateIMT(klass, tib, IMT);
    tib.setImt(IMT);
  }

  /**
   * Build and install an iTable for the given class interface pair
   * (used for iTable miss on searched iTables).
   */
  private static void installITable(RVMClass C, RVMClass I) {
    TIB tib = C.getTypeInformationBlock();
    ITableArray iTables = tib.getITableArray();

    if (iTables == null) {
      iTables = MemoryManager.newITableArray(2);
      tib.setITableArray(iTables);
    } else {
      for(int i=0; i < iTables.length(); i++) {
        if (iTables.get(i).isFor(I)) {
          return; // some other thread just built the iTable
        }
      }
      ITableArray tmp = MemoryManager.newITableArray(iTables.length() + 1);
      for(int i=0; i < iTables.length(); i++) {
        tmp.set(i, iTables.get(i));
      }
      iTables = tmp;
      tib.setITableArray(iTables);
    }
    if (VM.VerifyAssertions) VM._assert(iTables.get(iTables.length() - 1) == null);
    ITable iTable = buildITable(C, I);
    iTables.set(iTables.length() - 1, iTable);
    // iTables[0] is a move to front cache; fill it here so we can
    // assume it always contains some iTable.
    iTables.set(0, iTable);
  }

  /**
   * Build a single ITable for the pair of class C and interface I
   */
  private static ITable buildITable(RVMClass C, RVMClass I) {
    RVMMethod[] interfaceMethods = I.getDeclaredMethods();
    TIB tib = C.getTypeInformationBlock();
    ITable iTable = MemoryManager.newITable(interfaceMethods.length + 1);
    iTable.set(0, I);
    for (RVMMethod im : interfaceMethods) {
      if (im.isClassInitializer()) continue;
      if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract());
      RVMMethod vm = C.findVirtualMethod(im.getName(), im.getDescriptor());
      // NOTE: if there is some error condition, then we are playing a dirty trick and
      //       pretending that a static method of RuntimeEntrypoints is a virtual method.
      //       Since the methods in question take no arguments, we can get away with this.
      if (vm == null || vm.isAbstract()) {
        vm = Entrypoints.raiseAbstractMethodError;
      } else if (!vm.isPublic()) {
        vm = Entrypoints.raiseIllegalAccessError;
      }
      if (vm.isStatic()) {
        vm.compile();
        iTable.set(getITableIndex(I, im.getName(), im.getDescriptor()), vm.getCurrentEntryCodeArray());
      } else {
        iTable.set(getITableIndex(I, im.getName(), im.getDescriptor()), tib.getVirtualMethod(vm.getOffset()));
      }
    }
    return iTable;
  }

  /*
  * PART III: Supporting low-level code for manipulating IMTs and ITables
  */

  /**
   * Return the index of the interface method m in the itable
   */
  public static int getITableIndex(RVMClass klass, Atom mname, Atom mdesc) {
    if (VM.VerifyAssertions) VM._assert(VM.BuildForITableInterfaceInvocation);
    if (VM.VerifyAssertions) VM._assert(klass.isInterface());
    RVMMethod[] methods = klass.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
      if (methods[i].getName() == mname && methods[i].getDescriptor() == mdesc) {
        return i + 1;
      }
    }
    return -1;
  }

  /**
   * If there is an an IMT or ITable entry that contains
   * compiled code for the argument method, then update it to
   * contain the current compiled code for the method.
   *
   * @param klass the RVMClass who's IMT/ITable is being reset
   * @param m the method that needs to be updated.
   */
  public static void updateTIBEntry(RVMClass klass, RVMMethod m) {
    TIB tib = klass.getTypeInformationBlock();
    if (VM.BuildForIMTInterfaceInvocation) {
      RVMMethod[] map = klass.noIMTConflictMap;
      if (map != null) {
        for (int i = 0; i < IMT_METHOD_SLOTS; i++) {
          if (map[i] == m) {
            IMT imt = tib.getImt();
            imt.set(i, m.getCurrentEntryCodeArray());
            return; // all done -- a method is in at most 1 IMT slot
          }
        }
      }
    } else if (VM.BuildForITableInterfaceInvocation) {
      if (tib.getITableArray() != null) {
        ITableArray iTables = tib.getITableArray();
        Atom name = m.getName();
        Atom desc = m.getDescriptor();
        for (int i=0; i< iTables.length(); i++) {
          ITable iTable = iTables.get(i);
          if (iTable != null) {
            RVMClass I = iTable.getInterfaceClass();
            RVMMethod[] interfaceMethods = I.getDeclaredMethods();
            for (RVMMethod im : interfaceMethods) {
              if (im.getName() == name && im.getDescriptor() == desc) {
                iTable.set(getITableIndex(I, name, desc), m.getCurrentEntryCodeArray());
              }
            }
          }
        }
      }
    }
  }

  /*
   * Helper class used for IMT construction
   */
  private static final class IMTDict {
    private final RVMClass klass;
    private final Link[] links;

    IMTDict(RVMClass c) {
      klass = c;
      links = new Link[IMT_METHOD_SLOTS];
    }

    // Convert from the internally visible IMTOffset to an index
    // into my internal data structure.
    private int getIndex(InterfaceMethodSignature sig) {
      int idx = sig.getIMTOffset().toInt() >> LOG_BYTES_IN_ADDRESS;
      return idx;
    }

    // count the number of signatures in the given IMT slot
    private int populationCount(int index) {
      Link p = links[index];
      int count = 0;
      while (p != null) {
        count++;
        p = p.next;
      }
      return count;
    }

    private RVMMethod getSoleTarget(int index) {
      if (VM.VerifyAssertions) VM._assert(populationCount(index) == 1);
      return links[index].method;
    }

    // Add an element to the IMT dictionary (does nothing if already there)
    public void addElement(InterfaceMethodSignature sig, RVMMethod m) {
      int index = getIndex(sig);
      Link p = links[index];
      if (p == null || p.signature.getId() > sig.getId()) {
        links[index] = new Link(sig, m, p);
      } else {
        Link q = p;
        while (p != null && p.signature.getId() <= sig.getId()) {
          if (p.signature.getId() == sig.getId()) return; // already there so nothing to do.
          q = p;
          p = p.next;
        }
        q.next = new Link(sig, m, p);
      }
    }

    // populate the
    public void populateIMT(RVMClass klass, TIB tib, IMT imt) {
      for (int slot = 0; slot < links.length; slot++) {
        int count = populationCount(slot);
        if (count == 0) {
          Entrypoints.raiseAbstractMethodError.compile();
          set(tib, imt, slot, Entrypoints.raiseAbstractMethodError.getCurrentEntryCodeArray());
        } else if (count == 1) {
          RVMMethod target = getSoleTarget(slot);
          if (target.isStatic()) {
            target.compile();
            set(tib, imt, slot, target.getCurrentEntryCodeArray());
          } else {
            set(tib, imt, slot, tib.getVirtualMethod(target.getOffset()));
            if (klass.noIMTConflictMap == null) {
              klass.noIMTConflictMap = new RVMMethod[IMT_METHOD_SLOTS];
            }
            klass.noIMTConflictMap[slot] = target;
          }
        } else {
          RVMMethod[] targets = new RVMMethod[count];
          int[] sigIds = new int[count];
          int idx = 0;
          for (Link p = links[slot]; p != null; idx++, p = p.next) {
            targets[idx] = p.method;
            sigIds[idx] = p.signature.getId();
          }
          CodeArray conflictResolutionStub = InterfaceMethodConflictResolver.createStub(sigIds, targets);
          klass.addCachedObject(Magic.codeArrayAsObject(conflictResolutionStub));
          set(tib, imt, slot, conflictResolutionStub);
        }
      }
    }

    private void set(TIB tib, IMT imt, int extSlot, CodeArray value) {
      imt.set(extSlot, value);
    }

    private static final class Link {
      final InterfaceMethodSignature signature;
      final RVMMethod method;
      Link next;

      Link(InterfaceMethodSignature sig, RVMMethod m, Link n) {
        signature = sig;
        method = m;
        next = n;
      }
    }
  }
}
