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