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.ia32;
014    
015    import org.jikesrvm.ArchitectureSpecific;
016    import org.jikesrvm.VM;
017    import org.jikesrvm.classloader.RVMMethod;
018    import org.jikesrvm.classloader.NativeMethod;
019    import org.jikesrvm.classloader.NormalMethod;
020    import org.jikesrvm.classloader.TypeReference;
021    import org.jikesrvm.compilers.common.CompiledMethod;
022    import org.jikesrvm.compilers.common.CompiledMethods;
023    import org.jikesrvm.compilers.common.assembler.ForwardReference;
024    import org.jikesrvm.compilers.common.assembler.ia32.Assembler;
025    import org.jikesrvm.ia32.BaselineConstants;
026    import org.jikesrvm.ia32.MachineCode;
027    import org.jikesrvm.ia32.ThreadLocalState;
028    import org.jikesrvm.jni.JNICompiledMethod;
029    import org.jikesrvm.objectmodel.ObjectModel;
030    import org.jikesrvm.runtime.ArchEntrypoints;
031    import org.jikesrvm.runtime.Entrypoints;
032    import org.jikesrvm.runtime.Statics;
033    import org.jikesrvm.runtime.Magic;
034    import org.jikesrvm.scheduler.RVMThread;
035    import org.vmmagic.unboxed.Address;
036    import org.vmmagic.unboxed.Offset;
037    
038    /**
039     * This class compiles the prolog and epilog for all code that makes
040     * the transition between Java and Native C for the 2 cases:
041     * <ul>
042     * <li>from Java to C:  all user-defined native methods</li>
043     * <li>C to Java:  all JNI functions in {@link org.jikesrvm.jni.JNIFunctions}</li>
044     * </ul>
045     * When performing the transitions the values in registers and on the stack need
046     * to be treated with care, but also when transitioning into Java we need to do
047     * a spin-wait if a GC is underway.
048     *
049     * Transitioning from Java to C then back:
050     * <ol>
051     * <li>Set up stack frame and save non-volatile registers</li>
052     * <li>Move all native method arguments on to stack (NB at this point all non-volatile state is saved)</li>
053     * <li>Set up jniEnv</li>
054     * <li>Record the frame pointer of the last Java frame (this) in the jniEnv</li>
055     * <li>Set up JNI refs, a stack that holds references accessed via JNI</li>
056     * <li>Call out to convert reference arguments to IDs</li>
057     * <li>Set processor as being "in native"</li>
058     * <li>Set up stack frame and registers for transition to C</li>
059     * <li>Call out to C</li>
060     * <li>Save result to stack</li>
061     * <li>Transition back from "in native" to "in Java", take care that the
062     *     Processor isn't "blocked in native", ie other processors have decided to
063     *     start a GC and we're not permitted to execute Java code whilst this
064     *     occurs</li>
065     * <li>Convert a reference result (currently a JNI ref) into a true reference</li>
066     * <li>Release JNI refs</li>
067     * <li>Restore stack and place result in register</li>
068     * <ol>
069     *
070     * Prologue generation from C to Java:
071     * <ol>
072     * <li>Set up stack frame with C arguments in Jikes RVM convention</li>
073     * <li>Set up extra stack frame entry that records the previous last top Java FP</li>
074     * <li>Transition from "in native" to "in Java" taking care of blocked (in GC)
075           case</li>
076     * </ol>
077     *
078     * Epilogue generation from Java to C:
079     * <ol>
080     * <li>Restore record of the previous last top Java FP in the jniEnv</li>
081     * <li>Transition from "in Java" to "in native"</li>
082     * <li>Set up result in registers. NB. JNIFunctions don't return references but
083     *     JNI refs directly, so we don't need to transition these</li>
084     * </ol>
085     */
086    public abstract class JNICompiler implements BaselineConstants {
087    
088      /** Dummy field to force compilation of the exception deliverer */
089      private org.jikesrvm.jni.ia32.JNIExceptionDeliverer unused;
090    
091      /** Offset of external functions field in JNIEnvironment */
092      private static final  int jniExternalFunctionsFieldOffset =
093        Entrypoints.JNIExternalFunctionsField.getOffset().toInt();
094    
095      // --- Java to C fields ---
096    
097      /** Location of non-volatile EDI register when saved to stack */
098      static final Offset EDI_SAVE_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET);
099      /** Location of non-volatile EBX register when saved to stack */
100      static final Offset EBX_SAVE_OFFSET = EDI_SAVE_OFFSET.minus(WORDSIZE);
101      /** Location of non-volatile EBP register when saved to stack */
102      static final Offset EBP_SAVE_OFFSET = EBX_SAVE_OFFSET.minus(WORDSIZE);
103      /** Location of an extra copy of the RVMThread.jniEnv when saved to stack */
104      private static final Offset JNI_ENV_OFFSET = EBP_SAVE_OFFSET.minus(WORDSIZE);
105      /** Location of a saved version of the field JNIEnvironment.basePointerOnEntryToNative */
106      private static final Offset BP_ON_ENTRY_OFFSET = JNI_ENV_OFFSET.minus(WORDSIZE);
107    
108      // --- C to Java fields ---
109      /**
110       * Stack frame location for saved JNIEnvironment.JNITopJavaFP that
111       * will be clobbered by a transition from Java to C.  Only used in
112       * the prologue & epilogue for JNIFunctions.
113       */
114      private static final Offset SAVED_JAVA_FP_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET);
115    
116      /**
117       * The following is used in BaselineCompilerImpl to compute offset to first local.
118       * Number of non-volatile GPRs and FPRs and then 1 slot for the SAVED_JAVA_FP.
119       */
120      public static final int SAVED_GPRS_FOR_JNI = NATIVE_NONVOLATILE_GPRS.length + NATIVE_NONVOLATILE_FPRS.length + 1;
121    
122      /**
123       * Compile a method to handle the Java to C transition and back
124       * Transitioning from Java to C then back:
125       * <ol>
126       * <li>Set up stack frame and save non-volatile registers<li>
127       * <li>Set up jniEnv - set up a register to hold JNIEnv and store
128       *     the Processor in the JNIEnv for easy access</li>
129       * <li>Move all native method arguments on to stack (NB at this point all
130       *     non-volatile state is saved)</li>
131       * <li>Record the frame pointer of the last Java frame (this) in the jniEnv</li>
132       * <li>Call out to convert reference arguments to IDs</li>
133       * <li>Set processor as being "in native"</li>
134       * <li>Set up stack frame and registers for transition to C</li>
135       * <li>Call out to C</li>
136       * <li>Save result to stack</li>
137       * <li>Transition back from "in native" to "in Java", take care that the
138       *     Processor isn't "blocked in native", ie other processors have decided to
139       *     start a GC and we're not permitted to execute Java code whilst this
140       *     occurs</li>
141       * <li>Convert a reference result (currently a JNI ref) into a true reference</li>
142       * <li>Release JNI refs</li>
143       * <li>Restore stack and place result in register</li>
144       * <ol>
145       */
146      public static synchronized CompiledMethod compile(NativeMethod method) {
147        // Meaning of constant offset into frame (assuming 4byte word size):
148        // Stack frame:
149        //        on entry          after prolog
150        //
151        //      high address        high address
152        //      |          |        |          | Caller frame
153        //      |          |        |          |
154        // +    |arg 0     |        |arg 0     | <- firstParameterOffset
155        // +    |arg 1     |        |arg 1     |
156        // +    |...       |        |...       |
157        // +8   |arg n-1   |        |arg n-1   | <- lastParameterOffset
158        // +4   |returnAddr|        |returnAddr|
159        //  0   +          +        +saved FP  + <- EBP/FP value in glue frame
160        // -4   |          |        |methodID  |
161        // -8   |          |        |saved EDI |
162        // -C   |          |        |saved EBX |
163        // -10  |          |        |saved EBP |
164        // -14  |          |        |saved ENV |  (JNIEnvironment)
165        // -18  |          |        |arg n-1   |  reordered args to native method
166        // -1C  |          |        | ...      |  ...
167        // -20  |          |        |arg 1     |  ...
168        // -24  |          |        |arg 0     |  ...
169        // -28  |          |        |class/obj |  required second arg to native method
170        // -2C  |          |        |jni funcs |  required first arg to native method
171        // -30  |          |        |          |
172        //      |          |        |          |
173        //      |          |        |          |
174        //       low address         low address
175        // Register values:
176        // EBP    - after step 1 EBP holds a frame pointer allowing easy
177        //          access to both this and the proceeding frame
178        // ESP    - gradually floats down as the stack frame is initialized
179        // S0/ECX - reference to the JNI environment after step 3
180    
181        JNICompiledMethod cm = (JNICompiledMethod)CompiledMethods.createCompiledMethod(method, CompiledMethod.JNI);
182        ArchitectureSpecific.Assembler asm = new ArchitectureSpecific.Assembler(100 /*, true*/);   // some size for the instruction array
183    
184        Address nativeIP = method.getNativeIP();
185        final Offset lastParameterOffset = Offset.fromIntSignExtend(2*WORDSIZE);
186        //final Offset firstParameterOffset = Offset.fromIntSignExtend(WORDSIZE+(method.getParameterWords() << LG_WORDSIZE));
187        final TypeReference[] args = method.getParameterTypes();
188    
189        // (1) Set up stack frame and save non-volatile registers
190    
191        // TODO:  check and resize stack once on the lowest Java to C transition
192        // on the stack.  Not needed if we use the thread original stack
193    
194        // set 2nd word of header = return address already pushed by CALL
195        asm.emitPUSH_RegDisp(THREAD_REGISTER, ArchEntrypoints.framePointerField.getOffset());
196    
197        // establish new frame
198        ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), SP);
199    
200        // set first word of header: method ID
201        if (VM.VerifyAssertions) VM._assert(STACKFRAME_METHOD_ID_OFFSET == -WORDSIZE);
202        asm.emitPUSH_Imm(cm.getId());
203    
204        // save nonvolatile registrs: EDI, EBX, EBP
205        if (VM.VerifyAssertions) VM._assert(EDI_SAVE_OFFSET.toInt() == -2*WORDSIZE);
206        asm.emitPUSH_Reg(EDI); // save nonvolatile EDI register
207        if (VM.VerifyAssertions) VM._assert(EBX_SAVE_OFFSET.toInt() == -3*WORDSIZE);
208        asm.emitPUSH_Reg(EBX); // save nonvolatile EBX register
209        if (VM.VerifyAssertions) VM._assert(EBP_SAVE_OFFSET.toInt() == -4*WORDSIZE);
210        asm.emitPUSH_Reg(EBP); // save nonvolatile EBP register
211    
212        // Establish EBP as the framepointer for use in the rest of the glue frame
213        asm.emitLEA_Reg_RegDisp(EBP, SP, Offset.fromIntSignExtend(4*WORDSIZE));
214    
215        // (2) Set up jniEnv - set up a register to hold JNIEnv and store
216        // the Processor in the JNIEnv for easy access
217    
218        // S0 = RVMThread.jniEnv
219        if (VM.BuildFor32Addr) {
220          asm.emitMOV_Reg_RegDisp(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset());
221        } else {
222          asm.emitMOV_Reg_RegDisp_Quad(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset());
223        }
224        if (VM.VerifyAssertions) VM._assert(JNI_ENV_OFFSET.toInt() == -5*WORDSIZE);
225        asm.emitPUSH_Reg(S0); // save JNI Env for after call
226    
227        if (VM.VerifyAssertions) VM._assert(BP_ON_ENTRY_OFFSET.toInt() == -6*WORDSIZE);
228        asm.emitPUSH_RegDisp(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset());
229        // save BP into JNIEnv
230        if (VM.BuildFor32Addr) {
231          asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBP);
232        } else {
233          asm.emitMOV_RegDisp_Reg_Quad(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBP);
234        }
235        // (3) Move all native method arguments on to stack (NB at this
236        // point all non-volatile state is saved)
237    
238        // (3.1) Count how many arguments could be passed in either FPRs or GPRs
239        int numFprArgs=0;
240        int numGprArgs=method.isStatic() ? 0 : 1;
241        for (TypeReference arg : args) {
242          if (arg.isFloatType() || arg.isDoubleType()) {
243            numFprArgs++;
244          } else if (VM.BuildFor32Addr && arg.isLongType()) {
245            numGprArgs+=2;
246          } else {
247            numGprArgs++;
248          }
249        }
250        // (3.2) Walk over arguments backwards pushing either from memory or registers
251        Offset currentArg = lastParameterOffset;
252        int argFpr=numFprArgs-1;
253        int argGpr=numGprArgs-1;
254        for (int i=args.length-1; i >= 0; i--) {
255          TypeReference arg = args[i];
256          if (arg.isFloatType()) {
257            if (argFpr < PARAMETER_FPRS.length) {
258              asm.emitPUSH_Reg(T0); // make space
259              if (SSE2_FULL) {
260                asm.emitMOVSS_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]);
261              } else {
262                asm.emitFSTP_RegInd_Reg(SP, (FPR)PARAMETER_FPRS[argFpr]);
263              }
264            } else {
265              asm.emitPUSH_RegDisp(EBP, currentArg);
266            }
267            argFpr--;
268          } else if (arg.isDoubleType()) {
269            if (VM.BuildFor32Addr) {
270              if (argFpr < PARAMETER_FPRS.length) {
271                asm.emitPUSH_Reg(T0); // make space
272                asm.emitPUSH_Reg(T0); // need 2 slots with 32bit addresses
273                if (SSE2_FULL) {
274                  asm.emitMOVSD_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]);
275                } else {
276                  asm.emitFSTP_RegInd_Reg_Quad(SP, (FPR)PARAMETER_FPRS[argFpr]);
277                }
278              } else {
279                asm.emitPUSH_RegDisp(EBP, currentArg.plus(WORDSIZE));
280                asm.emitPUSH_RegDisp(EBP, currentArg);  // need 2 slots with 32bit addresses
281              }
282            } else {
283              if (argFpr < PARAMETER_FPRS.length) {
284                asm.emitPUSH_Reg(T0); // make space
285                if (SSE2_FULL) {
286                  asm.emitMOVSD_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]);
287                } else {
288                  asm.emitFSTP_RegInd_Reg_Quad(SP, (FPR)PARAMETER_FPRS[argFpr]);
289                }
290              } else {
291                asm.emitPUSH_RegDisp(EBP, currentArg);
292              }
293            }
294            argFpr--;
295            currentArg = currentArg.plus(WORDSIZE);
296          } else if (VM.BuildFor32Addr && arg.isLongType()) {
297            if (argGpr < PARAMETER_GPRS.length) {
298              asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr-1]);
299              asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr]);
300            } else if (argGpr - 1 < PARAMETER_GPRS.length) {
301              asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr-1]);
302              asm.emitPUSH_RegDisp(EBP, currentArg);
303            } else {
304              asm.emitPUSH_RegDisp(EBP, currentArg.plus(WORDSIZE));
305              asm.emitPUSH_RegDisp(EBP, currentArg);
306            }
307            argGpr-=2;
308            currentArg = currentArg.plus(WORDSIZE);
309          } else {
310            if (argGpr < PARAMETER_GPRS.length) {
311              asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr]);
312            } else {
313              asm.emitPUSH_RegDisp(EBP, currentArg);
314            }
315            argGpr--;
316            if (VM.BuildFor64Addr && arg.isLongType()) {
317              currentArg = currentArg.plus(WORDSIZE);
318            }
319          }
320          currentArg = currentArg.plus(WORDSIZE);
321        }
322        // (3.3) push class or object argument
323        if (method.isStatic()) {
324          // push java.lang.Class object for klass
325          Offset klassOffset = Offset.fromIntSignExtend(
326              Statics.findOrCreateObjectLiteral(method.getDeclaringClass().getClassForType()));
327          asm.emitPUSH_Abs(Magic.getTocPointer().plus(klassOffset));
328        } else {
329          if (VM.VerifyAssertions) VM._assert(argGpr == 0);
330          asm.emitPUSH_Reg(PARAMETER_GPRS[0]);
331        }
332        // (3.4) push a pointer to the JNI functions that will be
333        // dereferenced in native code
334        asm.emitPUSH_Reg(S0);
335        if (jniExternalFunctionsFieldOffset != 0) {
336          if (VM.BuildFor32Addr) {
337            asm.emitADD_RegInd_Imm(ESP, jniExternalFunctionsFieldOffset);
338          } else {
339            asm.emitADD_RegInd_Imm_Quad(ESP, jniExternalFunctionsFieldOffset);
340          }
341        }
342    
343        // (4) Call out to convert reference arguments to IDs, set thread as
344        // being "in native" and record the frame pointer of the last Java frame
345        // (this) in the jniEnv
346    
347        // Encode reference arguments into a long
348        int encodedReferenceOffsets=0;
349        for (int i=0, pos=0; i < args.length; i++, pos++) {
350          TypeReference arg = args[i];
351          if (arg.isReferenceType()) {
352            if (VM.VerifyAssertions) VM._assert(pos < 32);
353            encodedReferenceOffsets |= 1 << pos;
354          } else if (arg.isLongType() || arg.isDoubleType()) {
355            pos++;
356          }
357        }
358        // Call out to JNI environment JNI entry
359        if (VM.BuildFor32Addr) {
360          asm.emitMOV_Reg_RegDisp(PARAMETER_GPRS[0], EBP, JNI_ENV_OFFSET);
361        } else {
362          asm.emitMOV_Reg_RegDisp_Quad(PARAMETER_GPRS[0], EBP, JNI_ENV_OFFSET);
363        }
364        asm.emitPUSH_Reg(PARAMETER_GPRS[0]);
365        asm.emitMOV_Reg_Imm(PARAMETER_GPRS[1], encodedReferenceOffsets);
366        asm.emitPUSH_Reg(PARAMETER_GPRS[1]);
367        ObjectModel.baselineEmitLoadTIB(asm, S0.value(), PARAMETER_GPRS[0].value());
368        asm.emitCALL_RegDisp(S0, Entrypoints.jniEntry.getOffset());
369    
370        // (5) Set up stack frame and registers for transition to C
371        int argsPassedInRegister=0;
372        if (VM.BuildFor64Addr) {
373          int gpRegistersInUse=2;
374          int fpRegistersInUse=0;
375          boolean dataOnStack = false;
376          asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[0]); // JNI env
377          asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[1]); // Object/Class
378          argsPassedInRegister+=2;
379          for (TypeReference arg : method.getParameterTypes()) {
380            if (arg.isFloatType()) {
381              if (fpRegistersInUse < NATIVE_PARAMETER_FPRS.length) {
382                // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up
383                if (dataOnStack) throw new Error("Unsupported native method parameter list");
384                asm.emitMOVSS_Reg_RegInd((XMM)NATIVE_PARAMETER_FPRS[fpRegistersInUse], SP);
385                asm.emitPOP_Reg(T0);
386                fpRegistersInUse++;
387                argsPassedInRegister++;
388              } else {
389                // no register available so we have data on the stack
390                dataOnStack = true;
391              }
392            } else if (arg.isDoubleType()) {
393              if (fpRegistersInUse < NATIVE_PARAMETER_FPRS.length) {
394                // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up
395                if (dataOnStack) throw new Error("Unsupported native method parameter list");
396                asm.emitMOVSD_Reg_RegInd((XMM)NATIVE_PARAMETER_FPRS[fpRegistersInUse], SP);
397                asm.emitPOP_Reg(T0);
398                asm.emitPOP_Reg(T0);
399                fpRegistersInUse++;
400                argsPassedInRegister+=2;
401              } else {
402                // no register available so we have data on the stack
403                dataOnStack = true;
404              }
405            } else {
406              if (gpRegistersInUse < NATIVE_PARAMETER_GPRS.length) {
407                // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up
408                if (dataOnStack) throw new Error("Unsupported native method parameter list");
409                asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[gpRegistersInUse]);
410                gpRegistersInUse++;
411                argsPassedInRegister++;
412              } else {
413                // no register available so we have data on the stack
414                dataOnStack = true;
415              }
416            }
417          }
418        }
419    
420        // (6) Call out to C
421        // move address of native code to invoke into T0
422        if (VM.BuildFor32Addr) {
423          asm.emitMOV_Reg_Imm(T0, nativeIP.toInt());
424        } else {
425          asm.emitMOV_Reg_Imm_Quad(T0, nativeIP.toLong());
426        }
427        // make the call to native code
428        asm.emitCALL_Reg(T0);
429    
430        // (7) Discard parameters on stack
431        // TODO: optimize stack adjustment
432        if (VM.BuildFor32Addr) {
433          // throw away args, class/this ptr and env
434          int argsToThrowAway = method.getParameterWords()+2-argsPassedInRegister;
435          if (argsToThrowAway != 0) {
436            asm.emitADD_Reg_Imm(SP, argsToThrowAway << LG_WORDSIZE);
437          }
438        } else {
439          // throw away args, class/this ptr and env
440          int argsToThrowAway = args.length+2-argsPassedInRegister;
441          if (argsToThrowAway != 0) {
442            asm.emitADD_Reg_Imm_Quad(SP, argsToThrowAway << LG_WORDSIZE);
443          }
444        }
445    
446        // (8) Save result to stack
447        final TypeReference returnType = method.getReturnType();
448        if (returnType.isVoidType()) {
449          // Nothing to save
450        } else if (returnType.isFloatType()) {
451          asm.emitPUSH_Reg(T0); // adjust stack
452          if (VM.BuildFor32Addr) {
453            asm.emitFSTP_RegInd_Reg(ESP, FP0);
454          } else {
455            asm.emitMOVSS_RegInd_Reg(ESP, XMM0);
456          }
457        } else if (returnType.isDoubleType()) {
458          asm.emitPUSH_Reg(T0); // adjust stack
459          asm.emitPUSH_Reg(T0); // adjust stack
460          if (VM.BuildFor32Addr) {
461            asm.emitFSTP_RegInd_Reg_Quad(ESP, FP0);
462          } else {
463            asm.emitMOVSD_RegInd_Reg(ESP, XMM0);
464          }
465        } else if (VM.BuildFor32Addr && returnType.isLongType()) {
466          asm.emitPUSH_Reg(T0);
467          asm.emitPUSH_Reg(T1);
468        } else {
469          // Ensure sign-extension is correct
470          if (returnType.isBooleanType()) {
471            asm.emitMOVZX_Reg_Reg_Byte(T0, T0);
472          } else if (returnType.isByteType()) {
473            asm.emitMOVSX_Reg_Reg_Byte(T0, T0);
474          } else if (returnType.isCharType()) {
475            asm.emitMOVZX_Reg_Reg_Word(T0, T0);
476          } else if (returnType.isShortType()) {
477            asm.emitMOVSX_Reg_Reg_Word(T0, T0);
478          }
479          asm.emitPUSH_Reg(T0);
480        }
481    
482        // (9) Recover RVM style frame
483        // (9.1) reload JNIEnvironment from glue frame
484        if (VM.BuildFor32Addr) {
485          asm.emitMOV_Reg_RegDisp(S0, EBP, JNICompiler.JNI_ENV_OFFSET);
486        } else {
487          asm.emitMOV_Reg_RegDisp_Quad(S0, EBP, JNICompiler.JNI_ENV_OFFSET);
488        }
489        // (9.2) Reload thread register from JNIEnvironment
490        ThreadLocalState.emitLoadThread(asm, S0, Entrypoints.JNIEnvSavedTRField.getOffset());
491    
492        // (9.3) Establish frame pointer to this glue method
493        ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), EBP);
494    
495        // (10) Transition back from "in native" to "in Java", convert a reference
496        // result (currently a JNI ref) into a true reference, release JNI refs
497        asm.emitMOV_Reg_Reg(PARAMETER_GPRS[0], S0); // 1st arg is JNI Env
498        if (returnType.isReferenceType()) {
499          asm.emitPOP_Reg(PARAMETER_GPRS[1]);       // 2nd arg is ref result
500        } else {
501          // place dummy (null) operand on stack
502          asm.emitXOR_Reg_Reg(PARAMETER_GPRS[1], PARAMETER_GPRS[1]);
503        }
504        asm.emitPUSH_Reg(S0);                       // save JNIEnv
505        asm.emitPUSH_Reg(S0);                       // push arg 1
506        asm.emitPUSH_Reg(PARAMETER_GPRS[1]);        // push arg 2
507        // Do the call
508        ObjectModel.baselineEmitLoadTIB(asm, S0.value(), S0.value());
509        asm.emitCALL_RegDisp(S0, Entrypoints.jniExit.getOffset());
510        asm.emitPOP_Reg(S0); // restore JNIEnv
511    
512        // (11) Restore stack and place result in register
513        // place result in register
514        if (returnType.isVoidType()) {
515          // Nothing to save
516        } else if (returnType.isReferenceType()) {
517          // value already in register
518        } else if (returnType.isFloatType()) {
519          if (SSE2_FULL) {
520            asm.emitMOVSS_Reg_RegInd(XMM0, ESP);
521          } else {
522            asm.emitFLD_Reg_RegInd(FP0, ESP);
523          }
524          asm.emitPOP_Reg(T0); // adjust stack
525        } else if (returnType.isDoubleType()) {
526          if (SSE2_FULL) {
527            asm.emitMOVSD_Reg_RegInd(XMM0, ESP);
528          } else {
529            asm.emitFLD_Reg_RegInd_Quad(FP0, ESP);
530          }
531          asm.emitPOP_Reg(T0); // adjust stack
532          asm.emitPOP_Reg(T0); // adjust stack
533        } else if (VM.BuildFor32Addr && returnType.isLongType()) {
534          asm.emitPOP_Reg(T0);
535          asm.emitPOP_Reg(T1);
536        } else {
537          asm.emitPOP_Reg(T0);
538        }
539    
540        asm.emitPOP_Reg(EBX); // saved previous native BP
541        asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBX);
542        asm.emitPOP_Reg(EBX); // throw away JNI env
543        asm.emitPOP_Reg(EBP); // restore non-volatile EBP
544        asm.emitPOP_Reg(EBX); // restore non-volatile EBX
545        asm.emitPOP_Reg(EDI); // restore non-volatile EDI
546        asm.emitPOP_Reg(S0);  // throw away cmid
547        asm.emitPOP_RegDisp(THREAD_REGISTER, ArchEntrypoints.framePointerField.getOffset());
548    
549        // (12) Return to caller
550        // pop parameters from stack (Note that parameterWords does not include "this")
551        if (method.isStatic()) {
552          asm.emitRET_Imm(method.getParameterWords() << LG_WORDSIZE);
553        } else {
554          asm.emitRET_Imm((method.getParameterWords() + 1) << LG_WORDSIZE);
555        }
556    
557        MachineCode machineCode = new ArchitectureSpecific.MachineCode(asm.getMachineCodes(), null);
558        cm.compileComplete(machineCode.getInstructions());
559        return cm;
560      }
561    
562      /**
563       * Handle the C to Java transition:  JNI methods in JNIFunctions.java.
564       * Create a prologue for the baseline compiler.
565       * <pre>
566       * NOTE:
567       *   -We need THREAD_REGISTER to access Java environment; we can get it from
568       *    the JNIEnv* (which is an interior pointer to the JNIEnvironment)
569       *   -Unlike the powerPC scheme which has a special prolog preceding
570       *    the normal Java prolog, the Intel scheme replaces the Java prolog
571       *    completely with the special prolog
572       *
573       *            Stack on entry            Stack at end of prolog after call
574       *             high memory                       high memory
575       *            |            |                   |            |
576       *    EBP ->  |saved FP    |                   |saved FP    |
577       *            |  ...       |                   |  ...       |
578       *            |            |                   |            |
579       *            |arg n-1     |                   |arg n-1     |
580       * native     |  ...       |                   |  ...       |
581       * caller     |arg 0       | JNIEnv*           |arg 0       | JNIEnvironment
582       *    ESP ->  |return addr |                   |return addr |
583       *            |            |           EBP ->  |saved FP    | outer most native frame pointer
584       *            |            |                   |methodID    | normal MethodID for JNI function
585       *            |            |                   |saved JavaFP| offset to preceeding java frame
586       *            |            |                   |saved nonvol| to be used for nonvolatile storage
587       *            |            |                   |  ...       |   including ebp on entry
588       *            |            |                   |arg 0       | copied in reverse order (JNIEnvironment)
589       *            |            |                   |  ...       |
590       *            |            |           ESP ->  |arg n-1     |
591       *            |            |                   |            | normally compiled Java code continue
592       *            |            |                   |            |
593       *            |            |                   |            |
594       *            |            |                   |            |
595       *             low memory                        low memory
596       * </pre>
597       */
598      public static void generateGlueCodeForJNIMethod(Assembler asm, NormalMethod method, int methodID) {
599        // Variable tracking the depth of the stack as we generate the prologue
600        int stackDepth=0;
601        // 1st word of header = return address already pushed by CALL
602        // 2nd word of header = space for frame pointer
603        if (VM.VerifyAssertions) VM._assert(STACKFRAME_FRAME_POINTER_OFFSET == stackDepth << LG_WORDSIZE);
604        asm.emitPUSH_Reg(EBP);
605        stackDepth--;
606        // start new frame:  set FP to point to the new frame
607        if (VM.BuildFor32Addr) {
608          asm.emitMOV_Reg_Reg(EBP, SP);
609        } else {
610          asm.emitMOV_Reg_Reg_Quad(EBP, SP);
611        }
612        // set 3rd word of header: method ID
613        if (VM.VerifyAssertions) VM._assert(STACKFRAME_METHOD_ID_OFFSET == stackDepth << LG_WORDSIZE);
614        asm.emitPUSH_Imm(methodID);
615        stackDepth--;
616        // buy space for the SAVED_JAVA_FP
617        if (VM.VerifyAssertions) VM._assert(STACKFRAME_BODY_OFFSET == stackDepth << LG_WORDSIZE);
618        asm.emitPUSH_Reg(T0);
619        stackDepth--;
620        // store non-volatiles
621        for (GPR r : NATIVE_NONVOLATILE_GPRS) {
622          if(r != EBP) {
623            asm.emitPUSH_Reg(r);
624          } else {
625            asm.emitPUSH_RegInd(EBP); // save original EBP value
626          }
627          stackDepth--;
628        }
629        for (FloatingPointMachineRegister r : NATIVE_NONVOLATILE_FPRS) {
630          // TODO: we assume non-volatile will hold at most a double
631          asm.emitPUSH_Reg(T0); // adjust space for double
632          asm.emitPUSH_Reg(T0);
633          stackDepth-=2;
634          if (r instanceof XMM) {
635            asm.emitMOVSD_RegInd_Reg(SP, (XMM)r);
636          } else {
637            // NB this will fail for anything other than FPR0
638            asm.emitFST_RegInd_Reg_Quad(SP, (FPR)r);
639          }
640        }
641        if (VM.VerifyAssertions) VM._assert(stackDepth << LG_WORDSIZE == STACKFRAME_BODY_OFFSET - (SAVED_GPRS_FOR_JNI << LG_WORDSIZE), "of2fp="+stackDepth+" sg4j="+SAVED_GPRS_FOR_JNI);
642        // Adjust first param from JNIEnv* to JNIEnvironment.
643        final Offset firstStackArgOffset = Offset.fromIntSignExtend(2 * WORDSIZE);
644        if (jniExternalFunctionsFieldOffset != 0) {
645          if (NATIVE_PARAMETER_GPRS.length > 0) {
646            if (VM.BuildFor32Addr) {
647              asm.emitSUB_Reg_Imm(NATIVE_PARAMETER_GPRS[0], jniExternalFunctionsFieldOffset);
648            } else {
649              asm.emitSUB_Reg_Imm_Quad(NATIVE_PARAMETER_GPRS[0], jniExternalFunctionsFieldOffset);
650            }
651          } else {
652            if (VM.BuildFor32Addr) {
653              asm.emitSUB_RegDisp_Imm(EBP, firstStackArgOffset, jniExternalFunctionsFieldOffset);
654            } else {
655              asm.emitSUB_RegDisp_Imm_Quad(EBP, firstStackArgOffset, jniExternalFunctionsFieldOffset);
656            }
657          }
658        }
659    
660        // copy the arguments in reverse order
661        final TypeReference[] argTypes = method.getParameterTypes(); // does NOT include implicit this or class ptr
662        Offset stackArgOffset = firstStackArgOffset;
663        final int startOfStackedArgs = stackDepth+1; // negative value relative to EBP
664        int argGPR = 0;
665        int argFPR = 0;
666        for (TypeReference argType : argTypes) {
667          if (argType.isFloatType()) {
668            if (argFPR < NATIVE_PARAMETER_FPRS.length) {
669              asm.emitPUSH_Reg(T0); // adjust stack
670              if (VM.BuildForSSE2) {
671                asm.emitMOVSS_RegInd_Reg(SP, (XMM)NATIVE_PARAMETER_FPRS[argFPR]);
672              } else {
673                asm.emitFSTP_RegInd_Reg(SP, FP0);
674              }
675              argFPR++;
676            } else {
677              asm.emitPUSH_RegDisp(EBP, stackArgOffset);
678              stackArgOffset = stackArgOffset.plus(WORDSIZE);
679            }
680            stackDepth--;
681          } else if (argType.isDoubleType()) {
682            if (argFPR < NATIVE_PARAMETER_FPRS.length) {
683              asm.emitPUSH_Reg(T0); // adjust stack
684              asm.emitPUSH_Reg(T0);
685              if (VM.BuildForSSE2) {
686                asm.emitMOVSD_RegInd_Reg(SP, (XMM)NATIVE_PARAMETER_FPRS[argGPR]);
687              } else {
688                asm.emitFSTP_RegInd_Reg_Quad(SP, FP0);
689              }
690              argFPR++;
691            } else {
692              if (VM.BuildFor32Addr) {
693                asm.emitPUSH_RegDisp(EBP, stackArgOffset.plus(WORDSIZE));
694                asm.emitPUSH_RegDisp(EBP, stackArgOffset);
695                stackArgOffset = stackArgOffset.plus(2*WORDSIZE);
696              } else {
697                asm.emitPUSH_Reg(T0); // adjust stack
698                asm.emitPUSH_RegDisp(EBP, stackArgOffset);
699                stackArgOffset = stackArgOffset.plus(WORDSIZE);
700              }
701            }
702            stackDepth-=2;
703          } else if (argType.isLongType()) {
704            if (VM.BuildFor32Addr) {
705              if (argGPR+1 < NATIVE_PARAMETER_GPRS.length) {
706                asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]);
707                asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR+1]);
708                argGPR+=2;
709              } else if (argGPR < NATIVE_PARAMETER_GPRS.length) {
710                asm.emitPUSH_RegDisp(EBP, stackArgOffset);
711                asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]);
712                argGPR++;
713                stackArgOffset = stackArgOffset.plus(WORDSIZE);
714              } else {
715                asm.emitPUSH_RegDisp(EBP, stackArgOffset.plus(WORDSIZE));
716                asm.emitPUSH_RegDisp(EBP, stackArgOffset);
717                stackArgOffset = stackArgOffset.plus(WORDSIZE*2);
718              }
719              stackDepth-=2;
720            } else {
721              asm.emitPUSH_Reg(T0); // adjust stack
722              if (argGPR < NATIVE_PARAMETER_GPRS.length) {
723                asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]);
724                argGPR++;
725              } else {
726                asm.emitPUSH_RegDisp(EBP, stackArgOffset);
727                stackDepth-=2;
728                stackArgOffset = stackArgOffset.plus(WORDSIZE);
729              }
730              stackDepth-=2;
731            }
732          } else {
733            // Reference, int or smaller type
734            // NB we don't convert JNI references to true references (as
735            // in the entry/exit to a JNI method) as our JNI functions
736            // expect integer arguments
737            if (argGPR < NATIVE_PARAMETER_GPRS.length) {
738              asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]);
739              argGPR++;
740            } else {
741              asm.emitPUSH_RegDisp(EBP, stackArgOffset);
742              stackArgOffset = stackArgOffset.plus(WORDSIZE);
743            }
744            stackDepth--;
745          }
746        }
747    
748        // START of code sequence to atomically change thread status from
749        // IN_JNI to IN_JAVA, looping in a call to
750        // RVMThread.leaveJNIBlockedFromJNIFunctionCallMethod if
751        // BLOCKED_IN_NATIVE
752        int retryLabel = asm.getMachineCodeIndex(); // backward branch label
753    
754        // Restore THREAD_REGISTER from JNIEnvironment
755        if (VM.BuildFor32Addr) {
756          asm.emitMOV_Reg_RegDisp(EBX, EBP, Offset.fromIntSignExtend((startOfStackedArgs-1) * WORDSIZE));   // pick up arg 0 (from our frame)
757        } else {
758          asm.emitMOV_Reg_RegDisp_Quad(EBX, EBP, Offset.fromIntSignExtend((startOfStackedArgs-1) * WORDSIZE));   // pick up arg 0 (from our frame)
759        }
760        ThreadLocalState.emitLoadThread(asm, EBX, Entrypoints.JNIEnvSavedTRField.getOffset());
761    
762        // what we need to keep in mind at this point:
763        // - EBX has JNI env (but it's nonvolatile)
764        // - EBP has the FP (but it's nonvolatile)
765        // - stack has the args but not the locals
766        // - TR has been restored
767    
768        // attempt to change the thread state to IN_JAVA
769        asm.emitMOV_Reg_Imm(T0, RVMThread.IN_JNI);
770        asm.emitMOV_Reg_Imm(T1, RVMThread.IN_JAVA);
771        ThreadLocalState.emitCompareAndExchangeField(
772          asm,
773          Entrypoints.execStatusField.getOffset(),
774          T1);
775    
776        // if we succeeded, move on, else go into slow path
777        ForwardReference doneLeaveJNIRef = asm.forwardJcc(Assembler.EQ);
778    
779        // make the slow call
780        asm.emitCALL_Abs(
781          Magic.getTocPointer().plus(
782            Entrypoints.leaveJNIBlockedFromJNIFunctionCallMethod.getOffset()));
783    
784        // arrive here when we've switched to IN_JAVA
785        doneLeaveJNIRef.resolve(asm);
786        // END of code sequence to change state from IN_JNI to IN_JAVA
787    
788        // status is now IN_JAVA. GC can not occur while we execute on a processor
789        // in this state, so it is safe to access fields of objects.
790        // RVM TR register has been restored and EBX contains a pointer to
791        // the thread's JNIEnvironment.
792    
793        // done saving, bump SP to reserve room for the local variables
794        // SP should now be at the point normally marked as emptyStackOffset
795        int numLocalVariables = method.getLocalWords() - method.getParameterWords();
796        // TODO: optimize this space adjustment
797        if (VM.BuildFor32Addr) {
798          asm.emitSUB_Reg_Imm(SP, (numLocalVariables << LG_WORDSIZE));
799        } else {
800          asm.emitSUB_Reg_Imm_Quad(SP, (numLocalVariables << LG_WORDSIZE));
801        }
802        // Retrieve -> preceeding "top" java FP from jniEnv and save in current
803        // frame of JNIFunction
804        if (VM.BuildFor32Addr) {
805          asm.emitMOV_Reg_RegDisp(S0, EBX, Entrypoints.JNITopJavaFPField.getOffset());
806        } else {
807          asm.emitMOV_Reg_RegDisp_Quad(S0, EBX, Entrypoints.JNITopJavaFPField.getOffset());
808        }
809        // get offset from current FP and save in hdr of current frame
810        if (VM.BuildFor32Addr) {
811          asm.emitSUB_Reg_Reg(S0, EBP);
812          asm.emitMOV_RegDisp_Reg(EBP, SAVED_JAVA_FP_OFFSET, S0);
813        } else {
814          asm.emitSUB_Reg_Reg_Quad(S0, EBP);
815          asm.emitMOV_RegDisp_Reg_Quad(EBP, SAVED_JAVA_FP_OFFSET, S0);
816        }
817    
818        // clobber the saved frame pointer with that from the JNIEnvironment (work around for omit-frame-pointer)
819        if (VM.BuildFor32Addr) {
820          asm.emitMOV_Reg_RegDisp(S0, EBX, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset());
821          asm.emitMOV_RegInd_Reg(EBP, S0);
822        } else {
823          asm.emitMOV_Reg_RegDisp_Quad(S0, EBX, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset());
824          asm.emitMOV_RegInd_Reg_Quad(EBP, S0);
825        }
826    
827        // put framePointer in Thread following Jikes RVM conventions.
828        ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), EBP);
829    
830        // at this point: TR has been restored &
831        // processor status = IN_JAVA,
832        // arguments for the call have been setup, space on the stack for locals
833        // has been acquired.
834    
835        // finally proceed with the normal Java compiled code
836        // skip the thread switch test for now, see BaselineCompilerImpl.genThreadSwitchTest(true)
837        //asm.emitNOP(1); // end of prologue marker
838      }
839    
840      /**
841       * Handle the C to Java transition:  JNI methods in JNIFunctions.java.
842       * Create an epilogue for the baseline compiler.
843       */
844      public static void generateEpilogForJNIMethod(Assembler asm, RVMMethod method) {
845        // assume RVM TR regs still valid. potentially T1 & T0 contain return
846        // values and should not be modified. we use regs saved in prolog and restored
847        // before return to do whatever needs to be done.
848    
849        if (VM.BuildFor32Addr) {
850          // if returning long, switch the order of the hi/lo word in T0 and T1
851          if (method.getReturnType().isLongType()) {
852            asm.emitPUSH_Reg(T1);
853            asm.emitMOV_Reg_Reg(T1, T0);
854            asm.emitPOP_Reg(T0);
855          } else {
856            if (SSE2_FULL && VM.BuildFor32Addr) {
857              // Marshall from XMM0 -> FP0
858              if (method.getReturnType().isDoubleType()) {
859                if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr);
860                asm.emitMOVSD_RegDisp_Reg(THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset(), XMM0);
861                asm.emitFLD_Reg_RegDisp_Quad(FP0, THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset());
862              } else if (method.getReturnType().isFloatType()) {
863                if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr);
864                asm.emitMOVSS_RegDisp_Reg(THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset(), XMM0);
865                asm.emitFLD_Reg_RegDisp(FP0, THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset());
866              }
867            }
868          }
869        }
870    
871        // current processor status is IN_JAVA, so we only GC at yieldpoints
872    
873        // S0 <- JNIEnvironment
874        if (VM.BuildFor32Addr) {
875          asm.emitMOV_Reg_RegDisp(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset());
876        } else {
877          asm.emitMOV_Reg_RegDisp_Quad(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset());
878        }
879    
880        // set jniEnv TopJavaFP using value saved in frame in prolog
881        if (VM.BuildFor32Addr) {
882          asm.emitMOV_Reg_RegDisp(EDI, EBP, SAVED_JAVA_FP_OFFSET);      // EDI<-saved TopJavaFP (offset)
883          asm.emitADD_Reg_Reg(EDI, EBP);                                // change offset from FP into address
884          asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNITopJavaFPField.getOffset(), EDI); // jniEnv.TopJavaFP <- EDI
885        } else {
886          asm.emitMOV_Reg_RegDisp_Quad(EDI, EBP, SAVED_JAVA_FP_OFFSET);      // EDI<-saved TopJavaFP (offset)
887          asm.emitADD_Reg_Reg_Quad(EDI, EBP);                                // change offset from FP into address
888          asm.emitMOV_RegDisp_Reg_Quad(S0, Entrypoints.JNITopJavaFPField.getOffset(), EDI); // jniEnv.TopJavaFP <- EDI
889        }
890    
891        // NOTE: we could save the TR in the JNI env, but no need, that would have
892        // already been done.
893    
894        // what's going on here:
895        // - SP and EBP have important stuff in them, but that's fine, since
896        //   a call will restore SP and EBP is non-volatile for RVM code
897        // - TR still refers to the thread
898    
899        // save return values
900        asm.emitPUSH_Reg(T0);
901        asm.emitPUSH_Reg(T1);
902    
903        // attempt to change the thread state to IN_JNI
904        asm.emitMOV_Reg_Imm(T0, RVMThread.IN_JAVA);
905        asm.emitMOV_Reg_Imm(T1, RVMThread.IN_JNI);
906        ThreadLocalState.emitCompareAndExchangeField(
907          asm,
908          Entrypoints.execStatusField.getOffset(),
909          T1);
910    
911        // if success, skip the slow path call
912        ForwardReference doneEnterJNIRef = asm.forwardJcc(Assembler.EQ);
913    
914        // fast path failed, make the call
915        asm.emitCALL_Abs(
916          Magic.getTocPointer().plus(
917            Entrypoints.enterJNIBlockedFromJNIFunctionCallMethod.getOffset()));
918    
919        // OK - we reach here when we have set the state to IN_JNI
920        doneEnterJNIRef.resolve(asm);
921    
922        // restore return values
923        asm.emitPOP_Reg(T1);
924        asm.emitPOP_Reg(T0);
925    
926        // reload native/C nonvolatile regs - saved in prolog
927        for (FloatingPointMachineRegister r : NATIVE_NONVOLATILE_FPRS) {
928          // TODO: we assume non-volatile will hold at most a double
929          if (r instanceof XMM) {
930            asm.emitMOVSD_Reg_RegInd((XMM)r, SP);
931          } else {
932            // NB this will fail for anything other than FPR0
933            asm.emitFLD_Reg_RegInd_Quad((FPR)r, SP);
934          }
935          asm.emitPOP_Reg(T0); // adjust space for double
936          asm.emitPOP_Reg(T0);
937        }
938        // NB when EBP is restored it isn't our outer most EBP but rather than
939        // nonvolatile push as the 1st instruction of the prologue
940        for (int i=NATIVE_NONVOLATILE_GPRS.length-1; i >= 0; i--) {
941          GPR r = NATIVE_NONVOLATILE_GPRS[i];
942          asm.emitPOP_Reg(r);
943        }
944    
945        // Discard JNIEnv, CMID and outer most native frame pointer
946        if (VM.BuildFor32Addr) {
947          asm.emitADD_Reg_Imm(SP, 3*WORDSIZE); // discard current stack frame
948        } else {
949          asm.emitADD_Reg_Imm_Quad(SP, 3*WORDSIZE); // discard current stack frame
950        }
951        asm.emitRET();                // return to caller
952      }
953    }