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