/* Copyright (c) 2008-2014, Avian Contributors

   Permission to use, copy, modify, and/or distribute this software
   for any purpose with or without fee is hereby granted, provided
   that the above copyright notice and this permission notice appear
   in all copies.

   There is NO WARRANTY for this software.  See license.txt for
   details. */

#include "avian/jnienv.h"
#include "avian/machine.h"
#include "avian/util.h"
#include "avian/processor.h"
#include "avian/constants.h"

#include <avian/util/runtime-array.h>

using namespace vm;

namespace {

namespace local {

jint JNICALL AttachCurrentThread(Machine* m, Thread** t, void*)
{
  *t = static_cast<Thread*>(m->localThread->get());
  if (*t == 0) {
    *t = attachThread(m, false);
  }
  return 0;
}

jint JNICALL AttachCurrentThreadAsDaemon(Machine* m, Thread** t, void*)
{
  *t = static_cast<Thread*>(m->localThread->get());
  if (*t == 0) {
    *t = attachThread(m, true);
  }
  return 0;
}

jint JNICALL DetachCurrentThread(Machine* m)
{
  Thread* t = static_cast<Thread*>(m->localThread->get());
  if (t) {
    // todo: detaching the root thread seems to cause stability
    // problems which I haven't yet had a chance to investigate
    // thoroughly.  Meanwhile, we just ignore requests to detach it,
    // which leaks a bit of memory but should be harmless otherwise.
    if (m->rootThread != t) {
      m->localThread->set(0);

      ACQUIRE_RAW(t, t->m->stateLock);

      enter(t, Thread::ActiveState);

      t->javaThread->peer() = 0;

      enter(t, Thread::ZombieState);

      t->state = Thread::JoinedState;
    }

    return 0;
  } else {
    return -1;
  }
}

uint64_t destroyJavaVM(Thread* t, uintptr_t*)
{
  // wait for other non-daemon threads to exit
  {
    ACQUIRE(t, t->m->stateLock);
    while (t->m->liveCount - t->m->daemonCount > 1) {
      t->m->stateLock->wait(t->systemThread, 0);
    }
  }

  {
    ENTER(t, Thread::ActiveState);

    t->m->classpath->shutDown(t);
  }

  // wait again in case the Classpath::shutDown process started new
  // threads:
  {
    ACQUIRE(t, t->m->stateLock);
    while (t->m->liveCount - t->m->daemonCount > 1) {
      t->m->stateLock->wait(t->systemThread, 0);
    }

    enter(t, Thread::ExclusiveState);
  }

  shutDown(t);

  return 1;
}

jint JNICALL DestroyJavaVM(Machine* m)
{
  Thread* t;
  AttachCurrentThread(m, &t, 0);

  if (runRaw(t, destroyJavaVM, 0)) {
    t->exit();
    return 0;
  } else {
    return -1;
  }
}

jint JNICALL GetEnv(Machine* m, Thread** t, jint version)
{
  *t = static_cast<Thread*>(m->localThread->get());
  if (*t) {
    if (version <= JNI_VERSION_1_6) {
      return AVIAN_JNI_OK;
    } else {
      return AVIAN_JNI_EVERSION;
    }
  } else {
    return AVIAN_JNI_EDETACHED;
  }
}

jint JNICALL GetVersion(Thread* t)
{
  ENTER(t, Thread::ActiveState);

  return JNI_VERSION_1_6;
}

jsize JNICALL GetStringLength(Thread* t, jstring s)
{
  ENTER(t, Thread::ActiveState);

  return (*s)->length(t);
}

const jchar* JNICALL GetStringChars(Thread* t, jstring s, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  jchar* chars = static_cast<jchar*>(
      t->m->heap->allocate(((*s)->length(t) + 1) * sizeof(jchar)));
  stringChars(t, *s, chars);

  if (isCopy)
    *isCopy = true;
  return chars;
}

void JNICALL ReleaseStringChars(Thread* t, jstring s, const jchar* chars)
{
  ENTER(t, Thread::ActiveState);

  t->m->heap->free(chars, ((*s)->length(t) + 1) * sizeof(jchar));
}

void JNICALL
    GetStringRegion(Thread* t, jstring s, jsize start, jsize length, jchar* dst)
{
  ENTER(t, Thread::ActiveState);

  stringChars(t, *s, start, length, dst);
}

const jchar* JNICALL GetStringCritical(Thread* t, jstring s, jboolean* isCopy)
{
  if (t->criticalLevel == 0) {
    enter(t, Thread::ActiveState);
  }

  ++t->criticalLevel;

  if (isCopy) {
    *isCopy = true;
  }

  object data = (*s)->data();
  if (objectClass(t, data) == type(t, GcByteArray::Type)) {
    return GetStringChars(t, s, isCopy);
  } else {
    return &cast<GcCharArray>(t, data)->body()[(*s)->offset(t)];
  }
}

void JNICALL ReleaseStringCritical(Thread* t, jstring s, const jchar* chars)
{
  if (objectClass(t, (*s)->data()) == type(t, GcByteArray::Type)) {
    ReleaseStringChars(t, s, chars);
  }

  if ((--t->criticalLevel) == 0) {
    enter(t, Thread::IdleState);
  }
}

jsize JNICALL GetStringUTFLength(Thread* t, jstring s)
{
  ENTER(t, Thread::ActiveState);

  return stringUTFLength(t, *s);
}

const char* JNICALL GetStringUTFChars(Thread* t, jstring s, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  int length = stringUTFLength(t, *s);
  char* chars = static_cast<char*>(t->m->heap->allocate(length + 1));
  stringUTFChars(t, *s, chars, length);

  if (isCopy)
    *isCopy = true;
  return chars;
}

void JNICALL ReleaseStringUTFChars(Thread* t, jstring s, const char* chars)
{
  ENTER(t, Thread::ActiveState);

  t->m->heap->free(chars, stringUTFLength(t, *s) + 1);
}

void JNICALL GetStringUTFRegion(Thread* t,
                                jstring s,
                                jsize start,
                                jsize length,
                                char* dst)
{
  ENTER(t, Thread::ActiveState);

  stringUTFChars(
      t, *s, start, length, dst, stringUTFLength(t, *s, start, length));
}

jsize JNICALL GetArrayLength(Thread* t, jarray array)
{
  ENTER(t, Thread::ActiveState);

  return fieldAtOffset<uintptr_t>(*array, BytesPerWord);
}

uint64_t newString(Thread* t, uintptr_t* arguments)
{
  const jchar* chars = reinterpret_cast<const jchar*>(arguments[0]);
  jsize size = arguments[1];

  GcCharArray* a = makeCharArray(t, size);
  if (size) {
    memcpy(a->body().begin(), chars, size * sizeof(jchar));
  }

  return reinterpret_cast<uint64_t>(
      makeLocalReference(t, t->m->classpath->makeString(t, a, 0, size)));
}

jstring JNICALL NewString(Thread* t, const jchar* chars, jsize size)
{
  if (chars == 0)
    return 0;

  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(chars), static_cast<uintptr_t>(size)};

  return reinterpret_cast<jstring>(run(t, newString, arguments));
}

uint64_t newStringUTF(Thread* t, uintptr_t* arguments)
{
  const char* chars = reinterpret_cast<const char*>(arguments[0]);

  object array = parseUtf8(t, chars, strlen(chars));

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t,
      t->m->classpath->makeString(
          t, array, 0, fieldAtOffset<uintptr_t>(array, BytesPerWord) - 1)));
}

jstring JNICALL NewStringUTF(Thread* t, const char* chars)
{
  if (chars == 0)
    return 0;

  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(chars)};

  return reinterpret_cast<jstring>(run(t, newStringUTF, arguments));
}

void replace(int a, int b, const char* in, int8_t* out)
{
  while (*in) {
    *out = (*in == a ? b : *in);
    ++in;
    ++out;
  }
  *out = 0;
}

uint64_t defineClass(Thread* t, uintptr_t* arguments)
{
  jobject loader = reinterpret_cast<jobject>(arguments[0]);
  const uint8_t* buffer = reinterpret_cast<const uint8_t*>(arguments[1]);
  jsize length = arguments[2];

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t,
      getJClass(
          t,
          cast<GcClass>(t,
                        defineClass(t,
                                    loader ? cast<GcClassLoader>(t, *loader)
                                           : roots(t)->bootLoader(),
                                    buffer,
                                    length)))));
}

jclass JNICALL DefineClass(Thread* t,
                           const char*,
                           jobject loader,
                           const jbyte* buffer,
                           jsize length)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(loader),
                           reinterpret_cast<uintptr_t>(buffer),
                           static_cast<uintptr_t>(length)};

  return reinterpret_cast<jclass>(run(t, defineClass, arguments));
}

uint64_t findClass(Thread* t, uintptr_t* arguments)
{
  const char* name = reinterpret_cast<const char*>(arguments[0]);

  GcByteArray* n = makeByteArray(t, strlen(name) + 1);
  replace('.', '/', name, n->body().begin());

  GcMethod* caller = getCaller(t, 0);

  GcClass* c
      = resolveClass(t,
                     caller ? t->m->classpath->libraryClassLoader(t, caller)
                            : roots(t)->appLoader(),
                     n);

  if (t->m->classpath->mayInitClasses()) {
    PROTECT(t, c);

    initClass(t, c);
  }

  return reinterpret_cast<uint64_t>(makeLocalReference(t, getJClass(t, c)));
}

jclass JNICALL FindClass(Thread* t, const char* name)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(name)};

  return reinterpret_cast<jclass>(run(t, findClass, arguments));
}

uint64_t throwNew(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  const char* message = reinterpret_cast<const char*>(arguments[1]);

  GcString* m = 0;
  PROTECT(t, m);

  if (message) {
    m = makeString(t, "%s", message);
  }

  object trace = makeTrace(t);
  PROTECT(t, trace);

  t->exception = cast<GcThrowable>(t, make(t, (*c)->vmClass()));
  t->exception->setMessage(t, m);
  t->exception->setTrace(t, trace);

  return 1;
}

jint JNICALL ThrowNew(Thread* t, jclass c, const char* message)
{
  if (t->exception) {
    return -1;
  }

  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), reinterpret_cast<uintptr_t>(message)};

  return run(t, throwNew, arguments) ? 0 : -1;
}

jint JNICALL Throw(Thread* t, jthrowable throwable)
{
  if (t->exception) {
    return -1;
  }

  ENTER(t, Thread::ActiveState);

  t->exception = *throwable;

  return 0;
}

