/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   nativeglue.c -- glue for calling between Java<=>C.
   Created: Chris Toshok <toshok@hungry.com>
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "runtimeint.h"
#include "arch.h"
#include "qop.h"
#include "ClazzFile.h"
#include "compat.h"

#include "op_stack.h"

#ifdef HAVE_LIBFFI
/* XXX there needs to be a better solution to this. */
#undef PACKAGE
#undef VERSION
#include "ffi.h"
#endif

extern PRLogModuleInfo* nativeGlueLm;

/*
 * Convert (any/class/Name, method) to Java_any_class_Name_method.
 */
static void
create_short_native_name(HungryEnv *henv,
                         char *prefix,
                         char *classname,
                         MethodStruct *method,
                         char *func_name,
                         int func_name_length)
{
  int ret_val;
  char hacked_name[1000];
  char hacked_method[1000];

  if (HVM_SigFormatStringToNativeName_buf(henv, classname,
                                          hacked_name, sizeof(hacked_name))
      || HVM_SigFormatStringToNativeName_buf(henv, method->name,
                                             hacked_method, sizeof(hacked_method)))
    {
      /* XXX */
      abort();
    }

  ret_val = PR_snprintf(func_name, func_name_length,
#if defined HAVE_LIBFFI
                        "%s_%s_%s",
#else
                        "%s_%s_%s_stub",
#endif
                        prefix, hacked_name, hacked_method);

  if (-1 == ret_val)
    { /* XXX Output was truncated.  What should we do? */
      fprintf(stderr, "Error: Class method %s.%s truncated.\n",
              classname, method->name);
    }

  slashes_to_underscores(func_name);
}


/*
 * Convert (any/class/Name, method) to Java_any_class_Name_method__sig.
 */
static void
create_long_native_name(HungryEnv *henv,
                        char *prefix,
                        char *classname,
                        MethodStruct *method,
                        char *func_name,
                        int func_name_length)
{
  int retval;
  char hacked_name[1000];
  char hacked_method[1000];
  char hacked_sig[1000];

  if (HVM_SigFormatStringToNativeName_buf(henv, classname,
                                          hacked_name, sizeof(hacked_name))
      || HVM_SigFormatStringToNativeName_buf(henv, method->name,
                                             hacked_method, sizeof(hacked_method))
      || HVM_SigFormatStringToNativeName_buf(henv, method->sig_str,
                                             hacked_sig, sizeof(hacked_sig)))
    {
      /* XXX */
      abort();
    }

  retval = PR_snprintf(func_name, func_name_length,
#if defined HAVE_LIBFFI
                       "%s_%s_%s__%s",
#else
                       "%s_%s_%s__%s_stub",
#endif
                       prefix, hacked_name, hacked_method, hacked_sig);

  if (-1 == retval)
    { /* XXX Output was truncated.  What should we do? */
      fprintf(stderr, "Error: Class method %s.%s truncated.\n",
              classname, method->name);
    }

  slashes_to_underscores(func_name);
}

static void
resolve_native_func(HungryEnv *henv, MethodStruct *method)
{
  char native_func_name[1024];
  char *class_name = getClassName(henv, method->clazz);

  /* check for Hungry short, then JNI short, then Hungry long, the JNI long */
  create_short_native_name(henv, "Hungry", class_name, method,
                           native_func_name, sizeof(native_func_name));

  method->native_func = (NativeFunc)HVM_DllFindFunction(native_func_name);

  if (NULL == method->native_func)
    {
#ifdef WITH_JNI
      create_short_native_name(henv, "Java", class_name, method,
                               native_func_name, sizeof(native_func_name));

      method->native_func = (NativeFunc)HVM_DllFindFunction(native_func_name);
      if (method->native_func)
        method->jni_call = PR_TRUE;
#endif

      if (NULL == method->native_func)
        {
          create_long_native_name(henv, "Hungry", class_name, method,
                                  native_func_name, sizeof(native_func_name));

          method->native_func = (NativeFunc)HVM_DllFindFunction(native_func_name);

#ifdef WITH_JNI
          if (NULL == method->native_func)
            {
              create_long_native_name(henv, "Java", class_name, method,
                                      native_func_name, sizeof(native_func_name));

              method->native_func = (NativeFunc)HVM_DllFindFunction(native_func_name);
              if (method->native_func)
                method->jni_call = PR_TRUE;
            }
#endif
        }
    }

  if (method->native_func == NULL) /* what do we do here? */
    {
      char *msg;

      msg = PR_smprintf("%s.%s", class_name, method->name);

          PR_ASSERT(NULL != msg);

      HVM_ExceptionThrow(henv, java_lang_UnsatisfiedLinkError,
                      msg);
      PR_DELETE(msg);
    }
}

