x86 JNI compiler and unit tests.
Change-Id: I4c2e10328961a2e8e27c90777fe2a93737b21143
diff --git a/src/jni_compiler.cc b/src/jni_compiler.cc
new file mode 100644
index 0000000..67f8bc8
--- /dev/null
+++ b/src/jni_compiler.cc
@@ -0,0 +1,290 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: irogers@google.com (Ian Rogers)
+#include "src/jni_compiler.h"
+#include <sys/mman.h>
+#include "src/assembler.h"
+#include "src/calling_convention.h"
+#include "src/macros.h"
+#include "src/managed_register.h"
+#include "src/logging.h"
+#include "src/thread.h"
+
+namespace art {
+
+// Generate the JNI bridge for the given method, general contract:
+// - Arguments are in the managed runtime format, either on stack or in
+// registers, a reference to the method object is supplied as part of this
+// convention.
+//
+void JniCompiler::Compile(Assembler* jni_asm, Method* native_method) {
+ CHECK(native_method->IsNative());
+ JniCallingConvention jni_conv(native_method);
+ ManagedRuntimeCallingConvention mr_conv(native_method);
+ const bool is_static = native_method->IsStatic();
+
+ // 1. Build the frame
+ const size_t frame_size(jni_conv.FrameSize());
+ jni_asm->BuildFrame(frame_size, mr_conv.MethodRegister());
+
+ // 2. Save callee save registers that aren't callee save in the native code
+ // TODO: implement computing the difference of the callee saves
+ // and saving
+
+ // 3. Set up the StackHandleBlock
+ mr_conv.ResetIterator(FrameOffset(frame_size));
+ jni_conv.ResetIterator(FrameOffset(0));
+ jni_asm->StoreImmediateToFrame(jni_conv.ShbNumRefsOffset(),
+ jni_conv.HandleCount(),
+ mr_conv.InterproceduralScratchRegister());
+ jni_asm->CopyRawPtrFromThread(jni_conv.ShbLinkOffset(),
+ Thread::TopShbOffset(),
+ mr_conv.InterproceduralScratchRegister());
+ jni_asm->StoreStackOffsetToThread(Thread::TopShbOffset(),
+ jni_conv.ShbOffset(),
+ mr_conv.InterproceduralScratchRegister());
+
+ // 4. Place incoming reference arguments into handle block
+ jni_conv.Next(); // Skip JNIEnv*
+ // 4.5. Create Class argument for static methods out of passed method
+ if (is_static) {
+ FrameOffset handle_offset = jni_conv.CurrentParamHandleOffset();
+ // Check handle offset is within frame
+ CHECK_LT(handle_offset.Uint32Value(), frame_size);
+ jni_asm->LoadRef(jni_conv.InterproceduralScratchRegister(),
+ mr_conv.MethodRegister(), Method::ClassOffset());
+ jni_asm->ValidateRef(jni_conv.InterproceduralScratchRegister(), false);
+ jni_asm->StoreRef(handle_offset, jni_conv.InterproceduralScratchRegister());
+ jni_conv.Next(); // handlerized so move to next argument
+ }
+ while (mr_conv.HasNext()) {
+ CHECK(jni_conv.HasNext());
+ bool ref_param = jni_conv.IsCurrentParamAReference();
+ CHECK(!ref_param || mr_conv.IsCurrentParamAReference());
+ // References need handlerization and the handle address passing
+ if (ref_param) {
+ // Compute handle offset, note null is handlerized but its boxed value
+ // must be NULL
+ FrameOffset handle_offset = jni_conv.CurrentParamHandleOffset();
+ // Check handle offset is within frame
+ CHECK_LT(handle_offset.Uint32Value(), frame_size);
+ bool input_in_reg = mr_conv.IsCurrentParamInRegister();
+ CHECK(input_in_reg || mr_conv.IsCurrentParamOnStack());
+ if (input_in_reg) {
+ LOG(FATAL) << "UNTESTED";
+ ManagedRegister in_reg = mr_conv.CurrentParamRegister();
+ jni_asm->ValidateRef(in_reg, mr_conv.IsCurrentParamPossiblyNull());
+ jni_asm->StoreRef(handle_offset, in_reg);
+ } else {
+ FrameOffset in_off = mr_conv.CurrentParamStackOffset();
+ jni_asm->ValidateRef(in_off, mr_conv.IsCurrentParamPossiblyNull());
+ jni_asm->CopyRef(handle_offset, in_off,
+ mr_conv.InterproceduralScratchRegister());
+ }
+ }
+ mr_conv.Next();
+ jni_conv.Next();
+ }
+
+ // 5. Acquire lock for synchronized methods. Done here as references are held
+ // live in handle block but we're in managed code and can work on
+ // references
+ if (native_method->IsSynchronized()) {
+ jni_conv.ResetIterator(FrameOffset(0));
+ jni_conv.Next(); // skip JNI environment
+ jni_asm->LockReferenceOnStack(jni_conv.CurrentParamHandleOffset());
+ }
+
+ // 6. Transition from being in managed to native code
+ // TODO: write out anchor, ensure the transition to native follow a store
+ // fence.
+ jni_asm->StoreImmediateToThread(Thread::StateOffset(), Thread::kNative,
+ mr_conv.InterproceduralScratchRegister());
+
+ // 7. Move frame down to allow space for out going args. Do for as short a
+ // time as possible to aid profiling..
+ const size_t out_arg_size = jni_conv.OutArgSize();
+ jni_asm->IncreaseFrameSize(out_arg_size);
+
+ // 8. Iterate over arguments placing values from managed calling convention in
+ // to the convention required for a native call (shuffling). For references
+ // place an index/pointer to the reference after checking whether it is
+ // NULL (which must be encoded as NULL).
+ // NB. we do this prior to materializing the JNIEnv* and static's jclass to
+ // give as many free registers for the shuffle as possible
+ mr_conv.ResetIterator(FrameOffset(frame_size+out_arg_size));
+ jni_conv.ResetIterator(FrameOffset(out_arg_size));
+ jni_conv.Next(); // Skip JNIEnv*
+ if (is_static) {
+ FrameOffset handle_offset = jni_conv.CurrentParamHandleOffset();
+ if (jni_conv.IsCurrentParamOnStack()) {
+ FrameOffset out_off = jni_conv.CurrentParamStackOffset();
+ jni_asm->CreateStackHandle(out_off, handle_offset,
+ mr_conv.InterproceduralScratchRegister(),
+ false);
+ } else {
+ ManagedRegister out_reg = jni_conv.CurrentParamRegister();
+ jni_asm->CreateStackHandle(out_reg, handle_offset,
+ ManagedRegister::NoRegister(), false);
+ }
+ jni_conv.Next();
+ }
+ while (mr_conv.HasNext()) {
+ CHECK(jni_conv.HasNext());
+ bool input_in_reg = mr_conv.IsCurrentParamInRegister();
+ bool output_in_reg = jni_conv.IsCurrentParamInRegister();
+ FrameOffset handle_offset(0);
+ bool null_allowed = false;
+ bool ref_param = jni_conv.IsCurrentParamAReference();
+ CHECK(!ref_param || mr_conv.IsCurrentParamAReference());
+ CHECK(input_in_reg || mr_conv.IsCurrentParamOnStack());
+ CHECK(output_in_reg || jni_conv.IsCurrentParamOnStack());
+ // References need handlerization and the handle address passing
+ if (ref_param) {
+ null_allowed = mr_conv.IsCurrentParamPossiblyNull();
+ // Compute handle offset. Note null is placed in the SHB but the jobject
+ // passed to the native code must be null (not a pointer into the SHB
+ // as with regular references).
+ handle_offset = jni_conv.CurrentParamHandleOffset();
+ // Check handle offset is within frame.
+ CHECK_LT(handle_offset.Uint32Value(), (frame_size+out_arg_size));
+ }
+ if (input_in_reg && output_in_reg) {
+ LOG(FATAL) << "UNTESTED";
+ ManagedRegister in_reg = mr_conv.CurrentParamRegister();
+ ManagedRegister out_reg = jni_conv.CurrentParamRegister();
+ if (ref_param) {
+ jni_asm->CreateStackHandle(out_reg, handle_offset, in_reg,
+ null_allowed);
+ } else {
+ jni_asm->Move(out_reg, in_reg);
+ }
+ } else if (!input_in_reg && !output_in_reg) {
+ FrameOffset out_off = jni_conv.CurrentParamStackOffset();
+ if (ref_param) {
+ jni_asm->CreateStackHandle(out_off, handle_offset,
+ mr_conv.InterproceduralScratchRegister(),
+ null_allowed);
+ } else {
+ FrameOffset in_off = mr_conv.CurrentParamStackOffset();
+ size_t param_size = mr_conv.CurrentParamSizeInBytes();
+ CHECK_EQ(param_size, jni_conv.CurrentParamSizeInBytes());
+ jni_asm->Copy(out_off, in_off, mr_conv.InterproceduralScratchRegister(),
+ param_size);
+ }
+ } else if (!input_in_reg && output_in_reg) {
+ LOG(FATAL) << "UNTESTED";
+ FrameOffset in_off = mr_conv.CurrentParamStackOffset();
+ ManagedRegister out_reg = jni_conv.CurrentParamRegister();
+ // Check that incoming stack arguments are above the current stack frame.
+ CHECK_GT(in_off.Uint32Value(), frame_size);
+ if (ref_param) {
+ jni_asm->CreateStackHandle(out_reg, handle_offset,
+ ManagedRegister::NoRegister(), null_allowed);
+ } else {
+ unsigned int param_size = mr_conv.CurrentParamSizeInBytes();
+ CHECK_EQ(param_size, jni_conv.CurrentParamSizeInBytes());
+ jni_asm->Load(out_reg, in_off, param_size);
+ }
+ } else {
+ LOG(FATAL) << "UNTESTED";
+ CHECK(input_in_reg && !output_in_reg);
+ ManagedRegister in_reg = mr_conv.CurrentParamRegister();
+ FrameOffset out_off = jni_conv.CurrentParamStackOffset();
+ // Check outgoing argument is within frame
+ CHECK_LT(out_off.Uint32Value(), frame_size);
+ if (ref_param) {
+ // TODO: recycle value in in_reg rather than reload from handle
+ jni_asm->CreateStackHandle(out_off, handle_offset,
+ mr_conv.InterproceduralScratchRegister(),
+ null_allowed);
+ } else {
+ size_t param_size = mr_conv.CurrentParamSizeInBytes();
+ CHECK_EQ(param_size, jni_conv.CurrentParamSizeInBytes());
+ jni_asm->Store(out_off, in_reg, param_size);
+ }
+ }
+ mr_conv.Next();
+ jni_conv.Next();
+ }
+ // 9. Create 1st argument, the JNI environment ptr
+ jni_conv.ResetIterator(FrameOffset(out_arg_size));
+ if (jni_conv.IsCurrentParamInRegister()) {
+ jni_asm->LoadRawPtrFromThread(jni_conv.CurrentParamRegister(),
+ Thread::JniEnvOffset());
+ } else {
+ jni_asm->CopyRawPtrFromThread(jni_conv.CurrentParamStackOffset(),
+ Thread::JniEnvOffset(),
+ jni_conv.InterproceduralScratchRegister());
+ }
+
+ // 10. Plant call to native code associated with method
+ jni_asm->Call(mr_conv.MethodRegister(), Method::NativeMethodOffset(),
+ mr_conv.InterproceduralScratchRegister());
+
+ // 11. Release outgoing argument area
+ jni_asm->DecreaseFrameSize(out_arg_size);
+
+ // 12. Transition from being in native to managed code, possibly entering a
+ // safepoint
+ jni_asm->StoreImmediateToThread(Thread::StateOffset(), Thread::kRunnable,
+ mr_conv.InterproceduralScratchRegister());
+ // TODO: check for safepoint transition
+
+ // 13. Move to first handle offset
+ jni_conv.ResetIterator(FrameOffset(0));
+ jni_conv.Next(); // skip JNI environment
+
+ // 14. Release lock for synchronized methods (done in the managed state so
+ // references can be touched)
+ if (native_method->IsSynchronized()) {
+ jni_asm->UnLockReferenceOnStack(jni_conv.CurrentParamHandleOffset());
+ }
+
+ // 15. Place result in correct register possibly dehandlerizing
+ if (jni_conv.IsReturnAReference()) {
+ jni_asm->LoadReferenceFromStackHandle(mr_conv.ReturnRegister(),
+ jni_conv.ReturnRegister(),
+ jni_conv.CurrentParamHandleOffset());
+ } else {
+ jni_asm->Move(mr_conv.ReturnRegister(), jni_conv.ReturnRegister());
+ }
+
+ // 16. Remove stack handle block from thread
+ jni_asm->CopyRawPtrToThread(Thread::TopShbOffset(), jni_conv.ShbLinkOffset(),
+ jni_conv.InterproceduralScratchRegister());
+
+ // 17. Remove activation
+ jni_asm->RemoveFrame(frame_size);
+
+ // 18. Finalize code generation
+ size_t cs = jni_asm->CodeSize();
+ MemoryRegion code(AllocateCode(cs), cs);
+ jni_asm->FinalizeInstructions(code);
+ native_method->SetCode(code.pointer());
+}
+
+void* JniCompiler::AllocateCode(size_t size) {
+ CHECK_LT(((jni_code_top_ - jni_code_) + size), jni_code_size_);
+ void *result = jni_code_top_;
+ jni_code_top_ += size;
+ return result;
+}
+
+JniCompiler::JniCompiler() {
+ // TODO: this shouldn't be managed by the JniCompiler, we should have a
+ // code cache.
+ jni_code_size_ = 4096;
+ jni_code_ = static_cast<byte*>(mmap(NULL, jni_code_size_,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
+ CHECK_NE(MAP_FAILED, jni_code_);
+ jni_code_top_ = jni_code_;
+}
+
+JniCompiler::~JniCompiler() {
+ // TODO: this shouldn't be managed by the JniCompiler, we should have a
+ // code cache.
+ CHECK_EQ(0, munmap(jni_code_, jni_code_size_));
+}
+
+} // namespace art