jobject JNICALL NewLocalRef(Thread* t, jobject o)
{
  ENTER(t, Thread::ActiveState);

  return makeLocalReference(t, *o);
}

void JNICALL DeleteLocalRef(Thread* t, jobject r)
{
  ENTER(t, Thread::ActiveState);

  disposeLocalReference(t, r);
}

jboolean JNICALL ExceptionCheck(Thread* t)
{
  return t->exception != 0;
}

uint64_t getObjectClass(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);

  return reinterpret_cast<uint64_t>(
      makeLocalReference(t, getJClass(t, objectClass(t, *o))));
}

jclass JNICALL GetObjectClass(Thread* t, jobject o)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o)};

  return reinterpret_cast<jclass>(run(t, getObjectClass, arguments));
}

uint64_t getSuperclass(Thread* t, uintptr_t* arguments)
{
  GcClass* class_ = (*reinterpret_cast<jclass>(arguments[0]))->vmClass();
  if (class_->flags() & ACC_INTERFACE) {
    return 0;
  } else {
    GcClass* super = class_->super();
    return super ? reinterpret_cast<uint64_t>(
                       makeLocalReference(t, getJClass(t, super)))
                 : 0;
  }
}

jclass JNICALL GetSuperclass(Thread* t, jclass c)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c)};

  return reinterpret_cast<jclass>(run(t, getSuperclass, arguments));
}

uint64_t isInstanceOf(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jclass c = reinterpret_cast<jclass>(arguments[1]);

  return instanceOf(t, (*c)->vmClass(), *o);
}

jboolean JNICALL IsInstanceOf(Thread* t, jobject o, jclass c)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), reinterpret_cast<uintptr_t>(c)};

  return run(t, isInstanceOf, arguments);
}

uint64_t isAssignableFrom(Thread* t, uintptr_t* arguments)
{
  jclass b = reinterpret_cast<jclass>(arguments[0]);
  jclass a = reinterpret_cast<jclass>(arguments[1]);

  return isAssignableFrom(t, (*a)->vmClass(), (*b)->vmClass());
}

jboolean JNICALL IsAssignableFrom(Thread* t, jclass b, jclass a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(b), reinterpret_cast<uintptr_t>(a)};

  return run(t, isAssignableFrom, arguments);
}

GcMethod* findMethod(Thread* t, jclass c, const char* name, const char* spec)
{
  GcByteArray* n = makeByteArray(t, "%s", name);
  PROTECT(t, n);

  GcByteArray* s = makeByteArray(t, "%s", spec);
  return vm::findMethod(t, (*c)->vmClass(), n, s);
}

jint methodID(Thread* t, GcMethod* method)
{
  int id = method->nativeID();

  loadMemoryBarrier();

  if (id == 0) {
    PROTECT(t, method);

    ACQUIRE(t, t->m->referenceLock);

    if (method->nativeID() == 0) {
      GcVector* v = vectorAppend(t, roots(t)->jNIMethodTable(), method);
      // sequence point, for gc (don't recombine statements)
      roots(t)->setJNIMethodTable(t, v);

      storeStoreMemoryBarrier();

      method->nativeID() = roots(t)->jNIMethodTable()->size();
    }
  }

  return method->nativeID();
}

uint64_t getMethodID(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  const char* name = reinterpret_cast<const char*>(arguments[1]);
  const char* spec = reinterpret_cast<const char*>(arguments[2]);

  GcMethod* method = findMethod(t, c, name, spec);

  assertT(t, (method->flags() & ACC_STATIC) == 0);

  return methodID(t, method);
}

jmethodID JNICALL
    GetMethodID(Thread* t, jclass c, const char* name, const char* spec)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           reinterpret_cast<uintptr_t>(name),
                           reinterpret_cast<uintptr_t>(spec)};

  return run(t, getMethodID, arguments);
}

uint64_t getStaticMethodID(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  const char* name = reinterpret_cast<const char*>(arguments[1]);
  const char* spec = reinterpret_cast<const char*>(arguments[2]);

  GcMethod* method = findMethod(t, c, name, spec);

  assertT(t, method->flags() & ACC_STATIC);

  return methodID(t, method);
}

jmethodID JNICALL
    GetStaticMethodID(Thread* t, jclass c, const char* name, const char* spec)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           reinterpret_cast<uintptr_t>(name),
                           reinterpret_cast<uintptr_t>(spec)};

  return run(t, getStaticMethodID, arguments);
}

GcMethod* getMethod(Thread* t, jmethodID m)
{
  assertT(t, m);

  GcMethod* method
      = cast<GcMethod>(t, roots(t)->jNIMethodTable()->body()[m - 1]);

  assertT(t, (method->flags() & ACC_STATIC) == 0);

  return method;
}

uint64_t newObjectV(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  jmethodID m = arguments[1];
  va_list* a = reinterpret_cast<va_list*>(arguments[2]);

  object o = make(t, (*c)->vmClass());
  PROTECT(t, o);

  t->m->processor->invokeList(t, getMethod(t, m), o, true, *a);

  return reinterpret_cast<uint64_t>(makeLocalReference(t, o));
}

jobject JNICALL NewObjectV(Thread* t, jclass c, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return reinterpret_cast<jobject>(run(t, newObjectV, arguments));
}

jobject JNICALL NewObject(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jobject r = NewObjectV(t, c, m, a);

  va_end(a);

  return r;
}

uint64_t newObjectA(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  jmethodID m = arguments[1];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[2]);

  object o = make(t, (*c)->vmClass());
  PROTECT(t, o);

  t->m->processor->invokeArray(t, getMethod(t, m), o, a);

  return reinterpret_cast<uint64_t>(makeLocalReference(t, o));
}

jobject JNICALL NewObjectA(Thread* t, jclass c, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), m, reinterpret_cast<uintptr_t>(a)};

  return reinterpret_cast<jobject>(run(t, newObjectA, arguments));
}

uint64_t callObjectMethodV(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  va_list* a = reinterpret_cast<va_list*>(arguments[2]);

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t, t->m->processor->invokeList(t, getMethod(t, m), *o, true, *a)));
}

jobject JNICALL CallObjectMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return reinterpret_cast<jobject>(run(t, callObjectMethodV, arguments));
}

jobject JNICALL CallObjectMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jobject r = CallObjectMethodV(t, o, m, a);

  va_end(a);

  return r;
}

uint64_t callObjectMethodA(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[2]);

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t, t->m->processor->invokeArray(t, getMethod(t, m), *o, a)));
}

jobject JNICALL
    CallObjectMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return reinterpret_cast<jobject>(run(t, callObjectMethodA, arguments));
}

uint64_t callIntMethodV(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  va_list* a = reinterpret_cast<va_list*>(arguments[2]);

  return cast<GcInt>(
             t, t->m->processor->invokeList(t, getMethod(t, m), *o, true, *a))
      ->value();
}

jboolean JNICALL
    CallBooleanMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callIntMethodV, arguments) != 0;
}

jboolean JNICALL CallBooleanMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jboolean r = CallBooleanMethodV(t, o, m, a);

  va_end(a);

  return r;
}

uint64_t callIntMethodA(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[2]);

  return cast<GcInt>(t, t->m->processor->invokeArray(t, getMethod(t, m), *o, a))
      ->value();
}

jboolean JNICALL
    CallBooleanMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callIntMethodA, arguments) != 0;
}

jbyte JNICALL CallByteMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callIntMethodV, arguments);
}

jbyte JNICALL CallByteMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jbyte r = CallByteMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jbyte JNICALL
    CallByteMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callIntMethodA, arguments);
}

jchar JNICALL CallCharMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callIntMethodV, arguments);
}

jchar JNICALL CallCharMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jchar r = CallCharMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jchar JNICALL
    CallCharMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callIntMethodA, arguments);
}

jshort JNICALL CallShortMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callIntMethodV, arguments);
}

jshort JNICALL CallShortMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jshort r = CallShortMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jshort JNICALL
    CallShortMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callIntMethodA, arguments);
}

jint JNICALL CallIntMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callIntMethodV, arguments);
}

jint JNICALL CallIntMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jint r = CallIntMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jint JNICALL CallIntMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callIntMethodA, arguments);
}

uint64_t callLongMethodV(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  va_list* a = reinterpret_cast<va_list*>(arguments[2]);

  return cast<GcLong>(
             t, t->m->processor->invokeList(t, getMethod(t, m), *o, true, *a))
      ->value();
}

jlong JNICALL CallLongMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callLongMethodV, arguments);
}

jlong JNICALL CallLongMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jlong r = CallLongMethodV(t, o, m, a);

  va_end(a);

  return r;
}

uint64_t callLongMethodA(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[2]);

  return cast<GcLong>(t,
                      t->m->processor->invokeArray(t, getMethod(t, m), *o, a))
      ->value();
}

jlong JNICALL
    CallLongMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callLongMethodA, arguments);
}

jfloat JNICALL CallFloatMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return bitsToFloat(run(t, callIntMethodV, arguments));
}

jfloat JNICALL CallFloatMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jfloat r = CallFloatMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jfloat JNICALL
    CallFloatMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return bitsToFloat(run(t, callIntMethodA, arguments));
}

jdouble JNICALL CallDoubleMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return bitsToDouble(run(t, callLongMethodV, arguments));
}

jdouble JNICALL CallDoubleMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jdouble r = CallDoubleMethodV(t, o, m, a);

  va_end(a);

  return r;
}

jdouble JNICALL
    CallDoubleMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  return bitsToDouble(run(t, callLongMethodA, arguments));
}

uint64_t callVoidMethodV(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  va_list* a = reinterpret_cast<va_list*>(arguments[2]);

  t->m->processor->invokeList(t, getMethod(t, m), *o, true, *a);

  return 0;
}

void JNICALL CallVoidMethodV(Thread* t, jobject o, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o),
                           m,
                           reinterpret_cast<uintptr_t>(VA_LIST(a))};

  run(t, callVoidMethodV, arguments);
}

void JNICALL CallVoidMethod(Thread* t, jobject o, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  CallVoidMethodV(t, o, m, a);

  va_end(a);
}

uint64_t callVoidMethodA(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  jmethodID m = arguments[1];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[2]);

  t->m->processor->invokeArray(t, getMethod(t, m), *o, a);

  return 0;
}

