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    }