/* -*- Mode: C; c-file-style: "gnu" -*-
   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 "qop.h"
#include "jniint.h"
#include "log.h"
#include "dynamic_loading.h"
#include "exceptions.h"
#include "interp.h"
#include "sig.h"
#include "ClazzFile.h"
#include "compat.h"
#include "primitive-class.h"
#include "util.h"

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

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>

#include "op_stack.h"

#define MYLOG "NativeGlue"

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

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

  ret_val = snprintf(func_name, func_name_length,
#if defined HAVE_LIBFFI && !defined PROFILING
		     "Java_%s_%s",
#else
		     "Java_%s_%s_stub",
#endif
		     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(JNIEnv *env, 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 (SIG_formatStringToNativeName_buf(env, classname,
				       hacked_name, sizeof(hacked_name))
      || SIG_formatStringToNativeName_buf(env, method->name,
					  hacked_method, sizeof(hacked_method))
      || SIG_formatStringToNativeName_buf(env, method->sig_str,
					  hacked_sig, sizeof(hacked_sig)))
    {
      /* XXX */
      abort();
    }

  retval = snprintf(func_name, func_name_length,
#if defined HAVE_LIBFFI && !defined PROFILING
		    "Java_%s_%s__%s",
#else
		    "Java_%s_%s__%s_stub",
#endif
		    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(JNIEnv *env, MethodStruct *method)
{
  char native_func_name[1024];
  char *class_name = getClassName(env, method->clazz);
  
  create_short_native_name(env, class_name, method,
			   native_func_name, sizeof(native_func_name));

  JAVARLOG1(MYLOG, 1, "   short native stub name is %s()\n", native_func_name);
  
  method->native_func = (NativeFunc)DLL_findFunction(native_func_name);

  if (method->native_func == NULL)
    {
      /* now we try the long name. */
      create_long_native_name(env, class_name, method,
			      native_func_name, sizeof(native_func_name));

      method->native_func = (NativeFunc)DLL_findFunction(native_func_name);
    }

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

      msg = (char*)malloc(sizeof(char) 
			  * (strlen(class_name)
			     + strlen(method->name) + 2));

      assert(NULL != msg);
      sprintf(msg, "%s.%s", class_name, method->name);

      throw_Exception(env, "java/lang/UnsatisfiedLinkError",
		      msg);
      free(msg);
    }
}

#ifdef HAVE_LIBFFI
static ffi_type *
sig_to_ffi_type(Signature *sig)
{
  switch(sig->any.tag)
    {
    case SIG_PRIM:
      {
	switch (sig->prim.type)
	  {
	  case SIG_JBOOLEAN:
	    return &ffi_type_uint8;
	  case SIG_JBYTE:
	    return &ffi_type_sint8;
	  case SIG_JCHAR:
	    return &ffi_type_uint16;
	  case SIG_JSHORT:
	    return &ffi_type_sint16;
	  case SIG_JINT:
	    return &ffi_type_sint32;
	  case SIG_JFLOAT:
	    return &ffi_type_float;
	  case SIG_JDOUBLE:
	    return &ffi_type_double;
	  case SIG_JLONG:
	    return &ffi_type_sint64;
	  case SIG_JVOID:
	    return &ffi_type_void;
	  case SIG_JOBJECT:
	    return &ffi_type_pointer;
	  default:
	    assert(0);
	    return NULL;
	  }
      }
    case SIG_CLASS:
      /* fallthrough */
    case SIG_ARRAY:
      return &ffi_type_pointer;
    default:
      assert(0);
      return NULL;
    }
}

static void*
sig_to_jvalue_addr(Signature *sig,
		   jvalue *v)
{
  switch (sig->any.tag)
    {
    case SIG_PRIM:
      {
	switch (sig->prim.type)
	  {
	  case SIG_JBOOLEAN:
	    return &v->z;
	  case SIG_JBYTE:
	    return &v->b;
	  case SIG_JCHAR:
	    return &v->c;
	  case SIG_JSHORT:
	    return &v->s;
	  case SIG_JINT:
	    return &v->i;
	  case SIG_JFLOAT:
	    return &v->f;
	  case SIG_JDOUBLE:
	    return &v->d;
	  case SIG_JLONG:
	    return &v->j;
	  case SIG_JVOID:
	    return NULL; /* XXX */
	  case SIG_JOBJECT:
	    return &v->l;
	  default:
	    assert(0);
	    return NULL;
	  }
      }
    case SIG_CLASS:
    case SIG_ARRAY:
      return &v->l;
    default:
      assert(0);
      return NULL;
    }
}
#endif

