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 *
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 (ie 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 }