/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * JNI method invocation.  This is used to call a C/C++ JNI method.  The
 * argument list has to be pushed onto the native stack according to
 * local calling conventions.
 *
 * This version supports the "old" ARM ABI.
 */

#include <machine/cpu-features.h>

#ifndef __ARM_EABI__

/*
Function prototype:

void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc,
    const u4* argv, const char* signature, void* func, JValue* pReturn)

The method we are calling has the form:

  return_type func(JNIEnv* pEnv, ClassObject* clazz, ...)
    -or-
  return_type func(JNIEnv* pEnv, Object* this, ...)

We receive a collection of 32-bit values which correspond to arguments from
the interpreter (e.g. float occupies one, double occupies two).  It's up to
us to convert these into local calling conventions.
 */

/*
ARM ABI notes:

r0-r3 hold first 4 args to a method
r9 is given special treatment in some situations, but not for us
r10 (sl) seems to be generally available
r11 (fp) is used by gcc
r12 (ip) is scratch -- not preserved across method calls
r13 (sp) should be managed carefully in case a signal arrives
r14 (lr) must be preserved
r15 (pc) can be tinkered with directly

r0 holds returns <= 4 bytes
r0-r1 hold returns of 5-8 bytes, low word in r0

Stack is "full descending".  Only the arguments that don't fit in the first 4
registers are placed on the stack.  "sp" points at the first stacked argument
(i.e. the 5th arg).

VFP: single-precision results in s0, double-precision results in d0.

Happily we don't have to do anything special here -- the args from the
interpreter work directly as C/C++ args on ARM (with the "classic" ABI).
*/

    .text
    .align  2
    .global dvmPlatformInvoke
    .type   dvmPlatformInvoke, %function

/*
On entry:
  r0  JNIEnv
  r1  clazz (NULL for virtual method calls, non-NULL for static)
  r2  arg info (ignored)
  r3  argc
  [sp]     argv
  [sp,#4]  signature (ignored)
  [sp,#8]  func
  [sp,#12] pReturn
*/
dvmPlatformInvoke:
    @ Standard gcc stack frame setup.  We don't need to push the original
    @ sp or the current pc if "-fomit-frame-pointer" is in use for the
    @ rest of the code.  If we don't plan to use a debugger we can speed
    @ this up a little.
    mov     ip, sp
    stmfd   sp!, {r4, r5, r6, fp, ip, lr, pc}
    sub     fp, ip, #4          @ set up fp, same way gdb does

    @ We need to push a variable number of arguments onto the stack.
    @ Rather than keep a count and pop them off after, we just hold on to
    @ the stack pointers.
    @
    @ In theory we don't need to keep sp -- we can do an ldmdb instead of
    @ an ldmia -- but we're doing the gcc frame trick where we push the
    @ pc on with stmfd and don't pop it off.
    mov     r4, ip
    mov     r5, sp

    @ argc is already in a scratch register (r3).  Put argv into one.  Note
    @ argv can't go into r0-r3 because we need to use it to load those.
    ldr     ip, [r4, #0]        @ ip <-- argv

    @ Is this a static method?
    cmp     r1, #0

    @ No: set r1 to *argv++, and set argc--.
    @ (r0=pEnv, r1=this)
    ldreq   r1, [ip], #4
    subeq   r3, r3, #1

    @ While we still have the use of r2/r3, copy excess args from argv
    @ to the stack.  We need to push the last item in argv first, and we
    @ want the first two items in argv to end up in r2/r3.
    subs    r3, r3, #2
    ble     .Lno_copy

    @ If there are N args, we want to skip 0 and 1, and push (N-1)..2.  We
    @ have N-2 in r3.  If we set argv=argv+1, we can count from N-2 to 1
    @ inclusive and get the right set of args.
    add     r6, ip, #4

.Lcopy:
    @ *--sp = argv[count]
    ldr     r2, [r6, r3, lsl #2]
    str     r2, [sp, #-4]!
    subs    r3, r3, #1
    bne     .Lcopy

.Lno_copy:
    @ Load the last two args.  These are coming out of the interpreted stack,
    @ and the VM preserves an overflow region at the bottom, so it should be
    @ safe to load two items out of argv even if we're at the end.
    ldr     r2, [ip]
    ldr     r3, [ip, #4]

    @ Show time.  Tuck the pc into lr and load the pc from the method
    @ address supplied by the caller.  The value for "pc" is offset by 8
    @ due to instruction prefetching.
    @
#ifdef __ARM_HAVE_PC_INTERWORK
    mov     lr, pc
    ldr     pc, [r4, #8]
#else
    ldr     ip, [r4, #8]
    blx     ip
#endif

    @ We're back, result is in r0 or (for long/double) r0-r1.
    @
    @ In theory, we need to use the "return type" arg to figure out what
    @ we have and how to return it.  However, unless we have an FPU,
    @ all we need to do is copy r0-r1 into the JValue union.
    ldr     ip, [r4, #12]
    stmia   ip, {r0-r1}

#ifdef __ARM_HAVE_PC_INTERWORK
    @ Restore the registers we saved and return.  Note this remaps stuff,
    @ so that "sp" comes from "ip", "pc" comes from "lr", and the "pc"
    @ we pushed on evaporates when we restore "sp".
    ldmfd   r5, {r4, r5, r6, fp, sp, pc}
#else
    ldmfd   r5, {r4, r5, r6, fp, sp, lr}
    bx      lr
#endif

#endif /*__ARM_EABI__*/