void JNICALL CallVoidMethodA(Thread* t, jobject o, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), m, reinterpret_cast<uintptr_t>(a)};

  run(t, callVoidMethodA, arguments);
}

GcMethod* getStaticMethod(Thread* t, jmethodID m)
{
  assertT(t, m);

  GcMethod* method
      = cast<GcMethod>(t, roots(t)->jNIMethodTable()->body()[m - 1]);

  assertT(t, method->flags() & ACC_STATIC);

  return method;
}

uint64_t callStaticObjectMethodV(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  va_list* a = reinterpret_cast<va_list*>(arguments[1]);

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t, t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, *a)));
}

jobject JNICALL
    CallStaticObjectMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return reinterpret_cast<jobject>(run(t, callStaticObjectMethodV, arguments));
}

jobject JNICALL CallStaticObjectMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jobject r = CallStaticObjectMethodV(t, c, m, a);

  va_end(a);

  return r;
}

uint64_t callStaticObjectMethodA(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[1]);

  return reinterpret_cast<uint64_t>(makeLocalReference(
      t, t->m->processor->invokeArray(t, getStaticMethod(t, m), 0, a)));
}

jobject JNICALL
    CallStaticObjectMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return reinterpret_cast<jobject>(run(t, callStaticObjectMethodA, arguments));
}

uint64_t callStaticIntMethodV(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  va_list* a = reinterpret_cast<va_list*>(arguments[1]);

  return cast<GcInt>(t,
                     t->m->processor->invokeList(
                         t, getStaticMethod(t, m), 0, true, *a))->value();
}

jboolean JNICALL
    CallStaticBooleanMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticIntMethodV, arguments) != 0;
}

jboolean JNICALL CallStaticBooleanMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jboolean r = CallStaticBooleanMethodV(t, c, m, a);

  va_end(a);

  return r;
}

uint64_t callStaticIntMethodA(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[1]);

  return cast<GcInt>(
             t, t->m->processor->invokeArray(t, getStaticMethod(t, m), 0, a))
      ->value();
}

jboolean JNICALL
    CallStaticBooleanMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticIntMethodA, arguments) != 0;
}

jbyte JNICALL CallStaticByteMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticIntMethodV, arguments);
}

jbyte JNICALL CallStaticByteMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jbyte r = CallStaticByteMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jbyte JNICALL
    CallStaticByteMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticIntMethodA, arguments);
}

jchar JNICALL CallStaticCharMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticIntMethodV, arguments);
}

jchar JNICALL CallStaticCharMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jchar r = CallStaticCharMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jchar JNICALL
    CallStaticCharMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticIntMethodA, arguments);
}

jshort JNICALL CallStaticShortMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticIntMethodV, arguments);
}

jshort JNICALL CallStaticShortMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jshort r = CallStaticShortMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jshort JNICALL
    CallStaticShortMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticIntMethodA, arguments);
}

jint JNICALL CallStaticIntMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticIntMethodV, arguments);
}

jint JNICALL CallStaticIntMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jint r = CallStaticIntMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jint JNICALL
    CallStaticIntMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticIntMethodA, arguments);
}

uint64_t callStaticLongMethodV(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  va_list* a = reinterpret_cast<va_list*>(arguments[1]);

  return cast<GcLong>(t,
                      t->m->processor->invokeList(
                          t, getStaticMethod(t, m), 0, true, *a))->value();
}

jlong JNICALL CallStaticLongMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return run(t, callStaticLongMethodV, arguments);
}

jlong JNICALL CallStaticLongMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jlong r = CallStaticLongMethodV(t, c, m, a);

  va_end(a);

  return r;
}

uint64_t callStaticLongMethodA(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[1]);

  return cast<GcLong>(
             t, t->m->processor->invokeArray(t, getStaticMethod(t, m), 0, a))
      ->value();
}

jlong JNICALL
    CallStaticLongMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return run(t, callStaticLongMethodA, arguments);
}

jfloat JNICALL CallStaticFloatMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return bitsToFloat(run(t, callStaticIntMethodV, arguments));
}

jfloat JNICALL CallStaticFloatMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jfloat r = CallStaticFloatMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jfloat JNICALL
    CallStaticFloatMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return bitsToFloat(run(t, callStaticIntMethodA, arguments));
}

jdouble JNICALL
    CallStaticDoubleMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  return bitsToDouble(run(t, callStaticLongMethodV, arguments));
}

jdouble JNICALL CallStaticDoubleMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  jdouble r = CallStaticDoubleMethodV(t, c, m, a);

  va_end(a);

  return r;
}

jdouble JNICALL
    CallStaticDoubleMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  return bitsToDouble(run(t, callStaticLongMethodA, arguments));
}

uint64_t callStaticVoidMethodV(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  va_list* a = reinterpret_cast<va_list*>(arguments[1]);

  t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, *a);

  return 0;
}

void JNICALL CallStaticVoidMethodV(Thread* t, jclass, jmethodID m, va_list a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(VA_LIST(a))};

  run(t, callStaticVoidMethodV, arguments);
}

void JNICALL CallStaticVoidMethod(Thread* t, jclass c, jmethodID m, ...)
{
  va_list a;
  va_start(a, m);

  CallStaticVoidMethodV(t, c, m, a);

  va_end(a);
}

uint64_t callStaticVoidMethodA(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[0];
  const jvalue* a = reinterpret_cast<const jvalue*>(arguments[1]);

  t->m->processor->invokeArray(t, getStaticMethod(t, m), 0, a);

  return 0;
}

void JNICALL
    CallStaticVoidMethodA(Thread* t, jclass, jmethodID m, const jvalue* a)
{
  uintptr_t arguments[] = {m, reinterpret_cast<uintptr_t>(a)};

  run(t, callStaticVoidMethodA, arguments);
}

jint fieldID(Thread* t, GcField* field)
{
  int id = field->nativeID();

  loadMemoryBarrier();

  if (id == 0) {
    PROTECT(t, field);

    ACQUIRE(t, t->m->referenceLock);

    if (field->nativeID() == 0) {
      GcVector* v = vectorAppend(t, roots(t)->jNIFieldTable(), field);
      // sequence point, for gc (don't recombine statements)
      roots(t)->setJNIFieldTable(t, v);

      storeStoreMemoryBarrier();

      field->nativeID() = roots(t)->jNIFieldTable()->size();
    }
  }

  return field->nativeID();
}

uint64_t getFieldID(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  const char* name = reinterpret_cast<const char*>(arguments[1]);
  const char* spec = reinterpret_cast<const char*>(arguments[2]);

  return fieldID(t, resolveField(t, (*c)->vmClass(), name, spec));
}

jfieldID JNICALL
    GetFieldID(Thread* t, jclass c, const char* name, const char* spec)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           reinterpret_cast<uintptr_t>(name),
                           reinterpret_cast<uintptr_t>(spec)};

  return run(t, getFieldID, arguments);
}

jfieldID JNICALL
    GetStaticFieldID(Thread* t, jclass c, const char* name, const char* spec)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           reinterpret_cast<uintptr_t>(name),
                           reinterpret_cast<uintptr_t>(spec)};

  return run(t, getFieldID, arguments);
}

GcField* getField(Thread* t, jfieldID f)
{
  assertT(t, f);

  GcField* field = cast<GcField>(t, roots(t)->jNIFieldTable()->body()[f - 1]);

  assertT(t, (field->flags() & ACC_STATIC) == 0);

  return field;
}

uint64_t getObjectField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return reinterpret_cast<uintptr_t>(
      makeLocalReference(t, fieldAtOffset<object>(*o, field->offset())));
}

jobject JNICALL GetObjectField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return reinterpret_cast<jobject>(run(t, getObjectField, arguments));
}

uint64_t getBooleanField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jboolean>(*o, field->offset());
}

jboolean JNICALL GetBooleanField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getBooleanField, arguments);
}

uint64_t getByteField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jbyte>(*o, field->offset());
}

jbyte JNICALL GetByteField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getByteField, arguments);
}

uint64_t getCharField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jchar>(*o, field->offset());
}

jchar JNICALL GetCharField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getCharField, arguments);
}

uint64_t getShortField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jshort>(*o, field->offset());
}

jshort JNICALL GetShortField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getShortField, arguments);
}

uint64_t getIntField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jint>(*o, field->offset());
}

jint JNICALL GetIntField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getIntField, arguments);
}

uint64_t getLongField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jlong>(*o, field->offset());
}

jlong JNICALL GetLongField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return run(t, getLongField, arguments);
}

uint64_t getFloatField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return floatToBits(fieldAtOffset<jfloat>(*o, field->offset()));
}

jfloat JNICALL GetFloatField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return bitsToFloat(run(t, getFloatField, arguments));
}

uint64_t getDoubleField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return doubleToBits(fieldAtOffset<jdouble>(*o, field->offset()));
}

jdouble JNICALL GetDoubleField(Thread* t, jobject o, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field};

  return bitsToDouble(run(t, getDoubleField, arguments));
}

uint64_t setObjectField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jobject v = reinterpret_cast<jobject>(arguments[2]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  setField(t, *o, field->offset(), (v ? *v : 0));

  return 1;
}

void JNICALL SetObjectField(Thread* t, jobject o, jfieldID field, jobject v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), field, reinterpret_cast<uintptr_t>(v)};

  run(t, setObjectField, arguments);
}

uint64_t setBooleanField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jboolean v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jboolean>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetBooleanField(Thread* t, jobject o, jfieldID field, jboolean v)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field, v};

  run(t, setBooleanField, arguments);
}

uint64_t setByteField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jbyte v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jbyte>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetByteField(Thread* t, jobject o, jfieldID field, jbyte v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), field, static_cast<uintptr_t>(v)};

  run(t, setByteField, arguments);
}

uint64_t setCharField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jchar v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jchar>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetCharField(Thread* t, jobject o, jfieldID field, jchar v)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(o), field, v};

  run(t, setCharField, arguments);
}

uint64_t setShortField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jshort v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jshort>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetShortField(Thread* t, jobject o, jfieldID field, jshort v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), field, static_cast<uintptr_t>(v)};

  run(t, setShortField, arguments);
}

uint64_t setIntField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jint v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jint>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetIntField(Thread* t, jobject o, jfieldID field, jint v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), field, static_cast<uintptr_t>(v)};

  run(t, setIntField, arguments);
}