static jvalue
do_native_method_call_with_args(JNIEnv *env,
				MethodStruct *method,
				jvalue *args,
				Signature *sig)
{
  HungryJNIEnv *henv = (HungryJNIEnv*)env;
  StackFrame *new_frame = push_frame(henv->_java_info, 0);

  new_frame->flags |= FRAME_NATIVE;
  METHOD(new_frame) = method;
#ifdef DEBUG
  CLASSNAME(new_frame) = getClassName(env, method->clazz);
  METHODNAME(new_frame) = strdup (METHOD(new_frame)->name);
  assert(NULL != METHODNAME(new_frame));
#endif

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

  OPSTACK_TOP(new_frame) = OPSTACK(new_frame)->stack_top;

  if (method->access_flags & ACC_STATIC)
    THISPTR(new_frame) = NULL;
  else
    THISPTR(new_frame) = 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(ENV(new_frame),
				 METHOD(new_frame),
				 THISPTR(new_frame));


#ifdef HAVE_LIBFFI
  {
    MethodStruct *method = METHOD(new_frame);
    ffi_type* rtype = sig_to_ffi_type(sig->method.return_type);
    ffi_type** atypes;
    ffi_cif cif;
    void *rvalue;
    void **params;
    int i;
    int num_params = SIG_numParams(env, sig);

    params = (void**)malloc(sizeof(void*) * (num_params + 2));
    atypes = (ffi_type**)malloc(sizeof(ffi_type*) * (num_params + 2));

    for (i = 2; i < 2 + num_params; i++)
      {
	atypes[ i ] = sig_to_ffi_type(sig->method.params[ i - 2 ]);
	params[ i ] = sig_to_jvalue_addr(sig->method.params[ i - 2 ],
					 &args[ i ]);
      }

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

    if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI,
		     num_params + 2,
		     rtype, atypes) != FFI_OK)
      {
	assert(0);
      }

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

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

    free(params);
    free(atypes);
  }
#else
  (*METHOD(new_frame)->native_func)(env, args);
#endif
  
  if (!SIG_isVoid(env, sig->method.return_type))
    {
      switch(sig->method.return_type->any.tag)
	{
	case SIG_PRIM:
	  THREAD_INFO(new_frame)->return_value.tag =
	    sig->method.return_type->prim.type;
	  break;
	case SIG_ARRAY:
	case SIG_CLASS:
	  THREAD_INFO(new_frame)->return_value.tag = SIG_JOBJECT;
	  break;
	case SIG_METHOD:
	  assert(0); /* this shouldn't ever happen. */
	  break;
	}
    }
  else
    {
      THREAD_INFO(new_frame)->return_value.tag = SIG_JVOID;
    }

  THREAD_INFO(new_frame)->return_value.value = args[0];

  {
    SigPrimType* tag = &THREAD_INFO(new_frame)->return_value.tag;
    if (*tag == SIG_JBOOLEAN
#ifdef HAVE_LIBFFI
	|| *tag == SIG_JCHAR
	|| *tag == SIG_JBYTE
	|| *tag == SIG_JSHORT
#endif
	) 
      {
	/* returned value is stored as a jint; neglecting to correct 
	   return_value.tag induces an incorrect promotion to jint 
	   in op_stack_push_value() on bigendian hosts */
	*tag = SIG_JINT;
      }
  }
  
  maybe_push_return_value(new_frame);
  
  /* then pop the frame off */
  pop_frame(THREAD_INFO(new_frame));

  return THREAD_INFO(new_frame)->return_value.value;
}

