/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   objects.c -- runtime code for allocating objects.
   Created: Chris Toshok <toshok@hungry.com>, 10-Aug-1997
 */
/*
  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 "ClazzFile.h"

extern PRLogModuleInfo* objectLm;

static int
alignment_of_type(char *sig_type)
{
#ifdef __GNUC__
#define RETURN_ALIGNMENTOF(type) return __alignof__(type)
#else

  /* This should be optimized away by the compiler. [pere] */
#define RETURN_ALIGNMENTOF(type) \
PR_BEGIN_MACRO \
      struct alignment_struct \
         { char c; type p; } *t = NULL; \
      if ((char*) &(t->c) != (char*)t) \
        {\
          PR_ASSERT(NULL == "Bad alignment"); \
          return -1; \
        } \
      return (int)&(t->p); \
PR_END_MACRO
#endif

  switch (*sig_type)
    {
    case JSIG_BOOLEAN:  RETURN_ALIGNMENTOF(PRUint8);
    case JSIG_BYTE:     RETURN_ALIGNMENTOF(PRInt8);
    case JSIG_CHAR:     RETURN_ALIGNMENTOF(PRUint16);
    case JSIG_SHORT:    RETURN_ALIGNMENTOF(PRInt16);
    case JSIG_INT:      RETURN_ALIGNMENTOF(PRInt32);
    case JSIG_LONG:     RETURN_ALIGNMENTOF(PRInt64);
    case JSIG_FLOAT:    RETURN_ALIGNMENTOF(float);
    case JSIG_DOUBLE:   RETURN_ALIGNMENTOF(PRFloat64);
    case JSIG_OBJECT:
    case JSIG_ARRAY:    RETURN_ALIGNMENTOF(japhar_object*);
    default:
      printf ("invalid sig_type %c\n", *sig_type);
      PR_ASSERT(0);
      return -1;
    }
#undef RETURN_ALIGNMENTOF
}

static PRSize
set_field_offset(PRSize current_offset, FieldStruct *field)
{
  int field_size = 0;

  PR_ASSERT(NULL != field);
  PR_ASSERT(0 <= current_offset);

  if (field->field_offset != VALUE_UNRESOLVED)
    {
      field_size = HVM_SigSizeofStrInBytes(field->sig_str);
    }
  else
    {
      int field_alignment = 0;

      field_size = HVM_SigSizeofStrInBytes(field->sig_str);
      field_alignment = alignment_of_type(field->sig_str);

      /* Align field */
      field->field_offset = (current_offset + field_alignment-1) &
        ~(field_alignment-1);
    }
  return field->field_offset + field_size;
}

void
calculate_instance_field_offsets(HungryEnv *henv, ClazzFile *cf)
{
  int i;
  int current_offset;
  ClazzFile *parent_class = getSuperClass(henv, cf);

  if (parent_class == NULL)
    current_offset = 0;
  else
    current_offset = parent_class->size_of_instance_field_block;

  for (i = 0; i < cf->num_fields; i ++)
    {
      FieldStruct *field = cf->fields[i];

      if (field->access_flags & ACC_STATIC)
        continue;

      if (field->clazz != cf)
        break;

      current_offset = set_field_offset(current_offset, field);
    }

  cf->size_of_instance_field_block = current_offset;
}

static void
calculate_static_field_offsets(HungryEnv *henv, ClazzFile *cf)
{
  int i;
  int current_offset = 0;

  for (i = 0; i < cf->num_fields; i ++)
    {
      FieldStruct *field = cf->fields[i];

      if (!(field->access_flags & ACC_STATIC))
        continue;

      if (field->clazz != cf)
        break;

      current_offset = set_field_offset(current_offset, field);
    }
}

/* See objects.h to understand how this works. */
PRSize
object_size_without_fields()
{
  return (int) &((japhar_object*)NULL)->fields;
}

PR_IMPLEMENT(japhar_object*)
HVM_ObjectClone(HungryEnv *henv, japhar_object* old_obj)
{
  if (HVM_ClassIsArray(henv, old_obj->clazz))
    return HVM_ArrayClone(henv, old_obj);
  else
    return HVM_GCCloneObject(henv, old_obj);
}