uint64_t setLongField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jlong v;
  memcpy(&v, arguments + 2, sizeof(jlong));

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jlong>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetLongField(Thread* t, jobject o, jfieldID field, jlong v)
{
  uintptr_t arguments[2 + (sizeof(jlong) / BytesPerWord)];
  arguments[0] = reinterpret_cast<uintptr_t>(o);
  arguments[1] = field;
  memcpy(arguments + 2, &v, sizeof(jlong));

  run(t, setLongField, arguments);
}

uint64_t setFloatField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jfloat v = bitsToFloat(arguments[2]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jfloat>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetFloatField(Thread* t, jobject o, jfieldID field, jfloat v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(o), field, floatToBits(v)};

  run(t, setFloatField, arguments);
}

uint64_t setDoubleField(Thread* t, uintptr_t* arguments)
{
  jobject o = reinterpret_cast<jobject>(arguments[0]);
  GcField* field = getField(t, arguments[1]);
  jdouble v;
  memcpy(&v, arguments + 2, sizeof(jdouble));

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jdouble>(*o, field->offset()) = v;

  return 1;
}

void JNICALL SetDoubleField(Thread* t, jobject o, jfieldID field, jdouble v)
{
  uintptr_t arguments[2 + (sizeof(jdouble) / BytesPerWord)];
  arguments[0] = reinterpret_cast<uintptr_t>(o);
  arguments[1] = field;
  memcpy(arguments + 2, &v, sizeof(jdouble));

  run(t, setDoubleField, arguments);
}

GcField* getStaticField(Thread* t, jfieldID f)
{
  assertT(t, f);

  GcField* field = cast<GcField>(t, roots(t)->jNIFieldTable()->body()[f - 1]);

  assertT(t, field->flags() & ACC_STATIC);

  return field;
}

uint64_t getStaticObjectField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return reinterpret_cast<uintptr_t>(makeLocalReference(
      t, fieldAtOffset<object>(c->vmClass()->staticTable(), field->offset())));
}

jobject JNICALL GetStaticObjectField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return reinterpret_cast<jobject>(run(t, getStaticObjectField, arguments));
}

uint64_t getStaticBooleanField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jboolean>(c->vmClass()->staticTable(), field->offset());
}

jboolean JNICALL GetStaticBooleanField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticBooleanField, arguments);
}

uint64_t getStaticByteField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jbyte>(c->vmClass()->staticTable(), field->offset());
}

jbyte JNICALL GetStaticByteField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticByteField, arguments);
}

uint64_t getStaticCharField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jchar>(c->vmClass()->staticTable(), field->offset());
}

jchar JNICALL GetStaticCharField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticCharField, arguments);
}

uint64_t getStaticShortField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jshort>(c->vmClass()->staticTable(), field->offset());
}

jshort JNICALL GetStaticShortField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticShortField, arguments);
}

uint64_t getStaticIntField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jint>(c->vmClass()->staticTable(), field->offset());
}

jint JNICALL GetStaticIntField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticIntField, arguments);
}

uint64_t getStaticLongField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return fieldAtOffset<jlong>(c->vmClass()->staticTable(), field->offset());
}

jlong JNICALL GetStaticLongField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return run(t, getStaticLongField, arguments);
}

uint64_t getStaticFloatField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return floatToBits(
      fieldAtOffset<jfloat>(c->vmClass()->staticTable(), field->offset()));
}

jfloat JNICALL GetStaticFloatField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return bitsToFloat(run(t, getStaticFloatField, arguments));
}

uint64_t getStaticDoubleField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_READ(t, field);

  return doubleToBits(
      fieldAtOffset<jdouble>(c->vmClass()->staticTable(), field->offset()));
}

jdouble JNICALL GetStaticDoubleField(Thread* t, jclass c, jfieldID field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field};

  return bitsToDouble(run(t, getStaticDoubleField, arguments));
}

uint64_t setStaticObjectField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jobject v = reinterpret_cast<jobject>(arguments[2]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  setField(t, c->vmClass()->staticTable(), field->offset(), (v ? *v : 0));

  return 1;
}

void JNICALL
    SetStaticObjectField(Thread* t, jclass c, jfieldID field, jobject v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), field, reinterpret_cast<uintptr_t>(v)};

  run(t, setStaticObjectField, arguments);
}

uint64_t setStaticBooleanField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jboolean v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jboolean>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL
    SetStaticBooleanField(Thread* t, jclass c, jfieldID field, jboolean v)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field, v};

  run(t, setStaticBooleanField, arguments);
}

uint64_t setStaticByteField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jbyte v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jbyte>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticByteField(Thread* t, jclass c, jfieldID field, jbyte v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), field, static_cast<uintptr_t>(v)};

  run(t, setStaticByteField, arguments);
}

uint64_t setStaticCharField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jchar v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jchar>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticCharField(Thread* t, jclass c, jfieldID field, jchar v)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c), field, v};

  run(t, setStaticCharField, arguments);
}

uint64_t setStaticShortField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jshort v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jshort>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticShortField(Thread* t, jclass c, jfieldID field, jshort v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), field, static_cast<uintptr_t>(v)};

  run(t, setStaticShortField, arguments);
}

uint64_t setStaticIntField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jint v = arguments[2];

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jint>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticIntField(Thread* t, jclass c, jfieldID field, jint v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), field, static_cast<uintptr_t>(v)};

  run(t, setStaticIntField, arguments);
}

uint64_t setStaticLongField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jlong v;
  memcpy(&v, arguments + 2, sizeof(jlong));

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jlong>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticLongField(Thread* t, jclass c, jfieldID field, jlong v)
{
  uintptr_t arguments[2 + (sizeof(jlong) / BytesPerWord)];
  arguments[0] = reinterpret_cast<uintptr_t>(c);
  arguments[1] = field;
  memcpy(arguments + 2, &v, sizeof(jlong));

  run(t, setStaticLongField, arguments);
}

uint64_t setStaticFloatField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jfloat v = bitsToFloat(arguments[2]);

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jfloat>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL SetStaticFloatField(Thread* t, jclass c, jfieldID field, jfloat v)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(c), field, floatToBits(v)};

  run(t, setStaticFloatField, arguments);
}

uint64_t setStaticDoubleField(Thread* t, uintptr_t* arguments)
{
  GcJclass* c = cast<GcJclass>(t, *reinterpret_cast<jobject>(arguments[0]));

  initClass(t, c->vmClass());

  GcField* field = getStaticField(t, arguments[1]);
  jdouble v;
  memcpy(&v, arguments + 2, sizeof(jdouble));

  PROTECT(t, field);
  ACQUIRE_FIELD_FOR_WRITE(t, field);

  fieldAtOffset<jdouble>(c->vmClass()->staticTable(), field->offset()) = v;

  return 1;
}

void JNICALL
    SetStaticDoubleField(Thread* t, jclass c, jfieldID field, jdouble v)
{
  uintptr_t arguments[2 + (sizeof(jdouble) / BytesPerWord)];
  arguments[0] = reinterpret_cast<uintptr_t>(c);
  arguments[1] = field;
  memcpy(arguments + 2, &v, sizeof(jdouble));

  run(t, setStaticDoubleField, arguments);
}

jobject JNICALL newGlobalRef(Thread* t, jobject o, bool weak)
{
  ENTER(t, Thread::ActiveState);

  ACQUIRE(t, t->m->referenceLock);

  if (o) {
    for (Reference* r = t->m->jniReferences; r; r = r->next) {
      if (r->target == *o and r->weak == weak) {
        acquire(t, r);

        return &(r->target);
      }
    }

    Reference* r = new (t->m->heap->allocate(sizeof(Reference)))
        Reference(*o, &(t->m->jniReferences), weak);

    acquire(t, r);

    return &(r->target);
  } else {
    return 0;
  }
}

jobject JNICALL NewGlobalRef(Thread* t, jobject o)
{
  return newGlobalRef(t, o, false);
}

void JNICALL DeleteGlobalRef(Thread* t, jobject r)
{
  ENTER(t, Thread::ActiveState);

  ACQUIRE(t, t->m->referenceLock);

  if (r) {
    release(t, reinterpret_cast<Reference*>(r));
  }
}

jobject JNICALL NewWeakGlobalRef(Thread* t, jobject o)
{
  return newGlobalRef(t, o, true);
}

void JNICALL DeleteWeakGlobalRef(Thread* t, jobject r)
{
  DeleteGlobalRef(t, r);
}

jint JNICALL EnsureLocalCapacity(Thread*, jint)
{
  return 0;
}

jthrowable JNICALL ExceptionOccurred(Thread* t)
{
  ENTER(t, Thread::ActiveState);

  return reinterpret_cast<jthrowable>(makeLocalReference(t, t->exception));
}

void JNICALL ExceptionDescribe(Thread* t)
{
  ENTER(t, Thread::ActiveState);

  return printTrace(t, t->exception);
}

void JNICALL ExceptionClear(Thread* t)
{
  ENTER(t, Thread::ActiveState);

  t->exception = 0;
}

uint64_t newObjectArray(Thread* t, uintptr_t* arguments)
{
  jsize length = arguments[0];
  jclass class_ = reinterpret_cast<jclass>(arguments[1]);
  jobject init = reinterpret_cast<jobject>(arguments[2]);

  object a = makeObjectArray(t, (*class_)->vmClass(), length);
  object value = (init ? *init : 0);
  for (jsize i = 0; i < length; ++i) {
    reinterpret_cast<GcArray*>(a)->setBodyElement(t, i, value);
  }
  return reinterpret_cast<uint64_t>(makeLocalReference(t, a));
}

jobjectArray JNICALL
    NewObjectArray(Thread* t, jsize length, jclass class_, jobject init)
{
  uintptr_t arguments[] = {static_cast<uintptr_t>(length),
                           reinterpret_cast<uintptr_t>(class_),
                           reinterpret_cast<uintptr_t>(init)};

  return reinterpret_cast<jobjectArray>(run(t, newObjectArray, arguments));
}

jobject JNICALL
    GetObjectArrayElement(Thread* t, jobjectArray array, jsize index)
{
  ENTER(t, Thread::ActiveState);

  return makeLocalReference(
      t, objectArrayBody(t, reinterpret_cast<object>(*array), index));
}

void JNICALL SetObjectArrayElement(Thread* t,
                                   jobjectArray array,
                                   jsize index,
                                   jobject value)
{
  ENTER(t, Thread::ActiveState);

  setField(t,
           reinterpret_cast<object>(*array),
           ArrayBody + (index * BytesPerWord),
           (value ? *value : 0));
}

