001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.classloader;
014    
015    import org.jikesrvm.ArchitectureSpecific.CodeArray;
016    import org.jikesrvm.ArchitectureSpecific.InterfaceMethodConflictResolver;
017    import org.jikesrvm.VM;
018    import org.jikesrvm.SizeConstants;
019    import org.jikesrvm.mm.mminterface.MemoryManager;
020    import org.jikesrvm.objectmodel.IMT;
021    import org.jikesrvm.objectmodel.ITable;
022    import org.jikesrvm.objectmodel.ITableArray;
023    import org.jikesrvm.objectmodel.ObjectModel;
024    import org.jikesrvm.objectmodel.TIB;
025    import org.jikesrvm.objectmodel.TIBLayoutConstants;
026    import org.jikesrvm.runtime.Entrypoints;
027    import org.jikesrvm.runtime.Magic;
028    import org.jikesrvm.runtime.RuntimeEntrypoints;
029    import org.vmmagic.pragma.Entrypoint;
030    
031    /**
032     * Runtime system mechanisms and data structures to implement interface invocation.
033     * <p>
034     * We support two mechanisms:
035     * <pre>
036     *   IMT-based (Alpern, Cocchi, Fink, Grove, and Lieber OOPSLA'01).
037     *   ITable-based (searched at dispatch time with 1 entry move-to-front cache)
038      * </pre>
039     */
040    public class InterfaceInvocation implements TIBLayoutConstants, SizeConstants {
041    
042      /*
043       * PART I: runtime routines to implement the invokeinterface bytecode.
044       *         these routines are called from the generated code
045       *         as part of the interface invocation sequence.
046       */
047    
048      /**
049       * Resolve an interface method call.
050       * This routine is never called by the IMT-based dispatching code.
051       * It is only called for directly indexed ITables when the table
052       * index was unknown at compile time (i.e. the target Interface was not loaded).
053       *
054       * @param target object to which interface method is to be applied
055       * @param mid id of the MemberReference for the target interface method.
056       * @return machine code corresponding to desired interface method
057       */
058      @Entrypoint
059      public static CodeArray invokeInterface(Object target, int mid) throws IncompatibleClassChangeError {
060    
061        MethodReference mref = MemberReference.getMemberRef(mid).asMethodReference();
062        RVMMethod sought = mref.resolveInterfaceMethod();
063        RVMClass I = sought.getDeclaringClass();
064        RVMClass C = Magic.getObjectType(target).asClass();
065        if (VM.BuildForITableInterfaceInvocation) {
066          TIB tib = C.getTypeInformationBlock();
067          ITable iTable = findITable(tib, I.getInterfaceId());
068          return iTable.getCode(getITableIndex(I, mref.getName(), mref.getDescriptor()));
069        } else {
070          if (!RuntimeEntrypoints.isAssignableWith(I, C)) throw new IncompatibleClassChangeError();
071          RVMMethod found = C.findVirtualMethod(sought.getName(), sought.getDescriptor());
072          if (found == null) throw new IncompatibleClassChangeError();
073          return found.getCurrentEntryCodeArray();
074        }
075      }
076    
077      /**
078       * Return a reference to the itable for a given class, interface pair
079       * We might not have created the iTable yet, in which case we will do that and then return it.
080       *
081       * @param tib the TIB for the class
082       * @param id interface id of the interface sought (NOT dictionary id!!)
083       * @return iTable for desired interface
084       */
085      @Entrypoint
086      public static ITable findITable(TIB tib, int id) throws IncompatibleClassChangeError {
087        ITableArray iTables = tib.getITableArray();
088        // Search for the right ITable
089        RVMType I = RVMClass.getInterface(id);
090        if (iTables != null) {
091          // check the cache at slot 0
092          ITable iTable = iTables.get(0);
093          if (iTable.isFor(I)) {
094            return iTable; // cache hit :)
095          }
096    
097          // cache miss :(
098          // Have to search the 'real' entries for the iTable
099          for (int i = 1; i < iTables.length(); i++) {
100            iTable = iTables.get(i);
101            if (iTable.isFor(I)) {
102              // found it; update cache
103              iTables.set(0, iTable);
104              return iTable;
105            }
106          }
107        }
108    
109        // Didn't find the itable, so we don't yet know if
110        // the class implements the interface. :(((
111        // Therefore, we need to establish that and then
112        // look for the iTable again.
113        RVMClass C = (RVMClass) tib.getType();
114        if (!RuntimeEntrypoints.isAssignableWith(I, C)) throw new IncompatibleClassChangeError();
115        synchronized (C) {
116          installITable(C, (RVMClass) I);
117        }
118        ITable iTable = findITable(tib, id);
119        if (VM.VerifyAssertions) VM._assert(iTable != null);
120        return iTable;
121      }
122    
123      /**
124       * <code>mid</code> is the dictionary id of an interface method we are trying to invoke
125       * <code>RHStib</code> is the TIB of an object on which we are attempting to invoke it.
126       *
127       * We were unable to resolve the member reference at compile time.
128       * Therefore we must resolve it now and then call invokeinterfaceImplementsTest
129       * with the right LHSclass.
130       *
131       * @param mid     Dictionary id of the {@link MemberReference} for the target interface method.
132       * @param rhsObject  The object on which we are attempting to invoke the interface method
133       */
134      @Entrypoint
135      public static void unresolvedInvokeinterfaceImplementsTest(int mid, Object rhsObject)
136          throws IncompatibleClassChangeError {
137        RVMMethod sought = MemberReference.getMemberRef(mid).asMethodReference().resolveInterfaceMethod();
138        RVMClass LHSclass = sought.getDeclaringClass();
139        if (!LHSclass.isResolved()) {
140          LHSclass.resolve();
141        }
142        /* If the object is not null, ensure that it implements the interface.
143         * If it is null, then we return to our caller and let them raise the
144         * null pointer exception when they attempt to get the object's TIB so
145         * they can actually make the interface call.
146         */
147        if (rhsObject != null) {
148          TIB RHStib = ObjectModel.getTIB(rhsObject);
149          if (LHSclass.isInterface() && DynamicTypeCheck.instanceOfInterface(LHSclass, RHStib)) return;
150          // Raise an IncompatibleClassChangeError.
151          throw new IncompatibleClassChangeError();
152        }
153      }
154    
155      /*
156      * PART II: Code to initialize the interface dispatching data structures.
157      *          Called during the instantiate step of class loading.
158      *          Preconditions:
159      *            (1) the caller has the lock on the RVMClass object
160      *                whose data structures and being initialized.
161      *            (2) the VMT for the class contains valid code.
162      */
163    
164      /**
165       * Main entrypoint called from RVMClass.instantiate to
166       * initialize the interface dispatching data structures for
167       * the given class.
168       *
169       * @param klass the RVMClass to initialize the dispatch structures for.
170       */
171      public static void initializeDispatchStructures(RVMClass klass) {
172        // if klass is abstract, we'll never use the dispatching structures.
173        if (klass.isAbstract()) return;
174        RVMClass[] interfaces = klass.getAllImplementedInterfaces();
175        if (interfaces.length != 0) {
176          if (VM.BuildForIMTInterfaceInvocation) {
177            IMTDict d = buildIMTDict(klass, interfaces);
178            populateIMT(klass, d);
179          }
180        }
181      }
182    
183      /**
184       * Build up a description of the IMT contents for the given class.
185       * NOTE: this structure is only used during class loading, so
186       *       we don't have to worry about making it space efficient.
187       *
188       * @param klass the RVMClass whose IMT we are going to build.
189       * @return an IMTDict that describes the IMT we need to build for the class.
190       */
191      private static IMTDict buildIMTDict(RVMClass klass, RVMClass[] interfaces) {
192        IMTDict d = new IMTDict(klass);
193        for (RVMClass i : interfaces) {
194          RVMMethod[] interfaceMethods = i.getDeclaredMethods();
195          for (RVMMethod im : interfaceMethods) {
196            if (im.isClassInitializer()) continue;
197            if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract());
198            InterfaceMethodSignature sig = InterfaceMethodSignature.findOrCreate(im.getMemberRef());
199            RVMMethod vm = klass.findVirtualMethod(im.getName(), im.getDescriptor());
200            // NOTE: if there is some error condition, then we are playing a dirty trick and
201            //       pretending that a static method of RuntimeEntrypoints is a virtual method.
202            //       Since the methods in question take no arguments, we can get away with this.
203            if (vm == null || vm.isAbstract()) {
204              vm = Entrypoints.raiseAbstractMethodError;
205            } else if (!vm.isPublic()) {
206              vm = Entrypoints.raiseIllegalAccessError;
207            }
208            d.addElement(sig, vm);
209          }
210        }
211        return d;
212      }
213    
214      /**
215       * Populate an indirect IMT for C using the IMTDict d
216       */
217      private static void populateIMT(RVMClass klass, IMTDict d) {
218        TIB tib = klass.getTypeInformationBlock();
219        IMT IMT = MemoryManager.newIMT();
220        klass.setIMT(IMT);
221        d.populateIMT(klass, tib, IMT);
222        tib.setImt(IMT);
223      }
224    
225      /**
226       * Build and install an iTable for the given class interface pair
227       * (used for iTable miss on searched iTables).
228       */
229      private static void installITable(RVMClass C, RVMClass I) {
230        TIB tib = C.getTypeInformationBlock();
231        ITableArray iTables = tib.getITableArray();
232    
233        if (iTables == null) {
234          iTables = MemoryManager.newITableArray(2);
235          tib.setITableArray(iTables);
236        } else {
237          for(int i=0; i < iTables.length(); i++) {
238            if (iTables.get(i).isFor(I)) {
239              return; // some other thread just built the iTable
240            }
241          }
242          ITableArray tmp = MemoryManager.newITableArray(iTables.length() + 1);
243          for(int i=0; i < iTables.length(); i++) {
244            tmp.set(i, iTables.get(i));
245          }
246          iTables = tmp;
247          tib.setITableArray(iTables);
248        }
249        if (VM.VerifyAssertions) VM._assert(iTables.get(iTables.length() - 1) == null);
250        ITable iTable = buildITable(C, I);
251        iTables.set(iTables.length() - 1, iTable);
252        // iTables[0] is a move to front cache; fill it here so we can
253        // assume it always contains some iTable.
254        iTables.set(0, iTable);
255      }
256    
257      /**
258       * Build a single ITable for the pair of class C and interface I
259       */
260      private static ITable buildITable(RVMClass C, RVMClass I) {
261        RVMMethod[] interfaceMethods = I.getDeclaredMethods();
262        TIB tib = C.getTypeInformationBlock();
263        ITable iTable = MemoryManager.newITable(interfaceMethods.length + 1);
264        iTable.set(0, I);
265        for (RVMMethod im : interfaceMethods) {
266          if (im.isClassInitializer()) continue;
267          if (VM.VerifyAssertions) VM._assert(im.isPublic() && im.isAbstract());
268          RVMMethod vm = C.findVirtualMethod(im.getName(), im.getDescriptor());
269          // NOTE: if there is some error condition, then we are playing a dirty trick and
270          //       pretending that a static method of RuntimeEntrypoints is a virtual method.
271          //       Since the methods in question take no arguments, we can get away with this.
272          if (vm == null || vm.isAbstract()) {
273            vm = Entrypoints.raiseAbstractMethodError;
274          } else if (!vm.isPublic()) {
275            vm = Entrypoints.raiseIllegalAccessError;
276          }
277          if (vm.isStatic()) {
278            vm.compile();
279            iTable.set(getITableIndex(I, im.getName(), im.getDescriptor()), vm.getCurrentEntryCodeArray());
280          } else {
281            iTable.set(getITableIndex(I, im.getName(), im.getDescriptor()), tib.getVirtualMethod(vm.getOffset()));
282          }
283        }
284        return iTable;
285      }
286    
287      /*
288      * PART III: Supporting low-level code for manipulating IMTs and ITables
289      */
290    
291      /**
292       * Return the index of the interface method m in the itable
293       */
294      public static int getITableIndex(RVMClass klass, Atom mname, Atom mdesc) {
295        if (VM.VerifyAssertions) VM._assert(VM.BuildForITableInterfaceInvocation);
296        if (VM.VerifyAssertions) VM._assert(klass.isInterface());
297        RVMMethod[] methods = klass.getDeclaredMethods();
298        for (int i = 0; i < methods.length; i++) {
299          if (methods[i].getName() == mname && methods[i].getDescriptor() == mdesc) {
300            return i + 1;
301          }
302        }
303        return -1;
304      }
305    
306      /**
307       * If there is an an IMT or ITable entry that contains
308       * compiled code for the argument method, then update it to
309       * contain the current compiled code for the method.
310       *
311       * @param klass the RVMClass who's IMT/ITable is being reset
312       * @param m the method that needs to be updated.
313       */
314      public static void updateTIBEntry(RVMClass klass, RVMMethod m) {
315        TIB tib = klass.getTypeInformationBlock();
316        if (VM.BuildForIMTInterfaceInvocation) {
317          RVMMethod[] map = klass.noIMTConflictMap;
318          if (map != null) {
319            for (int i = 0; i < IMT_METHOD_SLOTS; i++) {
320              if (map[i] == m) {
321                IMT imt = tib.getImt();
322                imt.set(i, m.getCurrentEntryCodeArray());
323                return; // all done -- a method is in at most 1 IMT slot
324              }
325            }
326          }
327        } else if (VM.BuildForITableInterfaceInvocation) {
328          if (tib.getITableArray() != null) {
329            ITableArray iTables = tib.getITableArray();
330            Atom name = m.getName();
331            Atom desc = m.getDescriptor();
332            for (int i=0; i< iTables.length(); i++) {
333              ITable iTable = iTables.get(i);
334              if (iTable != null) {
335                RVMClass I = iTable.getInterfaceClass();
336                RVMMethod[] interfaceMethods = I.getDeclaredMethods();
337                for (RVMMethod im : interfaceMethods) {
338                  if (im.getName() == name && im.getDescriptor() == desc) {
339                    iTable.set(getITableIndex(I, name, desc), m.getCurrentEntryCodeArray());
340                  }
341                }
342              }
343            }
344          }
345        }
346      }
347    
348      /*
349       * Helper class used for IMT construction
350       */
351      private static final class IMTDict {
352        private final RVMClass klass;
353        private final Link[] links;
354    
355        IMTDict(RVMClass c) {
356          klass = c;
357          links = new Link[IMT_METHOD_SLOTS];
358        }
359    
360        // Convert from the internally visible IMTOffset to an index
361        // into my internal data structure.
362        private int getIndex(InterfaceMethodSignature sig) {
363          int idx = sig.getIMTOffset().toInt() >> LOG_BYTES_IN_ADDRESS;
364          return idx;
365        }
366    
367        // count the number of signatures in the given IMT slot
368        private int populationCount(int index) {
369          Link p = links[index];
370          int count = 0;
371          while (p != null) {
372            count++;
373            p = p.next;
374          }
375          return count;
376        }
377    
378        private RVMMethod getSoleTarget(int index) {
379          if (VM.VerifyAssertions) VM._assert(populationCount(index) == 1);
380          return links[index].method;
381        }
382    
383        // Add an element to the IMT dictionary (does nothing if already there)
384        public void addElement(InterfaceMethodSignature sig, RVMMethod m) {
385          int index = getIndex(sig);
386          Link p = links[index];
387          if (p == null || p.signature.getId() > sig.getId()) {
388            links[index] = new Link(sig, m, p);
389          } else {
390            Link q = p;
391            while (p != null && p.signature.getId() <= sig.getId()) {
392              if (p.signature.getId() == sig.getId()) return; // already there so nothing to do.
393              q = p;
394              p = p.next;
395            }
396            q.next = new Link(sig, m, p);
397          }
398        }
399    
400        // populate the
401        public void populateIMT(RVMClass klass, TIB tib, IMT imt) {
402          for (int slot = 0; slot < links.length; slot++) {
403            int count = populationCount(slot);
404            if (count == 0) {
405              Entrypoints.raiseAbstractMethodError.compile();
406              set(tib, imt, slot, Entrypoints.raiseAbstractMethodError.getCurrentEntryCodeArray());
407            } else if (count == 1) {
408              RVMMethod target = getSoleTarget(slot);
409              if (target.isStatic()) {
410                target.compile();
411                set(tib, imt, slot, target.getCurrentEntryCodeArray());
412              } else {
413                set(tib, imt, slot, tib.getVirtualMethod(target.getOffset()));
414                if (klass.noIMTConflictMap == null) {
415                  klass.noIMTConflictMap = new RVMMethod[IMT_METHOD_SLOTS];
416                }
417                klass.noIMTConflictMap[slot] = target;
418              }
419            } else {
420              RVMMethod[] targets = new RVMMethod[count];
421              int[] sigIds = new int[count];
422              int idx = 0;
423              for (Link p = links[slot]; p != null; idx++, p = p.next) {
424                targets[idx] = p.method;
425                sigIds[idx] = p.signature.getId();
426              }
427              CodeArray conflictResolutionStub = InterfaceMethodConflictResolver.createStub(sigIds, targets);
428              klass.addCachedObject(Magic.codeArrayAsObject(conflictResolutionStub));
429              set(tib, imt, slot, conflictResolutionStub);
430            }
431          }
432        }
433    
434        private void set(TIB tib, IMT imt, int extSlot, CodeArray value) {
435          imt.set(extSlot, value);
436        }
437    
438        private static final class Link {
439          final InterfaceMethodSignature signature;
440          final RVMMethod method;
441          Link next;
442    
443          Link(InterfaceMethodSignature sig, RVMMethod m, Link n) {
444            signature = sig;
445            method = m;
446            next = n;
447          }
448        }
449      }
450    }