static japhar_object*
new_object_no_init(HungryEnv *henv, ClazzFile *cf)
{
  japhar_object *new_obj;

  new_obj = HVM_GCAllocObject (henv, cf);
  if (new_obj == NULL)
    return NULL;

  /* initialize the hashcode for the object, and store it in the japhar_object*
     so we can return it again later in java.lang.Object.hashCode. */
  {
    InterpValue foo;

    foo.l = new_obj;
    new_obj->hash_code = foo.i;
  }

  new_obj->clazz = cf;

  PR_LOG(objectLm, PR_LOG_DEBUG,
         ("leaving new_object_no_init(), returning %p\n", new_obj));

  return new_obj;
}

PR_IMPLEMENT(japhar_object*)
HVM_ObjectNew(HungryEnv *henv, ClazzFile *cf)
{
  PR_LOG(objectLm, PR_LOG_DEBUG, ("in new_object(%s)\n",
                                  getClassName(henv, cf)));

  /* first we initialize the class. */
  initialize_class(henv, cf);
  if (henv->_exception)
    {
      return NULL;
    }

  return new_object_no_init(henv, cf);
}

PR_IMPLEMENT(ClazzFile*)
jclass_to_clazzfile(HungryEnv *henv, japhar_object* clazz)
{
  ClazzFile *result = (ClazzFile*) HVM_ObjectGetNativeState(clazz);

  PR_ASSERT(result != NULL);

  return result;
}

PR_IMPLEMENT(japhar_object*)
clazzfile_to_jclass(HungryEnv *henv, ClazzFile *cf)
{
  static ClazzFile *class_cf = NULL;

#ifdef DEBUG
  static int num_objects = 0;
#endif

  if (class_cf == NULL)
    class_cf = HVM_ClassFind(henv, java_lang_Class);

  PR_LOG(objectLm, PR_LOG_DEBUG, ("in clazzfile_to_jclass(cf=%s, %d)\n",
                                  getClassName(henv,cf), ++num_objects));

  if (cf->jcls)
    return cf->jcls;
  else
    {
      japhar_object* new_obj = new_object_no_init(henv, class_cf);

      PR_ASSERT(NULL != new_obj);

      HVM_ObjectSetNativeState(new_obj, cf);

      cf->jcls = new_obj;

      return new_obj;
    }
}

static void
object_finalize(japhar_object *obj)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  MethodStruct* finalize;

  PR_LOG(objectLm, PR_LOG_DEBUG, ("Finalizing object %p\n", obj));

  finalize = HVM_MethodFind(henv,
                            obj->clazz,
                            "finalize", "()V");

  if (finalize)
    {
      HVM_MethodCallA(henv, finalize, obj, NULL);
      if (henv->_exception)
        {
          /* XXX do something... */
          return;
        }
    }

  PR_LOG(objectLm, PR_LOG_DEBUG, ("   freeing memory.\n"));

  /* PR_FREE(obj); */
}

static PRBool
create_static_fields(HungryEnv *henv, ClazzFile *cf)
{
  if (cf->num_static_fields == 0)
    {
      cf->static_fields = NULL;
      return PR_TRUE;
    }
  else
    {
      cf->static_fields = (void*)jcalloc(henv, cf->num_static_fields,
                                         sizeof(JavaStackItem));
      return (NULL != cf->static_fields) ? PR_TRUE : PR_FALSE;
    }
}

PR_IMPLEMENT(PRBool)
HVM_ClassIsArray(HungryEnv *henv, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_ARRAY));
}

PR_IMPLEMENT(PRBool)
HVM_ClassIsPrimitive(HungryEnv *henv, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_PRIMITIVE));
}

PR_IMPLEMENT(PRBool)
HVM_ClassIsInterface(HungryEnv *henv, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_INTERFACE));
}

/* Interface or normal class */
static PRBool
is_instance_of_class(HungryEnv *henv, ClazzFile *sub, ClazzFile *super)
{
  PR_LOG(objectLm, PR_LOG_DEBUG, ("Testing %s instanceof class %s\n",
                                  getClassName(env, sub),
                                  getClassName(env, super)));

  PR_ASSERT(NULL != sub);
  PR_ASSERT(NULL != super);

  while (sub)
    {
      PRUint16 interface;

      if (sub == super)
        return PR_TRUE;

      for (interface = 0; interface < sub->num_interfaces; interface++)
        if ( is_instance_of_class(henv, sub->interface_tuples[ interface ].interface, super) )
          return PR_TRUE;

      if (sub->superclass_index == 0)
        return PR_FALSE;
      else
        sub = getSuperClass(henv, sub);
    }

  return PR_FALSE;
}