uint64_t newArray(Thread* t, uintptr_t* arguments)
{
  object (*constructor)(Thread*, unsigned)
      = reinterpret_cast<object (*)(Thread*, unsigned)>(arguments[0]);

  jsize length = arguments[1];

  return reinterpret_cast<uint64_t>(
      makeLocalReference(t, constructor(t, length)));
}

jbooleanArray JNICALL NewBooleanArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeBooleanArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jbooleanArray>(run(t, newArray, arguments));
}

object makeByteArray0(Thread* t, unsigned length)
{
  return makeByteArray(t, length);
}

jbyteArray JNICALL NewByteArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeByteArray0)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jbyteArray>(run(t, newArray, arguments));
}

jcharArray JNICALL NewCharArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeCharArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jcharArray>(run(t, newArray, arguments));
}

jshortArray JNICALL NewShortArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeShortArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jshortArray>(run(t, newArray, arguments));
}

jintArray JNICALL NewIntArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeIntArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jintArray>(run(t, newArray, arguments));
}

jlongArray JNICALL NewLongArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeLongArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jlongArray>(run(t, newArray, arguments));
}

jfloatArray JNICALL NewFloatArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeFloatArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jfloatArray>(run(t, newArray, arguments));
}

jdoubleArray JNICALL NewDoubleArray(Thread* t, jsize length)
{
  uintptr_t arguments[]
      = {reinterpret_cast<uintptr_t>(voidPointer(makeDoubleArray)),
         static_cast<uintptr_t>(length)};

  return reinterpret_cast<jdoubleArray>(run(t, newArray, arguments));
}

jboolean* JNICALL
    GetBooleanArrayElements(Thread* t, jbooleanArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jboolean);
  jboolean* p = static_cast<jboolean*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jbyte* JNICALL
    GetByteArrayElements(Thread* t, jbyteArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jbyte);
  jbyte* p = static_cast<jbyte*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jchar* JNICALL
    GetCharArrayElements(Thread* t, jcharArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jchar);
  jchar* p = static_cast<jchar*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jshort* JNICALL
    GetShortArrayElements(Thread* t, jshortArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jshort);
  jshort* p = static_cast<jshort*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jint* JNICALL GetIntArrayElements(Thread* t, jintArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jint);
  jint* p = static_cast<jint*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jlong* JNICALL
    GetLongArrayElements(Thread* t, jlongArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jlong);
  jlong* p = static_cast<jlong*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jfloat* JNICALL
    GetFloatArrayElements(Thread* t, jfloatArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jfloat);
  jfloat* p = static_cast<jfloat*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

jdouble* JNICALL
    GetDoubleArrayElements(Thread* t, jdoubleArray array, jboolean* isCopy)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jdouble);
  jdouble* p = static_cast<jdouble*>(t->m->heap->allocate(size));
  if (size) {
    memcpy(p, (*array)->body().begin(), size);
  }

  if (isCopy) {
    *isCopy = true;
  }

  return p;
}

void JNICALL ReleaseBooleanArrayElements(Thread* t,
                                         jbooleanArray array,
                                         jboolean* p,
                                         jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jboolean);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL
    ReleaseByteArrayElements(Thread* t, jbyteArray array, jbyte* p, jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jbyte);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL
    ReleaseCharArrayElements(Thread* t, jcharArray array, jchar* p, jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jchar);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL ReleaseShortArrayElements(Thread* t,
                                       jshortArray array,
                                       jshort* p,
                                       jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jshort);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL
    ReleaseIntArrayElements(Thread* t, jintArray array, jint* p, jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jint);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL
    ReleaseLongArrayElements(Thread* t, jlongArray array, jlong* p, jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jlong);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL ReleaseFloatArrayElements(Thread* t,
                                       jfloatArray array,
                                       jfloat* p,
                                       jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jfloat);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL ReleaseDoubleArrayElements(Thread* t,
                                        jdoubleArray array,
                                        jdouble* p,
                                        jint mode)
{
  ENTER(t, Thread::ActiveState);

  unsigned size = (*array)->length() * sizeof(jdouble);

  if (mode == 0 or mode == AVIAN_JNI_COMMIT) {
    if (size) {
      memcpy((*array)->body().begin(), p, size);
    }
  }

  if (mode == 0 or mode == AVIAN_JNI_ABORT) {
    t->m->heap->free(p, size);
  }
}

void JNICALL GetBooleanArrayRegion(Thread* t,
                                   jbooleanArray array,
                                   jint offset,
                                   jint length,
                                   jboolean* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jboolean));
  }
}

void JNICALL GetByteArrayRegion(Thread* t,
                                jbyteArray array,
                                jint offset,
                                jint length,
                                jbyte* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jbyte));
  }
}

void JNICALL GetCharArrayRegion(Thread* t,
                                jcharArray array,
                                jint offset,
                                jint length,
                                jchar* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jchar));
  }
}

void JNICALL GetShortArrayRegion(Thread* t,
                                 jshortArray array,
                                 jint offset,
                                 jint length,
                                 jshort* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jshort));
  }
}

void JNICALL GetIntArrayRegion(Thread* t,
                               jintArray array,
                               jint offset,
                               jint length,
                               jint* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jint));
  }
}

void JNICALL GetLongArrayRegion(Thread* t,
                                jlongArray array,
                                jint offset,
                                jint length,
                                jlong* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jlong));
  }
}

void JNICALL GetFloatArrayRegion(Thread* t,
                                 jfloatArray array,
                                 jint offset,
                                 jint length,
                                 jfloat* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jfloat));
  }
}

void JNICALL GetDoubleArrayRegion(Thread* t,
                                  jdoubleArray array,
                                  jint offset,
                                  jint length,
                                  jdouble* dst)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(dst, &(*array)->body()[offset], length * sizeof(jdouble));
  }
}

void JNICALL SetBooleanArrayRegion(Thread* t,
                                   jbooleanArray array,
                                   jint offset,
                                   jint length,
                                   const jboolean* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jboolean));
  }
}

void JNICALL SetByteArrayRegion(Thread* t,
                                jbyteArray array,
                                jint offset,
                                jint length,
                                const jbyte* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jbyte));
  }
}

void JNICALL SetCharArrayRegion(Thread* t,
                                jcharArray array,
                                jint offset,
                                jint length,
                                const jchar* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jchar));
  }
}

void JNICALL SetShortArrayRegion(Thread* t,
                                 jshortArray array,
                                 jint offset,
                                 jint length,
                                 const jshort* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jshort));
  }
}

void JNICALL SetIntArrayRegion(Thread* t,
                               jintArray array,
                               jint offset,
                               jint length,
                               const jint* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jint));
  }
}

void JNICALL SetLongArrayRegion(Thread* t,
                                jlongArray array,
                                jint offset,
                                jint length,
                                const jlong* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jlong));
  }
}

void JNICALL SetFloatArrayRegion(Thread* t,
                                 jfloatArray array,
                                 jint offset,
                                 jint length,
                                 const jfloat* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jfloat));
  }
}

void JNICALL SetDoubleArrayRegion(Thread* t,
                                  jdoubleArray array,
                                  jint offset,
                                  jint length,
                                  const jdouble* src)
{
  ENTER(t, Thread::ActiveState);

  if (length) {
    memcpy(&(*array)->body()[offset], src, length * sizeof(jdouble));
  }
}

void* JNICALL
    GetPrimitiveArrayCritical(Thread* t, jarray array, jboolean* isCopy)
{
  if (t->criticalLevel == 0) {
    enter(t, Thread::ActiveState);
  }

  ++t->criticalLevel;

  if (isCopy) {
    *isCopy = true;
  }

  expect(t, *array);

  return reinterpret_cast<uintptr_t*>(*array) + 2;
}

void JNICALL ReleasePrimitiveArrayCritical(Thread* t, jarray, void*, jint)
{
  if ((--t->criticalLevel) == 0) {
    enter(t, Thread::IdleState);
  }
}

uint64_t fromReflectedMethod(Thread* t, uintptr_t* arguments)
{
  jobject m = reinterpret_cast<jobject>(arguments[0]);

  return methodID(t, t->m->classpath->getVMMethod(t, *m));
}

jmethodID JNICALL FromReflectedMethod(Thread* t, jobject method)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(method)};

  return static_cast<jmethodID>(run(t, fromReflectedMethod, arguments));
}

uint64_t toReflectedMethod(Thread* t, uintptr_t* arguments)
{
  jmethodID m = arguments[1];
  jboolean isStatic = arguments[2];

  return reinterpret_cast<uintptr_t>(makeLocalReference(
      t,
      t->m->classpath->makeJMethod(
          t, isStatic ? getStaticMethod(t, m) : getMethod(t, m))));
}

jobject JNICALL
    ToReflectedMethod(Thread* t, jclass c, jmethodID method, jboolean isStatic)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           static_cast<uintptr_t>(method),
                           static_cast<uintptr_t>(isStatic)};

  return reinterpret_cast<jobject>(run(t, toReflectedMethod, arguments));
}

uint64_t fromReflectedField(Thread* t, uintptr_t* arguments)
{
  jobject f = reinterpret_cast<jobject>(arguments[0]);

  return fieldID(t, t->m->classpath->getVMField(t, cast<GcJfield>(t, *f)));
}

jfieldID JNICALL FromReflectedField(Thread* t, jobject field)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(field)};

  return static_cast<jfieldID>(run(t, fromReflectedField, arguments));
}

uint64_t toReflectedField(Thread* t, uintptr_t* arguments)
{
  jfieldID f = arguments[1];
  jboolean isStatic = arguments[2];

  return reinterpret_cast<uintptr_t>(makeLocalReference(
      t,
      t->m->classpath->makeJField(
          t, isStatic ? getStaticField(t, f) : getField(t, f))));
}

jobject JNICALL
    ToReflectedField(Thread* t, jclass c, jfieldID field, jboolean isStatic)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           static_cast<uintptr_t>(field),
                           static_cast<uintptr_t>(isStatic)};

  return reinterpret_cast<jobject>(run(t, toReflectedField, arguments));
}

