x86 JNI compiler and unit tests.

Change-Id: I4c2e10328961a2e8e27c90777fe2a93737b21143
diff --git a/src/jni_compiler_test.cc b/src/jni_compiler_test.cc
new file mode 100644
index 0000000..59d8c2b
--- /dev/null
+++ b/src/jni_compiler_test.cc
@@ -0,0 +1,390 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: irogers@google.com (Ian Rogers)
+
+#include <sys/mman.h>
+#include "src/assembler.h"
+#include "src/class_linker.h"
+#include "src/common_test.h"
+#include "src/dex_file.h"
+#include "src/jni_compiler.h"
+#include "src/runtime.h"
+#include "src/thread.h"
+#include "gtest/gtest.h"
+
+namespace art {
+
+class JniCompilerTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    // Create runtime and attach thread
+    runtime_ = Runtime::Create();
+    CHECK(runtime_->AttachCurrentThread());
+    // Create thunk code that performs the native to managed transition
+    thunk_code_size_ = 4096;
+    thunk_ = mmap(NULL, thunk_code_size_, PROT_READ | PROT_WRITE | PROT_EXEC,
+                  MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+    CHECK_NE(MAP_FAILED, thunk_);
+    Assembler thk_asm;
+    // TODO: shouldn't have machine specific code in a general purpose file
+#if defined(__i386__)
+    thk_asm.pushl(EDI);                   // preserve EDI
+    thk_asm.movl(EAX, Address(ESP, 8));   // EAX = method->GetCode()
+    thk_asm.movl(EDI, Address(ESP, 12));  // EDI = method
+    thk_asm.pushl(Immediate(0));          // push pad
+    thk_asm.pushl(Immediate(0));          // push pad
+    thk_asm.pushl(Address(ESP, 40));      // push pad  or jlong high
+    thk_asm.pushl(Address(ESP, 40));      // push jint or jlong low
+    thk_asm.pushl(Address(ESP, 40));      // push jint or jlong high
+    thk_asm.pushl(Address(ESP, 40));      // push jint or jlong low
+    thk_asm.pushl(Address(ESP, 40));      // push jobject
+    thk_asm.call(EAX);                    // Continue in method->GetCode()
+    thk_asm.addl(ESP, Immediate(28));     // pop arguments
+    thk_asm.popl(EDI);                    // restore EDI
+    thk_asm.ret();
+#else
+    LOG(FATAL) << "Unimplemented";
+#endif
+    size_t cs = thk_asm.CodeSize();
+    MemoryRegion code(thunk_, cs);
+    thk_asm.FinalizeInstructions(code);
+    thunk_entry1_ = reinterpret_cast<jint (*)(const void*, art::Method*,
+                                              jobject, jint, jint, jint)
+                                    >(code.pointer());
+    thunk_entry2_ = reinterpret_cast<jdouble (*)(const void*, art::Method*,
+                                                 jobject, jdouble, jdouble)
+                                    >(code.pointer());
+  }
+
+  virtual void TearDown() {
+    // Release thunk code
+    CHECK(runtime_->DetachCurrentThread());
+    CHECK_EQ(0, munmap(thunk_, thunk_code_size_));
+  }
+
+  // Run generated code associated with method passing and returning int size
+  // arguments
+  jvalue RunMethod(Method* method, jvalue a, jvalue b, jvalue c, jvalue d) {
+    jvalue result;
+    // sanity checks
+    EXPECT_NE(static_cast<void*>(NULL), method->GetCode());
+    EXPECT_EQ(0u, Thread::Current()->NumShbHandles());
+    EXPECT_EQ(Thread::kRunnable, Thread::Current()->GetState());
+    // perform call
+    result.i = (*thunk_entry1_)(method->GetCode(), method, a.l, b.i, c.i, d.i);
+    // sanity check post-call
+    EXPECT_EQ(0u, Thread::Current()->NumShbHandles());
+    EXPECT_EQ(Thread::kRunnable, Thread::Current()->GetState());
+    return result;
+  }
+
+  // Run generated code associated with method passing and returning double size
+  // arguments
+  jvalue RunMethodD(Method* method, jvalue a, jvalue b, jvalue c) {
+    jvalue result;
+    // sanity checks
+    EXPECT_NE(static_cast<void*>(NULL), method->GetCode());
+    EXPECT_EQ(0u, Thread::Current()->NumShbHandles());
+    EXPECT_EQ(Thread::kRunnable, Thread::Current()->GetState());
+    // perform call
+    result.d = (*thunk_entry2_)(method->GetCode(), method, a.l, b.d, c.d);
+    // sanity check post-call
+    EXPECT_EQ(0u, Thread::Current()->NumShbHandles());
+    EXPECT_EQ(Thread::kRunnable, Thread::Current()->GetState());
+    return result;
+  }
+
+  Runtime* runtime_;
+  void* thunk_;
+  size_t thunk_code_size_;
+  jint (*thunk_entry1_)(const void*, Method*, jobject, jint, jint, jint);
+  jdouble (*thunk_entry2_)(const void*, Method*, jobject, jdouble, jdouble);
+};
+
+int gJava_MyClass_foo_calls = 0;
+void Java_MyClass_foo(JNIEnv*, jobject) {
+  EXPECT_EQ(1u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_foo_calls++;
+}
+
+int gJava_MyClass_fooI_calls = 0;
+jint Java_MyClass_fooI(JNIEnv*, jobject, jint x) {
+  EXPECT_EQ(1u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_fooI_calls++;
+  return x;
+}
+
+int gJava_MyClass_fooII_calls = 0;
+jint Java_MyClass_fooII(JNIEnv*, jobject, jint x, jint y) {
+  EXPECT_EQ(1u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_fooII_calls++;
+  return x - y;  // non-commutative operator
+}
+
+int gJava_MyClass_fooDD_calls = 0;
+jdouble Java_MyClass_fooDD(JNIEnv*, jobject, jdouble x, jdouble y) {
+  EXPECT_EQ(1u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_fooDD_calls++;
+  return x - y;  // non-commutative operator
+}
+
+int gJava_MyClass_fooIOO_calls = 0;
+jobject Java_MyClass_fooIOO(JNIEnv*, jobject thisObject, jint x, jobject y,
+                            jobject z) {
+  EXPECT_EQ(3u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_fooIOO_calls++;
+  switch (x) {
+    case 1:
+      return y;
+    case 2:
+      return z;
+    default:
+      return thisObject;
+  }
+}
+
+int gJava_MyClass_fooSIOO_calls = 0;
+jobject Java_MyClass_fooSIOO(JNIEnv*, jclass klass, jint x, jobject y,
+                             jobject z) {
+  EXPECT_EQ(3u, Thread::Current()->NumShbHandles());
+  EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
+  gJava_MyClass_fooSIOO_calls++;
+  switch (x) {
+    case 1:
+      return y;
+    case 2:
+      return z;
+    default:
+      return klass;
+  }
+}
+
+TEST_F(JniCompilerTest, CompileAndRunNoArgMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindVirtualMethod("foo");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  // JNIEnv* env = Thread::Current()->GetJniEnv();
+  // JNINativeMethod methods[] = {{"foo", "()V", (void*)&Java_MyClass_foo}};
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_foo));
+
+  jvalue a;
+  a.l = (jobject)NULL;
+  EXPECT_EQ(0, gJava_MyClass_foo_calls);
+  RunMethod(method, a, a, a, a);
+  EXPECT_EQ(1, gJava_MyClass_foo_calls);
+  RunMethod(method, a, a, a, a);
+  EXPECT_EQ(2, gJava_MyClass_foo_calls);
+}
+
+TEST_F(JniCompilerTest, CompileAndRunIntMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindVirtualMethod("fooI");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_fooI));
+
+  jvalue a, b, c;
+  a.l = (jobject)NULL;
+  b.i = 42;
+  EXPECT_EQ(0, gJava_MyClass_fooI_calls);
+  c = RunMethod(method, a, b, a, a);
+  ASSERT_EQ(42, c.i);
+  EXPECT_EQ(1, gJava_MyClass_fooI_calls);
+  b.i = 0xCAFED00D;
+  c = RunMethod(method, a, b, a, a);
+  ASSERT_EQ((jint)0xCAFED00D, c.i);
+  EXPECT_EQ(2, gJava_MyClass_fooI_calls);
+}
+
+TEST_F(JniCompilerTest, CompileAndRunIntIntMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindVirtualMethod("fooII");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_fooII));
+
+  jvalue a, b, c, d;
+  a.l = (jobject)NULL;
+  b.i = 99;
+  c.i = 10;
+  EXPECT_EQ(0, gJava_MyClass_fooII_calls);
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ(99 - 10, d.i);
+  EXPECT_EQ(1, gJava_MyClass_fooII_calls);
+  b.i = 0xCAFEBABE;
+  c.i = 0xCAFED00D;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jint)(0xCAFEBABE - 0xCAFED00D), d.i);
+  EXPECT_EQ(2, gJava_MyClass_fooII_calls);
+}
+
+
+TEST_F(JniCompilerTest, CompileAndRunDoubleDoubleMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindVirtualMethod("fooDD");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_fooDD));
+
+  jvalue a, b, c, d;
+  a.l = (jobject)NULL;
+  b.d = 99;
+  c.d = 10;
+  EXPECT_EQ(0, gJava_MyClass_fooDD_calls);
+  d = RunMethodD(method, a, b, c);
+  ASSERT_EQ(b.d - c.d, d.d);
+  EXPECT_EQ(1, gJava_MyClass_fooDD_calls);
+  b.d = 3.14159265358979323846;
+  c.d = 0.69314718055994530942;
+  d = RunMethodD(method, a, b, c);
+  ASSERT_EQ(b.d - c.d, d.d);
+  EXPECT_EQ(2, gJava_MyClass_fooDD_calls);
+}
+
+TEST_F(JniCompilerTest, CompileAndRunIntObjectObjectMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindVirtualMethod("fooIOO");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_fooIOO));
+
+  jvalue a, b, c, d, e;
+  a.l = (jobject)NULL;
+  b.i = 0;
+  c.l = (jobject)NULL;
+  d.l = (jobject)NULL;
+  EXPECT_EQ(0, gJava_MyClass_fooIOO_calls);
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)NULL, e.l);
+  EXPECT_EQ(1, gJava_MyClass_fooIOO_calls);
+  a.l = (jobject)8;
+  b.i = 0;
+  c.l = (jobject)NULL;
+  d.l = (jobject)16;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)8, e.l);
+  EXPECT_EQ(2, gJava_MyClass_fooIOO_calls);
+  b.i = 1;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)NULL, e.l);
+  EXPECT_EQ(3, gJava_MyClass_fooIOO_calls);
+  b.i = 2;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)16, e.l);
+  EXPECT_EQ(4, gJava_MyClass_fooIOO_calls);
+  a.l = (jobject)8;
+  b.i = 0;
+  c.l = (jobject)16;
+  d.l = (jobject)NULL;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)8, e.l);
+  EXPECT_EQ(5, gJava_MyClass_fooIOO_calls);
+  b.i = 1;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)16, e.l);
+  EXPECT_EQ(6, gJava_MyClass_fooIOO_calls);
+  b.i = 2;
+  e = RunMethod(method, a, b, c, d);
+  ASSERT_EQ((jobject)NULL, e.l);
+  EXPECT_EQ(7, gJava_MyClass_fooIOO_calls);
+}
+
+TEST_F(JniCompilerTest, CompileAndRunStaticIntObjectObjectMethod) {
+  scoped_ptr<DexFile> dex(OpenDexFileBase64(kMyClassNativesDex));
+  scoped_ptr<ClassLinker> linker(ClassLinker::Create());
+  linker->AppendToClassPath(dex.get());
+  Class* klass = linker->FindClass("LMyClass;", NULL);
+  Method* method = klass->FindDirectMethod("fooSIOO");
+
+  Assembler jni_asm;
+  JniCompiler jni_compiler;
+  jni_compiler.Compile(&jni_asm, method);
+
+  // TODO: should really use JNIEnv to RegisterNative, but missing a
+  // complete story on this, so hack the RegisterNative below
+  method->RegisterNative(reinterpret_cast<void*>(&Java_MyClass_fooSIOO));
+
+  jvalue a, b, c, d;
+  a.i = 0;
+  b.l = (jobject)NULL;
+  c.l = (jobject)NULL;
+  EXPECT_EQ(0, gJava_MyClass_fooSIOO_calls);
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)method->GetClass(), d.l);
+  EXPECT_EQ(1, gJava_MyClass_fooSIOO_calls);
+  a.i = 0;
+  b.l = (jobject)NULL;
+  c.l = (jobject)16;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)method->GetClass(), d.l);
+  EXPECT_EQ(2, gJava_MyClass_fooSIOO_calls);
+  a.i = 1;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)NULL, d.l);
+  EXPECT_EQ(3, gJava_MyClass_fooSIOO_calls);
+  a.i = 2;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)16, d.l);
+  EXPECT_EQ(4, gJava_MyClass_fooSIOO_calls);
+  a.i = 0;
+  b.l = (jobject)16;
+  c.l = (jobject)NULL;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)method->GetClass(), d.l);
+  EXPECT_EQ(5, gJava_MyClass_fooSIOO_calls);
+  a.i = 1;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)16, d.l);
+  EXPECT_EQ(6, gJava_MyClass_fooSIOO_calls);
+  a.i = 2;
+  d = RunMethod(method, a, b, c, a);
+  ASSERT_EQ((jobject)NULL, d.l);
+  EXPECT_EQ(7, gJava_MyClass_fooSIOO_calls);
+}
+
+}  // namespace art