static PRBool
is_instance_of_array(HungryEnv *henv, ClazzFile *sub, ClazzFile *super)
{

  char *subname = getClassName(env, sub);
  char *supername = getClassName(env, super);

  PR_LOG(objectLm, PR_LOG_DEBUG,
         ("Testing %s instanceof array %s\n", subname, supername));

  /* The easy case */
  if (!PL_strcmp(subname, supername))
    return PR_TRUE;

  /*
   * Skip array dimentions until one of the classes are a
   * non-array type.
   */
  while (HVM_ClassIsArray(henv, sub) && HVM_ClassIsArray(henv, super)) {
    sub =  HVM_ArrayGetElementType(henv, sub);
    super =  HVM_ArrayGetElementType(henv, super);
  }

  PR_ASSERT(NULL != sub);
  PR_ASSERT(NULL != super);

  if (HVM_ClassIsPrimitive(henv, super)) /* Must match exactly */
    return (sub == super);

  /* The object element class can not be cast to an array */
  if (HVM_ClassIsArray(henv, super))
    return PR_FALSE;

  /* Arrays can only be cast to java.lang.Object */
  if (HVM_ClassIsArray(henv, sub))
    return (super == HVM_ClassFind(henv, java_lang_Object));

  /* Hm, is this the correct test? */
  return HVM_ClassIsAssignableFrom(henv, sub, super);
}

PR_IMPLEMENT(PRBool)
HVM_ObjectIsInstanceOf(HungryEnv *henv, japhar_object* obj, ClazzFile *cf)
{
  PR_ASSERT(NULL != cf);
  PR_ASSERT(NULL != henv);
  PR_ASSERT(NULL != obj);

  PR_LOG(objectLm, PR_LOG_DEBUG, ("Testing %s instanceof %s\n",
                                  getClassName(henv, obj->clazz),
                                  getClassName(henv, cf)));

  if (HVM_ClassIsArray(henv, cf))
    return is_instance_of_array(henv, obj->clazz, cf);
  else
    return is_instance_of_class(henv, obj->clazz, cf);
}

PRBool
HVM_ClassIsAssignableFrom(HungryEnv *henv,
                          ClazzFile *clazz1,
                          ClazzFile *clazz2)
{
  if (HVM_ClassIsArray(henv, clazz1))
    return is_instance_of_array(henv, clazz1, clazz2);
  else
    return is_instance_of_class(henv, clazz1, clazz2);
}

/* the guts of the following functions come from
   "The Java Language Specification" by Gosling et. al */

static void
call_initializers(HungryEnv *henv, ClazzFile *cf)
{
  MethodStruct* clinit;
  ClazzFile *superclass = getSuperClass(henv, cf);

  if (superclass)
    call_initializers(henv, superclass);

  /* call our static initializers */
  if (cf->access_flags & HAS_CLINIT)
    {
      cf->access_flags &= ~HAS_CLINIT;

      clinit = HVM_MethodFindStatic(henv, cf, "<clinit>", "()V");

      /* if the method isn't defined, clear the exception. */
      if (henv->_exception)
        {
          /* XXX there are some we should be legitimately concerned with,
             like OutOfMemoryError, etc. */
          henv->_exception = NULL;
        }

      if (clinit)
        {
          StackFrame *native_frame;

          native_frame = push_frame(henv, 0);
          native_frame->opstack_top = native_frame->_env->op_stack->stack_top;
          native_frame->this_pointer = NULL;
          native_frame->flags |= FRAME_NATIVE;

          native_frame->method = clinit;

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

          HVM_MethodCallStatic(henv, clinit, NULL);

          pop_frame(henv);
        }
    }
}