uint64_t registerNatives(Thread* t, uintptr_t* arguments)
{
  jclass c = reinterpret_cast<jclass>(arguments[0]);
  const JNINativeMethod* methods
      = reinterpret_cast<const JNINativeMethod*>(arguments[1]);
  jint methodCount = arguments[2];

  for (int i = 0; i < methodCount; ++i) {
    if (methods[i].function) {
      // Android's class library sometimes prepends a mysterious "!"
      // to the method signature, which we happily ignore:
      const char* sig = methods[i].signature;
      if (*sig == '!')
        ++sig;

      GcMethod* method
          = findMethodOrNull(t, (*c)->vmClass(), methods[i].name, sig);

      if (method == 0 or (method->flags() & ACC_NATIVE) == 0) {
        // The JNI spec says we must throw a NoSuchMethodError in this
        // case, but that would prevent using a code shrinker like
        // ProGuard effectively.  Instead, we just ignore it.

        if (false) {
          fprintf(stderr,
                  "not found: %s.%s%s\n",
                  (*c)->vmClass()->name()->body().begin(),
                  methods[i].name,
                  sig);
          abort(t);
        }
      } else {
        registerNative(t, method, methods[i].function);
      }
    }
  }

  return 1;
}

jint JNICALL RegisterNatives(Thread* t,
                             jclass c,
                             const JNINativeMethod* methods,
                             jint methodCount)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(c),
                           reinterpret_cast<uintptr_t>(methods),
                           static_cast<uintptr_t>(methodCount)};

  return run(t, registerNatives, arguments) ? 0 : -1;
}

jint JNICALL UnregisterNatives(Thread* t, jclass c)
{
  ENTER(t, Thread::ActiveState);

  unregisterNatives(t, (*c)->vmClass());

  return 0;
}

uint64_t monitorOp(Thread* t, uintptr_t* arguments)
{
  void (*op)(Thread*, object)
      = reinterpret_cast<void (*)(Thread*, object)>(arguments[0]);

  jobject o = reinterpret_cast<jobject>(arguments[1]);

  op(t, *o);

  return 1;
}

void acquire0(Thread* t, object o)
{
  return acquire(t, o);
}

jint JNICALL MonitorEnter(Thread* t, jobject o)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(voidPointer(acquire0)),
                           reinterpret_cast<uintptr_t>(o)};

  return run(t, monitorOp, arguments) ? 0 : -1;
}

void release0(Thread* t, object o)
{
  return release(t, o);
}

jint JNICALL MonitorExit(Thread* t, jobject o)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(voidPointer(release0)),
                           reinterpret_cast<uintptr_t>(o)};

  return run(t, monitorOp, arguments) ? 0 : -1;
}

jint JNICALL GetJavaVM(Thread* t, Machine** m)
{
  *m = t->m;
  return 0;
}

jboolean JNICALL IsSameObject(Thread* t, jobject a, jobject b)
{
  if (a and b) {
    ENTER(t, Thread::ActiveState);

    return *a == *b;
  } else {
    return a == b;
  }
}

uint64_t pushLocalFrame(Thread* t, uintptr_t* arguments)
{
  if (t->m->processor->pushLocalFrame(t, arguments[0])) {
    return 1;
  } else {
    throw_(t, roots(t)->outOfMemoryError());
  }
}

jint JNICALL PushLocalFrame(Thread* t, jint capacity)
{
  uintptr_t arguments[] = {static_cast<uintptr_t>(capacity)};

  return run(t, pushLocalFrame, arguments) ? 0 : -1;
}

uint64_t popLocalFrame(Thread* t, uintptr_t* arguments)
{
  uint64_t r;
  jobject presult = reinterpret_cast<jobject>(arguments[0]);
  if (presult != NULL) {
    object result = *presult;
    PROTECT(t, result);

    t->m->processor->popLocalFrame(t);

    r = reinterpret_cast<uint64_t>(makeLocalReference(t, result));
  } else {
    t->m->processor->popLocalFrame(t);
    r = 0;
  }

  return r;
}

jobject JNICALL PopLocalFrame(Thread* t, jobject result)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(result)};

  return reinterpret_cast<jobject>(run(t, popLocalFrame, arguments));
}

uint64_t newDirectByteBuffer(Thread* t, uintptr_t* arguments)
{
  jlong capacity;
  memcpy(&capacity, arguments + 1, sizeof(jlong));

  return reinterpret_cast<uintptr_t>(makeLocalReference(
      t,
      t->m->classpath->makeDirectByteBuffer(
          t, reinterpret_cast<void*>(arguments[0]), capacity)));
}

jobject JNICALL NewDirectByteBuffer(Thread* t, void* p, jlong capacity)
{
  uintptr_t arguments[1 + (sizeof(jlong) / BytesPerWord)];
  arguments[0] = reinterpret_cast<uintptr_t>(p);
  memcpy(arguments + 1, &capacity, sizeof(jlong));

  return reinterpret_cast<jobject>(run(t, newDirectByteBuffer, arguments));
}

uint64_t getDirectBufferAddress(Thread* t, uintptr_t* arguments)
{
  return reinterpret_cast<uintptr_t>(t->m->classpath->getDirectBufferAddress(
      t, *reinterpret_cast<jobject>(arguments[0])));
}

void* JNICALL GetDirectBufferAddress(Thread* t, jobject b)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(b)};

  return reinterpret_cast<void*>(run(t, getDirectBufferAddress, arguments));
}

uint64_t getDirectBufferCapacity(Thread* t, uintptr_t* arguments)
{
  return t->m->classpath->getDirectBufferCapacity(
      t, *reinterpret_cast<jobject>(arguments[0]));
}

jlong JNICALL GetDirectBufferCapacity(Thread* t, jobject b)
{
  uintptr_t arguments[] = {reinterpret_cast<uintptr_t>(b)};

  return run(t, getDirectBufferCapacity, arguments);
}

struct JavaVMOption {
  char* optionString;
  void* extraInfo;
};

struct JavaVMInitArgs {
  jint version;

  jint nOptions;
  JavaVMOption* options;
  jboolean ignoreUnrecognized;
};

int parseSize(const char* s)
{
  unsigned length = strlen(s);
  RUNTIME_ARRAY(char, buffer, length + 1);

  if (length == 0)
    return 0;

  char suffix = s[length - 1];
  if (suffix== 'k' or suffix == 'K') {
    memcpy(RUNTIME_ARRAY_BODY(buffer), s, length - 1);
    RUNTIME_ARRAY_BODY(buffer)[length - 1] = 0;
    return atoi(RUNTIME_ARRAY_BODY(buffer)) * 1024;
  }

  if (suffix == 'm' or suffix == 'M') {
    memcpy(RUNTIME_ARRAY_BODY(buffer), s, length - 1);
    RUNTIME_ARRAY_BODY(buffer)[length - 1] = 0;
    return atoi(RUNTIME_ARRAY_BODY(buffer)) * 1024 * 1024;
  }

  if (suffix == 'g' or suffix == 'G') {
    memcpy(RUNTIME_ARRAY_BODY(buffer), s, length - 1);
    RUNTIME_ARRAY_BODY(buffer)[length - 1] = 0;
    return atoi(RUNTIME_ARRAY_BODY(buffer)) * 1024 * 1024 * 1024;
  }

  return atoi(s);
}

void append(char** p, const char* value, unsigned length, char tail)
{
  if (length) {
    memcpy(*p, value, length);
    *p += length;
    *((*p)++) = tail;
  }
}

uint64_t boot(Thread* t, uintptr_t*)
{
  GcThrowable* throwable = makeThrowable(t, GcNullPointerException::Type);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setNullPointerException(t, throwable);

  throwable = makeThrowable(t, GcArithmeticException::Type);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setArithmeticException(t, throwable);

  throwable = makeThrowable(t, GcArrayIndexOutOfBoundsException::Type);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setArrayIndexOutOfBoundsException(t, throwable);

  throwable = makeThrowable(t, GcOutOfMemoryError::Type);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setOutOfMemoryError(t, throwable);

  throwable = makeThrowable(t, GcThrowable::Type);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setShutdownInProgress(t, throwable);

  t->m->classpath->preBoot(t);

  t->javaThread = t->m->classpath->makeThread(t, 0);

  t->javaThread->peer() = reinterpret_cast<jlong>(t);

  GcThread* jthread = t->m->classpath->makeThread(t, t);
  // sequence point, for gc (don't recombine statements)
  roots(t)->setFinalizerThread(t, jthread);

  roots(t)->finalizerThread()->daemon() = true;

  t->m->classpath->boot(t);

  const char* port = findProperty(t, "avian.trace.port");
  if (port) {
    GcString* host = makeString(t, "0.0.0.0");
    PROTECT(t, host);

    GcMethod* method = resolveMethod(t,
                                     roots(t)->bootLoader(),
                                     "avian/Traces",
                                     "startTraceListener",
                                     "(Ljava/lang/String;I)V");

    t->m->processor->invoke(t, method, 0, host, atoi(port));
  }

  enter(t, Thread::IdleState);

  return 1;
}

}  // namespace local

}  // namespace

