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.jni;
014
015 import org.jikesrvm.VM;
016 import org.jikesrvm.SizeConstants;
017 import org.jikesrvm.mm.mminterface.MemoryManager;
018 import org.jikesrvm.classloader.RVMMethod;
019 import org.jikesrvm.compilers.common.CompiledMethods;
020 import org.jikesrvm.runtime.Magic;
021 import org.jikesrvm.runtime.RuntimeEntrypoints;
022 import org.jikesrvm.scheduler.RVMThread;
023 import org.vmmagic.pragma.Entrypoint;
024 import org.vmmagic.pragma.Inline;
025 import org.vmmagic.pragma.NoInline;
026 import org.vmmagic.pragma.Uninterruptible;
027 import org.vmmagic.pragma.Unpreemptible;
028 import org.vmmagic.pragma.Untraced;
029 import org.vmmagic.unboxed.Address;
030 import org.vmmagic.unboxed.AddressArray;
031 import org.vmmagic.unboxed.ObjectReference;
032 import org.vmmagic.unboxed.Offset;
033
034 /**
035 * A JNIEnvironment is created for each Java thread.
036 */
037 public final class JNIEnvironment implements SizeConstants {
038
039 /**
040 * initial size for JNI refs, later grow as needed
041 */
042 protected static final int JNIREFS_ARRAY_LENGTH = 100;
043
044 /**
045 * sometimes we put stuff onto the jnirefs array bypassing the code
046 * that makes sure that it does not overflow (evil assembly code in the
047 * jni stubs that would be painful to fix). So, we keep some space
048 * between the max value in JNIRefsMax and the actual size of the
049 * array. How much is governed by this field.
050 */
051 protected static final int JNIREFS_FUDGE_LENGTH = 50;
052
053 /**
054 * This is the shared JNI function table used by native code
055 * to invoke methods in @link{JNIFunctions}.
056 */
057 public static FunctionTable JNIFunctions;
058
059 /**
060 * For the PowerOpenABI we need a linkage triple instead of just
061 * a function pointer.
062 * This is an array of such triples that matches JNIFunctions.
063 */
064 public static LinkageTripletTable LinkageTriplets;
065
066 /**
067 * This is the pointer to the shared JNIFunction table.
068 * When we invoke a native method, we adjust the pointer we
069 * pass to the native code such that this field is at offset 0.
070 * In other words, we turn a JNIEnvironment into a JNIEnv*
071 * by handing the native code an interior pointer to
072 * this object that points directly to this field.
073 */
074 @SuppressWarnings({"unused", "UnusedDeclaration"})
075 // used by native code
076 @Entrypoint
077 private final Address externalJNIFunctions =
078 VM.BuildForPowerOpenABI ? Magic.objectAsAddress(LinkageTriplets) : Magic.objectAsAddress(JNIFunctions);
079
080 /**
081 * For saving processor register on entry to native,
082 * to be restored on JNI call from native
083 */
084 @Entrypoint
085 @Untraced
086 protected RVMThread savedTRreg;
087
088 /**
089 * For saving JTOC register on entry to native,
090 * to be restored on JNI call from native (only used on PowerPC)
091 */
092 @Entrypoint
093 @Untraced
094 private final Address savedJTOC = VM.BuildForPowerPC ? Magic.getTocPointer() : Address.zero();
095
096 /**
097 * When native code doesn't maintain a base pointer we can't chain
098 * through the base pointers when walking the stack. This field
099 * holds the basePointer on entry to the native code in such a case,
100 * and is pushed onto the stack if we re-enter Java code (e.g. to
101 * handle a JNI function). This field is currently only used on IA32.
102 */
103 @Entrypoint
104 private Address basePointerOnEntryToNative = Address.fromIntSignExtend(0xF00BAAA1);
105
106 /**
107 * When transitioning between Java and C and back, we may want to stop a thread
108 * returning into Java and executing mutator code when a GC is in progress.
109 * When in C code, the C code may never return. In these situations we need a
110 * frame pointer at which to begin scanning the stack. This field holds this
111 * value. NB. these fields don't chain together on the stack as we walk through
112 * native frames by knowing their return addresses are outside of our heaps
113 */
114 @Entrypoint
115 protected Address JNITopJavaFP;
116
117 /**
118 * Currently pending exception (null if none)
119 */
120 @Entrypoint
121 @Untraced
122 protected Throwable pendingException;
123
124 /**
125 * We allocate JNIEnvironments in the immortal heap (so we
126 * can hand them directly to C code). Therefore, we must do some
127 * kind of pooling of JNIEnvironment instances.
128 * This is the free list of unused instances.
129 */
130 protected JNIEnvironment next;
131
132 /**
133 * Pool of available JNIEnvironments.
134 */
135 protected static JNIEnvironment pool;
136
137 /**
138 * true if the bottom stack frame is native,
139 * such as thread for CreateJVM or AttachCurrentThread
140 */
141 protected boolean alwaysHasNativeFrame;
142
143 /**
144 * references passed to native code
145 */
146 @Entrypoint
147 @Untraced
148 public AddressArray JNIRefs;
149
150 /**
151 * Offset of current top ref in JNIRefs array
152 */
153 @Entrypoint
154 public int JNIRefsTop;
155
156 /**
157 * Offset of end (last entry) of JNIRefs array
158 */
159 @Entrypoint
160 protected int JNIRefsMax;
161
162 /**
163 * Previous frame boundary in JNIRefs array.
164 * NB unused on IA32
165 */
166 @Entrypoint
167 public int JNIRefsSavedFP;
168
169 /**
170 * Initialize a thread specific JNI environment.
171 */
172 protected void initializeState() {
173 JNIRefs = AddressArray.create(JNIREFS_ARRAY_LENGTH + JNIREFS_FUDGE_LENGTH);
174 JNIRefsTop = 0;
175 JNIRefsSavedFP = 0;
176 JNIRefsMax = (JNIREFS_ARRAY_LENGTH - 1) << LOG_BYTES_IN_ADDRESS;
177 alwaysHasNativeFrame = false;
178 }
179
180 /*
181 * Allocation and pooling
182 */
183
184 /**
185 * Create a thread specific JNI environment.
186 */
187 public static synchronized JNIEnvironment allocateEnvironment() {
188 JNIEnvironment env;
189 if (pool != null) {
190 env = pool;
191 pool = pool.next;
192 } else {
193 env = new JNIEnvironment();
194 }
195 env.initializeState();
196 return env;
197 }
198
199 /**
200 * Return a thread-specific JNI environment; must be called as part of
201 * terminating a thread that has a JNI environment allocated to it.
202 * @param env the JNIEnvironment to deallocate
203 */
204 @Unpreemptible("Deallocate environment but may contend with environment being allocated")
205 public static synchronized void deallocateEnvironment(JNIEnvironment env) {
206 env.savedTRreg = null; /* make sure that we don't have a reference back to
207 the thread, once the thread has died. */
208 env.next = pool;
209 pool = env;
210 }
211
212 /*
213 * accessor methods
214 */
215 @Uninterruptible
216 public boolean hasNativeStackFrame() {
217 return alwaysHasNativeFrame || JNIRefsTop != 0;
218 }
219
220 @Uninterruptible
221 public Address topJavaFP() {
222 return JNITopJavaFP;
223 }
224
225 @Uninterruptible
226 public AddressArray refsArray() {
227 return JNIRefs;
228 }
229
230 @Uninterruptible
231 public int refsTop() {
232 return JNIRefsTop;
233 }
234
235 @Uninterruptible
236 public int savedRefsFP() {
237 return JNIRefsSavedFP;
238 }
239
240 /**
241 * Check push of reference can succeed
242 * @param ref object to be pushed
243 * @param canGrow can the JNI reference array be grown?
244 */
245 @Uninterruptible("May be called from uninterruptible code")
246 @NoInline
247 private void checkPush(Object ref, boolean canGrow) {
248 final boolean debug=true;
249 VM._assert(MemoryManager.validRef(ObjectReference.fromObject(ref)));
250 if (JNIRefsTop < 0) {
251 if (debug) {
252 VM.sysWriteln("JNIRefsTop=", JNIRefsTop);
253 VM.sysWriteln("JNIRefs.length=", JNIRefs.length());
254 }
255 VM.sysFail("unchecked push to negative offset!");
256 }
257 if ((JNIRefsTop >> LOG_BYTES_IN_ADDRESS) >= JNIRefs.length()) {
258 if (debug) {
259 VM.sysWriteln("JNIRefsTop=", JNIRefsTop);
260 VM.sysWriteln("JNIRefs.length=", JNIRefs.length());
261 }
262 VM.sysFail("unchecked pushes exceeded fudge length!");
263 }
264 if (!canGrow) {
265 if ((JNIRefsTop+BYTES_IN_ADDRESS) >= JNIRefsMax) {
266 if (debug) {
267 VM.sysWriteln("JNIRefsTop=", JNIRefsTop);
268 VM.sysWriteln("JNIRefsMax=", JNIRefsMax);
269 }
270 VM.sysFail("unchecked push can't grow JNI refs!");
271 }
272 }
273 }
274
275 /**
276 * Push a reference onto thread local JNIRefs stack.
277 * To be used by JNI functions when returning a reference
278 * back to JNI native C code.
279 * @param ref the object to put on stack
280 * @return offset of entry in JNIRefs stack
281 */
282 public int pushJNIRef(Object ref) {
283 if (ref == null) {
284 return 0;
285 } else {
286 if (VM.VerifyAssertions) checkPush(ref, true);
287 JNIRefsTop += BYTES_IN_ADDRESS;
288 if (JNIRefsTop >= JNIRefsMax) {
289 JNIRefsMax *= 2;
290 AddressArray newrefs = AddressArray.create((JNIRefsMax >> LOG_BYTES_IN_ADDRESS) + JNIREFS_FUDGE_LENGTH);
291 for (int i = 0; i < JNIRefs.length(); i++) {
292 newrefs.set(i, JNIRefs.get(i));
293 }
294 JNIRefs = newrefs;
295 }
296 JNIRefs.set(JNIRefsTop >> LOG_BYTES_IN_ADDRESS, Magic.objectAsAddress(ref));
297 return JNIRefsTop;
298 }
299 }
300
301 /**
302 * Push a JNI ref, used on entry to JNI
303 * NB only used for Intel
304 * @param ref reference to place on stack or value of saved frame pointer
305 * @param isRef false if the reference isn't a frame pointer
306 */
307 @Uninterruptible("Encoding arguments on stack that won't be seen by GC")
308 @Inline
309 private int uninterruptiblePushJNIRef(Address ref, boolean isRef) {
310 if (isRef && ref.isZero()) {
311 return 0;
312 } else {
313 if (VM.VerifyAssertions) checkPush(isRef ? Magic.addressAsObject(ref) : null, false);
314 // we count all slots so that releasing them is straight forward
315 JNIRefsTop += BYTES_IN_ADDRESS;
316 // ensure null is always seen as slot zero
317 JNIRefs.set(JNIRefsTop >> LOG_BYTES_IN_ADDRESS, Magic.objectAsAddress(ref));
318 return JNIRefsTop;
319 }
320 }
321
322 /**
323 * Save data and perform necessary conversions for entry into JNI.
324 * NB only used for Intel.
325 *
326 * @param encodedReferenceOffsets
327 * bit mask marking which elements on the stack hold objects that need
328 * encoding as JNI ref identifiers
329 */
330 @Uninterruptible("Objects on the stack won't be recognized by GC, therefore don't allow GC")
331 @Entrypoint
332 public void entryToJNI(int encodedReferenceOffsets) {
333 // Save processor
334 savedTRreg = Magic.getThreadRegister();
335
336 // Save frame pointer of calling routine, once so that native stack frames
337 // are skipped and once for use by GC
338 Address callersFP = Magic.getCallerFramePointer(Magic.getFramePointer());
339 basePointerOnEntryToNative = callersFP; // NB old value saved on call stack
340 JNITopJavaFP = callersFP;
341
342 if (VM.traceJNI) {
343 RVMMethod m=
344 CompiledMethods.getCompiledMethod(
345 Magic.getCompiledMethodID(callersFP)).getMethod();
346 VM.sysWrite("calling JNI from ");
347 VM.sysWrite(m.getDeclaringClass().getDescriptor());
348 VM.sysWrite(" ");
349 VM.sysWrite(m.getName());
350 VM.sysWrite(m.getDescriptor());
351 VM.sysWriteln();
352 }
353
354 // Save current JNI ref stack pointer
355 if (JNIRefsTop > 0) {
356 uninterruptiblePushJNIRef(Address.fromIntSignExtend(JNIRefsSavedFP), false);
357 JNIRefsSavedFP = JNIRefsTop;
358 }
359
360 // Convert arguments on stack from objects to JNI references
361 Address fp = Magic.getFramePointer();
362 Offset argOffset = Offset.fromIntSignExtend(5*BYTES_IN_ADDRESS);
363 fp.store(uninterruptiblePushJNIRef(fp.loadAddress(argOffset),true), argOffset);
364 while (encodedReferenceOffsets != 0) {
365 argOffset = argOffset.plus(BYTES_IN_ADDRESS);
366 if ((encodedReferenceOffsets & 1) != 0) {
367 fp.store(uninterruptiblePushJNIRef(fp.loadAddress(argOffset), true), argOffset);
368 }
369 encodedReferenceOffsets >>>= 1;
370 }
371 // Transition processor from IN_JAVA to IN_JNI
372 RVMThread.enterJNIFromCallIntoNative();
373 }
374
375 /**
376 * Restore data, throw pending exceptions or convert return value for exit
377 * from JNI. NB only used for Intel.
378 *
379 * @param offset
380 * offset into JNI reference tables of result
381 * @return Object encoded by offset or null if offset is 0
382 */
383 @Unpreemptible("Don't allow preemption when we're not in a sane state. " +
384 "Code can throw exceptions so not uninterruptible.")
385 @Entrypoint
386 public Object exitFromJNI(int offset) {
387 // Transition processor from IN_JNI to IN_JAVA
388 RVMThread.leaveJNIFromCallIntoNative();
389
390 // Restore JNI ref top and saved frame pointer
391 JNIRefsTop = 0;
392 if (JNIRefsSavedFP > 0) {
393 JNIRefsTop = JNIRefsSavedFP - BYTES_IN_ADDRESS;
394 JNIRefsSavedFP = JNIRefs.get(JNIRefsSavedFP >> LOG_BYTES_IN_ADDRESS).toInt();
395 }
396
397 // Throw and clear any pending exceptions
398 Throwable pe = pendingException;
399 if (pe != null) {
400 pendingException = null;
401 RuntimeEntrypoints.athrow(pe);
402 // NB. we will never reach here
403 }
404 // Lookup result
405 Object result;
406 if (offset == 0) {
407 result = null;
408 } else if (offset < 0) {
409 result = JNIGlobalRefTable.ref(offset);
410 } else {
411 result = Magic.addressAsObject(JNIRefs.get(offset >> LOG_BYTES_IN_ADDRESS));
412 }
413 return result;
414 }
415
416 /**
417 * Get a reference from the JNIRefs stack.
418 * @param offset in JNIRefs stack
419 * @return reference at that offset
420 */
421 public Object getJNIRef(int offset) {
422 if (offset > JNIRefsTop) {
423 VM.sysWrite("JNI ERROR: getJNIRef for illegal offset > TOP, ");
424 VM.sysWrite(offset);
425 VM.sysWrite("(top is ");
426 VM.sysWrite(JNIRefsTop);
427 VM.sysWrite(")\n");
428 RVMThread.dumpStack();
429 return null;
430 }
431 if (offset < 0) {
432 return JNIGlobalRefTable.ref(offset);
433 } else {
434 return Magic.addressAsObject(JNIRefs.get(offset >> LOG_BYTES_IN_ADDRESS));
435 }
436 }
437
438 /**
439 * Remove a reference from the JNIRefs stack.
440 * @param offset in JNIRefs stack
441 */
442 public void deleteJNIRef(int offset) {
443 if (offset > JNIRefsTop) {
444 VM.sysWrite("JNI ERROR: getJNIRef for illegal offset > TOP, ");
445 VM.sysWrite(offset);
446 VM.sysWrite("(top is ");
447 VM.sysWrite(JNIRefsTop);
448 VM.sysWrite(")\n");
449 }
450
451 JNIRefs.set(offset >> LOG_BYTES_IN_ADDRESS, Address.zero());
452
453 if (offset == JNIRefsTop) JNIRefsTop -= BYTES_IN_ADDRESS;
454 }
455
456 /**
457 * Dump the JNIRefs stack to the sysWrite stream
458 */
459 @Uninterruptible
460 public void dumpJniRefsStack() {
461 int jniRefOffset = JNIRefsTop;
462 VM.sysWrite("\n* * dump of JNIEnvironment JniRefs Stack * *\n");
463 VM.sysWrite("* JNIRefs = ");
464 VM.sysWrite(Magic.objectAsAddress(JNIRefs));
465 VM.sysWrite(" * JNIRefsTop = ");
466 VM.sysWrite(JNIRefsTop);
467 VM.sysWrite(" * JNIRefsSavedFP = ");
468 VM.sysWrite(JNIRefsSavedFP);
469 VM.sysWrite(".\n*\n");
470 while (jniRefOffset >= 0) {
471 VM.sysWrite(jniRefOffset);
472 VM.sysWrite(" ");
473 VM.sysWrite(Magic.objectAsAddress(JNIRefs).plus(jniRefOffset));
474 VM.sysWrite(" ");
475 MemoryManager.dumpRef(JNIRefs.get(jniRefOffset >> LOG_BYTES_IN_ADDRESS).toObjectReference());
476 jniRefOffset -= BYTES_IN_ADDRESS;
477 }
478 VM.sysWrite("\n* * end of dump * *\n");
479 }
480
481 /**
482 * Record an exception as pending so that it will be delivered on the return
483 * to the Java caller; clear the exception by recording null
484 * @param e An exception or error
485 */
486 public void recordException(Throwable e) {
487 // don't overwrite the first exception except to clear it
488 if (pendingException == null || e == null) {
489 pendingException = e;
490 }
491 }
492
493 /**
494 * @return the pending exception
495 */
496 public Throwable getException() {
497 return pendingException;
498 }
499
500 /**
501 * Initialize the array of JNI functions.
502 * This function is called during bootimage writing.
503 */
504 public static void initFunctionTable(FunctionTable functions) {
505 JNIFunctions = functions;
506 if (VM.BuildForPowerOpenABI) {
507 // Allocate the linkage triplets in the bootimage too (so they won't move)
508 LinkageTriplets = LinkageTripletTable.allocate(functions.length());
509 for (int i = 0; i < functions.length(); i++) {
510 LinkageTriplets.set(i, AddressArray.create(3));
511 }
512 }
513 }
514
515 /**
516 * Initialization required during VM booting; only does something if
517 * we are on a platform that needs linkage triplets.
518 */
519 public static void boot() {
520 if (VM.BuildForPowerOpenABI) {
521 // fill in the TOC and IP entries for each linkage triplet
522 for (int i = 0; i < JNIFunctions.length(); i++) {
523 AddressArray triplet = LinkageTriplets.get(i);
524 triplet.set(1, Magic.getTocPointer());
525 triplet.set(0, Magic.objectAsAddress(JNIFunctions.get(i)));
526 }
527 }
528 }
529 }