static void
initialize_class_recurse(HungryEnv *henv,
                         ClazzFile *cf,
                         ClazzFile *subclass)
{
  PRMonitor* clazz_monitor;

  if (cf == NULL)
    return;

  if (!cf->monitor)
    cf->monitor = PR_NewMonitor();

  clazz_monitor = cf->monitor;

  PR_EnterMonitor(clazz_monitor);

 try_and_initialize:
  switch (cf->initialization_state)
    {
    case CLASS_INIT_FAILED:
      PR_ExitMonitor(clazz_monitor);
      HVM_ExceptionThrow(henv, java_lang_NoClassDefFoundError, getClassName(henv, cf));
      return;
    case CLASS_INIT_FINISHED:
      PR_ExitMonitor(clazz_monitor);
      return;
    case CLASS_INIT_IN_PROGRESS:
      if (cf->initializing_thread == PR_GetCurrentThread())
        {
          /* we're in the process of initializing it already,
             so just return. */
          PR_ExitMonitor(clazz_monitor);
          return;
        }
      else
        {
          /* someone else is initializing it, so wait until they're
             done and try again. */
          PR_Wait(clazz_monitor, PR_INTERVAL_NO_TIMEOUT);
          goto try_and_initialize;
        }
    case CLASS_NOT_INITIALIZED:
      {
        cf->initialization_state = CLASS_INIT_IN_PROGRESS;
        cf->initializing_thread = PR_GetCurrentThread();

        calculate_static_field_offsets(henv, cf);
        if ( ! create_static_fields(henv, cf) )
          /*
           * Hm, this is probably out of memory error.
           * Let someone else try again later. [pere]
           */
          {
            cf->initialization_state = CLASS_NOT_INITIALIZED;
            cf->initializing_thread = NULL;

            PR_NotifyAll(clazz_monitor);
            PR_ExitMonitor(clazz_monitor);
            return;
          }

        PR_NotifyAll(clazz_monitor);
        PR_ExitMonitor(clazz_monitor);

        if (getSuperClass(henv, cf))
          {
            initialize_class_recurse(henv, getSuperClass(henv, cf), subclass);

            cf->nesting_level = getSuperClass(henv, cf)->nesting_level + 1;
          }
        else
          cf->nesting_level = 0;

        if (henv->_exception)
          {
            PR_EnterMonitor(clazz_monitor);
            cf->initialization_state = CLASS_INIT_FAILED;
            cf->initializing_thread = NULL;

            PR_NotifyAll(clazz_monitor);

            PR_ExitMonitor(clazz_monitor);

            /* don't clear the exception */
            return;
          }

        calculate_instance_field_offsets(henv, cf);

        /* we need to only do this next block after we've recursively
           initialized all the superclasses */
        if (cf == subclass)
          {
            call_initializers(henv, subclass);
          }

        if (henv->_exception)
          {
            PR_EnterMonitor(clazz_monitor);
            cf->initialization_state = CLASS_INIT_FAILED;
            cf->initializing_thread = NULL;

            PR_NotifyAll(clazz_monitor);

            PR_ExitMonitor(clazz_monitor);

            henv->_exception = NULL; /* XXX */

            HVM_ExceptionThrow(henv,
                               java_lang_ExceptionInInitializerError,
                               cf->class_name);

            /* don't clear the exception */
            return;
          }

        /* if we've made it here, things worked. */
        PR_EnterMonitor(clazz_monitor);

        cf->initialization_state = CLASS_INIT_FINISHED;
        cf->initializing_thread = NULL;

        PR_NotifyAll(clazz_monitor);
        PR_ExitMonitor(clazz_monitor);
        return;
      }
    }
}

void
initialize_class(HungryEnv *henv, ClazzFile *cf)
{
  /* we race here, but it doesn't matter -- if we get a false negative
     no tremendous loss. */
  if (cf->initialization_state == CLASS_INIT_FINISHED)
    return;
  initialize_class_recurse(henv, cf, cf);
}

PR_IMPLEMENT(void*)
HVM_ObjectGetNativeState(japhar_object* object_ref)
{
#if WITH_SPORTSMODEL
  return SM_GET_FIELD (japhar_object, object_ref, native_state);
#else
  return object_ref->native_state;
#endif
}

PR_IMPLEMENT(void)
HVM_ObjectSetNativeState(japhar_object* object_ref,
                         void *native_state)
{
#if WITH_SPORTSMODEL
  SM_SET_FIELD (japhar_object, object_ref, native_state, native_state);
#else
  object_ref->native_state = native_state;
#endif
}

/* necessary for JDK1.2 */
PRBool
HVM_ObjectIsReference(HungryEnv *henv,
                      japhar_object* obj)
{
  ClazzFile* ref_class = NULL;
  int checked = 0;

  if (ref_class == NULL && checked == 0)
    {
      ref_class = HVM_ClassFind(henv, java_lang_ref_Reference);
      checked = 1;
    }

  if (!ref_class)
    return PR_FALSE;

  return HVM_ObjectIsInstanceOf(henv, obj, ref_class);
}

/* necessary for JDK1.2 */
japhar_object*
HVM_ObjectGetReference(HungryEnv *henv,
                       japhar_object* obj)
{
  ClazzFile *ref_class = obj->clazz;
  MethodStruct* get;
  InterpValue rv;

  get = HVM_MethodFind(henv, ref_class, "get", "()Ljava/lang/Object;");

  rv = HVM_MethodCallA(henv, get, obj, NULL);
  return rv.l;
}