#ifdef HAVE_LIBFFI
static ffi_type *
sig_to_ffi_type(char *sig)
{
  switch(*sig)
    {
    case 'Z':  return &ffi_type_uint8;
    case 'B':  return &ffi_type_sint8;
    case 'C':  return &ffi_type_uint16;
    case 'S':  return &ffi_type_sint16;
    case 'I':  return &ffi_type_sint32;
    case 'F':  return &ffi_type_float;
    case 'D':  return &ffi_type_double;
    case 'J':  return &ffi_type_sint64;
    case 'V':  return &ffi_type_void;
    case 'L':  return &ffi_type_pointer;
    case '[':  return &ffi_type_pointer;
    default:
      PR_ASSERT(0);
      return NULL;
    }
}

static void*
sig_to_value_addr(char *sig,
                  InterpValue *v)
{
  switch (*sig)
    {
    case 'Z':  return &v->z;
    case 'B':  return &v->b;
    case 'C':  return &v->c;
    case 'S':  return &v->s;
    case 'I':  return &v->i;
    case 'F':  return &v->f;
    case 'D':  return &v->d;
    case 'J':  return &v->j;
    case 'L':  case '[':  return &v->l;
    case 'V':  return NULL;
    default:
      PR_ASSERT(0);
      return NULL;
    }
}
#endif

static InterpValue
do_native_method_call_with_args(HungryEnv *henv,
                                MethodStruct *method,
                                InterpValue *args,
                                HVMSignature *sig)
{
  StackFrame *new_frame = push_frame(henv, 0);

  new_frame->flags |= FRAME_NATIVE;
  new_frame->method = method;

  /* get the function pointer for this native method, if we don't already
     know it. */
  if (!method->native_func)
    resolve_native_func(henv, method);
  if (!method->native_func)
    {
      /* pop the frame off now so we can't possibly catch the exception */
      pop_frame(henv);

      return henv->return_value.value;
    }

  new_frame->opstack_top = henv->op_stack->stack_top;

  if (method->access_flags & ACC_STATIC)
    new_frame->this_pointer = NULL;
  else
    new_frame->this_pointer = args[1].l;

  /* XXX this is a problem since if the native method can't be found,
     and it's supposed to be synchronous, we end up exitting the monitor up
     above in pop_frame. */
  maybe_enter_monitor_for_method(henv,
                                 new_frame->method,
                                 new_frame->this_pointer);


#ifdef HAVE_LIBFFI
  {
    void *rvalue;
    void **params;
    int i;
    int num_params = HVM_SigNumParams(henv, sig);

    params = (void**)PR_MALLOC(sizeof(void*) * (num_params + 2));

    if (!method->cif_created)
      {
        ffi_type** atypes;
        ffi_type* rtype = sig_to_ffi_type(sig->method.return_type);
        atypes = (ffi_type**)PR_MALLOC(sizeof(ffi_type*) * (num_params + 2));

        /*
        ** these never change.
        **
        ** parameter 0 is the JNIEnv* (or HungryEnv*)
        **
        ** parameter 1 is either the object (for nonstatic
        ** methods) or the classes.
        */
        atypes[ 0 ] = &ffi_type_pointer;
        atypes[ 1 ] = &ffi_type_pointer;

        for (i = 2; i < 2 + num_params; i++)
          atypes[ i ] = sig_to_ffi_type(sig->method.params[ i - 2 ]);

        if (ffi_prep_cif(&method->cif, FFI_DEFAULT_ABI,
                         num_params + 2,
                         rtype, atypes) != FFI_OK)
          {
            PR_ASSERT(0);
          }
        method->cif_created = 1;
        /* XXX  PR_DELETE(atypes); */
      }

#ifdef WITH_JNI
    if (method->jni_call)
      params[ 0 ] = (void*)&henv->thread_env;
    else
#endif
      params[ 0 ] = (void*)&henv;
    params[ 1 ] = &args[ 1 ].l;
    for (i = 2; i < 2 + num_params; i++)
      {
        params[ i ] = sig_to_value_addr(sig->method.params[ i - 2 ],
                                        &args[ i ]);
      }

    rvalue = sig_to_value_addr(sig->method.return_type, &args[0]);

    ffi_call(&method->cif,
             FFI_FN(method->native_func),
             rvalue, params);

    PR_DELETE(params);
  }
#else
  if (method->jni_call)
    method->native_func(henv->thread_env, args);
  else
    method->native_func(henv, args);
#endif

  {
    StackFrame *parent = new_frame->parent;

    if (parent < new_frame->_env->stack_highwater
        && ! (parent->flags & FRAME_NATIVE) )
      {
        char tag = *sig->method.return_type;
        if (tag == JSIG_BOOLEAN
#ifdef HAVE_LIBFFI
            || tag == JSIG_CHAR
            || tag == JSIG_BYTE
            || tag == JSIG_SHORT
#endif
            )
          {
            /* returned value is stored as a PRInt32; neglecting to correct
               return_value.tag induces an incorrect promotion to jint
               in op_stack_push_value() on bigendian hosts */
            tag = JSIG_INT;
          }

        /* restore the old stack top -- this must use f->opstack_top!! */
        new_frame->_env->op_stack->stack_top = new_frame->opstack_top;

        if (tag != JSIG_VOID)
          op_stack_push_value(new_frame->_env->op_stack,
                              tag,
                              args[0]);
      }
  }

  /* then pop the frame off */
  pop_frame(henv);

  return args[0];
}