namespace vm {

void populateJNITables(JavaVMVTable* vmTable, JNIEnvVTable* envTable)
{
  memset(vmTable, 0, sizeof(JavaVMVTable));

  vmTable->DestroyJavaVM = local::DestroyJavaVM;
  vmTable->AttachCurrentThread = local::AttachCurrentThread;
  vmTable->AttachCurrentThreadAsDaemon = local::AttachCurrentThreadAsDaemon;
  vmTable->DetachCurrentThread = local::DetachCurrentThread;
  vmTable->GetEnv = local::GetEnv;

  memset(envTable, 0, sizeof(JNIEnvVTable));

  envTable->GetVersion = local::GetVersion;
  envTable->GetStringLength = local::GetStringLength;
  envTable->GetStringChars = local::GetStringChars;
  envTable->ReleaseStringChars = local::ReleaseStringChars;
  envTable->GetStringRegion = local::GetStringRegion;
  envTable->GetStringCritical = local::GetStringCritical;
  envTable->ReleaseStringCritical = local::ReleaseStringCritical;
  envTable->GetStringUTFLength = local::GetStringUTFLength;
  envTable->GetStringUTFChars = local::GetStringUTFChars;
  envTable->ReleaseStringUTFChars = local::ReleaseStringUTFChars;
  envTable->GetStringUTFRegion = local::GetStringUTFRegion;
  envTable->GetArrayLength = local::GetArrayLength;
  envTable->NewString = local::NewString;
  envTable->NewStringUTF = local::NewStringUTF;
  envTable->DefineClass = local::DefineClass;
  envTable->FindClass = local::FindClass;
  envTable->ThrowNew = local::ThrowNew;
  envTable->Throw = local::Throw;
  envTable->ExceptionCheck = local::ExceptionCheck;
  envTable->NewDirectByteBuffer = local::NewDirectByteBuffer;
  envTable->GetDirectBufferAddress = local::GetDirectBufferAddress;
  envTable->GetDirectBufferCapacity = local::GetDirectBufferCapacity;
  envTable->NewLocalRef = local::NewLocalRef;
  envTable->DeleteLocalRef = local::DeleteLocalRef;
  envTable->GetObjectClass = local::GetObjectClass;
  envTable->GetSuperclass = local::GetSuperclass;
  envTable->IsInstanceOf = local::IsInstanceOf;
  envTable->IsAssignableFrom = local::IsAssignableFrom;
  envTable->GetFieldID = local::GetFieldID;
  envTable->GetMethodID = local::GetMethodID;
  envTable->GetStaticMethodID = local::GetStaticMethodID;
  envTable->NewObjectV = local::NewObjectV;
  envTable->NewObjectA = local::NewObjectA;
  envTable->NewObject = local::NewObject;
  envTable->CallObjectMethodV = local::CallObjectMethodV;
  envTable->CallObjectMethodA = local::CallObjectMethodA;
  envTable->CallObjectMethod = local::CallObjectMethod;
  envTable->CallBooleanMethodV = local::CallBooleanMethodV;
  envTable->CallBooleanMethodA = local::CallBooleanMethodA;
  envTable->CallBooleanMethod = local::CallBooleanMethod;
  envTable->CallByteMethodV = local::CallByteMethodV;
  envTable->CallByteMethodA = local::CallByteMethodA;
  envTable->CallByteMethod = local::CallByteMethod;
  envTable->CallCharMethodV = local::CallCharMethodV;
  envTable->CallCharMethodA = local::CallCharMethodA;
  envTable->CallCharMethod = local::CallCharMethod;
  envTable->CallShortMethodV = local::CallShortMethodV;
  envTable->CallShortMethodA = local::CallShortMethodA;
  envTable->CallShortMethod = local::CallShortMethod;
  envTable->CallIntMethodV = local::CallIntMethodV;
  envTable->CallIntMethodA = local::CallIntMethodA;
  envTable->CallIntMethod = local::CallIntMethod;
  envTable->CallLongMethodV = local::CallLongMethodV;
  envTable->CallLongMethodA = local::CallLongMethodA;
  envTable->CallLongMethod = local::CallLongMethod;
  envTable->CallFloatMethodV = local::CallFloatMethodV;
  envTable->CallFloatMethodA = local::CallFloatMethodA;
  envTable->CallFloatMethod = local::CallFloatMethod;
  envTable->CallDoubleMethodV = local::CallDoubleMethodV;
  envTable->CallDoubleMethodA = local::CallDoubleMethodA;
  envTable->CallDoubleMethod = local::CallDoubleMethod;
  envTable->CallVoidMethodV = local::CallVoidMethodV;
  envTable->CallVoidMethodA = local::CallVoidMethodA;
  envTable->CallVoidMethod = local::CallVoidMethod;
  envTable->CallStaticObjectMethodV = local::CallStaticObjectMethodV;
  envTable->CallStaticObjectMethodA = local::CallStaticObjectMethodA;
  envTable->CallStaticObjectMethod = local::CallStaticObjectMethod;
  envTable->CallStaticBooleanMethodV = local::CallStaticBooleanMethodV;
  envTable->CallStaticBooleanMethodA = local::CallStaticBooleanMethodA;
  envTable->CallStaticBooleanMethod = local::CallStaticBooleanMethod;
  envTable->CallStaticByteMethodV = local::CallStaticByteMethodV;
  envTable->CallStaticByteMethodA = local::CallStaticByteMethodA;
  envTable->CallStaticByteMethod = local::CallStaticByteMethod;
  envTable->CallStaticCharMethodV = local::CallStaticCharMethodV;
  envTable->CallStaticCharMethodA = local::CallStaticCharMethodA;
  envTable->CallStaticCharMethod = local::CallStaticCharMethod;
  envTable->CallStaticShortMethodV = local::CallStaticShortMethodV;
  envTable->CallStaticShortMethodA = local::CallStaticShortMethodA;
  envTable->CallStaticShortMethod = local::CallStaticShortMethod;
  envTable->CallStaticIntMethodV = local::CallStaticIntMethodV;
  envTable->CallStaticIntMethodA = local::CallStaticIntMethodA;
  envTable->CallStaticIntMethod = local::CallStaticIntMethod;
  envTable->CallStaticLongMethodV = local::CallStaticLongMethodV;
  envTable->CallStaticLongMethodA = local::CallStaticLongMethodA;
  envTable->CallStaticLongMethod = local::CallStaticLongMethod;
  envTable->CallStaticFloatMethodV = local::CallStaticFloatMethodV;
  envTable->CallStaticFloatMethodA = local::CallStaticFloatMethodA;
  envTable->CallStaticFloatMethod = local::CallStaticFloatMethod;
  envTable->CallStaticDoubleMethodV = local::CallStaticDoubleMethodV;
  envTable->CallStaticDoubleMethodA = local::CallStaticDoubleMethodA;
  envTable->CallStaticDoubleMethod = local::CallStaticDoubleMethod;
  envTable->CallStaticVoidMethodV = local::CallStaticVoidMethodV;
  envTable->CallStaticVoidMethodA = local::CallStaticVoidMethodA;
  envTable->CallStaticVoidMethod = local::CallStaticVoidMethod;
  envTable->GetStaticFieldID = local::GetStaticFieldID;
  envTable->GetObjectField = local::GetObjectField;
  envTable->GetBooleanField = local::GetBooleanField;
  envTable->GetByteField = local::GetByteField;
  envTable->GetCharField = local::GetCharField;
  envTable->GetShortField = local::GetShortField;
  envTable->GetIntField = local::GetIntField;
  envTable->GetLongField = local::GetLongField;
  envTable->GetFloatField = local::GetFloatField;
  envTable->GetDoubleField = local::GetDoubleField;
  envTable->SetObjectField = local::SetObjectField;
  envTable->SetBooleanField = local::SetBooleanField;
  envTable->SetByteField = local::SetByteField;
  envTable->SetCharField = local::SetCharField;
  envTable->SetShortField = local::SetShortField;
  envTable->SetIntField = local::SetIntField;
  envTable->SetLongField = local::SetLongField;
  envTable->SetFloatField = local::SetFloatField;
  envTable->SetDoubleField = local::SetDoubleField;
  envTable->GetStaticObjectField = local::GetStaticObjectField;
  envTable->GetStaticBooleanField = local::GetStaticBooleanField;
  envTable->GetStaticByteField = local::GetStaticByteField;
  envTable->GetStaticCharField = local::GetStaticCharField;
  envTable->GetStaticShortField = local::GetStaticShortField;
  envTable->GetStaticIntField = local::GetStaticIntField;
  envTable->GetStaticLongField = local::GetStaticLongField;
  envTable->GetStaticFloatField = local::GetStaticFloatField;
  envTable->GetStaticDoubleField = local::GetStaticDoubleField;
  envTable->SetStaticObjectField = local::SetStaticObjectField;
  envTable->SetStaticBooleanField = local::SetStaticBooleanField;
  envTable->SetStaticByteField = local::SetStaticByteField;
  envTable->SetStaticCharField = local::SetStaticCharField;
  envTable->SetStaticShortField = local::SetStaticShortField;
  envTable->SetStaticIntField = local::SetStaticIntField;
  envTable->SetStaticLongField = local::SetStaticLongField;
  envTable->SetStaticFloatField = local::SetStaticFloatField;
  envTable->SetStaticDoubleField = local::SetStaticDoubleField;
  envTable->NewGlobalRef = local::NewGlobalRef;
  envTable->NewWeakGlobalRef = local::NewWeakGlobalRef;
  envTable->DeleteGlobalRef = local::DeleteGlobalRef;
  envTable->DeleteWeakGlobalRef = local::DeleteWeakGlobalRef;
  envTable->EnsureLocalCapacity = local::EnsureLocalCapacity;
  envTable->ExceptionOccurred = local::ExceptionOccurred;
  envTable->ExceptionDescribe = local::ExceptionDescribe;
  envTable->ExceptionClear = local::ExceptionClear;
  envTable->NewObjectArray = local::NewObjectArray;
  envTable->GetObjectArrayElement = local::GetObjectArrayElement;
  envTable->SetObjectArrayElement = local::SetObjectArrayElement;
  envTable->NewBooleanArray = local::NewBooleanArray;
  envTable->NewByteArray = local::NewByteArray;
  envTable->NewCharArray = local::NewCharArray;
  envTable->NewShortArray = local::NewShortArray;
  envTable->NewIntArray = local::NewIntArray;
  envTable->NewLongArray = local::NewLongArray;
  envTable->NewFloatArray = local::NewFloatArray;
  envTable->NewDoubleArray = local::NewDoubleArray;
  envTable->GetBooleanArrayElements = local::GetBooleanArrayElements;
  envTable->GetByteArrayElements = local::GetByteArrayElements;
  envTable->GetCharArrayElements = local::GetCharArrayElements;
  envTable->GetShortArrayElements = local::GetShortArrayElements;
  envTable->GetIntArrayElements = local::GetIntArrayElements;
  envTable->GetLongArrayElements = local::GetLongArrayElements;
  envTable->GetFloatArrayElements = local::GetFloatArrayElements;
  envTable->GetDoubleArrayElements = local::GetDoubleArrayElements;
  envTable->ReleaseBooleanArrayElements = local::ReleaseBooleanArrayElements;
  envTable->ReleaseByteArrayElements = local::ReleaseByteArrayElements;
  envTable->ReleaseCharArrayElements = local::ReleaseCharArrayElements;
  envTable->ReleaseShortArrayElements = local::ReleaseShortArrayElements;
  envTable->ReleaseIntArrayElements = local::ReleaseIntArrayElements;
  envTable->ReleaseLongArrayElements = local::ReleaseLongArrayElements;
  envTable->ReleaseFloatArrayElements = local::ReleaseFloatArrayElements;
  envTable->ReleaseDoubleArrayElements = local::ReleaseDoubleArrayElements;
  envTable->GetBooleanArrayRegion = local::GetBooleanArrayRegion;
  envTable->GetByteArrayRegion = local::GetByteArrayRegion;
  envTable->GetCharArrayRegion = local::GetCharArrayRegion;
  envTable->GetShortArrayRegion = local::GetShortArrayRegion;
  envTable->GetIntArrayRegion = local::GetIntArrayRegion;
  envTable->GetLongArrayRegion = local::GetLongArrayRegion;
  envTable->GetFloatArrayRegion = local::GetFloatArrayRegion;
  envTable->GetDoubleArrayRegion = local::GetDoubleArrayRegion;
  envTable->SetBooleanArrayRegion = local::SetBooleanArrayRegion;
  envTable->SetByteArrayRegion = local::SetByteArrayRegion;
  envTable->SetCharArrayRegion = local::SetCharArrayRegion;
  envTable->SetShortArrayRegion = local::SetShortArrayRegion;
  envTable->SetIntArrayRegion = local::SetIntArrayRegion;
  envTable->SetLongArrayRegion = local::SetLongArrayRegion;
  envTable->SetFloatArrayRegion = local::SetFloatArrayRegion;
  envTable->SetDoubleArrayRegion = local::SetDoubleArrayRegion;
  envTable->GetPrimitiveArrayCritical = local::GetPrimitiveArrayCritical;
  envTable->ReleasePrimitiveArrayCritical
      = local::ReleasePrimitiveArrayCritical;
  envTable->RegisterNatives = local::RegisterNatives;
  envTable->UnregisterNatives = local::UnregisterNatives;
  envTable->MonitorEnter = local::MonitorEnter;
  envTable->MonitorExit = local::MonitorExit;
  envTable->GetJavaVM = local::GetJavaVM;
  envTable->IsSameObject = local::IsSameObject;
  envTable->PushLocalFrame = local::PushLocalFrame;
  envTable->PopLocalFrame = local::PopLocalFrame;
  envTable->FromReflectedMethod = local::FromReflectedMethod;
  envTable->ToReflectedMethod = local::ToReflectedMethod;
  envTable->FromReflectedField = local::FromReflectedField;
  envTable->ToReflectedField = local::ToReflectedField;
}

}  // namespace vm