void
do_native_method_call(JNIEnv *env, MethodStruct *method)
{
  HungryJNIEnv *henv = (HungryJNIEnv*)env;
  Signature *sig = SIG_parseFromJavaSig(env, method->sig_str);
  int num_arguments = 0;
  jvalue args[100];
  int i;
#ifdef LOG
  char *class_name = getClassName(env, method->clazz);
#endif
  StackFrame *current_frame = TOPFRAME(henv->_java_info);

  JAVARLOG2(MYLOG, 1, "Inside do_native_method_call(%s.%s)\n",
	    getClassName(env, method->clazz), method->name);

  initialize_class(env, method->clazz);

  num_arguments = SIG_numParams(env, sig);

  for (i = num_arguments + 1;
       i > 1;
       i --)
    {
      op_stack_pop_value(env, OPSTACK(current_frame),
			 (sig->method.params[ i - 2  ]->any.tag == SIG_PRIM
			  ? sig->method.params[ i - 2 ]->prim.type
			  : SIG_JOBJECT),
			 &args[ i ]);
    }
  
  if (method->access_flags & ACC_STATIC)
    {
      /* fill in the class pointer */
      args[1].l = clazzfile_to_jclass(env, method->clazz);
    }
  else
    {
      /* pop the this pointer */
      op_stack_pop_value(env, OPSTACK(current_frame),
			 SIG_JOBJECT,
			 &args[ 1 ]);
    }

  do_native_method_call_with_args(env, method, args, sig);

  SIG_free(env, sig);
}

/* This is the generic method used by the two specific below. */
static jvalue
call_java_method(JNIEnv *env,
		 MethodStruct *method,
		 jobject obj, /* NULL for static calls */
		 jvalue *args)
{
  StackFrame *new_frame;
  HungryJNIEnv *henv = (HungryJNIEnv*)env;

  JAVARLOG2(MYLOG, 4, "calling %s.%s()\n",
	    method->clazz->class_name,
	    method->name);

  initialize_class(env, method->clazz);

  new_frame = create_frame_for_method(henv->_java_info, method);
  if (NULL == new_frame)
    {
      jvalue value;
      /*
       * Fill it with zero.  Assume jlong is the longest type.  I'm
       * not sure what this method should return on error, but...
       */
      value.j = 0;
      return value; /* create_frame_for_methods has already throwed */
    }

  fill_local_vars(new_frame, method, args, obj);

  OPSTACK_TOP(new_frame) = OPSTACK(new_frame)->stack_top;

  THREAD_INFO(new_frame)->return_value.value.j = 0;

  maybe_enter_monitor_for_method(env, method, obj);

  interp_loop(new_frame);

  OPSTACK(new_frame)->stack_top = OPSTACK_TOP(new_frame);

  return THREAD_INFO(new_frame)->return_value.value;
}
  
jvalue
CallJavaMethod (JNIEnv *env,
		MethodStruct *method,
		jobject obj,
		jvalue *args)
{
  japhar_obj real_obj;

  /* 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)
    {
      ClazzFile *cf;

      method = get_interface_method_info(env, obj,
					 &cf,
					 method->name, method->sig_str);

      real_obj = cast_obj((japhar_obj)obj,
			  cf);
    }
  else
    {
      real_obj = cast_obj((japhar_obj)obj,
			  method->clazz);
    }

  assert(real_obj);
  if(!real_obj)
    {
      /* this should be an exception, but I don't know what... */
      jvalue foo;

      foo.l = 0;
      return foo;
    }

  if (method->access_flags & ACC_NATIVE)
    {
      jvalue new_args[100];
      jvalue ret_val;
      Signature *sig = SIG_parseFromJavaSig(env, method->sig_str);
      int num_arguments = SIG_numParams(env, sig);

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

      new_args[1].l = real_obj;

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

      SIG_free(env, sig);

      return ret_val;
    }
  else
    {
      return call_java_method(env, method, real_obj, args);
    }
}

jvalue
CallStaticJavaMethod (JNIEnv *env,
		      MethodStruct *method,
		      jvalue *args)
{
  if (method->access_flags & ACC_NATIVE)
    {
      jvalue new_args[100];
      jvalue ret_val;
      Signature *sig = SIG_parseFromJavaSig(env, method->sig_str);
      int num_arguments = SIG_numParams(env, 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(env,
					        method, new_args, sig);
      SIG_free(env, sig);

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