void
do_native_method_call(HungryEnv *henv, MethodStruct *method)
{
  HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
  int num_arguments = 0;
  InterpValue args[100];
  int i;
#ifdef LOG
  char *class_name = getClassName(henv, method->clazz);
#endif
  StackFrame *current_frame = henv->top_frame;

  PR_LOG(nativeGlueLm, PR_LOG_DEBUG,
         ("Inside do_native_method_call(%s.%s)\n",
          getClassName(henv, method->clazz), method->name));

  num_arguments = HVM_SigNumParams(henv, sig);

  for (i = num_arguments + 1;
       i > 1;
       i --)
    {
      op_stack_pop_value(current_frame->_env->op_stack,
                         *sig->method.params[ i - 2  ],
                         args[ i ]);
    }

  if (method->access_flags & ACC_STATIC)
    {
      /* fill in the class pointer */
      args[1].l = clazzfile_to_jclass(henv, method->clazz);
    }
  else
    {
      /* pop the this pointer */
      op_stack_pop_value(current_frame->_env->op_stack,
                         JSIG_OBJECT,
                         args[ 1 ]);
    }

  do_native_method_call_with_args(henv, method, args, sig);

  HVM_SigFree(henv, sig);
}

/* This is the generic method used by the two specific below. */
static InterpValue
call_java_method(HungryEnv *henv,
                 MethodStruct *method,
                 japhar_object* obj, /* NULL for static calls */
                 InterpValue *args)
{
  StackFrame *new_frame;

  PR_LOG(nativeGlueLm, PR_LOG_DEBUG, ("calling %s.%s()\n",
                                      method->clazz->class_name,
                                      method->name));

  new_frame = create_frame_for_method(henv, method);
  if (NULL == new_frame)
    return henv->return_value.value;

  fill_local_vars(new_frame, method, args, obj);

  new_frame->opstack_top = henv->op_stack->stack_top;

  if (henv->vm->_verbose_flags & VERBOSE_METHOD)
    {
      int i;
      for (i = 0; i < new_frame->depth - 1; i ++) printf (" ");
      printf ("> %s.%s\n", getClassName(henv, method->clazz), method->name);
    }

  henv->return_value.value.j = 0;

  maybe_enter_monitor_for_method(henv, method, obj);

  interp_loop(new_frame);

  henv->op_stack->stack_top = new_frame->opstack_top;

  return henv->return_value.value;
}

