/* * 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.VM_CodeArray; import org.jikesrvm.ArchitectureSpecific.VM_InterfaceMethodConflictResolver; import org.jikesrvm.ArchitectureSpecific; import org.jikesrvm.VM; import org.jikesrvm.VM_SizeConstants; import org.jikesrvm.objectmodel.VM_TIBLayoutConstants; import org.jikesrvm.runtime.VM_Entrypoints; import org.jikesrvm.runtime.VM_Magic; import org.jikesrvm.runtime.VM_Runtime; import org.vmmagic.pragma.Entrypoint; // TODO !! Sort out interface invocation on subarch /** * Runtime system mechanisms and data structures to implement interface invocation. * * We support five mechanisms: * <pre> * IMT-based (Alpern, Cocchi, Fink, Grove, and Lieber OOPSLA'01). * - embedded directly in the TIB * - indirectly accessed off the TIB * ITable-based * - directly indexed (by interface id) iTables. * - searched (at dispatch time) * Naive, class object is searched for matching method on every dispatch. * </pre> */ public class VM_InterfaceInvocation implements VM_TIBLayoutConstants, VM_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 VM_MemberReference for the target interface method. * @return machine code corresponding to desired interface method */ @Entrypoint public static VM_CodeArray invokeInterface(Object target, int mid) throws IncompatibleClassChangeError { VM_MethodReference mref = VM_MemberReference.getMemberRef(mid).asMethodReference(); VM_Method sought = mref.resolveInterfaceMethod(false); VM_Class I = sought.getDeclaringClass(); VM_Class C = VM_Magic.getObjectType(target).asClass(); if (VM.BuildForITableInterfaceInvocation) { Object[] tib = C.getTypeInformationBlock(); Object[] iTable = findITable(tib, I.getInterfaceId()); return (VM_CodeArray) iTable[getITableIndex(I, mref.getName(), mref.getDescriptor())]; } else { if (!VM_Runtime.isAssignableWith(I, C)) throw new IncompatibleClassChangeError(); VM_Method found = C.findVirtualMethod(sought.getName(), sought.getDescriptor()); if (found == null) throw new IncompatibleClassChangeError(); return (ArchitectureSpecific.VM_CodeArray) found.getCurrentEntryCodeArray(false); } } /** * Return a reference to the itable for a given class, interface pair * Under searched iTables, 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 Object[] findITable(Object[] tib, int id) throws IncompatibleClassChangeError { Object[] iTables = (Object[]) tib[TIB_ITABLES_TIB_INDEX]; if (VM.DirectlyIndexedITables) { // ITable is at fixed offset return (Object[]) iTables[id]; } else { // Search for the right ITable VM_Type I = VM_Class.getInterface(id); if (iTables != null) { // check the cache at slot 0 Object[] iTable = (Object[]) iTables[0]; if (iTable[0] == 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 = (Object[]) iTables[i]; if (iTable[0] == I) { // found it; update cache iTables[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. VM_Class C = (VM_Class) tib[0]; if (!VM_Runtime.isAssignableWith(I, C)) throw new IncompatibleClassChangeError(); synchronized (C) { installITable(C, (VM_Class) I); } Object[] iTable = findITable(tib, id); if (VM.VerifyAssertions) VM._assert(iTable != null); return iTable; } } /** * LHSclass is an interface that RHS class must implement. * Raises an IncompatibleClassChangeError if RHStib does not * implement LHSclass. * * @param LHSclass an class (should be an interface) * @param RHStib the TIB of an object that must implement LHSclass */ @Entrypoint public static void invokeinterfaceImplementsTest(VM_Class LHSclass, Object[] RHStib) throws IncompatibleClassChangeError { if (!LHSclass.isResolved(false)) { LHSclass.resolve(false); } if (LHSclass.isInterface() && VM_DynamicTypeCheck.instanceOfInterface(LHSclass, RHStib)) return; // Raise an IncompatibleClassChangeError. throw new IncompatibleClassChangeError(); } /** * <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 VM_MemberReference} for the * target interface method. * @param RHStib The TIB of the object on which we are attempting to * invoke the interface method */ @Entrypoint public static void unresolvedInvokeinterfaceImplementsTest(int mid, Object[] RHStib) throws IncompatibleClassChangeError { VM_Method sought = VM_MemberReference.getMemberRef(mid).asMethodReference().resolveInterfaceMethod(false); invokeinterfaceImplementsTest(sought.getDeclaringClass(), RHStib); } /* * 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 VM_Class object * whose data structures and being initialized. * (2) the VMT for the class contains valid code. */ /** * Main entrypoint called from VM_Class.instantiate to * initialize the interface dispatching data structues for * the given class. * * @param klass the VM_Class to initialize the disaptch structures for. */ public static void initializeDispatchStructures(VM_Class klass) { // if klass is abstract, we'll never use the dispatching structures. if (klass.isAbstract()) return; VM_Class[] interfaces = klass.getAllImplementedInterfaces(); if (interfaces.length != 0) { if (VM.BuildForIMTInterfaceInvocation) { IMTDict d = buildIMTDict(klass, interfaces); if (VM.BuildForEmbeddedIMT) { populateEmbeddedIMT(klass, d); } else { populateIndirectIMT(klass, d); } } else if (VM.DirectlyIndexedITables) { populateITables(klass, interfaces); } } } /** * 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 VM_Class 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(VM_Class klass, VM_Class[] interfaces) { IMTDict d = new IMTDict(klass); for (VM_Class i : interfaces) { VM_Method[] interfaceMethods = i.getDeclaredMethods(); for (VM_Method im : interfaceMethods) { if (im.isClassInitializer()) continue; if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract()); VM_InterfaceMethodSignature sig = VM_InterfaceMethodSignature.findOrCreate(im.getMemberRef()); VM_Method 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 VM_Runtime is a virtual method. // Since the methods in question take no arguments, we can get away with this. if (vm == null || vm.isAbstract()) { vm = VM_Entrypoints.raiseAbstractMethodError; } else if (!vm.isPublic()) { vm = VM_Entrypoints.raiseIllegalAccessError; } d.addElement(sig, vm); } } return d; } /** * Populate an embedded IMT for C using the IMTDict d */ private static void populateEmbeddedIMT(VM_Class klass, IMTDict d) { Object[] tib = klass.getTypeInformationBlock(); d.populateIMT(tib, tib); } /** * Populate an indirect IMT for C using the IMTDict d */ private static void populateIndirectIMT(VM_Class klass, IMTDict d) { Object[] tib = klass.getTypeInformationBlock(); VM_CodeArray[] IMT = new VM_CodeArray[IMT_METHOD_SLOTS]; d.populateIMT(tib, IMT); tib[TIB_IMT_TIB_INDEX] = IMT; } /** * Populate the ITables array for DirectlyIndexedITables */ private static void populateITables(VM_Class klass, VM_Class[] interfaces) { int maxId = 0; for (VM_Class i : interfaces) { int cur = i.getInterfaceId(); if (cur > maxId) maxId = cur; } Object[][] iTables = new Object[maxId + 1][]; for (VM_Class interf : interfaces) { iTables[interf.getInterfaceId()] = buildITable(klass, interf); } Object[] tib = klass.getTypeInformationBlock(); tib[TIB_ITABLES_TIB_INDEX] = iTables; } /** * Build and install an iTable for the given class interface pair * (used for iTable miss on searched iTables). */ private static void installITable(VM_Class C, VM_Class I) { Object[] tib = C.getTypeInformationBlock(); Object[] iTables = (Object[]) tib[TIB_ITABLES_TIB_INDEX]; if (iTables == null) { iTables = new Object[2]; tib[TIB_ITABLES_TIB_INDEX] = iTables; } else { for (Object iTable : iTables) { if (((Object[]) iTable)[0] == I) { return; // some other thread just built the iTable } } Object[] tmp = new Object[iTables.length + 1]; System.arraycopy(iTables, 0, tmp, 0, iTables.length); iTables = tmp; tib[TIB_ITABLES_TIB_INDEX] = iTables; } if (VM.VerifyAssertions) VM._assert(iTables[iTables.length - 1] == null); Object[] iTable = buildITable(C, I); iTables[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[0] = iTable; } /** * Build a single ITable for the pair of class C and interface I */ private static Object[] buildITable(VM_Class C, VM_Class I) { VM_Method[] interfaceMethods = I.getDeclaredMethods(); Object[] tib = C.getTypeInformationBlock(); Object[] iTable = new Object[interfaceMethods.length + 1]; iTable[0] = I; for (VM_Method im : interfaceMethods) { if (im.isClassInitializer()) continue; if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract()); VM_Method 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 VM_Runtime is a virtual method. // Since the methods in question take no arguments, we can get away with this. if (vm == null || vm.isAbstract()) { vm = VM_Entrypoints.raiseAbstractMethodError; } else if (!vm.isPublic()) { vm = VM_Entrypoints.raiseIllegalAccessError; } if (vm.isStatic()) { vm.compile(false); iTable[getITableIndex(I, im.getName(), im.getDescriptor())] = vm.getCurrentEntryCodeArray(false); } else { iTable[getITableIndex(I, im.getName(), im.getDescriptor())] = tib[vm.getOffset().toInt() >> LOG_BYTES_IN_ADDRESS]; } } 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(VM_Class klass, VM_Atom mname, VM_Atom mdesc) { if (VM.VerifyAssertions) VM._assert(VM.BuildForITableInterfaceInvocation); if (VM.VerifyAssertions) VM._assert(klass.isInterface()); VM_Method[] 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 VM_Class who's IMT/ITable is being reset * @param m the method that needs to be updated. */ public static void updateTIBEntry(VM_Class klass, VM_Method m, boolean forSubArch) { // TODO - Fix for Subarch VM._assert(!forSubArch); Object[] tib = klass.getTypeInformationBlock(); if (VM.BuildForIMTInterfaceInvocation) { VM_Method[] map = klass.noIMTConflictMap; if (map != null) { for (int i = 0; i < IMT_METHOD_SLOTS; i++) { if (map[i] == m) { if (VM.BuildForIndirectIMT) { VM_CodeArray[] IMT = (VM_CodeArray[]) tib[TIB_IMT_TIB_INDEX]; IMT[i] = (ArchitectureSpecific.VM_CodeArray) m.getCurrentEntryCodeArray(false); } else { tib[i + TIB_FIRST_INTERFACE_METHOD_INDEX] = m.getCurrentEntryCodeArray(false); } return; // all done -- a method is in at most 1 IMT slot } } } } else if (VM.BuildForITableInterfaceInvocation) { if (tib[TIB_ITABLES_TIB_INDEX] != null) { Object[] iTables = (Object[]) tib[TIB_ITABLES_TIB_INDEX]; VM_Atom name = m.getName(); VM_Atom desc = m.getDescriptor(); for (Object element : iTables) { Object[] iTable = (Object[]) element; if (iTable != null) { VM_Class I = (VM_Class) iTable[0]; VM_Method[] interfaceMethods = I.getDeclaredMethods(); for (VM_Method im : interfaceMethods) { if (im.getName() == name && im.getDescriptor() == desc) { iTable[getITableIndex(I, name, desc)] = m.getCurrentEntryCodeArray(false); } } } } } } } /* * Helper class used for IMT construction */ private static final class IMTDict { private VM_Class klass; private Link[] links; IMTDict(VM_Class 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(VM_InterfaceMethodSignature sig) { int idx = sig.getIMTOffset().toInt() >> LOG_BYTES_IN_ADDRESS; if (VM.BuildForEmbeddedIMT) { idx -= TIB_FIRST_INTERFACE_METHOD_INDEX; } 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 VM_Method 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(VM_InterfaceMethodSignature sig, VM_Method 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(Object[] tib, Object[] IMT) { int adjust = VM.BuildForEmbeddedIMT ? TIB_FIRST_INTERFACE_METHOD_INDEX : 0; for (int slot = 0; slot < links.length; slot++) { int extSlot = slot + adjust; int count = populationCount(slot); if (count == 0) { VM_Entrypoints.raiseAbstractMethodError.compile(false); IMT[extSlot] = VM_Entrypoints.raiseAbstractMethodError.getCurrentEntryCodeArray(false); } else if (count == 1) { VM_Method target = getSoleTarget(slot); if (target.isStatic()) { target.compile(false); IMT[extSlot] = target.getCurrentEntryCodeArray(false); } else { IMT[extSlot] = tib[target.getOffset().toInt() >> LOG_BYTES_IN_ADDRESS]; if (klass.noIMTConflictMap == null) { klass.noIMTConflictMap = new VM_Method[IMT_METHOD_SLOTS]; } klass.noIMTConflictMap[slot] = target; } } else { VM_Method[] targets = new VM_Method[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(); } IMT[extSlot] = VM_InterfaceMethodConflictResolver.createStub(sigIds, targets); } } } private static class Link { final VM_InterfaceMethodSignature signature; final VM_Method method; Link next; Link(VM_InterfaceMethodSignature sig, VM_Method m, Link n) { signature = sig; method = m; next = n; } } } }