OSDN Git Service

Stage 3 of Thumb2 support. armv7-a now generates vfp code inline.
[android-x86/dalvik.git] / vm / InlineNative.c
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Inlined native functions.  These definitions replace interpreted or
19  * native implementations at runtime; "intrinsic" might be a better word.
20  */
21 #include "Dalvik.h"
22
23 #include <math.h>
24
25 #ifdef HAVE__MEMCMP16
26 /* hand-coded assembly implementation, available on some platforms */
27 //#warning "trying memcmp16"
28 //#define CHECK_MEMCMP16
29 /* "count" is in 16-bit units */
30 extern u4 __memcmp16(const u2* s0, const u2* s1, size_t count);
31 #endif
32
33 /*
34  * Some notes on "inline" functions.
35  *
36  * These are NOT simply native implementations.  A full method definition
37  * must still be provided.  Depending on the flags passed into the VM
38  * at runtime, the original or inline version may be selected by the
39  * DEX optimizer.
40  *
41  * PLEASE DO NOT use this as the default location for native methods.
42  * The difference between this and an "internal native" static method
43  * call on a 200MHz ARM 9 is roughly 370ns vs. 700ns.  The code here
44  * "secretly replaces" the other method, so you can't avoid having two
45  * implementations.  Since the DEX optimizer mode can't be known ahead
46  * of time, both implementations must be correct and complete.
47  *
48  * The only stuff that really needs to be here are methods that
49  * are high-volume or must be low-overhead, e.g. certain String/Math
50  * methods and some java.util.concurrent.atomic operations.
51  *
52  * Normally, a class is loaded and initialized the first time a static
53  * method is invoked.  This property is NOT preserved here.  If you need
54  * to access a static field in a class, you must ensure initialization
55  * yourself (cheap/easy way is to check the resolved-methods table, and
56  * resolve the method if it hasn't been).
57  *
58  * DO NOT replace "synchronized" methods.  We do not support method
59  * synchronization here.
60  *
61  * Remember that these functions are executing while the thread is in
62  * the "RUNNING" state, not the "NATIVE" state.  If you perform a blocking
63  * operation you can stall the entire VM if the GC or debugger wants to
64  * suspend the thread.  Since these are arguably native implementations
65  * rather than VM internals, prefer NATIVE to VMWAIT if you want to change
66  * the thread state.
67  *
68  * Always write results to 32-bit or 64-bit fields in "pResult", e.g. do
69  * not write boolean results to pResult->z.  The interpreter expects
70  * 32 or 64 bits to be set.
71  *
72  * Inline op methods return "false" if an exception was thrown, "true" if
73  * everything went well.
74  *
75  * DO NOT provide implementations of methods that can be overridden by a
76  * subclass, as polymorphism does not work correctly.  For safety you should
77  * only provide inline functions for classes/methods declared "final".
78  *
79  * It's best to avoid inlining the overridden version of a method.  For
80  * example, String.hashCode() is inherited from Object.hashCode().  Code
81  * calling String.hashCode() through an Object reference will run the
82  * "slow" version, while calling it through a String reference gets
83  * the inlined version.  It's best to have just one version unless there
84  * are clear performance gains.
85  *
86  * Because the actual method is not called, debugger breakpoints on these
87  * methods will not happen.  (TODO: have the code here find the original
88  * method and call it when the debugger is active.)  Additional steps have
89  * been taken to allow method profiling to produce correct results.
90  */
91
92
93 /*
94  * ===========================================================================
95  *      org.apache.harmony.dalvik.NativeTestTarget
96  * ===========================================================================
97  */
98
99 /*
100  * public static void emptyInlineMethod
101  *
102  * This exists only for benchmarks.
103  */
104 static bool org_apache_harmony_dalvik_NativeTestTarget_emptyInlineMethod(
105     u4 arg0, u4 arg1, u4 arg2, u4 arg3, JValue* pResult)
106 {
107     // do nothing
108     return true;
109 }
110
111
112 /*
113  * ===========================================================================
114  *      java.lang.String
115  * ===========================================================================
116  */
117
118 /*
119  * public char charAt(int index)
120  */
121 static bool javaLangString_charAt(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
122     JValue* pResult)
123 {
124     int count, offset;
125     ArrayObject* chars;
126
127     /* null reference check on "this" */
128     if (!dvmValidateObject((Object*) arg0))
129         return false;
130
131     //LOGI("String.charAt this=0x%08x index=%d\n", arg0, arg1);
132     count = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_count);
133     if ((s4) arg1 < 0 || (s4) arg1 >= count) {
134         dvmThrowException("Ljava/lang/StringIndexOutOfBoundsException;", NULL);
135         return false;
136     } else {
137         offset = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_offset);
138         chars = (ArrayObject*)
139             dvmGetFieldObject((Object*) arg0, gDvm.offJavaLangString_value);
140
141         pResult->i = ((const u2*) chars->contents)[arg1 + offset];
142         return true;
143     }
144 }
145
146 #ifdef CHECK_MEMCMP16
147 /*
148  * Utility function when we're evaluating alternative implementations.
149  */
150 static void badMatch(StringObject* thisStrObj, StringObject* compStrObj,
151     int expectResult, int newResult, const char* compareType)
152 {
153     ArrayObject* thisArray;
154     ArrayObject* compArray;
155     const char* thisStr;
156     const char* compStr;
157     int thisOffset, compOffset, thisCount, compCount;
158
159     thisCount =
160         dvmGetFieldInt((Object*) thisStrObj, gDvm.offJavaLangString_count);
161     compCount =
162         dvmGetFieldInt((Object*) compStrObj, gDvm.offJavaLangString_count);
163     thisOffset =
164         dvmGetFieldInt((Object*) thisStrObj, gDvm.offJavaLangString_offset);
165     compOffset =
166         dvmGetFieldInt((Object*) compStrObj, gDvm.offJavaLangString_offset);
167     thisArray = (ArrayObject*)
168         dvmGetFieldObject((Object*) thisStrObj, gDvm.offJavaLangString_value);
169     compArray = (ArrayObject*)
170         dvmGetFieldObject((Object*) compStrObj, gDvm.offJavaLangString_value);
171
172     thisStr = dvmCreateCstrFromString(thisStrObj);
173     compStr = dvmCreateCstrFromString(compStrObj);
174
175     LOGE("%s expected %d got %d\n", compareType, expectResult, newResult);
176     LOGE(" this (o=%d l=%d) '%s'\n", thisOffset, thisCount, thisStr);
177     LOGE(" comp (o=%d l=%d) '%s'\n", compOffset, compCount, compStr);
178     dvmPrintHexDumpEx(ANDROID_LOG_INFO, LOG_TAG,
179         ((const u2*) thisArray->contents) + thisOffset, thisCount*2,
180         kHexDumpLocal);
181     dvmPrintHexDumpEx(ANDROID_LOG_INFO, LOG_TAG,
182         ((const u2*) compArray->contents) + compOffset, compCount*2,
183         kHexDumpLocal);
184     dvmAbort();
185 }
186 #endif
187
188 /*
189  * public int compareTo(String s)
190  */
191 static bool javaLangString_compareTo(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
192     JValue* pResult)
193 {
194     /*
195      * Null reference check on "this".  Normally this is performed during
196      * the setup of the virtual method call.  We need to do it before
197      * anything else.  While we're at it, check out the other string,
198      * which must also be non-null.
199      */
200     if (!dvmValidateObject((Object*) arg0) ||
201         !dvmValidateObject((Object*) arg1))
202     {
203         return false;
204     }
205
206     /* quick test for comparison with itself */
207     if (arg0 == arg1) {
208         pResult->i = 0;
209         return true;
210     }
211
212     /*
213      * This would be simpler and faster if we promoted StringObject to
214      * a full representation, lining up the C structure fields with the
215      * actual object fields.
216      */
217     int thisCount, thisOffset, compCount, compOffset;
218     ArrayObject* thisArray;
219     ArrayObject* compArray;
220     const u2* thisChars;
221     const u2* compChars;
222     int i, minCount, countDiff;
223
224     thisCount = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_count);
225     compCount = dvmGetFieldInt((Object*) arg1, gDvm.offJavaLangString_count);
226     countDiff = thisCount - compCount;
227     minCount = (countDiff < 0) ? thisCount : compCount;
228     thisOffset = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_offset);
229     compOffset = dvmGetFieldInt((Object*) arg1, gDvm.offJavaLangString_offset);
230     thisArray = (ArrayObject*)
231         dvmGetFieldObject((Object*) arg0, gDvm.offJavaLangString_value);
232     compArray = (ArrayObject*)
233         dvmGetFieldObject((Object*) arg1, gDvm.offJavaLangString_value);
234     thisChars = ((const u2*) thisArray->contents) + thisOffset;
235     compChars = ((const u2*) compArray->contents) + compOffset;
236
237 #ifdef HAVE__MEMCMP16
238     /*
239      * Use assembly version, which returns the difference between the
240      * characters.  The annoying part here is that 0x00e9 - 0xffff != 0x00ea,
241      * because the interpreter converts the characters to 32-bit integers
242      * *without* sign extension before it subtracts them (which makes some
243      * sense since "char" is unsigned).  So what we get is the result of
244      * 0x000000e9 - 0x0000ffff, which is 0xffff00ea.
245      */
246     int otherRes = __memcmp16(thisChars, compChars, minCount);
247 # ifdef CHECK_MEMCMP16
248     for (i = 0; i < minCount; i++) {
249         if (thisChars[i] != compChars[i]) {
250             pResult->i = (s4) thisChars[i] - (s4) compChars[i];
251             if (pResult->i != otherRes) {
252                 badMatch((StringObject*) arg0, (StringObject*) arg1,
253                     pResult->i, otherRes, "compareTo");
254             }
255             return true;
256         }
257     }
258 # endif
259     if (otherRes != 0) {
260         pResult->i = otherRes;
261         return true;
262     }
263
264 #else
265     /*
266      * Straightforward implementation, examining 16 bits at a time.  Compare
267      * the characters that overlap, and if they're all the same then return
268      * the difference in lengths.
269      */
270     for (i = 0; i < minCount; i++) {
271         if (thisChars[i] != compChars[i]) {
272             pResult->i = (s4) thisChars[i] - (s4) compChars[i];
273             return true;
274         }
275     }
276 #endif
277
278     pResult->i = countDiff;
279     return true;
280 }
281
282 /*
283  * public boolean equals(Object anObject)
284  */
285 static bool javaLangString_equals(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
286     JValue* pResult)
287 {
288     /*
289      * Null reference check on "this".
290      */
291     if (!dvmValidateObject((Object*) arg0))
292         return false;
293
294     /* quick test for comparison with itself */
295     if (arg0 == arg1) {
296         pResult->i = true;
297         return true;
298     }
299
300     /*
301      * See if the other object is also a String.
302      *
303      * str.equals(null) is expected to return false, presumably based on
304      * the results of the instanceof test.
305      */
306     if (arg1 == 0 || ((Object*) arg0)->clazz != ((Object*) arg1)->clazz) {
307         pResult->i = false;
308         return true;
309     }
310
311     /*
312      * This would be simpler and faster if we promoted StringObject to
313      * a full representation, lining up the C structure fields with the
314      * actual object fields.
315      */
316     int thisCount, thisOffset, compCount, compOffset;
317     ArrayObject* thisArray;
318     ArrayObject* compArray;
319     const u2* thisChars;
320     const u2* compChars;
321     int i;
322
323     /* quick length check */
324     thisCount = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_count);
325     compCount = dvmGetFieldInt((Object*) arg1, gDvm.offJavaLangString_count);
326     if (thisCount != compCount) {
327         pResult->i = false;
328         return true;
329     }
330
331     thisOffset = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_offset);
332     compOffset = dvmGetFieldInt((Object*) arg1, gDvm.offJavaLangString_offset);
333     thisArray = (ArrayObject*)
334         dvmGetFieldObject((Object*) arg0, gDvm.offJavaLangString_value);
335     compArray = (ArrayObject*)
336         dvmGetFieldObject((Object*) arg1, gDvm.offJavaLangString_value);
337     thisChars = ((const u2*) thisArray->contents) + thisOffset;
338     compChars = ((const u2*) compArray->contents) + compOffset;
339
340 #ifdef HAVE__MEMCMP16
341     pResult->i = (__memcmp16(thisChars, compChars, thisCount) == 0);
342 # ifdef CHECK_MEMCMP16
343     int otherRes = (memcmp(thisChars, compChars, thisCount * 2) == 0);
344     if (pResult->i != otherRes) {
345         badMatch((StringObject*) arg0, (StringObject*) arg1,
346             otherRes, pResult->i, "equals-1");
347     }
348 # endif
349 #else
350     /*
351      * Straightforward implementation, examining 16 bits at a time.  The
352      * direction of the loop doesn't matter, and starting at the end may
353      * give us an advantage when comparing certain types of strings (e.g.
354      * class names).
355      *
356      * We want to go forward for benchmarks against __memcmp16 so we get a
357      * meaningful comparison when the strings don't match (could also test
358      * with palindromes).
359      */
360     //for (i = 0; i < thisCount; i++)
361     for (i = thisCount-1; i >= 0; --i)
362     {
363         if (thisChars[i] != compChars[i]) {
364             pResult->i = false;
365             return true;
366         }
367     }
368     pResult->i = true;
369 #endif
370
371     return true;
372 }
373
374 /*
375  * public int length()
376  */
377 static bool javaLangString_length(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
378     JValue* pResult)
379 {
380     //LOGI("String.length this=0x%08x pResult=%p\n", arg0, pResult);
381
382     /* null reference check on "this" */
383     if (!dvmValidateObject((Object*) arg0))
384         return false;
385
386     pResult->i = dvmGetFieldInt((Object*) arg0, gDvm.offJavaLangString_count);
387     return true;
388 }
389
390
391 /*
392  * ===========================================================================
393  *      java.lang.Math
394  * ===========================================================================
395  */
396
397 /*
398  * public static int abs(int)
399  */
400 static bool javaLangMath_abs_int(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
401     JValue* pResult)
402 {
403     s4 val = (s4) arg0;
404     pResult->i = (val >= 0) ? val : -val;
405     return true;
406 }
407
408 /*
409  * public static long abs(long)
410  */
411 static bool javaLangMath_abs_long(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
412     JValue* pResult)
413 {
414     union {
415         u4 arg[2];
416         s8 ll;
417     } convert;
418
419     convert.arg[0] = arg0;
420     convert.arg[1] = arg1;
421     s8 val = convert.ll;
422     pResult->j = (val >= 0) ? val : -val;
423     return true;
424 }
425
426 /*
427  * public static float abs(float)
428  */
429 static bool javaLangMath_abs_float(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
430     JValue* pResult)
431 {
432     union {
433         u4 arg;
434         float ff;
435     } convert;
436
437     /* clear the sign bit; assumes a fairly common fp representation */
438     convert.arg = arg0 & 0x7fffffff;
439     pResult->f = convert.ff;
440     return true;
441 }
442
443 /*
444  * public static double abs(double)
445  */
446 static bool javaLangMath_abs_double(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
447     JValue* pResult)
448 {
449     union {
450         u4 arg[2];
451         s8 ll;
452         double dd;
453     } convert;
454
455     /* clear the sign bit in the (endian-dependent) high word */
456     convert.arg[0] = arg0;
457     convert.arg[1] = arg1;
458     convert.ll &= 0x7fffffffffffffffULL;
459     pResult->d = convert.dd;
460     return true;
461 }
462
463 /*
464  * public static int min(int)
465  */
466 static bool javaLangMath_min_int(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
467     JValue* pResult)
468 {
469     pResult->i = ((s4) arg0 < (s4) arg1) ? arg0 : arg1;
470     return true;
471 }
472
473 /*
474  * public static int max(int)
475  */
476 static bool javaLangMath_max_int(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
477     JValue* pResult)
478 {
479     pResult->i = ((s4) arg0 > (s4) arg1) ? arg0 : arg1;
480     return true;
481 }
482
483 /*
484  * public static double sqrt(double)
485  *
486  * With ARM VFP enabled, gcc turns this into an fsqrtd instruction, followed
487  * by an fcmpd of the result against itself.  If it doesn't match (i.e.
488  * it's NaN), the libm sqrt() is invoked.
489  */
490 static bool javaLangMath_sqrt(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
491     JValue* pResult)
492 {
493     union {
494         u4 arg[2];
495         double dd;
496     } convert;
497
498     convert.arg[0] = arg0;
499     convert.arg[1] = arg1;
500     pResult->d = sqrt(convert.dd);
501     return true;
502 }
503
504 /*
505  * public static double cos(double)
506  */
507 static bool javaLangMath_cos(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
508     JValue* pResult)
509 {
510     union {
511         u4 arg[2];
512         double dd;
513     } convert;
514
515     convert.arg[0] = arg0;
516     convert.arg[1] = arg1;
517     pResult->d = cos(convert.dd);
518     return true;
519 }
520
521 /*
522  * public static double sin(double)
523  */
524 static bool javaLangMath_sin(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
525     JValue* pResult)
526 {
527     union {
528         u4 arg[2];
529         double dd;
530     } convert;
531
532     convert.arg[0] = arg0;
533     convert.arg[1] = arg1;
534     pResult->d = sin(convert.dd);
535     return true;
536 }
537
538
539 /*
540  * ===========================================================================
541  *      Infrastructure
542  * ===========================================================================
543  */
544
545 /*
546  * Table of methods.
547  *
548  * The DEX optimizer uses the class/method/signature string fields to decide
549  * which calls it can trample.  The interpreter just uses the function
550  * pointer field.
551  *
552  * IMPORTANT: you must update DALVIK_VM_BUILD in DalvikVersion.h if you make
553  * changes to this table.  Must also be kept in sync with NativeInlineOps
554  * enum in InlineNative.h.
555  */
556 const InlineOperation gDvmInlineOpsTable[] = {
557     { org_apache_harmony_dalvik_NativeTestTarget_emptyInlineMethod,
558         "Lorg/apache/harmony/dalvik/NativeTestTarget;",
559         "emptyInlineMethod", "()V" },
560
561     { javaLangString_charAt,
562         "Ljava/lang/String;", "charAt", "(I)C" },
563     { javaLangString_compareTo,
564         "Ljava/lang/String;", "compareTo", "(Ljava/lang/String;)I" },
565     { javaLangString_equals,
566         "Ljava/lang/String;", "equals", "(Ljava/lang/Object;)Z" },
567     { javaLangString_length,
568         "Ljava/lang/String;", "length", "()I" },
569
570     { javaLangMath_abs_int,
571         "Ljava/lang/Math;", "abs", "(I)I" },
572     { javaLangMath_abs_long,
573         "Ljava/lang/Math;", "abs", "(J)J" },
574     { javaLangMath_abs_float,
575         "Ljava/lang/Math;", "abs", "(F)F" },
576     { javaLangMath_abs_double,
577         "Ljava/lang/Math;", "abs", "(D)D" },
578     { javaLangMath_min_int,
579         "Ljava/lang/Math;", "min", "(II)I" },
580     { javaLangMath_max_int,
581         "Ljava/lang/Math;", "max", "(II)I" },
582     { javaLangMath_sqrt,
583         "Ljava/lang/Math;", "sqrt", "(D)D" },
584     { javaLangMath_cos,
585         "Ljava/lang/Math;", "cos", "(D)D" },
586     { javaLangMath_sin,
587         "Ljava/lang/Math;", "sin", "(D)D" },
588 };
589
590
591 /*
592  * Allocate some tables.
593  */
594 bool dvmInlineNativeStartup(void)
595 {
596 #ifdef WITH_PROFILER
597     gDvm.inlinedMethods =
598         (Method**) calloc(NELEM(gDvmInlineOpsTable), sizeof(Method*));
599     if (gDvm.inlinedMethods == NULL)
600         return false;
601 #endif
602
603     return true;
604 }
605
606 /*
607  * Free generated tables.
608  */
609 void dvmInlineNativeShutdown(void)
610 {
611 #ifdef WITH_PROFILER
612     free(gDvm.inlinedMethods);
613 #endif
614 }
615
616
617 /*
618  * Get a pointer to the inlineops table.
619  */
620 const InlineOperation* dvmGetInlineOpsTable(void)
621 {
622     return gDvmInlineOpsTable;
623 }
624
625 /*
626  * Get the number of entries in the inlineops table.
627  */
628 int dvmGetInlineOpsTableLength(void)
629 {
630     return NELEM(gDvmInlineOpsTable);
631 }
632
633 /*
634  * Make an inline call for the "debug" interpreter, used when the debugger
635  * or profiler is active.
636  */
637 bool dvmPerformInlineOp4Dbg(u4 arg0, u4 arg1, u4 arg2, u4 arg3,
638     JValue* pResult, int opIndex)
639 {
640     Thread* self = dvmThreadSelf();
641     bool result;
642
643     assert(opIndex >= 0 && opIndex < NELEM(gDvmInlineOpsTable));
644
645 #ifdef WITH_PROFILER
646     /*
647      * Populate the methods table on first use.  It's possible the class
648      * hasn't been resolved yet, so we need to do the full "calling the
649      * method for the first time" routine.  (It's probably okay to skip
650      * the access checks.)
651      *
652      * Currently assuming that we're only inlining stuff loaded by the
653      * bootstrap class loader.  This is a safe assumption for many reasons.
654      */
655     Method* method = gDvm.inlinedMethods[opIndex];
656     if (method == NULL) {
657         ClassObject* clazz;
658         
659         clazz = dvmFindClassNoInit(
660                 gDvmInlineOpsTable[opIndex].classDescriptor, NULL);
661         if (clazz == NULL) {
662             LOGW("Warning: can't find class '%s'\n", clazz->descriptor);
663             goto skip_prof;
664         }
665         method = dvmFindDirectMethodByDescriptor(clazz,
666                     gDvmInlineOpsTable[opIndex].methodName,
667                     gDvmInlineOpsTable[opIndex].methodSignature);
668         if (method == NULL)
669             method = dvmFindVirtualMethodByDescriptor(clazz,
670                         gDvmInlineOpsTable[opIndex].methodName,
671                         gDvmInlineOpsTable[opIndex].methodSignature);
672         if (method == NULL) {
673             LOGW("Warning: can't find method %s.%s %s\n",
674                 clazz->descriptor,
675                 gDvmInlineOpsTable[opIndex].methodName,
676                 gDvmInlineOpsTable[opIndex].methodSignature);
677             goto skip_prof;
678         }
679
680         gDvm.inlinedMethods[opIndex] = method;
681         IF_LOGV() {
682             char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
683             LOGV("Registered for profile: %s.%s %s\n",
684                 method->clazz->descriptor, method->name, desc);
685             free(desc);
686         }
687     }
688
689     TRACE_METHOD_ENTER(self, method);
690     result = (*gDvmInlineOpsTable[opIndex].func)(arg0, arg1, arg2, arg3,
691                 pResult);
692     TRACE_METHOD_EXIT(self, method);
693     return result;
694
695 skip_prof:
696 #endif
697     return (*gDvmInlineOpsTable[opIndex].func)(arg0, arg1, arg2, arg3, pResult);
698 }