PR_IMPLEMENT(InterpValue)
HVM_MethodCallA(HungryEnv *henv,
                MethodStruct *method,
                japhar_object* obj,
                InterpValue *args)
{
  /* in the case of interfaces, we have to do a second lookup when
     we actually call the method, to find the real method we should
     be calling. */
  if (method->clazz->access_flags & ACC_INTERFACE)
    {
      method = GetMethodForInterfaceMethod(henv, obj->clazz,
                                           method);
    }

  if (method->access_flags & ACC_NATIVE)
    {
      InterpValue new_args[100];
      InterpValue ret_val;
      HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
      int num_arguments = HVM_SigNumParams(henv, sig);

      if (num_arguments > 1)
        memcpy(&new_args[2], &args[1], num_arguments);

      new_args[1].l = obj;

      ret_val = do_native_method_call_with_args(henv,
                                                method, new_args, sig);

      HVM_SigFree(henv, sig);

      return ret_val;
    }
  else
    {
      return call_java_method(henv, method, obj, args);
    }
}

PR_IMPLEMENT(InterpValue)
HVM_MethodCall (HungryEnv *henv,
                MethodStruct *method,
                japhar_object* obj,
                ... )
{
  va_list varargs;
  InterpValue result;
  InterpValue *params = NULL;
  HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
  int num_params = HVM_SigNumParams(henv, sig);
  int i;

  if (0 < num_params)
    params = (InterpValue*)PR_Calloc(num_params, sizeof(InterpValue));

  va_start(varargs, obj);
  for (i = 0; i < num_params; i ++)
    {
      switch (HVM_SigSizeofStrInWords(henv, sig->method.params[i]))
        {
        case 1:
          params[i].i = va_arg(varargs, PRInt32);
          break;
        case 2:
          params[i].j = va_arg(varargs, PRInt64);
          break;
        }
    }
  va_end(varargs);

  result = HVM_MethodCallA(henv, method, obj, params);

  if (NULL != params)
    PR_DELETE(params);

  return result;
}

PR_IMPLEMENT(InterpValue)
HVM_MethodCallStaticA (HungryEnv *henv,
                       MethodStruct *method,
                       InterpValue *args)
{
  if (method->access_flags & ACC_NATIVE)
    {
      InterpValue new_args[100];
      InterpValue ret_val;
      HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
      int num_arguments = HVM_SigNumParams(henv, sig);

      if (num_arguments > 0)
        memcpy(&new_args[2], &args[1], num_arguments);

      new_args[1].l = 0;

      ret_val = do_native_method_call_with_args(henv,
                                                method, new_args, sig);
      HVM_SigFree(henv, sig);

      return ret_val;
    }
  else
    {
      return call_java_method(henv, method, 0, args);
    }
}

PR_IMPLEMENT(InterpValue)
HVM_MethodCallStatic (HungryEnv *henv,
                      MethodStruct *method,
                      ... )
{
  va_list varargs;
  InterpValue result;
  InterpValue *params = NULL;
  HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
  int num_params = HVM_SigNumParams(henv, sig);
  int i;

  if (0 < num_params)
    params = (InterpValue*)PR_Calloc(num_params, sizeof(InterpValue));

  va_start(varargs, method);
  for (i = 0; i < num_params; i ++)
    {
      switch (HVM_SigSizeofStrInWords(henv, sig->method.params[i]))
        {
        case 1:
          params[i].i = va_arg(varargs, PRInt32);
          break;
        case 2:
          params[i].j = va_arg(varargs, PRInt64);
          break;
        }
    }
  va_end(varargs);

  result = HVM_MethodCallStaticA(henv, method, params);

  if (NULL != params)
    PR_DELETE(params);

  return result;
}