extern "C" AVIAN_EXPORT jint JNICALL JNI_GetDefaultJavaVMInitArgs(void*)
{
  return 0;
}

extern "C" AVIAN_EXPORT jint JNICALL
    JNI_GetCreatedJavaVMs(Machine**, jsize, jsize*)
{
  // todo
  return -1;
}

extern "C" AVIAN_EXPORT jint JNICALL
    JNI_CreateJavaVM(Machine** m, Thread** t, void* args)
{
  local::JavaVMInitArgs* a = static_cast<local::JavaVMInitArgs*>(args);

  unsigned heapLimit = 0;
  unsigned stackLimit = 0;
  const char* bootLibraries = 0;
  const char* classpath = 0;
  const char* javaHome = AVIAN_JAVA_HOME;
  const char* embedPrefix = AVIAN_EMBED_PREFIX;
  const char* bootClasspathPrepend = "";
  const char* bootClasspath = 0;
  const char* bootClasspathAppend = "";
  const char* crashDumpDirectory = 0;

  unsigned propertyCount = 0;

  for (int i = 0; i < a->nOptions; ++i) {
    if (strncmp(a->options[i].optionString, "-X", 2) == 0) {
      const char* p = a->options[i].optionString + 2;
      if (strncmp(p, "mx", 2) == 0) {
        heapLimit = local::parseSize(p + 2);
      } else if (strncmp(p, "ss", 2) == 0) {
        stackLimit = local::parseSize(p + 2);
      } else if (strncmp(p,
                         BOOTCLASSPATH_PREPEND_OPTION ":",
                         sizeof(BOOTCLASSPATH_PREPEND_OPTION)) == 0) {
        bootClasspathPrepend = p + sizeof(BOOTCLASSPATH_PREPEND_OPTION);
      } else if (strncmp(
                     p, BOOTCLASSPATH_OPTION ":", sizeof(BOOTCLASSPATH_OPTION))
                 == 0) {
        bootClasspath = p + sizeof(BOOTCLASSPATH_OPTION);
      } else if (strncmp(p,
                         BOOTCLASSPATH_APPEND_OPTION ":",
                         sizeof(BOOTCLASSPATH_APPEND_OPTION)) == 0) {
        bootClasspathAppend = p + sizeof(BOOTCLASSPATH_APPEND_OPTION);
      }
    } else if (strncmp(a->options[i].optionString, "-D", 2) == 0) {
      const char* p = a->options[i].optionString + 2;
      if (strncmp(p, BOOTSTRAP_PROPERTY "=", sizeof(BOOTSTRAP_PROPERTY)) == 0) {
        bootLibraries = p + sizeof(BOOTSTRAP_PROPERTY);
      } else if (strncmp(p,
                         JAVA_COMMAND_PROPERTY "=",
                         sizeof(JAVA_COMMAND_PROPERTY)) == 0
                 or strncmp(p,
                            JAVA_LAUNCHER_PROPERTY "=",
                            sizeof(JAVA_LAUNCHER_PROPERTY)) == 0) {
        // this means we're being invoked via the javac or java
        // command, so the bootstrap library should be e.g. libjvm.so
        bootLibraries = SO_PREFIX "jvm" SO_SUFFIX;
      } else if (strncmp(p, CRASHDIR_PROPERTY "=", sizeof(CRASHDIR_PROPERTY))
                 == 0) {
        crashDumpDirectory = p + sizeof(CRASHDIR_PROPERTY);
      } else if (strncmp(p, CLASSPATH_PROPERTY "=", sizeof(CLASSPATH_PROPERTY))
                 == 0) {
        classpath = p + sizeof(CLASSPATH_PROPERTY);
      } else if (strncmp(p, JAVA_HOME_PROPERTY "=", sizeof(JAVA_HOME_PROPERTY))
                 == 0) {
        javaHome = p + sizeof(JAVA_HOME_PROPERTY);
      } else if (strncmp(p,
                         EMBED_PREFIX_PROPERTY "=",
                         sizeof(EMBED_PREFIX_PROPERTY)) == 0) {
        embedPrefix = p + sizeof(EMBED_PREFIX_PROPERTY);
      }

      ++propertyCount;
    }
  }

  if (heapLimit == 0)
    heapLimit = 128 * 1024 * 1024;

  if (stackLimit == 0)
    stackLimit = 128 * 1024;

  bool addClasspathProperty = classpath == 0;
  if (addClasspathProperty) {
    classpath = ".";
    ++propertyCount;
  }

  System* s = makeSystem();
  Heap* h = makeHeap(s, heapLimit);
  Classpath* c = makeClasspath(s, h, javaHome, embedPrefix);

  if (bootClasspath == 0) {
    bootClasspath = c->bootClasspath();
  }

  unsigned bcppl = strlen(bootClasspathPrepend);
  unsigned bcpl = strlen(bootClasspath);
  unsigned bcpal = strlen(bootClasspathAppend);

  unsigned bootClasspathBufferSize = bcppl + bcpl + bcpal + 3;
  RUNTIME_ARRAY(char, bootClasspathBuffer, bootClasspathBufferSize);
  char* bootClasspathPointer = RUNTIME_ARRAY_BODY(bootClasspathBuffer);
  if (bootClasspathBufferSize > 3) {
    local::append(&bootClasspathPointer,
                  bootClasspathPrepend,
                  bcppl,
                  bcpl + bcpal ? PATH_SEPARATOR : 0);
    local::append(
        &bootClasspathPointer, bootClasspath, bcpl, bcpal ? PATH_SEPARATOR : 0);
    local::append(&bootClasspathPointer, bootClasspathAppend, bcpal, 0);
  } else {
    *RUNTIME_ARRAY_BODY(bootClasspathBuffer) = 0;
  }

  char* bootLibrary = bootLibraries ? strdup(bootLibraries) : 0;
  char* bootLibraryEnd = bootLibrary ? strchr(bootLibrary, PATH_SEPARATOR) : 0;
  if (bootLibraryEnd)
    *bootLibraryEnd = 0;

  Finder* bf
      = makeFinder(s, h, RUNTIME_ARRAY_BODY(bootClasspathBuffer), bootLibrary);
  Finder* af = makeFinder(s, h, classpath, bootLibrary);
  if (bootLibrary)
    free(bootLibrary);
  Processor* p = makeProcessor(s, h, crashDumpDirectory, true);

  // reserve space for avian.version and file.encoding:
  propertyCount += 2;

  const char** properties = static_cast<const char**>(
      h->allocate(sizeof(const char*) * propertyCount));

  const char** propertyPointer = properties;

  const char** arguments = static_cast<const char**>(
      h->allocate(sizeof(const char*) * a->nOptions));

  const char** argumentPointer = arguments;

  for (int i = 0; i < a->nOptions; ++i) {
    if (strncmp(a->options[i].optionString, "-D", 2) == 0) {
      *(propertyPointer++) = a->options[i].optionString + 2;
    }
    *(argumentPointer++) = a->options[i].optionString;
  }

  unsigned cpl = strlen(classpath);
  RUNTIME_ARRAY(char, classpathProperty, cpl + strlen(CLASSPATH_PROPERTY) + 2);
  if (addClasspathProperty) {
    char* p = RUNTIME_ARRAY_BODY(classpathProperty);
    local::append(&p, CLASSPATH_PROPERTY, strlen(CLASSPATH_PROPERTY), '=');
    local::append(&p, classpath, cpl, 0);
    *(propertyPointer++) = RUNTIME_ARRAY_BODY(classpathProperty);
  }

  *(propertyPointer++) = "avian.version=" AVIAN_VERSION;

  // todo: should this be derived from the OS locale?  Should it be
  // overrideable via JavaVMInitArgs?
  *(propertyPointer++) = "file.encoding=UTF-8";

  *m = new (h->allocate(sizeof(Machine))) Machine(s,
                                                  h,
                                                  bf,
                                                  af,
                                                  p,
                                                  c,
                                                  properties,
                                                  propertyCount,
                                                  arguments,
                                                  a->nOptions,
                                                  stackLimit);

  h->free(properties, sizeof(const char*) * propertyCount);

  *t = p->makeThread(*m, 0, 0);

  enter(*t, Thread::ActiveState);
  enter(*t, Thread::IdleState);

  return run(*t, local::boot, 0) ? 0 : -1;
}
