diff options
Diffstat (limited to 'compiler')
36 files changed, 2402 insertions, 1505 deletions
diff --git a/compiler/Android.bp b/compiler/Android.bp index 01432687b5..595a824999 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -188,6 +188,7 @@ art_cc_defaults { "liblzma", ], include_dirs: ["art/disassembler"], + export_include_dirs: ["."], } gensrcs { diff --git a/compiler/compiler.h b/compiler/compiler.h index 9e5fb83121..ed42958a76 100644 --- a/compiler/compiler.h +++ b/compiler/compiler.h @@ -39,8 +39,9 @@ class Compiler { }; enum JniOptimizationFlags { - kNone, - kFastNative, + kNone = 0x0, + kFastNative = 0x1, + kCriticalNative = 0x2, }; static Compiler* Create(CompilerDriver* driver, Kind kind); diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index b5bc2fb116..daac7fbb96 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -616,17 +616,22 @@ static void CompileMethod(Thread* self, /* referrer */ nullptr, invoke_type); - bool fast_native = false; - if (LIKELY(method != nullptr)) { - fast_native = method->IsAnnotatedWithFastNative(); - } else { + // Query any JNI optimization annotations such as @FastNative or @CriticalNative. + Compiler::JniOptimizationFlags optimization_flags = Compiler::kNone; + if (UNLIKELY(method == nullptr)) { // Failed method resolutions happen very rarely, e.g. ancestor class cannot be resolved. DCHECK(self->IsExceptionPending()); self->ClearException(); + } else if (method->IsAnnotatedWithFastNative()) { + // TODO: Will no longer need this CHECK once we have verifier checking this. + CHECK(!method->IsAnnotatedWithCriticalNative()); + optimization_flags = Compiler::kFastNative; + } else if (method->IsAnnotatedWithCriticalNative()) { + // TODO: Will no longer need this CHECK once we have verifier checking this. + CHECK(!method->IsAnnotatedWithFastNative()); + optimization_flags = Compiler::kCriticalNative; } - Compiler::JniOptimizationFlags optimization_flags = - fast_native ? Compiler::kFastNative : Compiler::kNone; compiled_method = driver->GetCompiler()->JniCompile(access_flags, method_idx, dex_file, diff --git a/compiler/image_test.cc b/compiler/image_test.cc index e1ee0d2966..a18935f09a 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -73,10 +73,12 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { CHECK_EQ(0, mkdir_result) << image_dir; ScratchFile image_file(OS::CreateEmptyFile(image_filename.c_str())); - std::string oat_filename(image_filename, 0, image_filename.size() - 3); - oat_filename += "oat"; + std::string oat_filename = ReplaceFileExtension(image_filename, "oat"); ScratchFile oat_file(OS::CreateEmptyFile(oat_filename.c_str())); + std::string vdex_filename = ReplaceFileExtension(image_filename, "vdex"); + ScratchFile vdex_file(OS::CreateEmptyFile(vdex_filename.c_str())); + const uintptr_t requested_image_base = ART_BASE_ADDRESS; std::unordered_map<const DexFile*, size_t> dex_file_to_oat_index_map; std::vector<const char*> oat_filename_vector(1, oat_filename.c_str()); @@ -109,7 +111,7 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { oat_file.GetFile()); elf_writer->Start(); OatWriter oat_writer(/*compiling_boot_image*/true, &timings); - OutputStream* rodata = elf_writer->StartRoData(); + OutputStream* oat_rodata = elf_writer->StartRoData(); for (const DexFile* dex_file : dex_files) { ArrayRef<const uint8_t> raw_dex_file( reinterpret_cast<const uint8_t*>(&dex_file->GetHeader()), @@ -120,16 +122,18 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { } std::unique_ptr<MemMap> opened_dex_files_map; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; - bool dex_files_ok = oat_writer.WriteAndOpenDexFiles( - rodata, - oat_file.GetFile(), - compiler_driver_->GetInstructionSet(), - compiler_driver_->GetInstructionSetFeatures(), - &key_value_store, - /* verify */ false, // Dex files may be dex-to-dex-ed, don't verify. - &opened_dex_files_map, - &opened_dex_files); - ASSERT_TRUE(dex_files_ok); + { + bool dex_files_ok = oat_writer.WriteAndOpenDexFiles( + kIsVdexEnabled ? vdex_file.GetFile() : oat_file.GetFile(), + oat_rodata, + compiler_driver_->GetInstructionSet(), + compiler_driver_->GetInstructionSetFeatures(), + &key_value_store, + /* verify */ false, // Dex files may be dex-to-dex-ed, don't verify. + &opened_dex_files_map, + &opened_dex_files); + ASSERT_TRUE(dex_files_ok); + } bool image_space_ok = writer->PrepareImageAddressSpace(); ASSERT_TRUE(image_space_ok); @@ -138,17 +142,17 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { instruction_set_features_.get()); oat_writer.PrepareLayout(compiler_driver_.get(), writer.get(), dex_files, &patcher); size_t rodata_size = oat_writer.GetOatHeader().GetExecutableOffset(); - size_t text_size = oat_writer.GetSize() - rodata_size; + size_t text_size = oat_writer.GetOatSize() - rodata_size; elf_writer->SetLoadedSectionSizes(rodata_size, text_size, oat_writer.GetBssSize()); writer->UpdateOatFileLayout(/* oat_index */ 0u, elf_writer->GetLoadedSize(), oat_writer.GetOatDataOffset(), - oat_writer.GetSize()); + oat_writer.GetOatSize()); - bool rodata_ok = oat_writer.WriteRodata(rodata); + bool rodata_ok = oat_writer.WriteRodata(oat_rodata); ASSERT_TRUE(rodata_ok); - elf_writer->EndRoData(rodata); + elf_writer->EndRoData(oat_rodata); OutputStream* text = elf_writer->StartText(); bool text_ok = oat_writer.WriteCode(text); @@ -285,6 +289,7 @@ void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { image_file.Unlink(); oat_file.Unlink(); + vdex_file.Unlink(); int rmdir_result = rmdir(image_dir.c_str()); CHECK_EQ(0, rmdir_result); } diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc index 4b056f552a..28b7290bef 100644 --- a/compiler/jni/jni_cfi_test.cc +++ b/compiler/jni/jni_cfi_test.cc @@ -64,7 +64,12 @@ class JNICFITest : public CFITest { ArenaAllocator arena(&pool); std::unique_ptr<JniCallingConvention> jni_conv( - JniCallingConvention::Create(&arena, is_static, is_synchronized, shorty, isa)); + JniCallingConvention::Create(&arena, + is_static, + is_synchronized, + /*is_critical_native*/false, + shorty, + isa)); std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv( ManagedRuntimeCallingConvention::Create(&arena, is_static, is_synchronized, shorty, isa)); const int frame_size(jni_conv->FrameSize()); diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc index b83985a771..cdd4c68470 100644 --- a/compiler/jni/jni_compiler_test.cc +++ b/compiler/jni/jni_compiler_test.cc @@ -15,12 +15,14 @@ */ #include <memory> +#include <type_traits> #include <math.h> #include "art_method-inl.h" #include "class_linker.h" #include "common_compiler_test.h" +#include "compiler.h" #include "dex_file.h" #include "gtest/gtest.h" #include "indirect_reference_table.h" @@ -47,6 +49,171 @@ extern "C" JNIEXPORT jint JNICALL Java_MyClassNatives_sbar(JNIEnv*, jclass, jint namespace art { +enum class JniKind { + kNormal = Compiler::kNone, // Regular kind of un-annotated natives. + kFast = Compiler::kFastNative, // Native method annotated with @FastNative. + kCritical = Compiler::kCriticalNative, // Native method annotated with @CriticalNative. + kCount = Compiler::kCriticalNative + 1 // How many different types of JNIs we can have. +}; + +// Used to initialize array sizes that want to have different state per current jni. +static constexpr size_t kJniKindCount = static_cast<size_t>(JniKind::kCount); +// Do not use directly, use the helpers instead. +uint32_t gCurrentJni = static_cast<uint32_t>(JniKind::kNormal); + +// Is the current native method under test @CriticalNative? +static bool IsCurrentJniCritical() { + return gCurrentJni == static_cast<uint32_t>(JniKind::kCritical); +} + +// Is the current native method a plain-old non-annotated native? +static bool IsCurrentJniNormal() { + return gCurrentJni == static_cast<uint32_t>(JniKind::kNormal); +} + +// Signifify that a different kind of JNI is about to be tested. +static void UpdateCurrentJni(JniKind kind) { + gCurrentJni = static_cast<uint32_t>(kind); +} + +// (Match the name suffixes of native methods in MyClassNatives.java) +static std::string CurrentJniStringSuffix() { + switch (gCurrentJni) { + case static_cast<uint32_t>(JniKind::kNormal): { + return ""; + } + case static_cast<uint32_t>(JniKind::kFast): { + return "_Fast"; + } + case static_cast<uint32_t>(JniKind::kCritical): { + return "_Critical"; + } + default: + LOG(FATAL) << "Invalid current JNI value: " << gCurrentJni; + UNREACHABLE(); + } +} + +// Dummy values passed to our JNI handlers when we enter @CriticalNative. +// Normally @CriticalNative calling convention strips out the "JNIEnv*, jclass" parameters. +// However to avoid duplicating every single test method we have a templated handler +// that inserts dummy parameters (0,1) to make it compatible with a regular JNI handler. +static JNIEnv* const kCriticalDummyJniEnv = reinterpret_cast<JNIEnv*>(0xDEADFEAD); +static jclass const kCriticalDummyJniClass = reinterpret_cast<jclass>(0xBEAFBEEF); + +// Type trait. Returns true if "T" is the same type as one of the types in Args... +// +// Logically equal to OR(std::same_type<T, U> for all U in Args). +template <typename T, typename ... Args> +struct is_any_of; + +template <typename T, typename U, typename ... Args> +struct is_any_of<T, U, Args ...> { + using value_type = bool; + static constexpr const bool value = std::is_same<T, U>::value || is_any_of<T, Args ...>::value; +}; + +template <typename T, typename U> +struct is_any_of<T, U> { + using value_type = bool; + static constexpr const bool value = std::is_same<T, U>::value; +}; + +// Type traits for JNI types. +template <typename T> +struct jni_type_traits { + // True if type T ends up holding an object reference. False otherwise. + // (Non-JNI types will also be false). + static constexpr const bool is_ref = + is_any_of<T, jclass, jobject, jstring, jobjectArray, jintArray, + jcharArray, jfloatArray, jshortArray, jdoubleArray, jlongArray>::value; +}; + +template <typename ... Args> +struct count_refs_helper { + using value_type = size_t; + static constexpr const size_t value = 0; +}; + +template <typename Arg, typename ... Args> +struct count_refs_helper<Arg, Args ...> { + using value_type = size_t; + static constexpr size_t value = + (jni_type_traits<Arg>::is_ref ? 1 : 0) + count_refs_helper<Args ...>::value; +}; + +template <typename T, T fn> +struct count_refs_fn_helper; + +template <typename R, typename ... Args, R fn(Args...)> +struct count_refs_fn_helper<R(Args...), fn> : public count_refs_helper<Args...> {}; + +// Given a function type 'T' figure out how many of the parameter types are a reference. +// -- The implicit jclass and thisObject also count as 1 reference. +// +// Fields: +// * value - the result counting # of refs +// * value_type - the type of value (size_t) +template <typename T, T fn> +struct count_refs : public count_refs_fn_helper<T, fn> {}; + +// Base case: No parameters = 0 refs. +size_t count_nonnull_refs_helper() { + return 0; +} + +// SFINAE for ref types. 1 if non-null, 0 otherwise. +template <typename T> +size_t count_nonnull_refs_single_helper(T arg, + typename std::enable_if<jni_type_traits<T>::is_ref>::type* + = nullptr) { + return ((arg == NULL) ? 0 : 1); +} + +// SFINAE for non-ref-types. Always 0. +template <typename T> +size_t count_nonnull_refs_single_helper(T arg ATTRIBUTE_UNUSED, + typename std::enable_if<!jni_type_traits<T>::is_ref>::type* + = nullptr) { + return 0; +} + +// Recursive case. +template <typename T, typename ... Args> +size_t count_nonnull_refs_helper(T arg, Args ... args) { + return count_nonnull_refs_single_helper(arg) + count_nonnull_refs_helper(args...); +} + +// Given any list of parameters, check how many object refs there are and only count +// them if their runtime value is non-null. +// +// For example given (jobject, jint, jclass) we can get (2) if both #0/#2 are non-null, +// (1) if either #0/#2 are null but not both, and (0) if all parameters are null. +// Primitive parameters (including JNIEnv*, if present) are ignored. +template <typename ... Args> +size_t count_nonnull_refs(Args ... args) { + return count_nonnull_refs_helper(args...); +} + +template <typename T, T fn> +struct remove_extra_parameters_helper; + +template <typename R, typename Arg1, typename Arg2, typename ... Args, R fn(Arg1, Arg2, Args...)> +struct remove_extra_parameters_helper<R(Arg1, Arg2, Args...), fn> { + // Note: Do not use Args&& here to maintain C-style parameter types. + static R apply(Args... args) { + JNIEnv* env = kCriticalDummyJniEnv; + jclass kls = kCriticalDummyJniClass; + return fn(env, kls, args...); + } +}; + +// Given a function 'fn' create a function 'apply' which will omit the JNIEnv/jklass parameters +// +// i.e. if fn(JNIEnv*,jklass,a,b,c,d,e...) then apply(a,b,c,d,e,...) +template <typename T, T fn> +struct jni_remove_extra_parameters : public remove_extra_parameters_helper<T, fn> {}; + class JniCompilerTest : public CommonCompilerTest { protected: void SetUp() OVERRIDE { @@ -63,8 +230,11 @@ class JniCompilerTest : public CommonCompilerTest { check_generic_jni_ = generic; } - void CompileForTest(jobject class_loader, bool direct, - const char* method_name, const char* method_sig) { + private: + void CompileForTest(jobject class_loader, + bool direct, + const char* method_name, + const char* method_sig) { ScopedObjectAccess soa(Thread::Current()); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> loader( @@ -87,8 +257,28 @@ class JniCompilerTest : public CommonCompilerTest { } } - void SetUpForTest(bool direct, const char* method_name, const char* method_sig, + protected: + void CompileForTestWithCurrentJni(jobject class_loader, + bool direct, + const char* method_name_orig, + const char* method_sig) { + // Append the JNI kind to the method name, so that we automatically get the + // fast or critical versions of the same method. + std::string method_name_str = std::string(method_name_orig) + CurrentJniStringSuffix(); + const char* method_name = method_name_str.c_str(); + + CompileForTest(class_loader, direct, method_name, method_sig); + } + + void SetUpForTest(bool direct, + const char* method_name_orig, + const char* method_sig, void* native_fnptr) { + // Append the JNI kind to the method name, so that we automatically get the + // fast or critical versions of the same method. + std::string method_name_str = std::string(method_name_orig) + CurrentJniStringSuffix(); + const char* method_name = method_name_str.c_str(); + // Initialize class loader and compile method when runtime not started. if (!runtime_->IsStarted()) { { @@ -129,6 +319,7 @@ class JniCompilerTest : public CommonCompilerTest { } public: + // Available as statics so our JNI handlers can access these. static jclass jklass_; static jobject jobj_; static jobject class_loader_; @@ -151,6 +342,8 @@ class JniCompilerTest : public CommonCompilerTest { void RunStaticReturnTrueImpl(); void RunStaticReturnFalseImpl(); void RunGenericStaticReturnIntImpl(); + void RunGenericStaticReturnDoubleImpl(); + void RunGenericStaticReturnLongImpl(); void CompileAndRunStaticIntObjectObjectMethodImpl(); void CompileAndRunStaticSynchronizedIntObjectObjectMethodImpl(); void ExceptionHandlingImpl(); @@ -177,10 +370,13 @@ class JniCompilerTest : public CommonCompilerTest { void NormalNativeImpl(); void FastNativeImpl(); + void CriticalNativeImpl(); JNIEnv* env_; jstring library_search_path_; jmethodID jmethod_; + + private: bool check_generic_jni_; }; @@ -188,46 +384,238 @@ jclass JniCompilerTest::jklass_; jobject JniCompilerTest::jobj_; jobject JniCompilerTest::class_loader_; -#define JNI_TEST(TestName) \ +// Test the normal compiler and normal generic JNI only. +// The following features are unsupported in @FastNative: +// 1) JNI stubs (lookup via dlsym) when methods aren't explicitly registered +// 2) Returning objects from the JNI function +// 3) synchronized keyword +// -- TODO: We can support (1) if we remove the mutator lock assert during stub lookup. +# define JNI_TEST_NORMAL_ONLY(TestName) \ TEST_F(JniCompilerTest, TestName ## Default) { \ + SCOPED_TRACE("Normal JNI with compiler"); \ + gCurrentJni = static_cast<uint32_t>(JniKind::kNormal); \ TestName ## Impl(); \ } \ - \ TEST_F(JniCompilerTest, TestName ## Generic) { \ + SCOPED_TRACE("Normal JNI with generic"); \ + gCurrentJni = static_cast<uint32_t>(JniKind::kNormal); \ TEST_DISABLED_FOR_MIPS(); \ SetCheckGenericJni(true); \ TestName ## Impl(); \ } -int gJava_MyClassNatives_foo_calls = 0; -void Java_MyClassNatives_foo(JNIEnv* env, jobject thisObj) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - Locks::mutator_lock_->AssertNotHeld(Thread::Current()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_foo_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +// Test normal compiler, @FastNative compiler, and normal/@FastNative generic for normal natives. +#define JNI_TEST(TestName) \ + JNI_TEST_NORMAL_ONLY(TestName) \ + TEST_F(JniCompilerTest, TestName ## Fast) { \ + SCOPED_TRACE("@FastNative JNI with compiler"); \ + gCurrentJni = static_cast<uint32_t>(JniKind::kFast); \ + TestName ## Impl(); \ + } \ + \ + +// TODO: maybe. @FastNative generic JNI support? +#if 0 + TEST_F(JniCompilerTest, TestName ## FastGeneric) { \ + gCurrentJni = static_cast<uint32_t>(JniKind::kFast); \ + TEST_DISABLED_FOR_MIPS(); \ + SetCheckGenericJni(true); \ + TestName ## Impl(); \ + } +#endif + +#define JNI_TEST_CRITICAL_ONLY(TestName) \ + TEST_F(JniCompilerTest, TestName ## DefaultCritical) { \ + SCOPED_TRACE("@CriticalNative JNI with compiler"); \ + gCurrentJni = static_cast<uint32_t>(JniKind::kCritical); \ + TestName ## Impl(); \ + } + +// Test everything above and also the @CriticalNative compiler, and @CriticalNative generic JNI. +#define JNI_TEST_CRITICAL(TestName) \ + JNI_TEST(TestName) \ + JNI_TEST_CRITICAL_ONLY(TestName) \ + +// TODO: maybe, more likely since calling convention changed. @Criticalnative generic JNI support? +#if 0 + TEST_F(JniCompilerTest, TestName ## GenericCritical) { \ + gCurrentJni = static_cast<uint32_t>(JniKind::kCritical); \ + TestName ## Impl(); \ + } +#endif + +static void expectValidThreadState() { + // Normal JNI always transitions to "Native". Other JNIs stay in the "Runnable" state. + if (IsCurrentJniNormal()) { + EXPECT_EQ(kNative, Thread::Current()->GetState()); + } else { + EXPECT_EQ(kRunnable, Thread::Current()->GetState()); + } +} + +#define EXPECT_THREAD_STATE_FOR_CURRENT_JNI() expectValidThreadState() + +static void expectValidMutatorLockHeld() { + if (IsCurrentJniNormal()) { + Locks::mutator_lock_->AssertNotHeld(Thread::Current()); + } else { + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + } +} + +#define EXPECT_MUTATOR_LOCK_FOR_CURRENT_JNI() expectValidMutatorLockHeld() + +static void expectValidJniEnvAndObject(JNIEnv* env, jobject thisObj) { + if (!IsCurrentJniCritical()) { + EXPECT_EQ(Thread::Current()->GetJniEnv(), env); + ASSERT_TRUE(thisObj != nullptr); + EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); + } else { + LOG(FATAL) << "Objects are not supported for @CriticalNative, why is this being tested?"; + UNREACHABLE(); + } +} + +// Validates the JNIEnv to be the same as the current thread's JNIEnv, and makes sure +// that the object here is an instance of the class we registered the method with. +// +// Hard-fails if this somehow gets invoked for @CriticalNative since objects are unsupported. +#define EXPECT_JNI_ENV_AND_OBJECT_FOR_CURRENT_JNI(env, thisObj) \ + expectValidJniEnvAndObject(env, thisObj) + +static void expectValidJniEnvAndClass(JNIEnv* env, jclass kls) { + if (!IsCurrentJniCritical()) { + EXPECT_EQ(Thread::Current()->GetJniEnv(), env); + ASSERT_TRUE(kls != nullptr); + EXPECT_TRUE(env->IsSameObject(static_cast<jobject>(JniCompilerTest::jklass_), + static_cast<jobject>(kls))); + } else { + // This is pretty much vacuously true but catch any testing setup mistakes. + EXPECT_EQ(env, kCriticalDummyJniEnv); + EXPECT_EQ(kls, kCriticalDummyJniClass); + } +} + +// Validates the JNIEnv is the same as the current thread's JNIenv, and makes sure +// that the jclass we got in the JNI handler is the same one as the class the method was looked +// up for. +// +// (Checks are skipped for @CriticalNative since the two values are dummy). +#define EXPECT_JNI_ENV_AND_CLASS_FOR_CURRENT_JNI(env, kls) expectValidJniEnvAndClass(env, kls) + +// Temporarily disable the EXPECT_NUM_STACK_REFERENCES check (for a single test). +struct ScopedDisableCheckNumStackReferences { + ScopedDisableCheckNumStackReferences() { + sCheckNumStackReferences = false; + } + + ~ScopedDisableCheckNumStackReferences() { + sCheckNumStackReferences = true; + } + + static bool sCheckNumStackReferences; +}; + +bool ScopedDisableCheckNumStackReferences::sCheckNumStackReferences = true; + +static void expectNumStackReferences(size_t val1, size_t val2) { + // In rare cases when JNI functions call themselves recursively, + // disable this test because it will have a false negative. + if (!IsCurrentJniCritical() && ScopedDisableCheckNumStackReferences::sCheckNumStackReferences) { + /* @CriticalNative doesn't build a HandleScope, so this test is meaningless then. */ + ScopedObjectAccess soa(Thread::Current()); + + size_t actual_num = Thread::Current()->NumStackReferences(); + // XX: Not too sure what's going on. + // Sometimes null references get placed and sometimes they don't? + EXPECT_TRUE(val1 == actual_num || val2 == actual_num) + << "expected either " << val1 << " or " << val2 + << " number of stack references, but got: " << actual_num; + } +} + +#define EXPECT_NUM_STACK_REFERENCES(val1, val2) expectNumStackReferences(val1, val2) + +template <typename T, T fn> +struct make_jni_test_decorator; + +// Decorator for "static" JNI callbacks. +template <typename R, typename ... Args, R fn(JNIEnv*, jclass, Args...)> +struct make_jni_test_decorator<R(JNIEnv*, jclass kls, Args...), fn> { + static R apply(JNIEnv* env, jclass kls, Args ... args) { + EXPECT_THREAD_STATE_FOR_CURRENT_JNI(); + EXPECT_MUTATOR_LOCK_FOR_CURRENT_JNI(); + EXPECT_JNI_ENV_AND_CLASS_FOR_CURRENT_JNI(env, kls); + // All incoming parameters + the jclass get put into the transition's StackHandleScope. + EXPECT_NUM_STACK_REFERENCES(count_nonnull_refs(kls, args...), + (count_refs_helper<jclass, Args...>::value)); + + return fn(env, kls, args...); + } +}; + +// Decorator for instance JNI callbacks. +template <typename R, typename ... Args, R fn(JNIEnv*, jobject, Args...)> +struct make_jni_test_decorator<R(JNIEnv*, jobject, Args...), fn> { + static R apply(JNIEnv* env, jobject thisObj, Args ... args) { + EXPECT_THREAD_STATE_FOR_CURRENT_JNI(); + EXPECT_MUTATOR_LOCK_FOR_CURRENT_JNI(); + EXPECT_JNI_ENV_AND_OBJECT_FOR_CURRENT_JNI(env, thisObj); + // All incoming parameters + the implicit 'this' get put into the transition's StackHandleScope. + EXPECT_NUM_STACK_REFERENCES(count_nonnull_refs(thisObj, args...), + (count_refs_helper<jobject, Args...>::value)); + + return fn(env, thisObj, args...); + } +}; + +// Decorate the regular JNI callee with the extra gtest checks. +// This way we can have common test logic for everything generic like checking if a lock is held, +// checking handle scope state, etc. +#define MAKE_JNI_TEST_DECORATOR(fn) make_jni_test_decorator<decltype(fn), (fn)>::apply + +// Convert function f(JNIEnv*,jclass,a,b,c,d...) into f2(a,b,c,d...) +// -- This way we don't have to write out each implementation twice for @CriticalNative. +#define JNI_CRITICAL_WRAPPER(func) jni_remove_extra_parameters<decltype(func), (func)>::apply +// Get a function pointer whose calling convention either matches a regular native +// or a critical native depending on which kind of jni is currently under test. +// -- This also has the benefit of genering a compile time error if the 'func' doesn't properly +// have JNIEnv and jclass parameters first. +#define CURRENT_JNI_WRAPPER(func) \ + (IsCurrentJniCritical() \ + ? reinterpret_cast<void*>(&JNI_CRITICAL_WRAPPER(MAKE_JNI_TEST_DECORATOR(func))) \ + : reinterpret_cast<void*>(&MAKE_JNI_TEST_DECORATOR(func))) + +// Do the opposite of the above. Do *not* wrap the function, instead just cast it to a void*. +// Only for "TEST_JNI_NORMAL_ONLY" configs, and it inserts a test assert to ensure this is the case. +#define NORMAL_JNI_ONLY_NOWRAP(func) \ + ({ ASSERT_TRUE(IsCurrentJniNormal()); reinterpret_cast<void*>(&(func)); }) +// Same as above, but with nullptr. When we want to test the stub functionality. +#define NORMAL_JNI_ONLY_NULLPTR \ + ({ ASSERT_TRUE(IsCurrentJniNormal()); nullptr; }) + + +int gJava_MyClassNatives_foo_calls[kJniKindCount] = {}; +void Java_MyClassNatives_foo(JNIEnv*, jobject) { + gJava_MyClassNatives_foo_calls[gCurrentJni]++; } void JniCompilerTest::CompileAndRunNoArgMethodImpl() { - SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo)); + SetUpForTest(false, "foo", "()V", CURRENT_JNI_WRAPPER(Java_MyClassNatives_foo)); - EXPECT_EQ(0, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(0, gJava_MyClassNatives_foo_calls[gCurrentJni]); env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); - EXPECT_EQ(1, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(1, gJava_MyClassNatives_foo_calls[gCurrentJni]); env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); - EXPECT_EQ(2, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(2, gJava_MyClassNatives_foo_calls[gCurrentJni]); - gJava_MyClassNatives_foo_calls = 0; + gJava_MyClassNatives_foo_calls[gCurrentJni] = 0; } JNI_TEST(CompileAndRunNoArgMethod) void JniCompilerTest::CompileAndRunIntMethodThroughStubImpl() { - SetUpForTest(false, "bar", "(I)I", nullptr); + SetUpForTest(false, "bar", "(I)I", NORMAL_JNI_ONLY_NULLPTR); // calling through stub will link with &Java_MyClassNatives_bar std::string reason; @@ -239,10 +627,11 @@ void JniCompilerTest::CompileAndRunIntMethodThroughStubImpl() { EXPECT_EQ(25, result); } -JNI_TEST(CompileAndRunIntMethodThroughStub) +// TODO: Support @FastNative and @CriticalNative through stubs. +JNI_TEST_NORMAL_ONLY(CompileAndRunIntMethodThroughStub) void JniCompilerTest::CompileAndRunStaticIntMethodThroughStubImpl() { - SetUpForTest(true, "sbar", "(I)I", nullptr); + SetUpForTest(true, "sbar", "(I)I", NORMAL_JNI_ONLY_NULLPTR); // calling through stub will link with &Java_MyClassNatives_sbar std::string reason; @@ -254,174 +643,131 @@ void JniCompilerTest::CompileAndRunStaticIntMethodThroughStubImpl() { EXPECT_EQ(43, result); } -JNI_TEST(CompileAndRunStaticIntMethodThroughStub) +// TODO: Support @FastNative and @CriticalNative through stubs. +JNI_TEST_NORMAL_ONLY(CompileAndRunStaticIntMethodThroughStub) -int gJava_MyClassNatives_fooI_calls = 0; -jint Java_MyClassNatives_fooI(JNIEnv* env, jobject thisObj, jint x) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooI_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooI_calls[kJniKindCount] = {}; +jint Java_MyClassNatives_fooI(JNIEnv*, jobject, jint x) { + gJava_MyClassNatives_fooI_calls[gCurrentJni]++; return x; } void JniCompilerTest::CompileAndRunIntMethodImpl() { SetUpForTest(false, "fooI", "(I)I", - reinterpret_cast<void*>(&Java_MyClassNatives_fooI)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooI)); - EXPECT_EQ(0, gJava_MyClassNatives_fooI_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooI_calls[gCurrentJni]); jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 42); EXPECT_EQ(42, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooI_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooI_calls[gCurrentJni]); result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 0xCAFED00D); EXPECT_EQ(static_cast<jint>(0xCAFED00D), result); - EXPECT_EQ(2, gJava_MyClassNatives_fooI_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooI_calls[gCurrentJni]); - gJava_MyClassNatives_fooI_calls = 0; + gJava_MyClassNatives_fooI_calls[gCurrentJni] = 0; } JNI_TEST(CompileAndRunIntMethod) -int gJava_MyClassNatives_fooII_calls = 0; -jint Java_MyClassNatives_fooII(JNIEnv* env, jobject thisObj, jint x, jint y) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooII_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooII_calls[kJniKindCount] = {}; +jint Java_MyClassNatives_fooII(JNIEnv*, jobject, jint x, jint y) { + gJava_MyClassNatives_fooII_calls[gCurrentJni]++; return x - y; // non-commutative operator } void JniCompilerTest::CompileAndRunIntIntMethodImpl() { SetUpForTest(false, "fooII", "(II)I", - reinterpret_cast<void*>(&Java_MyClassNatives_fooII)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooII)); - EXPECT_EQ(0, gJava_MyClassNatives_fooII_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooII_calls[gCurrentJni]); jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 99, 10); EXPECT_EQ(99 - 10, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooII_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooII_calls[gCurrentJni]); result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 0xCAFEBABE, 0xCAFED00D); EXPECT_EQ(static_cast<jint>(0xCAFEBABE - 0xCAFED00D), result); - EXPECT_EQ(2, gJava_MyClassNatives_fooII_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooII_calls[gCurrentJni]); - gJava_MyClassNatives_fooII_calls = 0; + gJava_MyClassNatives_fooII_calls[gCurrentJni] = 0; } JNI_TEST(CompileAndRunIntIntMethod) -int gJava_MyClassNatives_fooJJ_calls = 0; -jlong Java_MyClassNatives_fooJJ(JNIEnv* env, jobject thisObj, jlong x, jlong y) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooJJ_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooJJ_calls[kJniKindCount] = {}; +jlong Java_MyClassNatives_fooJJ(JNIEnv*, jobject, jlong x, jlong y) { + gJava_MyClassNatives_fooJJ_calls[gCurrentJni]++; return x - y; // non-commutative operator } void JniCompilerTest::CompileAndRunLongLongMethodImpl() { SetUpForTest(false, "fooJJ", "(JJ)J", - reinterpret_cast<void*>(&Java_MyClassNatives_fooJJ)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooJJ)); - EXPECT_EQ(0, gJava_MyClassNatives_fooJJ_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooJJ_calls[gCurrentJni]); jlong a = INT64_C(0x1234567890ABCDEF); jlong b = INT64_C(0xFEDCBA0987654321); jlong result = env_->CallNonvirtualLongMethod(jobj_, jklass_, jmethod_, a, b); EXPECT_EQ(a - b, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooJJ_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooJJ_calls[gCurrentJni]); result = env_->CallNonvirtualLongMethod(jobj_, jklass_, jmethod_, b, a); EXPECT_EQ(b - a, result); - EXPECT_EQ(2, gJava_MyClassNatives_fooJJ_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooJJ_calls[gCurrentJni]); - gJava_MyClassNatives_fooJJ_calls = 0; + gJava_MyClassNatives_fooJJ_calls[gCurrentJni] = 0; } JNI_TEST(CompileAndRunLongLongMethod) -int gJava_MyClassNatives_fooDD_calls = 0; -jdouble Java_MyClassNatives_fooDD(JNIEnv* env, jobject thisObj, jdouble x, jdouble y) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooDD_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooDD_calls[kJniKindCount] = {}; +jdouble Java_MyClassNatives_fooDD(JNIEnv*, jobject, jdouble x, jdouble y) { + gJava_MyClassNatives_fooDD_calls[gCurrentJni]++; return x - y; // non-commutative operator } void JniCompilerTest::CompileAndRunDoubleDoubleMethodImpl() { SetUpForTest(false, "fooDD", "(DD)D", - reinterpret_cast<void*>(&Java_MyClassNatives_fooDD)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooDD)); - EXPECT_EQ(0, gJava_MyClassNatives_fooDD_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooDD_calls[gCurrentJni]); jdouble result = env_->CallNonvirtualDoubleMethod(jobj_, jklass_, jmethod_, 99.0, 10.0); EXPECT_DOUBLE_EQ(99.0 - 10.0, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooDD_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooDD_calls[gCurrentJni]); jdouble a = 3.14159265358979323846; jdouble b = 0.69314718055994530942; result = env_->CallNonvirtualDoubleMethod(jobj_, jklass_, jmethod_, a, b); EXPECT_DOUBLE_EQ(a - b, result); - EXPECT_EQ(2, gJava_MyClassNatives_fooDD_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooDD_calls[gCurrentJni]); - gJava_MyClassNatives_fooDD_calls = 0; + gJava_MyClassNatives_fooDD_calls[gCurrentJni] = 0; } -int gJava_MyClassNatives_fooJJ_synchronized_calls = 0; -jlong Java_MyClassNatives_fooJJ_synchronized(JNIEnv* env, jobject thisObj, jlong x, jlong y) { - // 1 = thisObj - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooJJ_synchronized_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooJJ_synchronized_calls[kJniKindCount] = {}; +jlong Java_MyClassNatives_fooJJ_synchronized(JNIEnv*, jobject, jlong x, jlong y) { + gJava_MyClassNatives_fooJJ_synchronized_calls[gCurrentJni]++; return x | y; } void JniCompilerTest::CompileAndRun_fooJJ_synchronizedImpl() { SetUpForTest(false, "fooJJ_synchronized", "(JJ)J", - reinterpret_cast<void*>(&Java_MyClassNatives_fooJJ_synchronized)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooJJ_synchronized)); - EXPECT_EQ(0, gJava_MyClassNatives_fooJJ_synchronized_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooJJ_synchronized_calls[gCurrentJni]); jlong a = 0x1000000020000000ULL; jlong b = 0x00ff000000aa0000ULL; jlong result = env_->CallNonvirtualLongMethod(jobj_, jklass_, jmethod_, a, b); EXPECT_EQ(a | b, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooJJ_synchronized_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooJJ_synchronized_calls[gCurrentJni]); - gJava_MyClassNatives_fooJJ_synchronized_calls = 0; + gJava_MyClassNatives_fooJJ_synchronized_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRun_fooJJ_synchronized) +JNI_TEST_NORMAL_ONLY(CompileAndRun_fooJJ_synchronized) -int gJava_MyClassNatives_fooIOO_calls = 0; -jobject Java_MyClassNatives_fooIOO(JNIEnv* env, jobject thisObj, jint x, jobject y, +int gJava_MyClassNatives_fooIOO_calls[kJniKindCount] = {}; +jobject Java_MyClassNatives_fooIOO(JNIEnv*, jobject thisObj, jint x, jobject y, jobject z) { - // 3 = this + y + z - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - gJava_MyClassNatives_fooIOO_calls++; - ScopedObjectAccess soa(Thread::Current()); - size_t null_args = (y == nullptr ? 1 : 0) + (z == nullptr ? 1 : 0); - EXPECT_TRUE(3U == Thread::Current()->NumStackReferences() || - (3U - null_args) == Thread::Current()->NumStackReferences()); + gJava_MyClassNatives_fooIOO_calls[gCurrentJni]++; switch (x) { case 1: return y; @@ -435,96 +781,89 @@ jobject Java_MyClassNatives_fooIOO(JNIEnv* env, jobject thisObj, jint x, jobject void JniCompilerTest::CompileAndRunIntObjectObjectMethodImpl() { SetUpForTest(false, "fooIOO", "(ILjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", - reinterpret_cast<void*>(&Java_MyClassNatives_fooIOO)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooIOO)); - EXPECT_EQ(0, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); jobject result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 0, nullptr, nullptr); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(1, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 0, nullptr, jklass_); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(2, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 1, nullptr, jklass_); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(3, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(3, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 2, nullptr, jklass_); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(4, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(4, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 0, jklass_, nullptr); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(5, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(5, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 1, jklass_, nullptr); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(6, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(6, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, 2, jklass_, nullptr); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(7, gJava_MyClassNatives_fooIOO_calls); + EXPECT_EQ(7, gJava_MyClassNatives_fooIOO_calls[gCurrentJni]); - gJava_MyClassNatives_fooIOO_calls = 0; + gJava_MyClassNatives_fooIOO_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRunIntObjectObjectMethod) +// TODO: Maybe. @FastNative support for returning Objects? +JNI_TEST_NORMAL_ONLY(CompileAndRunIntObjectObjectMethod) -int gJava_MyClassNatives_fooSII_calls = 0; -jint Java_MyClassNatives_fooSII(JNIEnv* env, jclass klass, jint x, jint y) { - // 1 = klass - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(klass != nullptr); - EXPECT_TRUE(env->IsInstanceOf(JniCompilerTest::jobj_, klass)); - gJava_MyClassNatives_fooSII_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooSII_calls[kJniKindCount] = {}; +jint Java_MyClassNatives_fooSII(JNIEnv* env ATTRIBUTE_UNUSED, + jclass klass ATTRIBUTE_UNUSED, + jint x, + jint y) { + gJava_MyClassNatives_fooSII_calls[gCurrentJni]++; return x + y; } void JniCompilerTest::CompileAndRunStaticIntIntMethodImpl() { SetUpForTest(true, "fooSII", "(II)I", - reinterpret_cast<void*>(&Java_MyClassNatives_fooSII)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooSII)); - EXPECT_EQ(0, gJava_MyClassNatives_fooSII_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooSII_calls[gCurrentJni]); jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 20, 30); EXPECT_EQ(50, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooSII_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooSII_calls[gCurrentJni]); - gJava_MyClassNatives_fooSII_calls = 0; + gJava_MyClassNatives_fooSII_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRunStaticIntIntMethod) +JNI_TEST_CRITICAL(CompileAndRunStaticIntIntMethod) -int gJava_MyClassNatives_fooSDD_calls = 0; -jdouble Java_MyClassNatives_fooSDD(JNIEnv* env, jclass klass, jdouble x, jdouble y) { - // 1 = klass - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(klass != nullptr); - EXPECT_TRUE(env->IsInstanceOf(JniCompilerTest::jobj_, klass)); - gJava_MyClassNatives_fooSDD_calls++; - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooSDD_calls[kJniKindCount] = {}; +jdouble Java_MyClassNatives_fooSDD(JNIEnv* env ATTRIBUTE_UNUSED, + jclass klass ATTRIBUTE_UNUSED, + jdouble x, + jdouble y) { + gJava_MyClassNatives_fooSDD_calls[gCurrentJni]++; return x - y; // non-commutative operator } void JniCompilerTest::CompileAndRunStaticDoubleDoubleMethodImpl() { SetUpForTest(true, "fooSDD", "(DD)D", - reinterpret_cast<void*>(&Java_MyClassNatives_fooSDD)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooSDD)); - EXPECT_EQ(0, gJava_MyClassNatives_fooSDD_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooSDD_calls[gCurrentJni]); jdouble result = env_->CallStaticDoubleMethod(jklass_, jmethod_, 99.0, 10.0); EXPECT_DOUBLE_EQ(99.0 - 10.0, result); - EXPECT_EQ(1, gJava_MyClassNatives_fooSDD_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooSDD_calls[gCurrentJni]); jdouble a = 3.14159265358979323846; jdouble b = 0.69314718055994530942; result = env_->CallStaticDoubleMethod(jklass_, jmethod_, a, b); EXPECT_DOUBLE_EQ(a - b, result); - EXPECT_DOUBLE_EQ(2, gJava_MyClassNatives_fooSDD_calls); + EXPECT_DOUBLE_EQ(2, gJava_MyClassNatives_fooSDD_calls[gCurrentJni]); - gJava_MyClassNatives_fooSDD_calls = 0; + gJava_MyClassNatives_fooSDD_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRunStaticDoubleDoubleMethod) +JNI_TEST_CRITICAL(CompileAndRunStaticDoubleDoubleMethod) // The x86 generic JNI code had a bug where it assumed a floating // point return value would be in xmm0. We use log, to somehow ensure @@ -534,27 +873,47 @@ jdouble Java_MyClassNatives_logD(JNIEnv*, jclass, jdouble x) { return log(x); } +jdouble Java_MyClassNatives_logD_notNormal(JNIEnv*, jclass, jdouble x) { + EXPECT_DOUBLE_EQ(2.0, x); + return log(x); +} + void JniCompilerTest::RunStaticLogDoubleMethodImpl() { - SetUpForTest(true, "logD", "(D)D", reinterpret_cast<void*>(&Java_MyClassNatives_logD)); + void* jni_handler; + if (IsCurrentJniNormal()) { + // This test seems a bit special, don't use a JNI wrapper here. + jni_handler = NORMAL_JNI_ONLY_NOWRAP(Java_MyClassNatives_logD); + } else { + jni_handler = CURRENT_JNI_WRAPPER(Java_MyClassNatives_logD_notNormal); + } + SetUpForTest(true, "logD", "(D)D", jni_handler); jdouble result = env_->CallStaticDoubleMethod(jklass_, jmethod_, 2.0); EXPECT_DOUBLE_EQ(log(2.0), result); } -JNI_TEST(RunStaticLogDoubleMethod) +JNI_TEST_CRITICAL(RunStaticLogDoubleMethod) jfloat Java_MyClassNatives_logF(JNIEnv*, jclass, jfloat x) { return logf(x); } void JniCompilerTest::RunStaticLogFloatMethodImpl() { - SetUpForTest(true, "logF", "(F)F", reinterpret_cast<void*>(&Java_MyClassNatives_logF)); + void* jni_handler; + if (IsCurrentJniNormal()) { + // This test seems a bit special, don't use a JNI wrapper here. + jni_handler = NORMAL_JNI_ONLY_NOWRAP(Java_MyClassNatives_logF); + } else { + jni_handler = CURRENT_JNI_WRAPPER(Java_MyClassNatives_logF); + } + + SetUpForTest(true, "logF", "(F)F", jni_handler); jfloat result = env_->CallStaticFloatMethod(jklass_, jmethod_, 2.0); EXPECT_FLOAT_EQ(logf(2.0), result); } -JNI_TEST(RunStaticLogFloatMethod) +JNI_TEST_CRITICAL(RunStaticLogFloatMethod) jboolean Java_MyClassNatives_returnTrue(JNIEnv*, jclass) { return JNI_TRUE; @@ -569,46 +928,67 @@ jint Java_MyClassNatives_returnInt(JNIEnv*, jclass) { } void JniCompilerTest::RunStaticReturnTrueImpl() { - SetUpForTest(true, "returnTrue", "()Z", reinterpret_cast<void*>(&Java_MyClassNatives_returnTrue)); + SetUpForTest(true, "returnTrue", "()Z", CURRENT_JNI_WRAPPER(Java_MyClassNatives_returnTrue)); jboolean result = env_->CallStaticBooleanMethod(jklass_, jmethod_); EXPECT_TRUE(result); } -JNI_TEST(RunStaticReturnTrue) +JNI_TEST_CRITICAL(RunStaticReturnTrue) void JniCompilerTest::RunStaticReturnFalseImpl() { SetUpForTest(true, "returnFalse", "()Z", - reinterpret_cast<void*>(&Java_MyClassNatives_returnFalse)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_returnFalse)); jboolean result = env_->CallStaticBooleanMethod(jklass_, jmethod_); EXPECT_FALSE(result); } -JNI_TEST(RunStaticReturnFalse) +JNI_TEST_CRITICAL(RunStaticReturnFalse) void JniCompilerTest::RunGenericStaticReturnIntImpl() { - SetUpForTest(true, "returnInt", "()I", reinterpret_cast<void*>(&Java_MyClassNatives_returnInt)); + SetUpForTest(true, "returnInt", "()I", CURRENT_JNI_WRAPPER(Java_MyClassNatives_returnInt)); jint result = env_->CallStaticIntMethod(jklass_, jmethod_); EXPECT_EQ(42, result); } -JNI_TEST(RunGenericStaticReturnInt) +JNI_TEST_CRITICAL(RunGenericStaticReturnInt) -int gJava_MyClassNatives_fooSIOO_calls = 0; -jobject Java_MyClassNatives_fooSIOO(JNIEnv* env, jclass klass, jint x, jobject y, - jobject z) { - // 3 = klass + y + z - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(klass != nullptr); - EXPECT_TRUE(env->IsInstanceOf(JniCompilerTest::jobj_, klass)); - gJava_MyClassNatives_fooSIOO_calls++; - ScopedObjectAccess soa(Thread::Current()); - size_t null_args = (y == nullptr ? 1 : 0) + (z == nullptr ? 1 : 0); - EXPECT_TRUE(3U == Thread::Current()->NumStackReferences() || - (3U - null_args) == Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_returnDouble_calls[kJniKindCount] = {}; +jdouble Java_MyClassNatives_returnDouble(JNIEnv*, jclass) { + gJava_MyClassNatives_returnDouble_calls[gCurrentJni]++; + return 4.0; +} + +void JniCompilerTest::RunGenericStaticReturnDoubleImpl() { + SetUpForTest(true, "returnDouble", "()D", CURRENT_JNI_WRAPPER(Java_MyClassNatives_returnDouble)); + + jdouble result = env_->CallStaticDoubleMethod(jklass_, jmethod_); + EXPECT_DOUBLE_EQ(4.0, result); + EXPECT_EQ(1, gJava_MyClassNatives_returnDouble_calls[gCurrentJni]); + + gJava_MyClassNatives_returnDouble_calls[gCurrentJni] = 0; +} + +JNI_TEST_CRITICAL(RunGenericStaticReturnDouble) + +jlong Java_MyClassNatives_returnLong(JNIEnv*, jclass) { + return 0xFEEDDEADFEEDL; +} + +void JniCompilerTest::RunGenericStaticReturnLongImpl() { + SetUpForTest(true, "returnLong", "()J", CURRENT_JNI_WRAPPER(Java_MyClassNatives_returnLong)); + + jlong result = env_->CallStaticLongMethod(jklass_, jmethod_); + EXPECT_EQ(0xFEEDDEADFEEDL, result); +} + +JNI_TEST_CRITICAL(RunGenericStaticReturnLong) + +int gJava_MyClassNatives_fooSIOO_calls[kJniKindCount] = {}; +jobject Java_MyClassNatives_fooSIOO(JNIEnv*, jclass klass, jint x, jobject y, jobject z) { + gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]++; switch (x) { case 1: return y; @@ -619,54 +999,45 @@ jobject Java_MyClassNatives_fooSIOO(JNIEnv* env, jclass klass, jint x, jobject y } } - void JniCompilerTest::CompileAndRunStaticIntObjectObjectMethodImpl() { SetUpForTest(true, "fooSIOO", "(ILjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", - reinterpret_cast<void*>(&Java_MyClassNatives_fooSIOO)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooSIOO)); - EXPECT_EQ(0, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); jobject result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, nullptr, nullptr); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(1, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(2, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 1, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(3, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(3, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 2, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(4, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(4, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(5, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(5, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 1, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(6, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(6, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 2, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(7, gJava_MyClassNatives_fooSIOO_calls); + EXPECT_EQ(7, gJava_MyClassNatives_fooSIOO_calls[gCurrentJni]); - gJava_MyClassNatives_fooSIOO_calls = 0; + gJava_MyClassNatives_fooSIOO_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRunStaticIntObjectObjectMethod) +// TODO: Maybe. @FastNative support for returning Objects? +JNI_TEST_NORMAL_ONLY(CompileAndRunStaticIntObjectObjectMethod) -int gJava_MyClassNatives_fooSSIOO_calls = 0; -jobject Java_MyClassNatives_fooSSIOO(JNIEnv* env, jclass klass, jint x, jobject y, jobject z) { - // 3 = klass + y + z - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(klass != nullptr); - EXPECT_TRUE(env->IsInstanceOf(JniCompilerTest::jobj_, klass)); - gJava_MyClassNatives_fooSSIOO_calls++; - ScopedObjectAccess soa(Thread::Current()); - size_t null_args = (y == nullptr ? 1 : 0) + (z == nullptr ? 1 : 0); - EXPECT_TRUE(3U == Thread::Current()->NumStackReferences() || - (3U - null_args) == Thread::Current()->NumStackReferences()); +int gJava_MyClassNatives_fooSSIOO_calls[kJniKindCount] = {}; +jobject Java_MyClassNatives_fooSSIOO(JNIEnv*, jclass klass, jint x, jobject y, jobject z) { + gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]++; switch (x) { case 1: return y; @@ -680,37 +1051,38 @@ jobject Java_MyClassNatives_fooSSIOO(JNIEnv* env, jclass klass, jint x, jobject void JniCompilerTest::CompileAndRunStaticSynchronizedIntObjectObjectMethodImpl() { SetUpForTest(true, "fooSSIOO", "(ILjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", - reinterpret_cast<void*>(&Java_MyClassNatives_fooSSIOO)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooSSIOO)); - EXPECT_EQ(0, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(0, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); jobject result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, nullptr, nullptr); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(1, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(1, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(2, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(2, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 1, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(3, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(3, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 2, nullptr, jobj_); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(4, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(4, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 0, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(jklass_, result)); - EXPECT_EQ(5, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(5, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 1, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(jobj_, result)); - EXPECT_EQ(6, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(6, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); result = env_->CallStaticObjectMethod(jklass_, jmethod_, 2, jobj_, nullptr); EXPECT_TRUE(env_->IsSameObject(nullptr, result)); - EXPECT_EQ(7, gJava_MyClassNatives_fooSSIOO_calls); + EXPECT_EQ(7, gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni]); - gJava_MyClassNatives_fooSSIOO_calls = 0; + gJava_MyClassNatives_fooSSIOO_calls[gCurrentJni] = 0; } -JNI_TEST(CompileAndRunStaticSynchronizedIntObjectObjectMethod) +// TODO: Maybe. @FastNative support for returning Objects? +JNI_TEST_NORMAL_ONLY(CompileAndRunStaticSynchronizedIntObjectObjectMethod) void Java_MyClassNatives_throwException(JNIEnv* env, jobject) { jclass c = env->FindClass("java/lang/RuntimeException"); @@ -724,30 +1096,30 @@ void JniCompilerTest::ExceptionHandlingImpl() { class_loader_ = LoadDex("MyClassNatives"); // all compilation needs to happen before Runtime::Start - CompileForTest(class_loader_, false, "foo", "()V"); - CompileForTest(class_loader_, false, "throwException", "()V"); - CompileForTest(class_loader_, false, "foo", "()V"); + CompileForTestWithCurrentJni(class_loader_, false, "foo", "()V"); + CompileForTestWithCurrentJni(class_loader_, false, "throwException", "()V"); + CompileForTestWithCurrentJni(class_loader_, false, "foo", "()V"); } // Start runtime to avoid re-initialization in SetupForTest. Thread::Current()->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); CHECK(started); - gJava_MyClassNatives_foo_calls = 0; + gJava_MyClassNatives_foo_calls[gCurrentJni] = 0; // Check a single call of a JNI method is ok - SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo)); + SetUpForTest(false, "foo", "()V", CURRENT_JNI_WRAPPER(Java_MyClassNatives_foo)); env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); - EXPECT_EQ(1, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(1, gJava_MyClassNatives_foo_calls[gCurrentJni]); EXPECT_FALSE(Thread::Current()->IsExceptionPending()); // Get class for exception we expect to be thrown ScopedLocalRef<jclass> jlre(env_, env_->FindClass("java/lang/RuntimeException")); SetUpForTest(false, "throwException", "()V", - reinterpret_cast<void*>(&Java_MyClassNatives_throwException)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_throwException)); // Call Java_MyClassNatives_throwException (JNI method that throws exception) env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); - EXPECT_EQ(1, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(1, gJava_MyClassNatives_foo_calls[gCurrentJni]); EXPECT_TRUE(env_->ExceptionCheck() == JNI_TRUE); ScopedLocalRef<jthrowable> exception(env_, env_->ExceptionOccurred()); env_->ExceptionClear(); @@ -756,9 +1128,9 @@ void JniCompilerTest::ExceptionHandlingImpl() { // Check a single call of a JNI method is ok SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo)); env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); - EXPECT_EQ(2, gJava_MyClassNatives_foo_calls); + EXPECT_EQ(2, gJava_MyClassNatives_foo_calls[gCurrentJni]); - gJava_MyClassNatives_foo_calls = 0; + gJava_MyClassNatives_foo_calls[gCurrentJni] = 0; } JNI_TEST(ExceptionHandling) @@ -782,7 +1154,7 @@ jint Java_MyClassNatives_nativeUpCall(JNIEnv* env, jobject thisObj, jint i) { mirror::StackTraceElement* ste = trace_array->Get(j); EXPECT_STREQ("MyClassNatives.java", ste->GetFileName()->ToModifiedUtf8().c_str()); EXPECT_STREQ("MyClassNatives", ste->GetDeclaringClass()->ToModifiedUtf8().c_str()); - EXPECT_STREQ("fooI", ste->GetMethodName()->ToModifiedUtf8().c_str()); + EXPECT_EQ(("fooI" + CurrentJniStringSuffix()), ste->GetMethodName()->ToModifiedUtf8()); } // end recursion @@ -790,7 +1162,9 @@ jint Java_MyClassNatives_nativeUpCall(JNIEnv* env, jobject thisObj, jint i) { } else { jclass jklass = env->FindClass("MyClassNatives"); EXPECT_TRUE(jklass != nullptr); - jmethodID jmethod = env->GetMethodID(jklass, "fooI", "(I)I"); + jmethodID jmethod = env->GetMethodID(jklass, + ("fooI" + CurrentJniStringSuffix()).c_str(), + "(I)I"); EXPECT_TRUE(jmethod != nullptr); // Recurse with i - 1 @@ -803,8 +1177,13 @@ jint Java_MyClassNatives_nativeUpCall(JNIEnv* env, jobject thisObj, jint i) { void JniCompilerTest::NativeStackTraceElementImpl() { SetUpForTest(false, "fooI", "(I)I", - reinterpret_cast<void*>(&Java_MyClassNatives_nativeUpCall)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_nativeUpCall)); + + // Usual # local references on stack check fails because nativeUpCall calls itself recursively, + // each time the # of local references will therefore go up. + ScopedDisableCheckNumStackReferences disable_num_stack_check; jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 10); + EXPECT_EQ(10+9+8+7+6+5+4+3+2+1, result); } @@ -816,13 +1195,14 @@ jobject Java_MyClassNatives_fooO(JNIEnv* env, jobject, jobject x) { void JniCompilerTest::ReturnGlobalRefImpl() { SetUpForTest(false, "fooO", "(Ljava/lang/Object;)Ljava/lang/Object;", - reinterpret_cast<void*>(&Java_MyClassNatives_fooO)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fooO)); jobject result = env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, jobj_); EXPECT_EQ(JNILocalRefType, env_->GetObjectRefType(result)); EXPECT_TRUE(env_->IsSameObject(result, jobj_)); } -JNI_TEST(ReturnGlobalRef) +// TODO: Maybe. @FastNative support for returning objects? +JNI_TEST_NORMAL_ONLY(ReturnGlobalRef) jint local_ref_test(JNIEnv* env, jobject thisObj, jint x) { // Add 10 local references @@ -834,7 +1214,7 @@ jint local_ref_test(JNIEnv* env, jobject thisObj, jint x) { } void JniCompilerTest::LocalReferenceTableClearingTestImpl() { - SetUpForTest(false, "fooI", "(I)I", reinterpret_cast<void*>(&local_ref_test)); + SetUpForTest(false, "fooI", "(I)I", CURRENT_JNI_WRAPPER(local_ref_test)); // 1000 invocations of a method that adds 10 local references for (int i = 0; i < 1000; i++) { jint result = env_->CallIntMethod(jobj_, jmethod_, i); @@ -855,7 +1235,7 @@ void my_arraycopy(JNIEnv* env, jclass klass, jobject src, jint src_pos, jobject void JniCompilerTest::JavaLangSystemArrayCopyImpl() { SetUpForTest(true, "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", - reinterpret_cast<void*>(&my_arraycopy)); + CURRENT_JNI_WRAPPER(my_arraycopy)); env_->CallStaticVoidMethod(jklass_, jmethod_, jobj_, 1234, jklass_, 5678, 9876); } @@ -872,7 +1252,7 @@ jboolean my_casi(JNIEnv* env, jobject unsafe, jobject obj, jlong offset, jint ex void JniCompilerTest::CompareAndSwapIntImpl() { SetUpForTest(false, "compareAndSwapInt", "(Ljava/lang/Object;JII)Z", - reinterpret_cast<void*>(&my_casi)); + CURRENT_JNI_WRAPPER(my_casi)); jboolean result = env_->CallBooleanMethod(jobj_, jmethod_, jobj_, INT64_C(0x12345678ABCDEF88), 0xCAFEF00D, 0xEBADF00D); EXPECT_EQ(result, JNI_TRUE); @@ -891,7 +1271,7 @@ jint my_gettext(JNIEnv* env, jclass klass, jlong val1, jobject obj1, jlong val2, void JniCompilerTest::GetTextImpl() { SetUpForTest(true, "getText", "(JLjava/lang/Object;JLjava/lang/Object;)I", - reinterpret_cast<void*>(&my_gettext)); + CURRENT_JNI_WRAPPER(my_gettext)); jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 0x12345678ABCDEF88ll, jobj_, INT64_C(0x7FEDCBA987654321), jobj_); EXPECT_EQ(result, 42); @@ -899,37 +1279,33 @@ void JniCompilerTest::GetTextImpl() { JNI_TEST(GetText) -int gJava_MyClassNatives_GetSinkProperties_calls = 0; -jarray Java_MyClassNatives_GetSinkProperties(JNIEnv* env, jobject thisObj, jstring s) { - // 1 = thisObj - Thread* self = Thread::Current(); - EXPECT_EQ(kNative, self->GetState()); - Locks::mutator_lock_->AssertNotHeld(self); - EXPECT_EQ(self->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); +int gJava_MyClassNatives_GetSinkProperties_calls[kJniKindCount] = {}; +jarray Java_MyClassNatives_GetSinkProperties(JNIEnv*, jobject thisObj, jstring s) { EXPECT_EQ(s, nullptr); - gJava_MyClassNatives_GetSinkProperties_calls++; + gJava_MyClassNatives_GetSinkProperties_calls[gCurrentJni]++; + + Thread* self = Thread::Current(); ScopedObjectAccess soa(self); - EXPECT_EQ(2U, self->NumStackReferences()); EXPECT_TRUE(self->HoldsLock(soa.Decode<mirror::Object*>(thisObj))); return nullptr; } void JniCompilerTest::GetSinkPropertiesNativeImpl() { SetUpForTest(false, "getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;", - reinterpret_cast<void*>(&Java_MyClassNatives_GetSinkProperties)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_GetSinkProperties)); - EXPECT_EQ(0, gJava_MyClassNatives_GetSinkProperties_calls); + EXPECT_EQ(0, gJava_MyClassNatives_GetSinkProperties_calls[gCurrentJni]); jarray result = down_cast<jarray>( env_->CallNonvirtualObjectMethod(jobj_, jklass_, jmethod_, nullptr)); EXPECT_EQ(nullptr, result); - EXPECT_EQ(1, gJava_MyClassNatives_GetSinkProperties_calls); + EXPECT_EQ(1, gJava_MyClassNatives_GetSinkProperties_calls[gCurrentJni]); - gJava_MyClassNatives_GetSinkProperties_calls = 0; + gJava_MyClassNatives_GetSinkProperties_calls[gCurrentJni] = 0; } -JNI_TEST(GetSinkPropertiesNative) +// @FastNative doesn't support 'synchronized' keyword and +// never will -- locking functions aren't fast. +JNI_TEST_NORMAL_ONLY(GetSinkPropertiesNative) // This should return jclass, but we're imitating a bug pattern. jobject Java_MyClassNatives_instanceMethodThatShouldReturnClass(JNIEnv* env, jobject) { @@ -943,39 +1319,59 @@ jobject Java_MyClassNatives_staticMethodThatShouldReturnClass(JNIEnv* env, jclas void JniCompilerTest::UpcallReturnTypeChecking_InstanceImpl() { SetUpForTest(false, "instanceMethodThatShouldReturnClass", "()Ljava/lang/Class;", - reinterpret_cast<void*>(&Java_MyClassNatives_instanceMethodThatShouldReturnClass)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_instanceMethodThatShouldReturnClass)); CheckJniAbortCatcher check_jni_abort_catcher; // This native method is bad, and tries to return a jstring as a jclass. env_->CallObjectMethod(jobj_, jmethod_); - check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()"); + check_jni_abort_catcher.Check(std::string() + "attempt to return an instance " + + "of java.lang.String from java.lang.Class " + + "MyClassNatives.instanceMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "()"); // Here, we just call the method incorrectly; we should catch that too. env_->CallObjectMethod(jobj_, jmethod_); - check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()"); + check_jni_abort_catcher.Check(std::string() + "attempt to return an instance " + + "of java.lang.String from java.lang.Class " + + "MyClassNatives.instanceMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "()"); env_->CallStaticObjectMethod(jklass_, jmethod_); - check_jni_abort_catcher.Check("calling non-static method java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass() with CallStaticObjectMethodV"); + check_jni_abort_catcher.Check(std::string() + "calling non-static method " + + "java.lang.Class " + + "MyClassNatives.instanceMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "() with CallStaticObjectMethodV"); } -JNI_TEST(UpcallReturnTypeChecking_Instance) +// TODO: Maybe support returning objects for @FastNative? +JNI_TEST_NORMAL_ONLY(UpcallReturnTypeChecking_Instance) void JniCompilerTest::UpcallReturnTypeChecking_StaticImpl() { SetUpForTest(true, "staticMethodThatShouldReturnClass", "()Ljava/lang/Class;", - reinterpret_cast<void*>(&Java_MyClassNatives_staticMethodThatShouldReturnClass)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_staticMethodThatShouldReturnClass)); CheckJniAbortCatcher check_jni_abort_catcher; // This native method is bad, and tries to return a jstring as a jclass. env_->CallStaticObjectMethod(jklass_, jmethod_); - check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()"); + check_jni_abort_catcher.Check(std::string() + "attempt to return an instance " + + "of java.lang.String from java.lang.Class " + + "MyClassNatives.staticMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "()"); // Here, we just call the method incorrectly; we should catch that too. env_->CallStaticObjectMethod(jklass_, jmethod_); - check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()"); + check_jni_abort_catcher.Check(std::string() + "attempt to return an instance " + + "of java.lang.String from java.lang.Class " + + "MyClassNatives.staticMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "()"); env_->CallObjectMethod(jobj_, jmethod_); - check_jni_abort_catcher.Check("calling static method java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass() with CallObjectMethodV"); + check_jni_abort_catcher.Check(std::string() + "calling static method " + + "java.lang.Class " + + "MyClassNatives.staticMethodThatShouldReturnClass" + + CurrentJniStringSuffix() + "() with CallObjectMethodV"); } -JNI_TEST(UpcallReturnTypeChecking_Static) +// TODO: Maybe support returning objects for @FastNative? +JNI_TEST_NORMAL_ONLY(UpcallReturnTypeChecking_Static) // This should take jclass, but we're imitating a bug pattern. void Java_MyClassNatives_instanceMethodThatShouldTakeClass(JNIEnv*, jobject, jclass) { @@ -990,12 +1386,14 @@ void JniCompilerTest::UpcallArgumentTypeChecking_InstanceImpl() { ScopedLogSeverity sls(LogSeverity::FATAL); SetUpForTest(false, "instanceMethodThatShouldTakeClass", "(ILjava/lang/Class;)V", - reinterpret_cast<void*>(&Java_MyClassNatives_instanceMethodThatShouldTakeClass)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_instanceMethodThatShouldTakeClass)); CheckJniAbortCatcher check_jni_abort_catcher; // We deliberately pass a bad second argument here. env_->CallVoidMethod(jobj_, jmethod_, 123, env_->NewStringUTF("not a class!")); - check_jni_abort_catcher.Check("bad arguments passed to void MyClassNatives.instanceMethodThatShouldTakeClass(int, java.lang.Class)"); + check_jni_abort_catcher.Check(std::string() + "bad arguments passed to void " + + "MyClassNatives.instanceMethodThatShouldTakeClass" + + CurrentJniStringSuffix() + "(int, java.lang.Class)"); } JNI_TEST(UpcallArgumentTypeChecking_Instance) @@ -1005,29 +1403,25 @@ void JniCompilerTest::UpcallArgumentTypeChecking_StaticImpl() { ScopedLogSeverity sls(LogSeverity::FATAL); SetUpForTest(true, "staticMethodThatShouldTakeClass", "(ILjava/lang/Class;)V", - reinterpret_cast<void*>(&Java_MyClassNatives_staticMethodThatShouldTakeClass)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_staticMethodThatShouldTakeClass)); CheckJniAbortCatcher check_jni_abort_catcher; // We deliberately pass a bad second argument here. env_->CallStaticVoidMethod(jklass_, jmethod_, 123, env_->NewStringUTF("not a class!")); - check_jni_abort_catcher.Check("bad arguments passed to void MyClassNatives.staticMethodThatShouldTakeClass(int, java.lang.Class)"); + check_jni_abort_catcher.Check(std::string() + "bad arguments passed to void " + + "MyClassNatives.staticMethodThatShouldTakeClass" + + CurrentJniStringSuffix() + "(int, java.lang.Class)"); } JNI_TEST(UpcallArgumentTypeChecking_Static) -jfloat Java_MyClassNatives_checkFloats(JNIEnv* env, jobject thisObj, jfloat f1, jfloat f2) { - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - ScopedObjectAccess soa(Thread::Current()); - EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); +jfloat Java_MyClassNatives_checkFloats(JNIEnv*, jobject, jfloat f1, jfloat f2) { return f1 - f2; // non-commutative operator } void JniCompilerTest::CompileAndRunFloatFloatMethodImpl() { SetUpForTest(false, "checkFloats", "(FF)F", - reinterpret_cast<void*>(&Java_MyClassNatives_checkFloats)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_checkFloats)); jfloat result = env_->CallNonvirtualFloatMethod(jobj_, jklass_, jmethod_, 99.0F, 10.0F); @@ -1042,28 +1436,22 @@ JNI_TEST(CompileAndRunFloatFloatMethod) void Java_MyClassNatives_checkParameterAlign(JNIEnv* env ATTRIBUTE_UNUSED, jobject thisObj ATTRIBUTE_UNUSED, - jint i1 ATTRIBUTE_UNUSED, - jlong l1 ATTRIBUTE_UNUSED) { -// EXPECT_EQ(kNative, Thread::Current()->GetState()); -// EXPECT_EQ(Thread::Current()->GetJniEnv(), env); -// EXPECT_TRUE(thisObj != nullptr); -// EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); -// ScopedObjectAccess soa(Thread::Current()); -// EXPECT_EQ(1U, Thread::Current()->NumStackReferences()); + jint i1, + jlong l1) { EXPECT_EQ(i1, 1234); EXPECT_EQ(l1, INT64_C(0x12345678ABCDEF0)); } void JniCompilerTest::CheckParameterAlignImpl() { SetUpForTest(false, "checkParameterAlign", "(IJ)V", - reinterpret_cast<void*>(&Java_MyClassNatives_checkParameterAlign)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_checkParameterAlign)); env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_, 1234, INT64_C(0x12345678ABCDEF0)); } JNI_TEST(CheckParameterAlign) -void Java_MyClassNatives_maxParamNumber(JNIEnv* env, jobject thisObj, +void Java_MyClassNatives_maxParamNumber(JNIEnv* env, jobject, jobject o0, jobject o1, jobject o2, jobject o3, jobject o4, jobject o5, jobject o6, jobject o7, jobject o8, jobject o9, jobject o10, jobject o11, jobject o12, jobject o13, jobject o14, jobject o15, jobject o16, jobject o17, jobject o18, jobject o19, jobject o20, jobject o21, jobject o22, jobject o23, @@ -1096,13 +1484,6 @@ void Java_MyClassNatives_maxParamNumber(JNIEnv* env, jobject thisObj, jobject o232, jobject o233, jobject o234, jobject o235, jobject o236, jobject o237, jobject o238, jobject o239, jobject o240, jobject o241, jobject o242, jobject o243, jobject o244, jobject o245, jobject o246, jobject o247, jobject o248, jobject o249, jobject o250, jobject o251, jobject o252, jobject o253) { - EXPECT_EQ(kNative, Thread::Current()->GetState()); - EXPECT_EQ(Thread::Current()->GetJniEnv(), env); - EXPECT_TRUE(thisObj != nullptr); - EXPECT_TRUE(env->IsInstanceOf(thisObj, JniCompilerTest::jklass_)); - ScopedObjectAccess soa(Thread::Current()); - EXPECT_GE(255U, Thread::Current()->NumStackReferences()); - // two tests possible if (o0 == nullptr) { // 1) everything is null @@ -1470,7 +1851,7 @@ const char* longSig = void JniCompilerTest::MaxParamNumberImpl() { SetUpForTest(false, "maxParamNumber", longSig, - reinterpret_cast<void*>(&Java_MyClassNatives_maxParamNumber)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_maxParamNumber)); jvalue args[254]; @@ -1497,7 +1878,7 @@ void JniCompilerTest::WithoutImplementationImpl() { // This will lead to error messages in the log. ScopedLogSeverity sls(LogSeverity::FATAL); - SetUpForTest(false, "withoutImplementation", "()V", nullptr); + SetUpForTest(false, "withoutImplementation", "()V", NORMAL_JNI_ONLY_NULLPTR); env_->CallVoidMethod(jobj_, jmethod_); @@ -1505,13 +1886,18 @@ void JniCompilerTest::WithoutImplementationImpl() { EXPECT_TRUE(env_->ExceptionCheck() == JNI_TRUE); } -JNI_TEST(WithoutImplementation) +// TODO: Don't test @FastNative here since it goes through a stub lookup (unsupported) which would +// normally fail with an exception, but fails with an assert. +JNI_TEST_NORMAL_ONLY(WithoutImplementation) void JniCompilerTest::WithoutImplementationRefReturnImpl() { // This will lead to error messages in the log. ScopedLogSeverity sls(LogSeverity::FATAL); - SetUpForTest(false, "withoutImplementationRefReturn", "()Ljava/lang/Object;", nullptr); + SetUpForTest(false, + "withoutImplementationRefReturn", + "()Ljava/lang/Object;", + NORMAL_JNI_ONLY_NULLPTR); env_->CallObjectMethod(jobj_, jmethod_); @@ -1519,7 +1905,8 @@ void JniCompilerTest::WithoutImplementationRefReturnImpl() { EXPECT_TRUE(env_->ExceptionCheck() == JNI_TRUE); } -JNI_TEST(WithoutImplementationRefReturn) +// TODO: Should work for @FastNative too. +JNI_TEST_NORMAL_ONLY(WithoutImplementationRefReturn) void Java_MyClassNatives_stackArgsIntsFirst(JNIEnv*, jclass, jint i1, jint i2, jint i3, jint i4, jint i5, jint i6, jint i7, jint i8, jint i9, @@ -1561,7 +1948,7 @@ void Java_MyClassNatives_stackArgsIntsFirst(JNIEnv*, jclass, jint i1, jint i2, j void JniCompilerTest::StackArgsIntsFirstImpl() { SetUpForTest(true, "stackArgsIntsFirst", "(IIIIIIIIIIFFFFFFFFFF)V", - reinterpret_cast<void*>(&Java_MyClassNatives_stackArgsIntsFirst)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_stackArgsIntsFirst)); jint i1 = 1; jint i2 = 2; @@ -1589,7 +1976,7 @@ void JniCompilerTest::StackArgsIntsFirstImpl() { f3, f4, f5, f6, f7, f8, f9, f10); } -JNI_TEST(StackArgsIntsFirst) +JNI_TEST_CRITICAL(StackArgsIntsFirst) void Java_MyClassNatives_stackArgsFloatsFirst(JNIEnv*, jclass, jfloat f1, jfloat f2, jfloat f3, jfloat f4, jfloat f5, jfloat f6, jfloat f7, @@ -1631,7 +2018,7 @@ void Java_MyClassNatives_stackArgsFloatsFirst(JNIEnv*, jclass, jfloat f1, jfloat void JniCompilerTest::StackArgsFloatsFirstImpl() { SetUpForTest(true, "stackArgsFloatsFirst", "(FFFFFFFFFFIIIIIIIIII)V", - reinterpret_cast<void*>(&Java_MyClassNatives_stackArgsFloatsFirst)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_stackArgsFloatsFirst)); jint i1 = 1; jint i2 = 2; @@ -1659,7 +2046,7 @@ void JniCompilerTest::StackArgsFloatsFirstImpl() { i4, i5, i6, i7, i8, i9, i10); } -JNI_TEST(StackArgsFloatsFirst) +JNI_TEST_CRITICAL(StackArgsFloatsFirst) void Java_MyClassNatives_stackArgsMixed(JNIEnv*, jclass, jint i1, jfloat f1, jint i2, jfloat f2, jint i3, jfloat f3, jint i4, jfloat f4, jint i5, @@ -1700,7 +2087,7 @@ void Java_MyClassNatives_stackArgsMixed(JNIEnv*, jclass, jint i1, jfloat f1, jin void JniCompilerTest::StackArgsMixedImpl() { SetUpForTest(true, "stackArgsMixed", "(IFIFIFIFIFIFIFIFIFIF)V", - reinterpret_cast<void*>(&Java_MyClassNatives_stackArgsMixed)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_stackArgsMixed)); jint i1 = 1; jint i2 = 2; @@ -1728,7 +2115,7 @@ void JniCompilerTest::StackArgsMixedImpl() { f7, i8, f8, i9, f9, i10, f10); } -JNI_TEST(StackArgsMixed) +JNI_TEST_CRITICAL(StackArgsMixed) void Java_MyClassNatives_stackArgsSignExtendedMips64(JNIEnv*, jclass, jint i1, jint i2, jint i3, jint i4, jint i5, jint i6, jint i7, jint i8) { @@ -1760,7 +2147,7 @@ void Java_MyClassNatives_stackArgsSignExtendedMips64(JNIEnv*, jclass, jint i1, j void JniCompilerTest::StackArgsSignExtendedMips64Impl() { SetUpForTest(true, "stackArgsSignExtendedMips64", "(IIIIIIII)V", - reinterpret_cast<void*>(&Java_MyClassNatives_stackArgsSignExtendedMips64)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_stackArgsSignExtendedMips64)); jint i1 = 1; jint i2 = 2; jint i3 = 3; @@ -1773,7 +2160,7 @@ void JniCompilerTest::StackArgsSignExtendedMips64Impl() { env_->CallStaticVoidMethod(jklass_, jmethod_, i1, i2, i3, i4, i5, i6, i7, i8); } -JNI_TEST(StackArgsSignExtendedMips64) +JNI_TEST_CRITICAL(StackArgsSignExtendedMips64) void Java_MyClassNatives_normalNative(JNIEnv*, jclass) { // Intentionally left empty. @@ -1785,15 +2172,18 @@ void JniCompilerTest::NormalNativeImpl() { SetUpForTest(/* direct */ true, "normalNative", "()V", - reinterpret_cast<void*>(&Java_MyClassNatives_normalNative)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_normalNative)); ScopedObjectAccess soa(Thread::Current()); ArtMethod* method = soa.DecodeMethod(jmethod_); ASSERT_TRUE(method != nullptr); + EXPECT_FALSE(method->IsAnnotatedWithCriticalNative()); EXPECT_FALSE(method->IsAnnotatedWithFastNative()); } -JNI_TEST(NormalNative) + +// TODO: just rename the java functions to the standard convention and remove duplicated tests +JNI_TEST_NORMAL_ONLY(NormalNative) // Methods annotated with @FastNative are considered "fast native" // -- Check that the annotation lookup succeeds. @@ -1805,14 +2195,53 @@ void JniCompilerTest::FastNativeImpl() { SetUpForTest(/* direct */ true, "fastNative", "()V", - reinterpret_cast<void*>(&Java_MyClassNatives_fastNative)); + CURRENT_JNI_WRAPPER(Java_MyClassNatives_fastNative)); ScopedObjectAccess soa(Thread::Current()); ArtMethod* method = soa.DecodeMethod(jmethod_); ASSERT_TRUE(method != nullptr); + EXPECT_FALSE(method->IsAnnotatedWithCriticalNative()); EXPECT_TRUE(method->IsAnnotatedWithFastNative()); } -JNI_TEST(FastNative) + +// TODO: just rename the java functions to the standard convention and remove duplicated tests +JNI_TEST_NORMAL_ONLY(FastNative) + +int gJava_myClassNatives_criticalNative_calls[kJniKindCount] = {}; +// Methods annotated with @CriticalNative are considered "critical native" +// -- Check that the annotation lookup succeeds. +void Java_MyClassNatives_criticalNative() { + gJava_myClassNatives_criticalNative_calls[gCurrentJni]++; +} + +void JniCompilerTest::CriticalNativeImpl() { + SetUpForTest(/* direct */ true, + // Important: Don't change the "current jni" yet to avoid a method name suffix. + "criticalNative", + "()V", + // TODO: Use CURRENT_JNI_WRAPPER instead which is more generic. + reinterpret_cast<void*>(&Java_MyClassNatives_criticalNative)); + + // TODO: remove this manual updating of the current JNI. Merge with the other tests. + UpdateCurrentJni(JniKind::kCritical); + ASSERT_TRUE(IsCurrentJniCritical()); + + ScopedObjectAccess soa(Thread::Current()); + ArtMethod* method = soa.DecodeMethod(jmethod_); + ASSERT_TRUE(method != nullptr); + + EXPECT_TRUE(method->IsAnnotatedWithCriticalNative()); + EXPECT_FALSE(method->IsAnnotatedWithFastNative()); + + EXPECT_EQ(0, gJava_myClassNatives_criticalNative_calls[gCurrentJni]); + env_->CallStaticVoidMethod(jklass_, jmethod_); + EXPECT_EQ(1, gJava_myClassNatives_criticalNative_calls[gCurrentJni]); + + gJava_myClassNatives_criticalNative_calls[gCurrentJni] = 0; +} + +// TODO: just rename the java functions to the standard convention and remove duplicated tests +JNI_TEST_NORMAL_ONLY(CriticalNative) } // namespace art diff --git a/compiler/jni/quick/arm/calling_convention_arm.cc b/compiler/jni/quick/arm/calling_convention_arm.cc index 0d16260f4b..3f29ae5dcb 100644 --- a/compiler/jni/quick/arm/calling_convention_arm.cc +++ b/compiler/jni/quick/arm/calling_convention_arm.cc @@ -24,15 +24,33 @@ namespace arm { static_assert(kArmPointerSize == PointerSize::k32, "Unexpected ARM pointer size"); -// Used by hard float. +// +// JNI calling convention constants. +// + +// List of parameters passed via registers for JNI. +// JNI uses soft-float, so there is only a GPR list. +static const Register kJniArgumentRegisters[] = { + R0, R1, R2, R3 +}; + +static const size_t kJniArgumentRegisterCount = arraysize(kJniArgumentRegisters); + +// +// Managed calling convention constants. +// + +// Used by hard float. (General purpose registers.) static const Register kHFCoreArgumentRegisters[] = { R0, R1, R2, R3 }; +// (VFP single-precision registers.) static const SRegister kHFSArgumentRegisters[] = { S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15 }; +// (VFP double-precision registers.) static const DRegister kHFDArgumentRegisters[] = { D0, D1, D2, D3, D4, D5, D6, D7 }; @@ -40,6 +58,10 @@ static const DRegister kHFDArgumentRegisters[] = { static_assert(arraysize(kHFDArgumentRegisters) * 2 == arraysize(kHFSArgumentRegisters), "ks d argument registers mismatch"); +// +// Shared managed+JNI calling convention constants. +// + static constexpr ManagedRegister kCalleeSaveRegisters[] = { // Core registers. ArmManagedRegister::FromCoreRegister(R5), @@ -255,23 +277,95 @@ const ManagedRegisterEntrySpills& ArmManagedRuntimeCallingConvention::EntrySpill } // JNI calling convention -ArmJniCallingConvention::ArmJniCallingConvention(bool is_static, bool is_synchronized, +ArmJniCallingConvention::ArmJniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, const char* shorty) - : JniCallingConvention(is_static, is_synchronized, shorty, kArmPointerSize) { - // Compute padding to ensure longs and doubles are not split in AAPCS. Ignore the 'this' jobject - // or jclass for static methods and the JNIEnv. We start at the aligned register r2. - size_t padding = 0; - for (size_t cur_arg = IsStatic() ? 0 : 1, cur_reg = 2; cur_arg < NumArgs(); cur_arg++) { + : JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty, + kArmPointerSize) { + // AAPCS 4.1 specifies fundamental alignments for each type. All of our stack arguments are + // usually 4-byte aligned, however longs and doubles must be 8 bytes aligned. Add padding to + // maintain 8-byte alignment invariant. + // + // Compute padding to ensure longs and doubles are not split in AAPCS. + size_t shift = 0; + + size_t cur_arg, cur_reg; + if (LIKELY(HasExtraArgumentsForJni())) { + // Ignore the 'this' jobject or jclass for static methods and the JNIEnv. + // We start at the aligned register r2. + // + // Ignore the first 2 parameters because they are guaranteed to be aligned. + cur_arg = NumImplicitArgs(); // skip the "this" arg. + cur_reg = 2; // skip {r0=JNIEnv, r1=jobject} / {r0=JNIEnv, r1=jclass} parameters (start at r2). + } else { + // Check every parameter. + cur_arg = 0; + cur_reg = 0; + } + + // TODO: Maybe should just use IsCurrentParamALongOrDouble instead to be cleaner? + // (this just seems like an unnecessary micro-optimization). + + // Shift across a logical register mapping that looks like: + // + // | r0 | r1 | r2 | r3 | SP | SP+4| SP+8 | SP+12 | ... | SP+n | SP+n+4 | + // + // (where SP is some arbitrary stack pointer that our 0th stack arg would go into). + // + // Any time there would normally be a long/double in an odd logical register, + // we have to push out the rest of the mappings by 4 bytes to maintain an 8-byte alignment. + // + // This works for both physical register pairs {r0, r1}, {r2, r3} and for when + // the value is on the stack. + // + // For example: + // (a) long would normally go into r1, but we shift it into r2 + // | INT | (PAD) | LONG | + // | r0 | r1 | r2 | r3 | + // + // (b) long would normally go into r3, but we shift it into SP + // | INT | INT | INT | (PAD) | LONG | + // | r0 | r1 | r2 | r3 | SP+4 SP+8| + // + // where INT is any <=4 byte arg, and LONG is any 8-byte arg. + for (; cur_arg < NumArgs(); cur_arg++) { if (IsParamALongOrDouble(cur_arg)) { - if ((cur_reg & 1) != 0) { - padding += 4; + if ((cur_reg & 1) != 0) { // check that it's in a logical contiguous register pair + shift += 4; cur_reg++; // additional bump to ensure alignment } - cur_reg++; // additional bump to skip extra long word + cur_reg += 2; // bump the iterator twice for every long argument + } else { + cur_reg++; // bump the iterator for every non-long argument } - cur_reg++; // bump the iterator for every argument } - padding_ = padding; + + if (cur_reg < kJniArgumentRegisterCount) { + // As a special case when, as a result of shifting (or not) there are no arguments on the stack, + // we actually have 0 stack padding. + // + // For example with @CriticalNative and: + // (int, long) -> shifts the long but doesn't need to pad the stack + // + // shift + // \/ + // | INT | (PAD) | LONG | (EMPTY) ... + // | r0 | r1 | r2 | r3 | SP ... + // /\ + // no stack padding + padding_ = 0; + } else { + padding_ = shift; + } + + // TODO: add some new JNI tests for @CriticalNative that introduced new edge cases + // (a) Using r0,r1 pair = f(long,...) + // (b) Shifting r1 long into r2,r3 pair = f(int, long, int, ...); + // (c) Shifting but not introducing a stack padding = f(int, long); } uint32_t ArmJniCallingConvention::CoreSpillMask() const { @@ -289,15 +383,34 @@ ManagedRegister ArmJniCallingConvention::ReturnScratchRegister() const { size_t ArmJniCallingConvention::FrameSize() { // Method*, LR and callee save area size, local reference segment state - size_t frame_data_size = static_cast<size_t>(kArmPointerSize) - + (2 + CalleeSaveRegisters().size()) * kFramePointerSize; - // References plus 2 words for HandleScope header - size_t handle_scope_size = HandleScope::SizeOf(kArmPointerSize, ReferenceCount()); + const size_t method_ptr_size = static_cast<size_t>(kArmPointerSize); + const size_t lr_return_addr_size = kFramePointerSize; + const size_t callee_save_area_size = CalleeSaveRegisters().size() * kFramePointerSize; + size_t frame_data_size = method_ptr_size + lr_return_addr_size + callee_save_area_size; + + if (LIKELY(HasLocalReferenceSegmentState())) { + // local reference segment state + frame_data_size += kFramePointerSize; + // TODO: Probably better to use sizeof(IRTSegmentState) here... + } + + // References plus link_ (pointer) and number_of_references_ (uint32_t) for HandleScope header + const size_t handle_scope_size = HandleScope::SizeOf(kArmPointerSize, ReferenceCount()); + + size_t total_size = frame_data_size; + if (LIKELY(HasHandleScope())) { + // HandleScope is sometimes excluded. + total_size += handle_scope_size; // handle scope size + } + // Plus return value spill area size - return RoundUp(frame_data_size + handle_scope_size + SizeOfReturnValue(), kStackAlignment); + total_size += SizeOfReturnValue(); + + return RoundUp(total_size, kStackAlignment); } size_t ArmJniCallingConvention::OutArgSize() { + // TODO: Identical to x86_64 except for also adding additional padding. return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize + padding_, kStackAlignment); } @@ -309,55 +422,70 @@ ArrayRef<const ManagedRegister> ArmJniCallingConvention::CalleeSaveRegisters() c // JniCallingConvention ABI follows AAPCS where longs and doubles must occur // in even register numbers and stack slots void ArmJniCallingConvention::Next() { + // Update the iterator by usual JNI rules. JniCallingConvention::Next(); - size_t arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - if ((itr_args_ >= 2) && - (arg_pos < NumArgs()) && - IsParamALongOrDouble(arg_pos)) { - // itr_slots_ needs to be an even number, according to AAPCS. - if ((itr_slots_ & 0x1u) != 0) { + + if (LIKELY(HasNext())) { // Avoid CHECK failure for IsCurrentParam + // Ensure slot is 8-byte aligned for longs/doubles (AAPCS). + if (IsCurrentParamALongOrDouble() && ((itr_slots_ & 0x1u) != 0)) { + // itr_slots_ needs to be an even number, according to AAPCS. itr_slots_++; } } } bool ArmJniCallingConvention::IsCurrentParamInRegister() { - return itr_slots_ < 4; + return itr_slots_ < kJniArgumentRegisterCount; } bool ArmJniCallingConvention::IsCurrentParamOnStack() { return !IsCurrentParamInRegister(); } -static const Register kJniArgumentRegisters[] = { - R0, R1, R2, R3 -}; ManagedRegister ArmJniCallingConvention::CurrentParamRegister() { - CHECK_LT(itr_slots_, 4u); - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - if ((itr_args_ >= 2) && IsParamALongOrDouble(arg_pos)) { - CHECK_EQ(itr_slots_, 2u); - return ArmManagedRegister::FromRegisterPair(R2_R3); + CHECK_LT(itr_slots_, kJniArgumentRegisterCount); + if (IsCurrentParamALongOrDouble()) { + // AAPCS 5.1.1 requires 64-bit values to be in a consecutive register pair: + // "A double-word sized type is passed in two consecutive registers (e.g., r0 and r1, or r2 and + // r3). The content of the registers is as if the value had been loaded from memory + // representation with a single LDM instruction." + if (itr_slots_ == 0u) { + return ArmManagedRegister::FromRegisterPair(R0_R1); + } else if (itr_slots_ == 2u) { + return ArmManagedRegister::FromRegisterPair(R2_R3); + } else { + // The register can either be R0 (+R1) or R2 (+R3). Cannot be other values. + LOG(FATAL) << "Invalid iterator register position for a long/double " << itr_args_; + UNREACHABLE(); + } } else { - return - ArmManagedRegister::FromCoreRegister(kJniArgumentRegisters[itr_slots_]); + // All other types can fit into one register. + return ArmManagedRegister::FromCoreRegister(kJniArgumentRegisters[itr_slots_]); } } FrameOffset ArmJniCallingConvention::CurrentParamStackOffset() { - CHECK_GE(itr_slots_, 4u); + CHECK_GE(itr_slots_, kJniArgumentRegisterCount); size_t offset = - displacement_.Int32Value() - OutArgSize() + ((itr_slots_ - 4) * kFramePointerSize); + displacement_.Int32Value() + - OutArgSize() + + ((itr_slots_ - kJniArgumentRegisterCount) * kFramePointerSize); CHECK_LT(offset, OutArgSize()); return FrameOffset(offset); } size_t ArmJniCallingConvention::NumberOfOutgoingStackArgs() { - size_t static_args = IsStatic() ? 1 : 0; // count jclass + size_t static_args = HasSelfClass() ? 1 : 0; // count jclass // regular argument parameters and this - size_t param_args = NumArgs() + NumLongOrDoubleArgs(); + size_t param_args = NumArgs() + NumLongOrDoubleArgs(); // twice count 8-byte args + // XX: Why is the long/ordouble counted twice but not JNIEnv* ??? // count JNIEnv* less arguments in registers - return static_args + param_args + 1 - 4; + size_t internal_args = (HasJniEnv() ? 1 : 0 /* jni env */); + size_t total_args = static_args + param_args + internal_args; + + return total_args - std::min(kJniArgumentRegisterCount, static_cast<size_t>(total_args)); + + // TODO: Very similar to x86_64 except for the return pc. } } // namespace arm diff --git a/compiler/jni/quick/arm/calling_convention_arm.h b/compiler/jni/quick/arm/calling_convention_arm.h index 7c717cc6b8..249f20225d 100644 --- a/compiler/jni/quick/arm/calling_convention_arm.h +++ b/compiler/jni/quick/arm/calling_convention_arm.h @@ -52,7 +52,10 @@ class ArmManagedRuntimeCallingConvention FINAL : public ManagedRuntimeCallingCon class ArmJniCallingConvention FINAL : public JniCallingConvention { public: - ArmJniCallingConvention(bool is_static, bool is_synchronized, const char* shorty); + ArmJniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, + const char* shorty); ~ArmJniCallingConvention() OVERRIDE {} // Calling convention ManagedRegister ReturnRegister() OVERRIDE; diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.cc b/compiler/jni/quick/arm64/calling_convention_arm64.cc index afa707d2a9..3fb7b56284 100644 --- a/compiler/jni/quick/arm64/calling_convention_arm64.cc +++ b/compiler/jni/quick/arm64/calling_convention_arm64.cc @@ -24,6 +24,13 @@ namespace arm64 { static_assert(kArm64PointerSize == PointerSize::k64, "Unexpected ARM64 pointer size"); +// Up to how many float-like (float, double) args can be enregistered. +// The rest of the args must go on the stack. +constexpr size_t kMaxFloatOrDoubleRegisterArguments = 8u; +// Up to how many integer-like (pointers, objects, longs, int, short, bool, etc) args can be +// enregistered. The rest of the args must go on the stack. +constexpr size_t kMaxIntLikeRegisterArguments = 8u; + static const XRegister kXArgumentRegisters[] = { X0, X1, X2, X3, X4, X5, X6, X7 }; @@ -211,9 +218,11 @@ const ManagedRegisterEntrySpills& Arm64ManagedRuntimeCallingConvention::EntrySpi } // JNI calling convention -Arm64JniCallingConvention::Arm64JniCallingConvention(bool is_static, bool is_synchronized, +Arm64JniCallingConvention::Arm64JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, const char* shorty) - : JniCallingConvention(is_static, is_synchronized, shorty, kArm64PointerSize) { + : JniCallingConvention(is_static, is_synchronized, is_critical_native, shorty, kArm64PointerSize) { } uint32_t Arm64JniCallingConvention::CoreSpillMask() const { @@ -230,38 +239,59 @@ ManagedRegister Arm64JniCallingConvention::ReturnScratchRegister() const { size_t Arm64JniCallingConvention::FrameSize() { // Method*, callee save area size, local reference segment state - size_t frame_data_size = kFramePointerSize + - CalleeSaveRegisters().size() * kFramePointerSize + sizeof(uint32_t); + // + // (Unlike x86_64, do not include return address, and the segment state is uint32 + // instead of pointer). + size_t method_ptr_size = static_cast<size_t>(kFramePointerSize); + size_t callee_save_area_size = CalleeSaveRegisters().size() * kFramePointerSize; + + size_t frame_data_size = method_ptr_size + callee_save_area_size; + if (LIKELY(HasLocalReferenceSegmentState())) { + frame_data_size += sizeof(uint32_t); + } // References plus 2 words for HandleScope header size_t handle_scope_size = HandleScope::SizeOf(kArm64PointerSize, ReferenceCount()); + + size_t total_size = frame_data_size; + if (LIKELY(HasHandleScope())) { + // HandleScope is sometimes excluded. + total_size += handle_scope_size; // handle scope size + } + // Plus return value spill area size - return RoundUp(frame_data_size + handle_scope_size + SizeOfReturnValue(), kStackAlignment); + total_size += SizeOfReturnValue(); + + return RoundUp(total_size, kStackAlignment); } size_t Arm64JniCallingConvention::OutArgSize() { + // Same as X86_64 return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize, kStackAlignment); } ArrayRef<const ManagedRegister> Arm64JniCallingConvention::CalleeSaveRegisters() const { + // Same as X86_64 return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); } bool Arm64JniCallingConvention::IsCurrentParamInRegister() { if (IsCurrentParamAFloatOrDouble()) { - return (itr_float_and_doubles_ < 8); + return (itr_float_and_doubles_ < kMaxFloatOrDoubleRegisterArguments); } else { - return ((itr_args_ - itr_float_and_doubles_) < 8); + return ((itr_args_ - itr_float_and_doubles_) < kMaxIntLikeRegisterArguments); } + // TODO: Can we just call CurrentParamRegister to figure this out? } bool Arm64JniCallingConvention::IsCurrentParamOnStack() { + // Is this ever not the same for all the architectures? return !IsCurrentParamInRegister(); } ManagedRegister Arm64JniCallingConvention::CurrentParamRegister() { CHECK(IsCurrentParamInRegister()); if (IsCurrentParamAFloatOrDouble()) { - CHECK_LT(itr_float_and_doubles_, 8u); + CHECK_LT(itr_float_and_doubles_, kMaxFloatOrDoubleRegisterArguments); if (IsCurrentParamADouble()) { return Arm64ManagedRegister::FromDRegister(kDArgumentRegisters[itr_float_and_doubles_]); } else { @@ -269,7 +299,7 @@ ManagedRegister Arm64JniCallingConvention::CurrentParamRegister() { } } else { int gp_reg = itr_args_ - itr_float_and_doubles_; - CHECK_LT(static_cast<unsigned int>(gp_reg), 8u); + CHECK_LT(static_cast<unsigned int>(gp_reg), kMaxIntLikeRegisterArguments); if (IsCurrentParamALong() || IsCurrentParamAReference() || IsCurrentParamJniEnv()) { return Arm64ManagedRegister::FromXRegister(kXArgumentRegisters[gp_reg]); } else { @@ -281,20 +311,30 @@ ManagedRegister Arm64JniCallingConvention::CurrentParamRegister() { FrameOffset Arm64JniCallingConvention::CurrentParamStackOffset() { CHECK(IsCurrentParamOnStack()); size_t args_on_stack = itr_args_ - - std::min(8u, itr_float_and_doubles_) - - std::min(8u, (itr_args_ - itr_float_and_doubles_)); + - std::min(kMaxFloatOrDoubleRegisterArguments, + static_cast<size_t>(itr_float_and_doubles_)) + - std::min(kMaxIntLikeRegisterArguments, + static_cast<size_t>(itr_args_ - itr_float_and_doubles_)); size_t offset = displacement_.Int32Value() - OutArgSize() + (args_on_stack * kFramePointerSize); CHECK_LT(offset, OutArgSize()); return FrameOffset(offset); + // TODO: Seems identical to X86_64 code. } size_t Arm64JniCallingConvention::NumberOfOutgoingStackArgs() { // all arguments including JNI args size_t all_args = NumArgs() + NumberOfExtraArgumentsForJni(); - size_t all_stack_args = all_args - - std::min(8u, static_cast<unsigned int>(NumFloatOrDoubleArgs())) - - std::min(8u, static_cast<unsigned int>((all_args - NumFloatOrDoubleArgs()))); + DCHECK_GE(all_args, NumFloatOrDoubleArgs()); + + size_t all_stack_args = + all_args + - std::min(kMaxFloatOrDoubleRegisterArguments, + static_cast<size_t>(NumFloatOrDoubleArgs())) + - std::min(kMaxIntLikeRegisterArguments, + static_cast<size_t>((all_args - NumFloatOrDoubleArgs()))); + + // TODO: Seems similar to X86_64 code except it doesn't count return pc. return all_stack_args; } diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.h b/compiler/jni/quick/arm64/calling_convention_arm64.h index 90b12e5462..56189427b6 100644 --- a/compiler/jni/quick/arm64/calling_convention_arm64.h +++ b/compiler/jni/quick/arm64/calling_convention_arm64.h @@ -52,7 +52,10 @@ class Arm64ManagedRuntimeCallingConvention FINAL : public ManagedRuntimeCallingC class Arm64JniCallingConvention FINAL : public JniCallingConvention { public: - Arm64JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty); + Arm64JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, + const char* shorty); ~Arm64JniCallingConvention() OVERRIDE {} // Calling convention ManagedRegister ReturnRegister() OVERRIDE; diff --git a/compiler/jni/quick/calling_convention.cc b/compiler/jni/quick/calling_convention.cc index c7ed9c92a2..9859b5da30 100644 --- a/compiler/jni/quick/calling_convention.cc +++ b/compiler/jni/quick/calling_convention.cc @@ -149,19 +149,44 @@ bool ManagedRuntimeCallingConvention::IsCurrentParamALong() { std::unique_ptr<JniCallingConvention> JniCallingConvention::Create(ArenaAllocator* arena, bool is_static, bool is_synchronized, + bool is_critical_native, const char* shorty, InstructionSet instruction_set) { + if (UNLIKELY(is_critical_native)) { + // Sanity check that the requested JNI instruction set + // is supported for critical natives. Not every one is. + switch (instruction_set) { + case kX86_64: + case kX86: + case kArm64: + case kArm: + case kThumb2: + break; + default: + is_critical_native = false; + LOG(WARNING) << "@CriticalNative support not implemented for " << instruction_set + << "; will crash at runtime if trying to invoke such a method."; + // TODO: implement for MIPS/MIPS64 + } + } + switch (instruction_set) { #ifdef ART_ENABLE_CODEGEN_arm case kArm: case kThumb2: return std::unique_ptr<JniCallingConvention>( - new (arena) arm::ArmJniCallingConvention(is_static, is_synchronized, shorty)); + new (arena) arm::ArmJniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty)); #endif #ifdef ART_ENABLE_CODEGEN_arm64 case kArm64: return std::unique_ptr<JniCallingConvention>( - new (arena) arm64::Arm64JniCallingConvention(is_static, is_synchronized, shorty)); + new (arena) arm64::Arm64JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty)); #endif #ifdef ART_ENABLE_CODEGEN_mips case kMips: @@ -176,12 +201,18 @@ std::unique_ptr<JniCallingConvention> JniCallingConvention::Create(ArenaAllocato #ifdef ART_ENABLE_CODEGEN_x86 case kX86: return std::unique_ptr<JniCallingConvention>( - new (arena) x86::X86JniCallingConvention(is_static, is_synchronized, shorty)); + new (arena) x86::X86JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty)); #endif #ifdef ART_ENABLE_CODEGEN_x86_64 case kX86_64: return std::unique_ptr<JniCallingConvention>( - new (arena) x86_64::X86_64JniCallingConvention(is_static, is_synchronized, shorty)); + new (arena) x86_64::X86_64JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty)); #endif default: LOG(FATAL) << "Unknown InstructionSet: " << instruction_set; @@ -199,27 +230,36 @@ FrameOffset JniCallingConvention::SavedLocalReferenceCookieOffset() const { } FrameOffset JniCallingConvention::ReturnValueSaveLocation() const { - // Segment state is 4 bytes long - return FrameOffset(SavedLocalReferenceCookieOffset().Int32Value() + 4); + if (LIKELY(HasHandleScope())) { + // Initial offset already includes the displacement. + // -- Remove the additional local reference cookie offset if we don't have a handle scope. + const size_t saved_local_reference_cookie_offset = + SavedLocalReferenceCookieOffset().Int32Value(); + // Segment state is 4 bytes long + const size_t segment_state_size = 4; + return FrameOffset(saved_local_reference_cookie_offset + segment_state_size); + } else { + // Include only the initial Method* as part of the offset. + CHECK_LT(displacement_.SizeValue(), + static_cast<size_t>(std::numeric_limits<int32_t>::max())); + return FrameOffset(displacement_.Int32Value() + static_cast<size_t>(frame_pointer_size_)); + } } bool JniCallingConvention::HasNext() { - if (itr_args_ <= kObjectOrClass) { + if (IsCurrentArgExtraForJni()) { return true; } else { - unsigned int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); + unsigned int arg_pos = GetIteratorPositionWithinShorty(); return arg_pos < NumArgs(); } } void JniCallingConvention::Next() { CHECK(HasNext()); - if (itr_args_ > kObjectOrClass) { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - if (IsParamALongOrDouble(arg_pos)) { - itr_longs_and_doubles_++; - itr_slots_++; - } + if (IsCurrentParamALong() || IsCurrentParamADouble()) { + itr_longs_and_doubles_++; + itr_slots_++; } if (IsCurrentParamAFloatOrDouble()) { itr_float_and_doubles_++; @@ -227,63 +267,73 @@ void JniCallingConvention::Next() { if (IsCurrentParamAReference()) { itr_refs_++; } + // This default/fallthrough case also covers the extra JNIEnv* argument, + // as well as any other single-slot primitives. itr_args_++; itr_slots_++; } bool JniCallingConvention::IsCurrentParamAReference() { - switch (itr_args_) { - case kJniEnv: - return false; // JNIEnv* - case kObjectOrClass: - return true; // jobject or jclass - default: { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - return IsParamAReference(arg_pos); - } + bool return_value; + if (SwitchExtraJniArguments(itr_args_, + false, // JNIEnv* + true, // jobject or jclass + /* out parameters */ + &return_value)) { + return return_value; + } else { + int arg_pos = GetIteratorPositionWithinShorty(); + return IsParamAReference(arg_pos); } } + bool JniCallingConvention::IsCurrentParamJniEnv() { + if (UNLIKELY(!HasJniEnv())) { + return false; + } return (itr_args_ == kJniEnv); } bool JniCallingConvention::IsCurrentParamAFloatOrDouble() { - switch (itr_args_) { - case kJniEnv: - return false; // JNIEnv* - case kObjectOrClass: - return false; // jobject or jclass - default: { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - return IsParamAFloatOrDouble(arg_pos); - } + bool return_value; + if (SwitchExtraJniArguments(itr_args_, + false, // jnienv* + false, // jobject or jclass + /* out parameters */ + &return_value)) { + return return_value; + } else { + int arg_pos = GetIteratorPositionWithinShorty(); + return IsParamAFloatOrDouble(arg_pos); } } bool JniCallingConvention::IsCurrentParamADouble() { - switch (itr_args_) { - case kJniEnv: - return false; // JNIEnv* - case kObjectOrClass: - return false; // jobject or jclass - default: { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - return IsParamADouble(arg_pos); - } + bool return_value; + if (SwitchExtraJniArguments(itr_args_, + false, // jnienv* + false, // jobject or jclass + /* out parameters */ + &return_value)) { + return return_value; + } else { + int arg_pos = GetIteratorPositionWithinShorty(); + return IsParamADouble(arg_pos); } } bool JniCallingConvention::IsCurrentParamALong() { - switch (itr_args_) { - case kJniEnv: - return false; // JNIEnv* - case kObjectOrClass: - return false; // jobject or jclass - default: { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); - return IsParamALong(arg_pos); - } + bool return_value; + if (SwitchExtraJniArguments(itr_args_, + false, // jnienv* + false, // jobject or jclass + /* out parameters */ + &return_value)) { + return return_value; + } else { + int arg_pos = GetIteratorPositionWithinShorty(); + return IsParamALong(arg_pos); } } @@ -297,19 +347,93 @@ FrameOffset JniCallingConvention::CurrentParamHandleScopeEntryOffset() { return FrameOffset(result); } -size_t JniCallingConvention::CurrentParamSize() { - if (itr_args_ <= kObjectOrClass) { +size_t JniCallingConvention::CurrentParamSize() const { + if (IsCurrentArgExtraForJni()) { return static_cast<size_t>(frame_pointer_size_); // JNIEnv or jobject/jclass } else { - int arg_pos = itr_args_ - NumberOfExtraArgumentsForJni(); + int arg_pos = GetIteratorPositionWithinShorty(); return ParamSize(arg_pos); } } -size_t JniCallingConvention::NumberOfExtraArgumentsForJni() { - // The first argument is the JNIEnv*. - // Static methods have an extra argument which is the jclass. - return IsStatic() ? 2 : 1; +size_t JniCallingConvention::NumberOfExtraArgumentsForJni() const { + if (LIKELY(HasExtraArgumentsForJni())) { + // The first argument is the JNIEnv*. + // Static methods have an extra argument which is the jclass. + return IsStatic() ? 2 : 1; + } else { + // Critical natives exclude the JNIEnv and the jclass/this parameters. + return 0; + } +} + +bool JniCallingConvention::HasHandleScope() const { + // Exclude HandleScope for @CriticalNative methods for optimization speed. + return is_critical_native_ == false; +} + +bool JniCallingConvention::HasLocalReferenceSegmentState() const { + // Exclude local reference segment states for @CriticalNative methods for optimization speed. + return is_critical_native_ == false; +} + +bool JniCallingConvention::HasJniEnv() const { + // Exclude "JNIEnv*" parameter for @CriticalNative methods. + return HasExtraArgumentsForJni(); +} + +bool JniCallingConvention::HasSelfClass() const { + if (!IsStatic()) { + // Virtual functions: There is never an implicit jclass parameter. + return false; + } else { + // Static functions: There is an implicit jclass parameter unless it's @CriticalNative. + return HasExtraArgumentsForJni(); + } +} + +bool JniCallingConvention::HasExtraArgumentsForJni() const { + // @CriticalNative jni implementations exclude both JNIEnv* and the jclass/jobject parameters. + return is_critical_native_ == false; } +unsigned int JniCallingConvention::GetIteratorPositionWithinShorty() const { + // We need to subtract out the extra JNI arguments if we want to use this iterator position + // with the inherited CallingConvention member functions, which rely on scanning the shorty. + // Note that our shorty does *not* include the JNIEnv, jclass/jobject parameters. + DCHECK_GE(itr_args_, NumberOfExtraArgumentsForJni()); + return itr_args_ - NumberOfExtraArgumentsForJni(); +} + +bool JniCallingConvention::IsCurrentArgExtraForJni() const { + if (UNLIKELY(!HasExtraArgumentsForJni())) { + return false; // If there are no extra args, we can never be an extra. + } + // Only parameters kJniEnv and kObjectOrClass are considered extra. + return itr_args_ <= kObjectOrClass; +} + +bool JniCallingConvention::SwitchExtraJniArguments(size_t switch_value, + bool case_jni_env, + bool case_object_or_class, + /* out parameters */ + bool* return_value) const { + DCHECK(return_value != nullptr); + if (UNLIKELY(!HasExtraArgumentsForJni())) { + return false; + } + + switch (switch_value) { + case kJniEnv: + *return_value = case_jni_env; + return true; + case kObjectOrClass: + *return_value = case_object_or_class; + return true; + default: + return false; + } +} + + } // namespace art diff --git a/compiler/jni/quick/calling_convention.h b/compiler/jni/quick/calling_convention.h index 995fa51d44..3d89146250 100644 --- a/compiler/jni/quick/calling_convention.h +++ b/compiler/jni/quick/calling_convention.h @@ -161,6 +161,12 @@ class CallingConvention : public DeletableArenaObject<kArenaAllocCallingConventi size_t NumArgs() const { return num_args_; } + // Implicit argument count: 1 for instance functions, 0 for static functions. + // (The implicit argument is only relevant to the shorty, i.e. + // the 0th arg is not in the shorty if it's implicit). + size_t NumImplicitArgs() const { + return IsStatic() ? 0 : 1; + } size_t NumLongOrDoubleArgs() const { return num_long_or_double_args_; } @@ -281,6 +287,7 @@ class JniCallingConvention : public CallingConvention { static std::unique_ptr<JniCallingConvention> Create(ArenaAllocator* arena, bool is_static, bool is_synchronized, + bool is_critical_native, const char* shorty, InstructionSet instruction_set); @@ -288,7 +295,8 @@ class JniCallingConvention : public CallingConvention { // always at the bottom of a frame, but this doesn't work for outgoing // native args). Includes alignment. virtual size_t FrameSize() = 0; - // Size of outgoing arguments, including alignment + // Size of outgoing arguments (stack portion), including alignment. + // -- Arguments that are passed via registers are excluded from this size. virtual size_t OutArgSize() = 0; // Number of references in stack indirect reference table size_t ReferenceCount() const; @@ -319,8 +327,11 @@ class JniCallingConvention : public CallingConvention { bool IsCurrentParamAFloatOrDouble(); bool IsCurrentParamADouble(); bool IsCurrentParamALong(); + bool IsCurrentParamALongOrDouble() { + return IsCurrentParamALong() || IsCurrentParamADouble(); + } bool IsCurrentParamJniEnv(); - size_t CurrentParamSize(); + size_t CurrentParamSize() const; virtual bool IsCurrentParamInRegister() = 0; virtual bool IsCurrentParamOnStack() = 0; virtual ManagedRegister CurrentParamRegister() = 0; @@ -359,18 +370,62 @@ class JniCallingConvention : public CallingConvention { kObjectOrClass = 1 }; + // TODO: remove this constructor once all are changed to the below one. JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty, PointerSize frame_pointer_size) - : CallingConvention(is_static, is_synchronized, shorty, frame_pointer_size) {} + : CallingConvention(is_static, is_synchronized, shorty, frame_pointer_size), + is_critical_native_(false) {} + + JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, + const char* shorty, + PointerSize frame_pointer_size) + : CallingConvention(is_static, is_synchronized, shorty, frame_pointer_size), + is_critical_native_(is_critical_native) {} // Number of stack slots for outgoing arguments, above which the handle scope is // located virtual size_t NumberOfOutgoingStackArgs() = 0; protected: - size_t NumberOfExtraArgumentsForJni(); + size_t NumberOfExtraArgumentsForJni() const; + + // Does the transition have a StackHandleScope? + bool HasHandleScope() const; + // Does the transition have a local reference segment state? + bool HasLocalReferenceSegmentState() const; + // Has a JNIEnv* parameter implicitly? + bool HasJniEnv() const; + // Has a 'jclass' parameter implicitly? + bool HasSelfClass() const; + + // Are there extra JNI arguments (JNIEnv* and maybe jclass)? + bool HasExtraArgumentsForJni() const; + + // Returns the position of itr_args_, fixed up by removing the offset of extra JNI arguments. + unsigned int GetIteratorPositionWithinShorty() const; + + // Is the current argument (at the iterator) an extra argument for JNI? + bool IsCurrentArgExtraForJni() const; + + const bool is_critical_native_; + + private: + // Shorthand for switching on the switch value but only IF there are extra JNI arguments. + // + // Puts the case value into return_value. + // * (switch_value == kJniEnv) => case_jni_env + // * (switch_value == kObjectOrClass) => case_object_or_class + // + // Returns false otherwise (or if there are no extra JNI arguments). + bool SwitchExtraJniArguments(size_t switch_value, + bool case_jni_env, + bool case_object_or_class, + /* out parameters */ + bool* return_value) const; }; } // namespace art diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc index d092c3f1f6..7e58d789d0 100644 --- a/compiler/jni/quick/jni_compiler.cc +++ b/compiler/jni/quick/jni_compiler.cc @@ -90,8 +90,10 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, const InstructionSetFeatures* instruction_set_features = driver->GetInstructionSetFeatures(); // i.e. if the method was annotated with @FastNative - const bool is_fast_native = - (static_cast<uint32_t>(optimization_flags) & Compiler::kFastNative) != 0; + const bool is_fast_native = (optimization_flags == Compiler::kFastNative); + + // i.e. if the method was annotated with @CriticalNative + bool is_critical_native = (optimization_flags == Compiler::kCriticalNative); VLOG(jni) << "JniCompile: Method :: " << art::PrettyMethod(method_idx, dex_file, /* with signature */ true) @@ -102,12 +104,50 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, << art::PrettyMethod(method_idx, dex_file, /* with signature */ true); } + if (UNLIKELY(is_critical_native)) { + VLOG(jni) << "JniCompile: Critical native method detected :: " + << art::PrettyMethod(method_idx, dex_file, /* with signature */ true); + } + + if (kIsDebugBuild) { + // Don't allow both @FastNative and @CriticalNative. They are mutually exclusive. + if (UNLIKELY(is_fast_native && is_critical_native)) { + LOG(FATAL) << "JniCompile: Method cannot be both @CriticalNative and @FastNative" + << art::PrettyMethod(method_idx, dex_file, /* with_signature */ true); + } + + // @CriticalNative - extra checks: + // -- Don't allow virtual criticals + // -- Don't allow synchronized criticals + // -- Don't allow any objects as parameter or return value + if (UNLIKELY(is_critical_native)) { + CHECK(is_static) + << "@CriticalNative functions cannot be virtual since that would" + << "require passing a reference parameter (this), which is illegal " + << art::PrettyMethod(method_idx, dex_file, /* with_signature */ true); + CHECK(!is_synchronized) + << "@CriticalNative functions cannot be synchronized since that would" + << "require passing a (class and/or this) reference parameter, which is illegal " + << art::PrettyMethod(method_idx, dex_file, /* with_signature */ true); + for (size_t i = 0; i < strlen(shorty); ++i) { + CHECK_NE(Primitive::kPrimNot, Primitive::GetType(shorty[i])) + << "@CriticalNative methods' shorty types must not have illegal references " + << art::PrettyMethod(method_idx, dex_file, /* with_signature */ true); + } + } + } + ArenaPool pool; ArenaAllocator arena(&pool); // Calling conventions used to iterate over parameters to method - std::unique_ptr<JniCallingConvention> main_jni_conv( - JniCallingConvention::Create(&arena, is_static, is_synchronized, shorty, instruction_set)); + std::unique_ptr<JniCallingConvention> main_jni_conv = + JniCallingConvention::Create(&arena, + is_static, + is_synchronized, + is_critical_native, + shorty, + instruction_set); bool reference_return = main_jni_conv->IsReturnAReference(); std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv( @@ -127,8 +167,13 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, jni_end_shorty = "V"; } - std::unique_ptr<JniCallingConvention> end_jni_conv(JniCallingConvention::Create( - &arena, is_static, is_synchronized, jni_end_shorty, instruction_set)); + std::unique_ptr<JniCallingConvention> end_jni_conv( + JniCallingConvention::Create(&arena, + is_static, + is_synchronized, + is_critical_native, + jni_end_shorty, + instruction_set)); // Assembler that holds generated instructions std::unique_ptr<JNIMacroAssembler<kPointerSize>> jni_asm = @@ -141,75 +186,89 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, const Offset monitor_enter(OFFSETOF_MEMBER(JNINativeInterface, MonitorEnter)); const Offset monitor_exit(OFFSETOF_MEMBER(JNINativeInterface, MonitorExit)); - // 1. Build the frame saving all callee saves - const size_t frame_size(main_jni_conv->FrameSize()); + // 1. Build the frame saving all callee saves, Method*, and PC return address. + const size_t frame_size(main_jni_conv->FrameSize()); // Excludes outgoing args. ArrayRef<const ManagedRegister> callee_save_regs = main_jni_conv->CalleeSaveRegisters(); __ BuildFrame(frame_size, mr_conv->MethodRegister(), callee_save_regs, mr_conv->EntrySpills()); DCHECK_EQ(jni_asm->cfi().GetCurrentCFAOffset(), static_cast<int>(frame_size)); - // 2. Set up the HandleScope - mr_conv->ResetIterator(FrameOffset(frame_size)); - main_jni_conv->ResetIterator(FrameOffset(0)); - __ StoreImmediateToFrame(main_jni_conv->HandleScopeNumRefsOffset(), - main_jni_conv->ReferenceCount(), - mr_conv->InterproceduralScratchRegister()); - - __ CopyRawPtrFromThread(main_jni_conv->HandleScopeLinkOffset(), - Thread::TopHandleScopeOffset<kPointerSize>(), - mr_conv->InterproceduralScratchRegister()); - __ StoreStackOffsetToThread(Thread::TopHandleScopeOffset<kPointerSize>(), - main_jni_conv->HandleScopeOffset(), - mr_conv->InterproceduralScratchRegister()); - - // 3. Place incoming reference arguments into handle scope - main_jni_conv->Next(); // Skip JNIEnv* - // 3.5. Create Class argument for static methods out of passed method - if (is_static) { - FrameOffset handle_scope_offset = main_jni_conv->CurrentParamHandleScopeEntryOffset(); - // Check handle scope offset is within frame - CHECK_LT(handle_scope_offset.Uint32Value(), frame_size); - // Note this LoadRef() doesn't need heap unpoisoning since it's from the ArtMethod. - // Note this LoadRef() does not include read barrier. It will be handled below. - __ LoadRef(main_jni_conv->InterproceduralScratchRegister(), - mr_conv->MethodRegister(), ArtMethod::DeclaringClassOffset(), false); - __ VerifyObject(main_jni_conv->InterproceduralScratchRegister(), false); - __ StoreRef(handle_scope_offset, main_jni_conv->InterproceduralScratchRegister()); - main_jni_conv->Next(); // in handle scope so move to next argument - } - while (mr_conv->HasNext()) { - CHECK(main_jni_conv->HasNext()); - bool ref_param = main_jni_conv->IsCurrentParamAReference(); - CHECK(!ref_param || mr_conv->IsCurrentParamAReference()); - // References need placing in handle scope and the entry value passing - if (ref_param) { - // Compute handle scope entry, note null is placed in the handle scope but its boxed value - // must be null. + if (LIKELY(!is_critical_native)) { + // NOTE: @CriticalNative methods don't have a HandleScope + // because they can't have any reference parameters or return values. + + // 2. Set up the HandleScope + mr_conv->ResetIterator(FrameOffset(frame_size)); + main_jni_conv->ResetIterator(FrameOffset(0)); + __ StoreImmediateToFrame(main_jni_conv->HandleScopeNumRefsOffset(), + main_jni_conv->ReferenceCount(), + mr_conv->InterproceduralScratchRegister()); + + __ CopyRawPtrFromThread(main_jni_conv->HandleScopeLinkOffset(), + Thread::TopHandleScopeOffset<kPointerSize>(), + mr_conv->InterproceduralScratchRegister()); + __ StoreStackOffsetToThread(Thread::TopHandleScopeOffset<kPointerSize>(), + main_jni_conv->HandleScopeOffset(), + mr_conv->InterproceduralScratchRegister()); + + // 3. Place incoming reference arguments into handle scope + main_jni_conv->Next(); // Skip JNIEnv* + // 3.5. Create Class argument for static methods out of passed method + if (is_static) { FrameOffset handle_scope_offset = main_jni_conv->CurrentParamHandleScopeEntryOffset(); - // Check handle scope offset is within frame and doesn't run into the saved segment state. + // Check handle scope offset is within frame CHECK_LT(handle_scope_offset.Uint32Value(), frame_size); - CHECK_NE(handle_scope_offset.Uint32Value(), - main_jni_conv->SavedLocalReferenceCookieOffset().Uint32Value()); - bool input_in_reg = mr_conv->IsCurrentParamInRegister(); - bool input_on_stack = mr_conv->IsCurrentParamOnStack(); - CHECK(input_in_reg || input_on_stack); - - if (input_in_reg) { - ManagedRegister in_reg = mr_conv->CurrentParamRegister(); - __ VerifyObject(in_reg, mr_conv->IsCurrentArgPossiblyNull()); - __ StoreRef(handle_scope_offset, in_reg); - } else if (input_on_stack) { - FrameOffset in_off = mr_conv->CurrentParamStackOffset(); - __ VerifyObject(in_off, mr_conv->IsCurrentArgPossiblyNull()); - __ CopyRef(handle_scope_offset, in_off, - mr_conv->InterproceduralScratchRegister()); + // Note this LoadRef() doesn't need heap unpoisoning since it's from the ArtMethod. + // Note this LoadRef() does not include read barrier. It will be handled below. + // + // scratchRegister = *method[DeclaringClassOffset()]; + __ LoadRef(main_jni_conv->InterproceduralScratchRegister(), + mr_conv->MethodRegister(), ArtMethod::DeclaringClassOffset(), false); + __ VerifyObject(main_jni_conv->InterproceduralScratchRegister(), false); + // *handleScopeOffset = scratchRegister + __ StoreRef(handle_scope_offset, main_jni_conv->InterproceduralScratchRegister()); + main_jni_conv->Next(); // in handle scope so move to next argument + } + // Place every reference into the handle scope (ignore other parameters). + while (mr_conv->HasNext()) { + CHECK(main_jni_conv->HasNext()); + bool ref_param = main_jni_conv->IsCurrentParamAReference(); + CHECK(!ref_param || mr_conv->IsCurrentParamAReference()); + // References need placing in handle scope and the entry value passing + if (ref_param) { + // Compute handle scope entry, note null is placed in the handle scope but its boxed value + // must be null. + FrameOffset handle_scope_offset = main_jni_conv->CurrentParamHandleScopeEntryOffset(); + // Check handle scope offset is within frame and doesn't run into the saved segment state. + CHECK_LT(handle_scope_offset.Uint32Value(), frame_size); + CHECK_NE(handle_scope_offset.Uint32Value(), + main_jni_conv->SavedLocalReferenceCookieOffset().Uint32Value()); + bool input_in_reg = mr_conv->IsCurrentParamInRegister(); + bool input_on_stack = mr_conv->IsCurrentParamOnStack(); + CHECK(input_in_reg || input_on_stack); + + if (input_in_reg) { + ManagedRegister in_reg = mr_conv->CurrentParamRegister(); + __ VerifyObject(in_reg, mr_conv->IsCurrentArgPossiblyNull()); + __ StoreRef(handle_scope_offset, in_reg); + } else if (input_on_stack) { + FrameOffset in_off = mr_conv->CurrentParamStackOffset(); + __ VerifyObject(in_off, mr_conv->IsCurrentArgPossiblyNull()); + __ CopyRef(handle_scope_offset, in_off, + mr_conv->InterproceduralScratchRegister()); + } } + mr_conv->Next(); + main_jni_conv->Next(); } - mr_conv->Next(); - main_jni_conv->Next(); - } - // 4. Write out the end of the quick frames. - __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>()); + // 4. Write out the end of the quick frames. + __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>()); + + // NOTE: @CriticalNative does not need to store the stack pointer to the thread + // because garbage collections are disabled within the execution of a + // @CriticalNative method. + // (TODO: We could probably disable it for @FastNative too). + } // if (!is_critical_native) // 5. Move frame down to allow space for out going args. const size_t main_out_arg_size = main_jni_conv->OutArgSize(); @@ -218,7 +277,9 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, // Call the read barrier for the declaring class loaded from the method for a static call. // Note that we always have outgoing param space available for at least two params. - if (kUseReadBarrier && is_static) { + if (kUseReadBarrier && is_static && !is_critical_native) { + // XX: Why is this necessary only for the jclass? Why not for every single object ref? + // Skip this for @CriticalNative because we didn't build a HandleScope to begin with. ThreadOffset<kPointerSize> read_barrier = QUICK_ENTRYPOINT_OFFSET(kPointerSize, pReadBarrierJni); main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); @@ -255,46 +316,56 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, // can occur. The result is the saved JNI local state that is restored by the exit call. We // abuse the JNI calling convention here, that is guaranteed to support passing 2 pointer // arguments. - ThreadOffset<kPointerSize> jni_start = - is_synchronized - ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodStartSynchronized) - : (is_fast_native - ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodFastStart) - : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodStart)); + FrameOffset locked_object_handle_scope_offset(0xBEEFDEAD); + if (LIKELY(!is_critical_native)) { + // Skip this for @CriticalNative methods. They do not call JniMethodStart. + ThreadOffset<kPointerSize> jni_start = + is_synchronized + ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodStartSynchronized) + : (is_fast_native + ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodFastStart) + : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodStart)); - main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); - FrameOffset locked_object_handle_scope_offset(0); - if (is_synchronized) { - // Pass object for locking. - main_jni_conv->Next(); // Skip JNIEnv. - locked_object_handle_scope_offset = main_jni_conv->CurrentParamHandleScopeEntryOffset(); main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); - if (main_jni_conv->IsCurrentParamOnStack()) { - FrameOffset out_off = main_jni_conv->CurrentParamStackOffset(); - __ CreateHandleScopeEntry(out_off, locked_object_handle_scope_offset, - mr_conv->InterproceduralScratchRegister(), false); + locked_object_handle_scope_offset = FrameOffset(0); + if (is_synchronized) { + // Pass object for locking. + main_jni_conv->Next(); // Skip JNIEnv. + locked_object_handle_scope_offset = main_jni_conv->CurrentParamHandleScopeEntryOffset(); + main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); + if (main_jni_conv->IsCurrentParamOnStack()) { + FrameOffset out_off = main_jni_conv->CurrentParamStackOffset(); + __ CreateHandleScopeEntry(out_off, locked_object_handle_scope_offset, + mr_conv->InterproceduralScratchRegister(), false); + } else { + ManagedRegister out_reg = main_jni_conv->CurrentParamRegister(); + __ CreateHandleScopeEntry(out_reg, locked_object_handle_scope_offset, + ManagedRegister::NoRegister(), false); + } + main_jni_conv->Next(); + } + if (main_jni_conv->IsCurrentParamInRegister()) { + __ GetCurrentThread(main_jni_conv->CurrentParamRegister()); + __ Call(main_jni_conv->CurrentParamRegister(), + Offset(jni_start), + main_jni_conv->InterproceduralScratchRegister()); } else { - ManagedRegister out_reg = main_jni_conv->CurrentParamRegister(); - __ CreateHandleScopeEntry(out_reg, locked_object_handle_scope_offset, - ManagedRegister::NoRegister(), false); + __ GetCurrentThread(main_jni_conv->CurrentParamStackOffset(), + main_jni_conv->InterproceduralScratchRegister()); + __ CallFromThread(jni_start, main_jni_conv->InterproceduralScratchRegister()); + } + if (is_synchronized) { // Check for exceptions from monitor enter. + __ ExceptionPoll(main_jni_conv->InterproceduralScratchRegister(), main_out_arg_size); } - main_jni_conv->Next(); - } - if (main_jni_conv->IsCurrentParamInRegister()) { - __ GetCurrentThread(main_jni_conv->CurrentParamRegister()); - __ Call(main_jni_conv->CurrentParamRegister(), - Offset(jni_start), - main_jni_conv->InterproceduralScratchRegister()); - } else { - __ GetCurrentThread(main_jni_conv->CurrentParamStackOffset(), - main_jni_conv->InterproceduralScratchRegister()); - __ CallFromThread(jni_start, main_jni_conv->InterproceduralScratchRegister()); } - if (is_synchronized) { // Check for exceptions from monitor enter. - __ ExceptionPoll(main_jni_conv->InterproceduralScratchRegister(), main_out_arg_size); + + // Store into stack_frame[saved_cookie_offset] the return value of JniMethodStart. + FrameOffset saved_cookie_offset( + FrameOffset(0xDEADBEEFu)); // @CriticalNative - use obviously bad value for debugging + if (LIKELY(!is_critical_native)) { + saved_cookie_offset = main_jni_conv->SavedLocalReferenceCookieOffset(); + __ Store(saved_cookie_offset, main_jni_conv->IntReturnRegister(), 4 /* sizeof cookie */); } - FrameOffset saved_cookie_offset = main_jni_conv->SavedLocalReferenceCookieOffset(); - __ Store(saved_cookie_offset, main_jni_conv->IntReturnRegister(), 4); // 7. Iterate over arguments placing values from managed calling convention in // to the convention required for a native call (shuffling). For references @@ -315,9 +386,13 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, for (uint32_t i = 0; i < args_count; ++i) { mr_conv->ResetIterator(FrameOffset(frame_size + main_out_arg_size)); main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); - main_jni_conv->Next(); // Skip JNIEnv*. - if (is_static) { - main_jni_conv->Next(); // Skip Class for now. + + // Skip the extra JNI parameters for now. + if (LIKELY(!is_critical_native)) { + main_jni_conv->Next(); // Skip JNIEnv*. + if (is_static) { + main_jni_conv->Next(); // Skip Class for now. + } } // Skip to the argument we're interested in. for (uint32_t j = 0; j < args_count - i - 1; ++j) { @@ -326,7 +401,7 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, } CopyParameter(jni_asm.get(), mr_conv.get(), main_jni_conv.get(), frame_size, main_out_arg_size); } - if (is_static) { + if (is_static && !is_critical_native) { // Create argument for Class mr_conv->ResetIterator(FrameOffset(frame_size + main_out_arg_size)); main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); @@ -344,24 +419,30 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, } } - // 8. Create 1st argument, the JNI environment ptr. + // Set the iterator back to the incoming Method*. main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size)); - // Register that will hold local indirect reference table - if (main_jni_conv->IsCurrentParamInRegister()) { - ManagedRegister jni_env = main_jni_conv->CurrentParamRegister(); - DCHECK(!jni_env.Equals(main_jni_conv->InterproceduralScratchRegister())); - __ LoadRawPtrFromThread(jni_env, Thread::JniEnvOffset<kPointerSize>()); - } else { - FrameOffset jni_env = main_jni_conv->CurrentParamStackOffset(); - __ CopyRawPtrFromThread(jni_env, - Thread::JniEnvOffset<kPointerSize>(), - main_jni_conv->InterproceduralScratchRegister()); + if (LIKELY(!is_critical_native)) { + // 8. Create 1st argument, the JNI environment ptr. + // Register that will hold local indirect reference table + if (main_jni_conv->IsCurrentParamInRegister()) { + ManagedRegister jni_env = main_jni_conv->CurrentParamRegister(); + DCHECK(!jni_env.Equals(main_jni_conv->InterproceduralScratchRegister())); + __ LoadRawPtrFromThread(jni_env, Thread::JniEnvOffset<kPointerSize>()); + } else { + FrameOffset jni_env = main_jni_conv->CurrentParamStackOffset(); + __ CopyRawPtrFromThread(jni_env, + Thread::JniEnvOffset<kPointerSize>(), + main_jni_conv->InterproceduralScratchRegister()); + } } // 9. Plant call to native code associated with method. - MemberOffset jni_entrypoint_offset = ArtMethod::EntryPointFromJniOffset( - InstructionSetPointerSize(instruction_set)); - __ Call(main_jni_conv->MethodStackOffset(), jni_entrypoint_offset, + MemberOffset jni_entrypoint_offset = + ArtMethod::EntryPointFromJniOffset(InstructionSetPointerSize(instruction_set)); + // FIXME: Not sure if MethodStackOffset will work here. What does it even do? + __ Call(main_jni_conv->MethodStackOffset(), + jni_entrypoint_offset, + // XX: Why not the jni conv scratch register? mr_conv->InterproceduralScratchRegister()); // 10. Fix differences in result widths. @@ -377,20 +458,45 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, } } - // 11. Save return value + // 11. Process return value FrameOffset return_save_location = main_jni_conv->ReturnValueSaveLocation(); if (main_jni_conv->SizeOfReturnValue() != 0 && !reference_return) { - if ((instruction_set == kMips || instruction_set == kMips64) && - main_jni_conv->GetReturnType() == Primitive::kPrimDouble && - return_save_location.Uint32Value() % 8 != 0) { - // Ensure doubles are 8-byte aligned for MIPS - return_save_location = FrameOffset(return_save_location.Uint32Value() - + static_cast<size_t>(kMipsPointerSize)); + if (LIKELY(!is_critical_native)) { + // For normal JNI, store the return value on the stack because the call to + // JniMethodEnd will clobber the return value. It will be restored in (13). + if ((instruction_set == kMips || instruction_set == kMips64) && + main_jni_conv->GetReturnType() == Primitive::kPrimDouble && + return_save_location.Uint32Value() % 8 != 0) { + // Ensure doubles are 8-byte aligned for MIPS + return_save_location = FrameOffset(return_save_location.Uint32Value() + + static_cast<size_t>(kMipsPointerSize)); + // TODO: refactor this into the JniCallingConvention code + // as a return value alignment requirement. + } + CHECK_LT(return_save_location.Uint32Value(), frame_size + main_out_arg_size); + __ Store(return_save_location, + main_jni_conv->ReturnRegister(), + main_jni_conv->SizeOfReturnValue()); + } else { + // For @CriticalNative only, + // move the JNI return register into the managed return register (if they don't match). + ManagedRegister jni_return_reg = main_jni_conv->ReturnRegister(); + ManagedRegister mr_return_reg = mr_conv->ReturnRegister(); + + // Check if the JNI return register matches the managed return register. + // If they differ, only then do we have to do anything about it. + // Otherwise the return value is already in the right place when we return. + if (!jni_return_reg.Equals(mr_return_reg)) { + // This is typically only necessary on ARM32 due to native being softfloat + // while managed is hardfloat. + // -- For example VMOV {r0, r1} -> D0; VMOV r0 -> S0. + __ Move(mr_return_reg, jni_return_reg, main_jni_conv->SizeOfReturnValue()); + } else if (jni_return_reg.IsNoRegister() && mr_return_reg.IsNoRegister()) { + // Sanity check: If the return value is passed on the stack for some reason, + // then make sure the size matches. + CHECK_EQ(main_jni_conv->SizeOfReturnValue(), mr_conv->SizeOfReturnValue()); + } } - CHECK_LT(return_save_location.Uint32Value(), frame_size + main_out_arg_size); - __ Store(return_save_location, - main_jni_conv->ReturnRegister(), - main_jni_conv->SizeOfReturnValue()); } // Increase frame size for out args if needed by the end_jni_conv. @@ -398,6 +504,8 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, if (end_out_arg_size > current_out_arg_size) { size_t out_arg_size_diff = end_out_arg_size - current_out_arg_size; current_out_arg_size = end_out_arg_size; + // TODO: This is redundant for @CriticalNative but we need to + // conditionally do __DecreaseFrameSize below. __ IncreaseFrameSize(out_arg_size_diff); saved_cookie_offset = FrameOffset(saved_cookie_offset.SizeValue() + out_arg_size_diff); locked_object_handle_scope_offset = @@ -407,65 +515,71 @@ static CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, // thread. end_jni_conv->ResetIterator(FrameOffset(end_out_arg_size)); - ThreadOffset<kPointerSize> jni_end(-1); - if (reference_return) { - // Pass result. - jni_end = is_synchronized - ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndWithReferenceSynchronized) - : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndWithReference); - SetNativeParameter(jni_asm.get(), end_jni_conv.get(), end_jni_conv->ReturnRegister()); - end_jni_conv->Next(); - } else { - jni_end = is_synchronized - ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndSynchronized) - : (is_fast_native - ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodFastEnd) - : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEnd)); - } - // Pass saved local reference state. - if (end_jni_conv->IsCurrentParamOnStack()) { - FrameOffset out_off = end_jni_conv->CurrentParamStackOffset(); - __ Copy(out_off, saved_cookie_offset, end_jni_conv->InterproceduralScratchRegister(), 4); - } else { - ManagedRegister out_reg = end_jni_conv->CurrentParamRegister(); - __ Load(out_reg, saved_cookie_offset, 4); - } - end_jni_conv->Next(); - if (is_synchronized) { - // Pass object for unlocking. + if (LIKELY(!is_critical_native)) { + // 12. Call JniMethodEnd + ThreadOffset<kPointerSize> jni_end(-1); + if (reference_return) { + // Pass result. + jni_end = is_synchronized + ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndWithReferenceSynchronized) + : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndWithReference); + SetNativeParameter(jni_asm.get(), end_jni_conv.get(), end_jni_conv->ReturnRegister()); + end_jni_conv->Next(); + } else { + jni_end = is_synchronized + ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEndSynchronized) + : (is_fast_native + ? QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodFastEnd) + : QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEnd)); + } + // Pass saved local reference state. if (end_jni_conv->IsCurrentParamOnStack()) { FrameOffset out_off = end_jni_conv->CurrentParamStackOffset(); - __ CreateHandleScopeEntry(out_off, locked_object_handle_scope_offset, - end_jni_conv->InterproceduralScratchRegister(), - false); + __ Copy(out_off, saved_cookie_offset, end_jni_conv->InterproceduralScratchRegister(), 4); } else { ManagedRegister out_reg = end_jni_conv->CurrentParamRegister(); - __ CreateHandleScopeEntry(out_reg, locked_object_handle_scope_offset, - ManagedRegister::NoRegister(), false); + __ Load(out_reg, saved_cookie_offset, 4); } end_jni_conv->Next(); - } - if (end_jni_conv->IsCurrentParamInRegister()) { - __ GetCurrentThread(end_jni_conv->CurrentParamRegister()); - __ Call(end_jni_conv->CurrentParamRegister(), - Offset(jni_end), - end_jni_conv->InterproceduralScratchRegister()); - } else { - __ GetCurrentThread(end_jni_conv->CurrentParamStackOffset(), - end_jni_conv->InterproceduralScratchRegister()); - __ CallFromThread(jni_end, end_jni_conv->InterproceduralScratchRegister()); - } + if (is_synchronized) { + // Pass object for unlocking. + if (end_jni_conv->IsCurrentParamOnStack()) { + FrameOffset out_off = end_jni_conv->CurrentParamStackOffset(); + __ CreateHandleScopeEntry(out_off, locked_object_handle_scope_offset, + end_jni_conv->InterproceduralScratchRegister(), + false); + } else { + ManagedRegister out_reg = end_jni_conv->CurrentParamRegister(); + __ CreateHandleScopeEntry(out_reg, locked_object_handle_scope_offset, + ManagedRegister::NoRegister(), false); + } + end_jni_conv->Next(); + } + if (end_jni_conv->IsCurrentParamInRegister()) { + __ GetCurrentThread(end_jni_conv->CurrentParamRegister()); + __ Call(end_jni_conv->CurrentParamRegister(), + Offset(jni_end), + end_jni_conv->InterproceduralScratchRegister()); + } else { + __ GetCurrentThread(end_jni_conv->CurrentParamStackOffset(), + end_jni_conv->InterproceduralScratchRegister()); + __ CallFromThread(jni_end, end_jni_conv->InterproceduralScratchRegister()); + } - // 13. Reload return value - if (main_jni_conv->SizeOfReturnValue() != 0 && !reference_return) { - __ Load(mr_conv->ReturnRegister(), return_save_location, mr_conv->SizeOfReturnValue()); - } + // 13. Reload return value + if (main_jni_conv->SizeOfReturnValue() != 0 && !reference_return) { + __ Load(mr_conv->ReturnRegister(), return_save_location, mr_conv->SizeOfReturnValue()); + // NIT: If it's @CriticalNative then we actually only need to do this IF + // the calling convention's native return register doesn't match the managed convention's + // return register. + } + } // if (!is_critical_native) // 14. Move frame up now we're done with the out arg space. __ DecreaseFrameSize(current_out_arg_size); // 15. Process pending exceptions from JNI call or monitor exit. - __ ExceptionPoll(main_jni_conv->InterproceduralScratchRegister(), 0); + __ ExceptionPoll(main_jni_conv->InterproceduralScratchRegister(), 0 /* stack_adjust */); // 16. Remove activation - need to restore callee save registers since the GC may have changed // them. @@ -497,7 +611,8 @@ template <PointerSize kPointerSize> static void CopyParameter(JNIMacroAssembler<kPointerSize>* jni_asm, ManagedRuntimeCallingConvention* mr_conv, JniCallingConvention* jni_conv, - size_t frame_size, size_t out_arg_size) { + size_t frame_size, + size_t out_arg_size) { bool input_in_reg = mr_conv->IsCurrentParamInRegister(); bool output_in_reg = jni_conv->IsCurrentParamInRegister(); FrameOffset handle_scope_offset(0); diff --git a/compiler/jni/quick/x86/calling_convention_x86.cc b/compiler/jni/quick/x86/calling_convention_x86.cc index 1d06f2685b..0bfcc3fb4d 100644 --- a/compiler/jni/quick/x86/calling_convention_x86.cc +++ b/compiler/jni/quick/x86/calling_convention_x86.cc @@ -24,6 +24,7 @@ namespace art { namespace x86 { static_assert(kX86PointerSize == PointerSize::k32, "Unexpected x86 pointer size"); +static_assert(kStackAlignment >= 16u, "IA-32 cdecl requires at least 16 byte stack alignment"); static constexpr ManagedRegister kCalleeSaveRegisters[] = { // Core registers. @@ -190,9 +191,15 @@ const ManagedRegisterEntrySpills& X86ManagedRuntimeCallingConvention::EntrySpill // JNI calling convention -X86JniCallingConvention::X86JniCallingConvention(bool is_static, bool is_synchronized, +X86JniCallingConvention::X86JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, const char* shorty) - : JniCallingConvention(is_static, is_synchronized, shorty, kX86PointerSize) { + : JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty, + kX86PointerSize) { } uint32_t X86JniCallingConvention::CoreSpillMask() const { @@ -204,13 +211,31 @@ uint32_t X86JniCallingConvention::FpSpillMask() const { } size_t X86JniCallingConvention::FrameSize() { - // Method*, return address and callee save area size, local reference segment state - size_t frame_data_size = static_cast<size_t>(kX86PointerSize) + - (2 + CalleeSaveRegisters().size()) * kFramePointerSize; - // References plus 2 words for HandleScope header - size_t handle_scope_size = HandleScope::SizeOf(kX86PointerSize, ReferenceCount()); + // Method*, PC return address and callee save area size, local reference segment state + const size_t method_ptr_size = static_cast<size_t>(kX86PointerSize); + const size_t pc_return_addr_size = kFramePointerSize; + const size_t callee_save_area_size = CalleeSaveRegisters().size() * kFramePointerSize; + size_t frame_data_size = method_ptr_size + pc_return_addr_size + callee_save_area_size; + + if (LIKELY(HasLocalReferenceSegmentState())) { // local ref. segment state + // Local reference segment state is sometimes excluded. + frame_data_size += kFramePointerSize; + } + + // References plus link_ (pointer) and number_of_references_ (uint32_t) for HandleScope header + const size_t handle_scope_size = HandleScope::SizeOf(kX86PointerSize, ReferenceCount()); + + size_t total_size = frame_data_size; + if (LIKELY(HasHandleScope())) { + // HandleScope is sometimes excluded. + total_size += handle_scope_size; // handle scope size + } + // Plus return value spill area size - return RoundUp(frame_data_size + handle_scope_size + SizeOfReturnValue(), kStackAlignment); + total_size += SizeOfReturnValue(); + + return RoundUp(total_size, kStackAlignment); + // TODO: Same thing as x64 except using different pointer size. Refactor? } size_t X86JniCallingConvention::OutArgSize() { @@ -239,11 +264,13 @@ FrameOffset X86JniCallingConvention::CurrentParamStackOffset() { } size_t X86JniCallingConvention::NumberOfOutgoingStackArgs() { - size_t static_args = IsStatic() ? 1 : 0; // count jclass + size_t static_args = HasSelfClass() ? 1 : 0; // count jclass // regular argument parameters and this size_t param_args = NumArgs() + NumLongOrDoubleArgs(); // count JNIEnv* and return pc (pushed after Method*) - size_t total_args = static_args + param_args + 2; + size_t internal_args = 1 /* return pc */ + (HasJniEnv() ? 1 : 0 /* jni env */); + // No register args. + size_t total_args = static_args + param_args + internal_args; return total_args; } diff --git a/compiler/jni/quick/x86/calling_convention_x86.h b/compiler/jni/quick/x86/calling_convention_x86.h index ff92fc9906..be83cdaad0 100644 --- a/compiler/jni/quick/x86/calling_convention_x86.h +++ b/compiler/jni/quick/x86/calling_convention_x86.h @@ -52,9 +52,13 @@ class X86ManagedRuntimeCallingConvention FINAL : public ManagedRuntimeCallingCon DISALLOW_COPY_AND_ASSIGN(X86ManagedRuntimeCallingConvention); }; +// Implements the x86 cdecl calling convention. class X86JniCallingConvention FINAL : public JniCallingConvention { public: - X86JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty); + X86JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, + const char* shorty); ~X86JniCallingConvention() OVERRIDE {} // Calling convention ManagedRegister ReturnRegister() OVERRIDE; diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc index cbf10bda49..8ca0ffe53e 100644 --- a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc +++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc @@ -25,8 +25,16 @@ namespace art { namespace x86_64 { constexpr size_t kFramePointerSize = static_cast<size_t>(PointerSize::k64); - static_assert(kX86_64PointerSize == PointerSize::k64, "Unexpected x86_64 pointer size"); +static_assert(kStackAlignment >= 16u, "System V AMD64 ABI requires at least 16 byte stack alignment"); + +// XMM0..XMM7 can be used to pass the first 8 floating args. The rest must go on the stack. +// -- Managed and JNI calling conventions. +constexpr size_t kMaxFloatOrDoubleRegisterArguments = 8u; +// Up to how many integer-like (pointers, objects, longs, int, short, bool, etc) args can be +// enregistered. The rest of the args must go on the stack. +// -- JNI calling convention only (Managed excludes RDI, so it's actually 5). +constexpr size_t kMaxIntLikeRegisterArguments = 6u; static constexpr ManagedRegister kCalleeSaveRegisters[] = { // Core registers. @@ -130,7 +138,7 @@ ManagedRegister X86_64ManagedRuntimeCallingConvention::CurrentParamRegister() { case 3: res = X86_64ManagedRegister::FromCpuRegister(R8); break; case 4: res = X86_64ManagedRegister::FromCpuRegister(R9); break; } - } else if (itr_float_and_doubles_ < 8) { + } else if (itr_float_and_doubles_ < kMaxFloatOrDoubleRegisterArguments) { // First eight float parameters are passed via XMM0..XMM7 res = X86_64ManagedRegister::FromXmmRegister( static_cast<FloatRegister>(XMM0 + itr_float_and_doubles_)); @@ -165,9 +173,15 @@ const ManagedRegisterEntrySpills& X86_64ManagedRuntimeCallingConvention::EntrySp // JNI calling convention -X86_64JniCallingConvention::X86_64JniCallingConvention(bool is_static, bool is_synchronized, +X86_64JniCallingConvention::X86_64JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, const char* shorty) - : JniCallingConvention(is_static, is_synchronized, shorty, kX86_64PointerSize) { + : JniCallingConvention(is_static, + is_synchronized, + is_critical_native, + shorty, + kX86_64PointerSize) { } uint32_t X86_64JniCallingConvention::CoreSpillMask() const { @@ -179,13 +193,30 @@ uint32_t X86_64JniCallingConvention::FpSpillMask() const { } size_t X86_64JniCallingConvention::FrameSize() { - // Method*, return address and callee save area size, local reference segment state - size_t frame_data_size = static_cast<size_t>(kX86_64PointerSize) + - (2 + CalleeSaveRegisters().size()) * kFramePointerSize; + // Method*, PC return address and callee save area size, local reference segment state + const size_t method_ptr_size = static_cast<size_t>(kX86_64PointerSize); + const size_t pc_return_addr_size = kFramePointerSize; + const size_t callee_save_area_size = CalleeSaveRegisters().size() * kFramePointerSize; + size_t frame_data_size = method_ptr_size + pc_return_addr_size + callee_save_area_size; + + if (LIKELY(HasLocalReferenceSegmentState())) { // local ref. segment state + // Local reference segment state is sometimes excluded. + frame_data_size += kFramePointerSize; + } + // References plus link_ (pointer) and number_of_references_ (uint32_t) for HandleScope header - size_t handle_scope_size = HandleScope::SizeOf(kX86_64PointerSize, ReferenceCount()); + const size_t handle_scope_size = HandleScope::SizeOf(kX86_64PointerSize, ReferenceCount()); + + size_t total_size = frame_data_size; + if (LIKELY(HasHandleScope())) { + // HandleScope is sometimes excluded. + total_size += handle_scope_size; // handle scope size + } + // Plus return value spill area size - return RoundUp(frame_data_size + handle_scope_size + SizeOfReturnValue(), kStackAlignment); + total_size += SizeOfReturnValue(); + + return RoundUp(total_size, kStackAlignment); } size_t X86_64JniCallingConvention::OutArgSize() { @@ -214,8 +245,9 @@ ManagedRegister X86_64JniCallingConvention::CurrentParamRegister() { case 3: res = X86_64ManagedRegister::FromCpuRegister(RCX); break; case 4: res = X86_64ManagedRegister::FromCpuRegister(R8); break; case 5: res = X86_64ManagedRegister::FromCpuRegister(R9); break; + static_assert(5u == kMaxIntLikeRegisterArguments - 1, "Missing case statement(s)"); } - } else if (itr_float_and_doubles_ < 8) { + } else if (itr_float_and_doubles_ < kMaxFloatOrDoubleRegisterArguments) { // First eight float parameters are passed via XMM0..XMM7 res = X86_64ManagedRegister::FromXmmRegister( static_cast<FloatRegister>(XMM0 + itr_float_and_doubles_)); @@ -224,24 +256,35 @@ ManagedRegister X86_64JniCallingConvention::CurrentParamRegister() { } FrameOffset X86_64JniCallingConvention::CurrentParamStackOffset() { - size_t offset = itr_args_ - - std::min(8U, itr_float_and_doubles_) // Float arguments passed through Xmm0..Xmm7 - - std::min(6U, itr_args_ - itr_float_and_doubles_); // Integer arguments passed through GPR - return FrameOffset(displacement_.Int32Value() - OutArgSize() + (offset * kFramePointerSize)); + CHECK(IsCurrentParamOnStack()); + size_t args_on_stack = itr_args_ + - std::min(kMaxFloatOrDoubleRegisterArguments, + static_cast<size_t>(itr_float_and_doubles_)) + // Float arguments passed through Xmm0..Xmm7 + - std::min(kMaxIntLikeRegisterArguments, + static_cast<size_t>(itr_args_ - itr_float_and_doubles_)); + // Integer arguments passed through GPR + size_t offset = displacement_.Int32Value() - OutArgSize() + (args_on_stack * kFramePointerSize); + CHECK_LT(offset, OutArgSize()); + return FrameOffset(offset); } +// TODO: Calling this "NumberArgs" is misleading. +// It's really more like NumberSlots (like itr_slots_) +// because doubles/longs get counted twice. size_t X86_64JniCallingConvention::NumberOfOutgoingStackArgs() { - size_t static_args = IsStatic() ? 1 : 0; // count jclass + size_t static_args = HasSelfClass() ? 1 : 0; // count jclass // regular argument parameters and this size_t param_args = NumArgs() + NumLongOrDoubleArgs(); // count JNIEnv* and return pc (pushed after Method*) - size_t total_args = static_args + param_args + 2; + size_t internal_args = 1 /* return pc */ + (HasJniEnv() ? 1 : 0 /* jni env */); + size_t total_args = static_args + param_args + internal_args; // Float arguments passed through Xmm0..Xmm7 // Other (integer) arguments passed through GPR (RDI, RSI, RDX, RCX, R8, R9) size_t total_stack_args = total_args - - std::min(8U, static_cast<unsigned int>(NumFloatOrDoubleArgs())) - - std::min(6U, static_cast<unsigned int>(NumArgs() - NumFloatOrDoubleArgs())); + - std::min(kMaxFloatOrDoubleRegisterArguments, static_cast<size_t>(NumFloatOrDoubleArgs())) + - std::min(kMaxIntLikeRegisterArguments, static_cast<size_t>(NumArgs() - NumFloatOrDoubleArgs())); return total_stack_args; } diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.h b/compiler/jni/quick/x86_64/calling_convention_x86_64.h index b98f5057e2..cdba334d81 100644 --- a/compiler/jni/quick/x86_64/calling_convention_x86_64.h +++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.h @@ -48,7 +48,10 @@ class X86_64ManagedRuntimeCallingConvention FINAL : public ManagedRuntimeCalling class X86_64JniCallingConvention FINAL : public JniCallingConvention { public: - X86_64JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty); + X86_64JniCallingConvention(bool is_static, + bool is_synchronized, + bool is_critical_native, + const char* shorty); ~X86_64JniCallingConvention() OVERRIDE {} // Calling convention ManagedRegister ReturnRegister() OVERRIDE; diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index b1e38113e5..78e9ca91b7 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -125,7 +125,8 @@ class OatTest : public CommonCompilerTest { /* profile_compilation_info */ nullptr)); } - bool WriteElf(File* file, + bool WriteElf(File* vdex_file, + File* oat_file, const std::vector<const DexFile*>& dex_files, SafeMap<std::string, std::string>& key_value_store, bool verify) { @@ -141,10 +142,11 @@ class OatTest : public CommonCompilerTest { return false; } } - return DoWriteElf(file, oat_writer, key_value_store, verify); + return DoWriteElf(vdex_file, oat_file, oat_writer, key_value_store, verify); } - bool WriteElf(File* file, + bool WriteElf(File* vdex_file, + File* oat_file, const std::vector<const char*>& dex_filenames, SafeMap<std::string, std::string>& key_value_store, bool verify) { @@ -155,10 +157,11 @@ class OatTest : public CommonCompilerTest { return false; } } - return DoWriteElf(file, oat_writer, key_value_store, verify); + return DoWriteElf(vdex_file, oat_file, oat_writer, key_value_store, verify); } - bool WriteElf(File* file, + bool WriteElf(File* vdex_file, + File* oat_file, File&& zip_fd, const char* location, SafeMap<std::string, std::string>& key_value_store, @@ -168,10 +171,11 @@ class OatTest : public CommonCompilerTest { if (!oat_writer.AddZippedDexFilesSource(std::move(zip_fd), location)) { return false; } - return DoWriteElf(file, oat_writer, key_value_store, verify); + return DoWriteElf(vdex_file, oat_file, oat_writer, key_value_store, verify); } - bool DoWriteElf(File* file, + bool DoWriteElf(File* vdex_file, + File* oat_file, OatWriter& oat_writer, SafeMap<std::string, std::string>& key_value_store, bool verify) { @@ -179,13 +183,13 @@ class OatTest : public CommonCompilerTest { compiler_driver_->GetInstructionSet(), compiler_driver_->GetInstructionSetFeatures(), &compiler_driver_->GetCompilerOptions(), - file); + oat_file); elf_writer->Start(); - OutputStream* rodata = elf_writer->StartRoData(); + OutputStream* oat_rodata = elf_writer->StartRoData(); std::unique_ptr<MemMap> opened_dex_files_map; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; - if (!oat_writer.WriteAndOpenDexFiles(rodata, - file, + if (!oat_writer.WriteAndOpenDexFiles(kIsVdexEnabled ? vdex_file : oat_file, + oat_rodata, compiler_driver_->GetInstructionSet(), compiler_driver_->GetInstructionSetFeatures(), &key_value_store, @@ -206,13 +210,13 @@ class OatTest : public CommonCompilerTest { instruction_set_features_.get()); oat_writer.PrepareLayout(compiler_driver_.get(), nullptr, dex_files, &patcher); size_t rodata_size = oat_writer.GetOatHeader().GetExecutableOffset(); - size_t text_size = oat_writer.GetSize() - rodata_size; + size_t text_size = oat_writer.GetOatSize() - rodata_size; elf_writer->SetLoadedSectionSizes(rodata_size, text_size, oat_writer.GetBssSize()); - if (!oat_writer.WriteRodata(rodata)) { + if (!oat_writer.WriteRodata(oat_rodata)) { return false; } - elf_writer->EndRoData(rodata); + elf_writer->EndRoData(oat_rodata); OutputStream* text = elf_writer->StartText(); if (!oat_writer.WriteCode(text)) { @@ -366,17 +370,21 @@ TEST_F(OatTest, WriteRead) { compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings2); } - ScratchFile tmp; + ScratchFile tmp_oat, tmp_vdex(tmp_oat, ".vdex"); SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "lue.art"); - bool success = WriteElf(tmp.GetFile(), class_linker->GetBootClassPath(), key_value_store, false); + bool success = WriteElf(tmp_vdex.GetFile(), + tmp_oat.GetFile(), + class_linker->GetBootClassPath(), + key_value_store, + false); ASSERT_TRUE(success); if (kCompile) { // OatWriter strips the code, regenerate to compare compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); } - std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp.GetFilename(), - tmp.GetFilename(), + std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp_oat.GetFilename(), + tmp_oat.GetFilename(), nullptr, nullptr, false, @@ -498,14 +506,14 @@ TEST_F(OatTest, EmptyTextSection) { compiler_driver_->SetDexFilesForOatFile(dex_files); compiler_driver_->CompileAll(class_loader, dex_files, &timings); - ScratchFile tmp; + ScratchFile tmp_oat, tmp_vdex(tmp_oat, ".vdex"); SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "test.art"); - bool success = WriteElf(tmp.GetFile(), dex_files, key_value_store, false); + bool success = WriteElf(tmp_vdex.GetFile(), tmp_oat.GetFile(), dex_files, key_value_store, false); ASSERT_TRUE(success); - std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp.GetFilename(), - tmp.GetFilename(), + std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp_oat.GetFilename(), + tmp_oat.GetFilename(), nullptr, nullptr, false, @@ -513,7 +521,8 @@ TEST_F(OatTest, EmptyTextSection) { nullptr, &error_msg)); ASSERT_TRUE(oat_file != nullptr); - EXPECT_LT(static_cast<size_t>(oat_file->Size()), static_cast<size_t>(tmp.GetFile()->GetLength())); + EXPECT_LT(static_cast<size_t>(oat_file->Size()), + static_cast<size_t>(tmp_oat.GetFile()->GetLength())); } static void MaybeModifyDexFileToFail(bool verify, std::unique_ptr<const DexFile>& data) { @@ -559,10 +568,14 @@ void OatTest::TestDexFileInput(bool verify, bool low_4gb) { ASSERT_TRUE(success); input_filenames.push_back(dex_file2.GetFilename().c_str()); - ScratchFile oat_file; + ScratchFile oat_file, vdex_file(oat_file, ".vdex"); SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kImageLocationKey, "test.art"); - success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store, verify); + success = WriteElf(vdex_file.GetFile(), + oat_file.GetFile(), + input_filenames, + key_value_store, + verify); // In verify mode, we expect failure. if (verify) { @@ -668,8 +681,9 @@ void OatTest::TestZipFileInput(bool verify) { // Test using the AddDexFileSource() interface with the zip file. std::vector<const char*> input_filenames { zip_file.GetFilename().c_str() }; // NOLINT [readability/braces] [4] - ScratchFile oat_file; - success = WriteElf(oat_file.GetFile(), input_filenames, key_value_store, verify); + ScratchFile oat_file, vdex_file(oat_file, ".vdex"); + success = WriteElf(vdex_file.GetFile(), oat_file.GetFile(), + input_filenames, key_value_store, verify); if (verify) { ASSERT_FALSE(success); @@ -713,8 +727,9 @@ void OatTest::TestZipFileInput(bool verify) { File zip_fd(dup(zip_file.GetFd()), /* check_usage */ false); ASSERT_NE(-1, zip_fd.Fd()); - ScratchFile oat_file; - success = WriteElf(oat_file.GetFile(), + ScratchFile oat_file, vdex_file(oat_file, ".vdex"); + success = WriteElf(vdex_file.GetFile(), + oat_file.GetFile(), std::move(zip_fd), zip_file.GetFilename().c_str(), key_value_store, diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index c9c5d24674..43e01d54a6 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -39,6 +39,8 @@ #include "gc/space/space.h" #include "handle_scope-inl.h" #include "image_writer.h" +#include "linker/buffered_output_stream.h" +#include "linker/file_output_stream.h" #include "linker/multi_oat_relative_patcher.h" #include "linker/output_stream.h" #include "mirror/array.h" @@ -51,6 +53,7 @@ #include "scoped_thread_state_change.h" #include "type_lookup_table.h" #include "utils/dex_cache_arrays_layout-inl.h" +#include "vdex_file.h" #include "verifier/method_verifier.h" #include "zip_archive.h" @@ -283,10 +286,13 @@ OatWriter::OatWriter(bool compiling_boot_image, TimingLogger* timings) image_writer_(nullptr), compiling_boot_image_(compiling_boot_image), dex_files_(nullptr), - size_(0u), + vdex_size_(0u), + vdex_dex_files_offset_(0u), + oat_size_(0u), bss_size_(0u), oat_data_offset_(0u), oat_header_(nullptr), + size_vdex_header_(0), size_dex_file_alignment_(0), size_executable_offset_alignment_(0), size_oat_header_(0), @@ -421,8 +427,8 @@ dchecked_vector<const char*> OatWriter::GetSourceLocations() const { } bool OatWriter::WriteAndOpenDexFiles( - OutputStream* rodata, - File* file, + File* vdex_file, + OutputStream* oat_rodata, InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, SafeMap<std::string, std::string>* key_value_store, @@ -431,37 +437,67 @@ bool OatWriter::WriteAndOpenDexFiles( /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { CHECK(write_state_ == WriteState::kAddingDexFileSources); - size_t offset = InitOatHeader(instruction_set, - instruction_set_features, - dchecked_integral_cast<uint32_t>(oat_dex_files_.size()), - key_value_store); - size_ = InitOatDexFiles(offset); + // Record the ELF rodata section offset, i.e. the beginning of the OAT data. + if (!RecordOatDataOffset(oat_rodata)) { + return false; + } std::unique_ptr<MemMap> dex_files_map; std::vector<std::unique_ptr<const DexFile>> dex_files; - if (!WriteDexFiles(rodata, file) || - !OpenDexFiles(file, verify, &dex_files_map, &dex_files)) { - return false; - } - // Do a bulk checksum update for Dex[]. Doing it piece by piece would be - // difficult because we're not using the OutputStream directly. - if (!oat_dex_files_.empty()) { - size_t size = size_ - oat_dex_files_[0].dex_file_offset_; - oat_header_->UpdateChecksum(dex_files_map->Begin(), size); + // Initialize VDEX and OAT headers. + if (kIsVdexEnabled) { + size_vdex_header_ = sizeof(VdexFile::Header); + vdex_size_ = size_vdex_header_; } + size_t oat_data_offset = InitOatHeader(instruction_set, + instruction_set_features, + dchecked_integral_cast<uint32_t>(oat_dex_files_.size()), + key_value_store); + oat_size_ = InitOatDexFiles(oat_data_offset); + + ChecksumUpdatingOutputStream checksum_updating_rodata(oat_rodata, oat_header_.get()); + + if (kIsVdexEnabled) { + std::unique_ptr<BufferedOutputStream> vdex_out( + MakeUnique<BufferedOutputStream>(MakeUnique<FileOutputStream>(vdex_file))); + + // Write DEX files into VDEX, mmap and open them. + if (!WriteDexFiles(vdex_out.get(), vdex_file) || + !OpenDexFiles(vdex_file, verify, &dex_files_map, &dex_files)) { + return false; + } + + // VDEX is finalized. Seek to the beginning of the file and write the header. + if (!WriteVdexHeader(vdex_out.get())) { + return false; + } + } else { + // Write DEX files into OAT, mmap and open them. + if (!WriteDexFiles(oat_rodata, vdex_file) || + !OpenDexFiles(vdex_file, verify, &dex_files_map, &dex_files)) { + return false; + } - ChecksumUpdatingOutputStream checksum_updating_rodata(rodata, oat_header_.get()); + // Do a bulk checksum update for Dex[]. Doing it piece by piece would be + // difficult because we're not using the OutputStream directly. + if (!oat_dex_files_.empty()) { + size_t size = oat_size_ - oat_dex_files_[0].dex_file_offset_; + oat_header_->UpdateChecksum(dex_files_map->Begin(), size); + } + } + // Write TypeLookupTables into OAT. if (!WriteTypeLookupTables(&checksum_updating_rodata, dex_files)) { return false; } - // Reserve space for class offsets and update class_offsets_offset_. + // Reserve space for class offsets in OAT and update class_offsets_offset_. for (OatDexFile& oat_dex_file : oat_dex_files_) { oat_dex_file.ReserveClassOffsets(this); } + // Write OatDexFiles into OAT. Needs to be done last, once offsets are collected. if (!WriteOatDexFiles(&checksum_updating_rodata)) { return false; } @@ -490,7 +526,7 @@ void OatWriter::PrepareLayout(const CompilerDriver* compiler, InstructionSet instruction_set = compiler_driver_->GetInstructionSet(); CHECK_EQ(instruction_set, oat_header_->GetInstructionSet()); - uint32_t offset = size_; + uint32_t offset = oat_size_; { TimingLogger::ScopedTiming split("InitOatClasses", timings_); offset = InitOatClasses(offset); @@ -507,11 +543,11 @@ void OatWriter::PrepareLayout(const CompilerDriver* compiler, TimingLogger::ScopedTiming split("InitOatCodeDexFiles", timings_); offset = InitOatCodeDexFiles(offset); } - size_ = offset; + oat_size_ = offset; if (!HasBootImage()) { // Allocate space for app dex cache arrays in the .bss section. - size_t bss_start = RoundUp(size_, kPageSize); + size_t bss_start = RoundUp(oat_size_, kPageSize); PointerSize pointer_size = GetInstructionSetPointerSize(instruction_set); bss_size_ = 0u; for (const DexFile* dex_file : *dex_files_) { @@ -1587,6 +1623,7 @@ bool OatWriter::WriteCode(OutputStream* out) { VLOG(compiler) << #x "=" << PrettySize(x) << " (" << (x) << "B)"; \ size_total += (x); + DO_STAT(size_vdex_header_); DO_STAT(size_dex_file_alignment_); DO_STAT(size_executable_offset_alignment_); DO_STAT(size_oat_header_); @@ -1622,13 +1659,14 @@ bool OatWriter::WriteCode(OutputStream* out) { DO_STAT(size_oat_class_method_offsets_); #undef DO_STAT - VLOG(compiler) << "size_total=" << PrettySize(size_total) << " (" << size_total << "B)"; \ - CHECK_EQ(file_offset + size_total, static_cast<size_t>(oat_end_file_offset)); - CHECK_EQ(size_, size_total); + VLOG(compiler) << "size_total=" << PrettySize(size_total) << " (" << size_total << "B)"; + + CHECK_EQ(vdex_size_ + oat_size_, size_total); + CHECK_EQ(file_offset + size_total - vdex_size_, static_cast<size_t>(oat_end_file_offset)); } - CHECK_EQ(file_offset + size_, static_cast<size_t>(oat_end_file_offset)); - CHECK_EQ(size_, relative_offset); + CHECK_EQ(file_offset + oat_size_, static_cast<size_t>(oat_end_file_offset)); + CHECK_EQ(oat_size_, relative_offset); write_state_ = WriteState::kWriteHeader; return true; @@ -1831,17 +1869,14 @@ bool OatWriter::ValidateDexFileHeader(const uint8_t* raw_header, const char* loc return true; } -bool OatWriter::WriteDexFiles(OutputStream* rodata, File* file) { - TimingLogger::ScopedTiming split("WriteDexFiles", timings_); +bool OatWriter::WriteDexFiles(OutputStream* out, File* file) { + TimingLogger::ScopedTiming split("Write Dex files", timings_); - // Get the elf file offset of the oat file. - if (!RecordOatDataOffset(rodata)) { - return false; - } + vdex_dex_files_offset_ = vdex_size_; // Write dex files. for (OatDexFile& oat_dex_file : oat_dex_files_) { - if (!WriteDexFile(rodata, file, &oat_dex_file)) { + if (!WriteDexFile(out, file, &oat_dex_file)) { return false; } } @@ -1856,45 +1891,50 @@ bool OatWriter::WriteDexFiles(OutputStream* rodata, File* file) { return true; } -bool OatWriter::WriteDexFile(OutputStream* rodata, File* file, OatDexFile* oat_dex_file) { - if (!SeekToDexFile(rodata, file, oat_dex_file)) { +bool OatWriter::WriteDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file) { + if (!SeekToDexFile(out, file, oat_dex_file)) { return false; } if (oat_dex_file->source_.IsZipEntry()) { - if (!WriteDexFile(rodata, file, oat_dex_file, oat_dex_file->source_.GetZipEntry())) { + if (!WriteDexFile(out, file, oat_dex_file, oat_dex_file->source_.GetZipEntry())) { return false; } } else if (oat_dex_file->source_.IsRawFile()) { - if (!WriteDexFile(rodata, file, oat_dex_file, oat_dex_file->source_.GetRawFile())) { + if (!WriteDexFile(out, file, oat_dex_file, oat_dex_file->source_.GetRawFile())) { return false; } } else { DCHECK(oat_dex_file->source_.IsRawData()); - if (!WriteDexFile(rodata, oat_dex_file, oat_dex_file->source_.GetRawData())) { + if (!WriteDexFile(out, oat_dex_file, oat_dex_file->source_.GetRawData())) { return false; } } // Update current size and account for the written data. - DCHECK_EQ(size_, oat_dex_file->dex_file_offset_); - size_ += oat_dex_file->dex_file_size_; + if (kIsVdexEnabled) { + DCHECK_EQ(vdex_size_, oat_dex_file->dex_file_offset_); + vdex_size_ += oat_dex_file->dex_file_size_; + } else { + DCHECK_EQ(oat_size_, oat_dex_file->dex_file_offset_); + oat_size_ += oat_dex_file->dex_file_size_; + } size_dex_file_ += oat_dex_file->dex_file_size_; return true; } bool OatWriter::SeekToDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file) { // Dex files are required to be 4 byte aligned. - size_t original_offset = size_; - size_t offset = RoundUp(original_offset, 4); - size_dex_file_alignment_ += offset - original_offset; + size_t initial_offset = kIsVdexEnabled ? vdex_size_ : oat_size_; + size_t start_offset = RoundUp(initial_offset, 4); + size_t file_offset = kIsVdexEnabled ? start_offset : (oat_data_offset_ + start_offset); + size_dex_file_alignment_ += start_offset - initial_offset; // Seek to the start of the dex file and flush any pending operations in the stream. // Verify that, after flushing the stream, the file is at the same offset as the stream. - uint32_t start_offset = oat_data_offset_ + offset; - off_t actual_offset = out->Seek(start_offset, kSeekSet); - if (actual_offset != static_cast<off_t>(start_offset)) { + off_t actual_offset = out->Seek(file_offset, kSeekSet); + if (actual_offset != static_cast<off_t>(file_offset)) { PLOG(ERROR) << "Failed to seek to dex file section. Actual: " << actual_offset - << " Expected: " << start_offset + << " Expected: " << file_offset << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; } @@ -1904,24 +1944,28 @@ bool OatWriter::SeekToDexFile(OutputStream* out, File* file, OatDexFile* oat_dex return false; } actual_offset = lseek(file->Fd(), 0, SEEK_CUR); - if (actual_offset != static_cast<off_t>(start_offset)) { + if (actual_offset != static_cast<off_t>(file_offset)) { PLOG(ERROR) << "Stream/file position mismatch! Actual: " << actual_offset - << " Expected: " << start_offset + << " Expected: " << file_offset << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; } - size_ = offset; - oat_dex_file->dex_file_offset_ = offset; + if (kIsVdexEnabled) { + vdex_size_ = start_offset; + } else { + oat_size_ = start_offset; + } + oat_dex_file->dex_file_offset_ = start_offset; return true; } -bool OatWriter::WriteDexFile(OutputStream* rodata, +bool OatWriter::WriteDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file, ZipEntry* dex_file) { - size_t start_offset = oat_data_offset_ + size_; - DCHECK_EQ(static_cast<off_t>(start_offset), rodata->Seek(0, kSeekCurrent)); + size_t start_offset = kIsVdexEnabled ? vdex_size_ : oat_data_offset_ + oat_size_; + DCHECK_EQ(static_cast<off_t>(start_offset), out->Seek(0, kSeekCurrent)); // Extract the dex file and get the extracted size. std::string error_msg; @@ -1984,13 +2028,13 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; } - actual_offset = rodata->Seek(end_offset, kSeekSet); + actual_offset = out->Seek(end_offset, kSeekSet); if (actual_offset != static_cast<off_t>(end_offset)) { PLOG(ERROR) << "Failed to seek stream to end of dex file. Actual: " << actual_offset << " Expected: " << end_offset << " File: " << oat_dex_file->GetLocation(); return false; } - if (!rodata->Flush()) { + if (!out->Flush()) { PLOG(ERROR) << "Failed to flush stream after seeking over dex file." << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; @@ -2000,7 +2044,8 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, if (extracted_size > oat_dex_file->dex_file_size_) { if (file->SetLength(end_offset) != 0) { PLOG(ERROR) << "Failed to truncate excessive dex file length." - << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); + << " File: " << oat_dex_file->GetLocation() + << " Output: " << file->GetPath(); return false; } } @@ -2008,12 +2053,12 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, return true; } -bool OatWriter::WriteDexFile(OutputStream* rodata, +bool OatWriter::WriteDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file, File* dex_file) { - size_t start_offset = oat_data_offset_ + size_; - DCHECK_EQ(static_cast<off_t>(start_offset), rodata->Seek(0, kSeekCurrent)); + size_t start_offset = kIsVdexEnabled ? vdex_size_ : oat_data_offset_ + oat_size_; + DCHECK_EQ(static_cast<off_t>(start_offset), out->Seek(0, kSeekCurrent)); off_t input_offset = lseek(dex_file->Fd(), 0, SEEK_SET); if (input_offset != static_cast<off_t>(0)) { @@ -2047,13 +2092,13 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; } - actual_offset = rodata->Seek(end_offset, kSeekSet); + actual_offset = out->Seek(end_offset, kSeekSet); if (actual_offset != static_cast<off_t>(end_offset)) { PLOG(ERROR) << "Failed to seek stream to end of dex file. Actual: " << actual_offset << " Expected: " << end_offset << " File: " << oat_dex_file->GetLocation(); return false; } - if (!rodata->Flush()) { + if (!out->Flush()) { PLOG(ERROR) << "Failed to flush stream after seeking over dex file." << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath(); return false; @@ -2062,7 +2107,7 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, return true; } -bool OatWriter::WriteDexFile(OutputStream* rodata, +bool OatWriter::WriteDexFile(OutputStream* out, OatDexFile* oat_dex_file, const uint8_t* dex_file) { // Note: The raw data has already been checked to contain the header @@ -2071,12 +2116,12 @@ bool OatWriter::WriteDexFile(OutputStream* rodata, DCHECK(ValidateDexFileHeader(dex_file, oat_dex_file->GetLocation())); const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(dex_file); - if (!rodata->WriteFully(dex_file, header->file_size_)) { + if (!out->WriteFully(dex_file, header->file_size_)) { PLOG(ERROR) << "Failed to write dex file " << oat_dex_file->GetLocation() - << " to " << rodata->GetLocation(); + << " to " << out->GetLocation(); return false; } - if (!rodata->Flush()) { + if (!out->Flush()) { PLOG(ERROR) << "Failed to flush stream after writing dex file." << " File: " << oat_dex_file->GetLocation(); return false; @@ -2146,16 +2191,18 @@ bool OatWriter::OpenDexFiles( } size_t map_offset = oat_dex_files_[0].dex_file_offset_; - size_t length = size_ - map_offset; + size_t length = kIsVdexEnabled ? (vdex_size_ - map_offset) : (oat_size_ - map_offset); + std::string error_msg; - std::unique_ptr<MemMap> dex_files_map(MemMap::MapFile(length, - PROT_READ | PROT_WRITE, - MAP_SHARED, - file->Fd(), - oat_data_offset_ + map_offset, - /* low_4gb */ false, - file->GetPath().c_str(), - &error_msg)); + std::unique_ptr<MemMap> dex_files_map(MemMap::MapFile( + length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + file->Fd(), + kIsVdexEnabled ? map_offset : (oat_data_offset_ + map_offset), + /* low_4gb */ false, + file->GetPath().c_str(), + &error_msg)); if (dex_files_map == nullptr) { LOG(ERROR) << "Failed to mmap() dex files from oat file. File: " << file->GetPath() << " error: " << error_msg; @@ -2210,10 +2257,18 @@ bool OatWriter::OpenDexFiles( } bool OatWriter::WriteTypeLookupTables( - OutputStream* rodata, + OutputStream* oat_rodata, const std::vector<std::unique_ptr<const DexFile>>& opened_dex_files) { TimingLogger::ScopedTiming split("WriteTypeLookupTables", timings_); + uint32_t expected_offset = oat_data_offset_ + oat_size_; + off_t actual_offset = oat_rodata->Seek(expected_offset, kSeekSet); + if (static_cast<uint32_t>(actual_offset) != expected_offset) { + PLOG(ERROR) << "Failed to seek to TypeLookupTable section. Actual: " << actual_offset + << " Expected: " << expected_offset << " File: " << oat_rodata->GetLocation(); + return false; + } + DCHECK_EQ(opened_dex_files.size(), oat_dex_files_.size()); for (size_t i = 0, size = opened_dex_files.size(); i != size; ++i) { OatDexFile* oat_dex_file = &oat_dex_files_[i]; @@ -2235,41 +2290,58 @@ bool OatWriter::WriteTypeLookupTables( TypeLookupTable* table = opened_dex_files[i]->GetTypeLookupTable(); // Type tables are required to be 4 byte aligned. - size_t original_offset = size_; - size_t rodata_offset = RoundUp(original_offset, 4); - size_t padding_size = rodata_offset - original_offset; + size_t initial_offset = oat_size_; + size_t rodata_offset = RoundUp(initial_offset, 4); + size_t padding_size = rodata_offset - initial_offset; if (padding_size != 0u) { std::vector<uint8_t> buffer(padding_size, 0u); - if (!rodata->WriteFully(buffer.data(), padding_size)) { + if (!oat_rodata->WriteFully(buffer.data(), padding_size)) { PLOG(ERROR) << "Failed to write lookup table alignment padding." << " File: " << oat_dex_file->GetLocation() - << " Output: " << rodata->GetLocation(); + << " Output: " << oat_rodata->GetLocation(); return false; } } DCHECK_EQ(oat_data_offset_ + rodata_offset, - static_cast<size_t>(rodata->Seek(0u, kSeekCurrent))); + static_cast<size_t>(oat_rodata->Seek(0u, kSeekCurrent))); DCHECK_EQ(table_size, table->RawDataLength()); - if (!rodata->WriteFully(table->RawData(), table_size)) { + if (!oat_rodata->WriteFully(table->RawData(), table_size)) { PLOG(ERROR) << "Failed to write lookup table." << " File: " << oat_dex_file->GetLocation() - << " Output: " << rodata->GetLocation(); + << " Output: " << oat_rodata->GetLocation(); return false; } oat_dex_file->lookup_table_offset_ = rodata_offset; - size_ += padding_size + table_size; + oat_size_ += padding_size + table_size; size_oat_lookup_table_ += table_size; size_oat_lookup_table_alignment_ += padding_size; } - if (!rodata->Flush()) { + if (!oat_rodata->Flush()) { PLOG(ERROR) << "Failed to flush stream after writing type lookup tables." - << " File: " << rodata->GetLocation(); + << " File: " << oat_rodata->GetLocation(); + return false; + } + + return true; +} + +bool OatWriter::WriteVdexHeader(OutputStream* vdex_out) { + off_t actual_offset = vdex_out->Seek(0, kSeekSet); + if (actual_offset != 0) { + PLOG(ERROR) << "Failed to seek to the beginning of vdex file. Actual: " << actual_offset + << " File: " << vdex_out->GetLocation(); + return false; + } + + VdexFile::Header vdex_header; + if (!vdex_out->WriteFully(&vdex_header, sizeof(VdexFile::Header))) { + PLOG(ERROR) << "Failed to write vdex header. File: " << vdex_out->GetLocation(); return false; } @@ -2329,11 +2401,11 @@ void OatWriter::OatDexFile::ReserveClassOffsets(OatWriter* oat_writer) { DCHECK_EQ(class_offsets_offset_, 0u); if (!class_offsets_.empty()) { // Class offsets are required to be 4 byte aligned. - size_t original_offset = oat_writer->size_; - size_t offset = RoundUp(original_offset, 4); - oat_writer->size_oat_class_offsets_alignment_ += offset - original_offset; + size_t initial_offset = oat_writer->oat_size_; + size_t offset = RoundUp(initial_offset, 4); + oat_writer->size_oat_class_offsets_alignment_ += offset - initial_offset; class_offsets_offset_ = offset; - oat_writer->size_ = offset + GetClassOffsetsRawSize(); + oat_writer->oat_size_ = offset + GetClassOffsetsRawSize(); } } diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index 93e2e440b3..77525f1a32 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -57,11 +57,6 @@ class MultiOatRelativePatcher; // ... // OatDexFile[D] // -// Dex[0] one variable sized DexFile for each OatDexFile. -// Dex[1] these are literal copies of the input .dex files. -// ... -// Dex[D] -// // TypeLookupTable[0] one descriptor to class def index hash table for each OatDexFile. // TypeLookupTable[1] // ... @@ -142,11 +137,12 @@ class OatWriter { CreateTypeLookupTable create_type_lookup_table = CreateTypeLookupTable::kDefault); dchecked_vector<const char*> GetSourceLocations() const; - // Write raw dex files to the .rodata section and open them from the oat file. The verify - // setting dictates whether the dex file verifier should check the dex files. This is generally - // the case, and should only be false for tests. - bool WriteAndOpenDexFiles(OutputStream* rodata, - File* file, + // Write raw dex files to the vdex file, mmap the file and open the dex files from it. + // Supporting data structures are written into the .rodata section of the oat file. + // The `verify` setting dictates whether the dex file verifier should check the dex files. + // This is generally the case, and should only be false for tests. + bool WriteAndOpenDexFiles(File* vdex_file, + OutputStream* oat_rodata, InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, SafeMap<std::string, std::string>* key_value_store, @@ -183,8 +179,8 @@ class OatWriter { return *oat_header_; } - size_t GetSize() const { - return size_; + size_t GetOatSize() const { + return oat_size_; } size_t GetBssSize() const { @@ -236,6 +232,25 @@ class OatWriter { // with a given DexMethodVisitor. bool VisitDexMethods(DexMethodVisitor* visitor); + bool WriteVdexHeader(OutputStream* vdex_out); + + bool WriteDexFiles(OutputStream* out, File* file); + bool WriteDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file); + bool SeekToDexFile(OutputStream* out, File* file, OatDexFile* oat_dex_file); + bool WriteDexFile(OutputStream* out, + File* file, + OatDexFile* oat_dex_file, + ZipEntry* dex_file); + bool WriteDexFile(OutputStream* out, + File* file, + OatDexFile* oat_dex_file, + File* dex_file); + bool WriteDexFile(OutputStream* out, OatDexFile* oat_dex_file, const uint8_t* dex_file); + bool OpenDexFiles(File* file, + bool verify, + /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, + /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); + size_t InitOatHeader(InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, uint32_t num_dex_files, @@ -253,20 +268,10 @@ class OatWriter { size_t WriteCodeDexFiles(OutputStream* out, const size_t file_offset, size_t relative_offset); bool RecordOatDataOffset(OutputStream* out); - bool ReadDexFileHeader(File* file, OatDexFile* oat_dex_file); + bool ReadDexFileHeader(File* oat_file, OatDexFile* oat_dex_file); bool ValidateDexFileHeader(const uint8_t* raw_header, const char* location); - bool WriteDexFiles(OutputStream* rodata, File* file); - bool WriteDexFile(OutputStream* rodata, File* file, OatDexFile* oat_dex_file); - bool SeekToDexFile(OutputStream* rodata, File* file, OatDexFile* oat_dex_file); - bool WriteDexFile(OutputStream* rodata, File* file, OatDexFile* oat_dex_file, ZipEntry* dex_file); - bool WriteDexFile(OutputStream* rodata, File* file, OatDexFile* oat_dex_file, File* dex_file); - bool WriteDexFile(OutputStream* rodata, OatDexFile* oat_dex_file, const uint8_t* dex_file); - bool WriteOatDexFiles(OutputStream* rodata); - bool OpenDexFiles(File* file, - bool verify, - /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, - /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); - bool WriteTypeLookupTables(OutputStream* rodata, + bool WriteOatDexFiles(OutputStream* oat_rodata); + bool WriteTypeLookupTables(OutputStream* oat_rodata, const std::vector<std::unique_ptr<const DexFile>>& opened_dex_files); bool WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delta); void SetMultiOatRelativePatcherAdjustment(); @@ -300,8 +305,14 @@ class OatWriter { // note OatFile does not take ownership of the DexFiles const std::vector<const DexFile*>* dex_files_; + // Size required for Vdex data structures. + size_t vdex_size_; + + // Offset of section holding Dex files inside Vdex. + size_t vdex_dex_files_offset_; + // Size required for Oat data structures. - size_t size_; + size_t oat_size_; // The size of the required .bss section holding the DexCache data. size_t bss_size_; @@ -324,6 +335,7 @@ class OatWriter { std::unique_ptr<const std::vector<uint8_t>> quick_to_interpreter_bridge_; // output stats + uint32_t size_vdex_header_; uint32_t size_dex_file_alignment_; uint32_t size_executable_offset_alignment_; uint32_t size_oat_header_; diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 5301a6bb3e..3cc2598f8f 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -4598,7 +4598,6 @@ void LocationsBuilderARM::VisitArraySet(HArraySet* instruction) { } if (needs_write_barrier) { // Temporary registers for the write barrier. - // These registers may be used for Baker read barriers too. locations->AddTemp(Location::RequiresRegister()); // Possibly used for ref. poisoning too. locations->AddTemp(Location::RequiresRegister()); } @@ -4716,127 +4715,42 @@ void InstructionCodeGeneratorARM::VisitArraySet(HArraySet* instruction) { __ Bind(&non_zero); } - if (kEmitCompilerReadBarrier) { - if (!kUseBakerReadBarrier) { - // When (non-Baker) read barriers are enabled, the type - // checking instrumentation requires two read barriers - // generated by CodeGeneratorARM::GenerateReadBarrierSlow: - // - // __ Mov(temp2, temp1); - // // /* HeapReference<Class> */ temp1 = temp1->component_type_ - // __ LoadFromOffset(kLoadWord, temp1, temp1, component_offset); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp1_loc, temp1_loc, temp2_loc, component_offset); - // - // // /* HeapReference<Class> */ temp2 = value->klass_ - // __ LoadFromOffset(kLoadWord, temp2, value, class_offset); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp2_loc, temp2_loc, value_loc, class_offset, temp1_loc); - // - // __ cmp(temp1, ShifterOperand(temp2)); - // - // However, the second read barrier may trash `temp`, as it - // is a temporary register, and as such would not be saved - // along with live registers before calling the runtime (nor - // restored afterwards). So in this case, we bail out and - // delegate the work to the array set slow path. - // - // TODO: Extend the register allocator to support a new - // "(locally) live temp" location so as to avoid always - // going into the slow path when read barriers are enabled? - // - // There is no such problem with Baker read barriers (see below). - __ b(slow_path->GetEntryLabel()); - } else { - Register temp3 = IP; - Location temp3_loc = Location::RegisterLocation(temp3); - - // Note: `temp3` (scratch register IP) cannot be used as - // `ref` argument of GenerateFieldLoadWithBakerReadBarrier - // calls below (see ReadBarrierMarkSlowPathARM for more - // details). - - // /* HeapReference<Class> */ temp1 = array->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp1_loc, - array, - class_offset, - temp3_loc, - /* needs_null_check */ true); - - // /* HeapReference<Class> */ temp1 = temp1->component_type_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp1_loc, - temp1, - component_offset, - temp3_loc, - /* needs_null_check */ false); - // Register `temp1` is not trashed by the read barrier - // emitted by GenerateFieldLoadWithBakerReadBarrier below, - // as that method produces a call to a ReadBarrierMarkRegX - // entry point, which saves all potentially live registers, - // including temporaries such a `temp1`. - // /* HeapReference<Class> */ temp2 = value->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp2_loc, - value, - class_offset, - temp3_loc, - /* needs_null_check */ false); - // If heap poisoning is enabled, `temp1` and `temp2` have - // been unpoisoned by the the previous calls to - // CodeGeneratorARM::GenerateFieldLoadWithBakerReadBarrier. - __ cmp(temp1, ShifterOperand(temp2)); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - Label do_put; - __ b(&do_put, EQ); - // We do not need to emit a read barrier for the - // following heap reference load, as `temp1` is only used - // in a comparison with null below, and this reference - // is not kept afterwards. - // /* HeapReference<Class> */ temp1 = temp1->super_class_ - __ LoadFromOffset(kLoadWord, temp1, temp1, super_offset); - // If heap poisoning is enabled, no need to unpoison - // `temp`, as we are comparing against null below. - __ CompareAndBranchIfNonZero(temp1, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ b(slow_path->GetEntryLabel(), NE); - } - } - } else { - // Non read barrier code. + // Note that when read barriers are enabled, the type checks + // are performed without read barriers. This is fine, even in + // the case where a class object is in the from-space after + // the flip, as a comparison involving such a type would not + // produce a false positive; it may of course produce a false + // negative, in which case we would take the ArraySet slow + // path. - // /* HeapReference<Class> */ temp1 = array->klass_ - __ LoadFromOffset(kLoadWord, temp1, array, class_offset); - codegen_->MaybeRecordImplicitNullCheck(instruction); + // /* HeapReference<Class> */ temp1 = array->klass_ + __ LoadFromOffset(kLoadWord, temp1, array, class_offset); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ MaybeUnpoisonHeapReference(temp1); + + // /* HeapReference<Class> */ temp1 = temp1->component_type_ + __ LoadFromOffset(kLoadWord, temp1, temp1, component_offset); + // /* HeapReference<Class> */ temp2 = value->klass_ + __ LoadFromOffset(kLoadWord, temp2, value, class_offset); + // If heap poisoning is enabled, no need to unpoison `temp1` + // nor `temp2`, as we are comparing two poisoned references. + __ cmp(temp1, ShifterOperand(temp2)); + + if (instruction->StaticTypeOfArrayIsObjectArray()) { + Label do_put; + __ b(&do_put, EQ); + // If heap poisoning is enabled, the `temp1` reference has + // not been unpoisoned yet; unpoison it now. __ MaybeUnpoisonHeapReference(temp1); - // /* HeapReference<Class> */ temp1 = temp1->component_type_ - __ LoadFromOffset(kLoadWord, temp1, temp1, component_offset); - // /* HeapReference<Class> */ temp2 = value->klass_ - __ LoadFromOffset(kLoadWord, temp2, value, class_offset); - // If heap poisoning is enabled, no need to unpoison `temp1` - // nor `temp2`, as we are comparing two poisoned references. - __ cmp(temp1, ShifterOperand(temp2)); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - Label do_put; - __ b(&do_put, EQ); - // If heap poisoning is enabled, the `temp1` reference has - // not been unpoisoned yet; unpoison it now. - __ MaybeUnpoisonHeapReference(temp1); - - // /* HeapReference<Class> */ temp1 = temp1->super_class_ - __ LoadFromOffset(kLoadWord, temp1, temp1, super_offset); - // If heap poisoning is enabled, no need to unpoison - // `temp1`, as we are comparing against null below. - __ CompareAndBranchIfNonZero(temp1, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ b(slow_path->GetEntryLabel(), NE); - } + // /* HeapReference<Class> */ temp1 = temp1->super_class_ + __ LoadFromOffset(kLoadWord, temp1, temp1, super_offset); + // If heap poisoning is enabled, no need to unpoison + // `temp1`, as we are comparing against null below. + __ CompareAndBranchIfNonZero(temp1, slow_path->GetEntryLabel()); + __ Bind(&do_put); + } else { + __ b(slow_path->GetEntryLabel(), NE); } } @@ -5508,17 +5422,6 @@ void InstructionCodeGeneratorARM::GenerateClassInitializationCheck( HLoadString::LoadKind CodeGeneratorARM::GetSupportedLoadStringKind( HLoadString::LoadKind desired_string_load_kind) { - if (kEmitCompilerReadBarrier) { - switch (desired_string_load_kind) { - case HLoadString::LoadKind::kBootImageLinkTimeAddress: - case HLoadString::LoadKind::kBootImageLinkTimePcRelative: - case HLoadString::LoadKind::kBootImageAddress: - // TODO: Implement for read barrier. - return HLoadString::LoadKind::kDexCacheViaMethod; - default: - break; - } - } switch (desired_string_load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: DCHECK(!GetCompilerOptions().GetCompilePic()); @@ -5571,13 +5474,11 @@ void InstructionCodeGeneratorARM::VisitLoadString(HLoadString* load) { switch (load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: { - DCHECK(!kEmitCompilerReadBarrier); __ LoadLiteral(out, codegen_->DeduplicateBootImageStringLiteral(load->GetDexFile(), load->GetStringIndex())); return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { - DCHECK(!kEmitCompilerReadBarrier); CodeGeneratorARM::PcRelativePatchInfo* labels = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); __ BindTrackedLabel(&labels->movw_label); @@ -5589,7 +5490,6 @@ void InstructionCodeGeneratorARM::VisitLoadString(HLoadString* load) { return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageAddress: { - DCHECK(!kEmitCompilerReadBarrier); DCHECK_NE(load->GetAddress(), 0u); uint32_t address = dchecked_integral_cast<uint32_t>(load->GetAddress()); __ LoadLiteral(out, codegen_->DeduplicateBootImageAddressLiteral(address)); @@ -5845,7 +5745,6 @@ void LocationsBuilderARM::VisitCheckCast(HCheckCast* instruction) { bool throws_into_catch = instruction->CanThrowIntoCatchBlock(); TypeCheckKind type_check_kind = instruction->GetTypeCheckKind(); - bool baker_read_barrier_slow_path = false; switch (type_check_kind) { case TypeCheckKind::kExactCheck: case TypeCheckKind::kAbstractClassCheck: @@ -5854,7 +5753,6 @@ void LocationsBuilderARM::VisitCheckCast(HCheckCast* instruction) { call_kind = (throws_into_catch || kEmitCompilerReadBarrier) ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall; // In fact, call on a fatal (non-returning) slow path. - baker_read_barrier_slow_path = kUseBakerReadBarrier && !throws_into_catch; break; case TypeCheckKind::kArrayCheck: case TypeCheckKind::kUnresolvedCheck: @@ -5864,9 +5762,6 @@ void LocationsBuilderARM::VisitCheckCast(HCheckCast* instruction) { } LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, call_kind); - if (baker_read_barrier_slow_path) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } locations->SetInAt(0, Location::RequiresRegister()); locations->SetInAt(1, Location::RequiresRegister()); // Note that TypeCheckSlowPathARM uses this "temp" register too. diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 36f7b4d914..179bf76f5b 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -336,36 +336,6 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathARM64); }; -class LoadStringSlowPathARM64 : public SlowPathCodeARM64 { - public: - explicit LoadStringSlowPathARM64(HLoadString* instruction) : SlowPathCodeARM64(instruction) {} - - void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { - LocationSummary* locations = instruction_->GetLocations(); - DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); - CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen); - - __ Bind(GetEntryLabel()); - SaveLiveRegisters(codegen, locations); - - InvokeRuntimeCallingConvention calling_convention; - const uint32_t string_index = instruction_->AsLoadString()->GetStringIndex(); - __ Mov(calling_convention.GetRegisterAt(0).W(), string_index); - arm64_codegen->InvokeRuntime(kQuickResolveString, instruction_, instruction_->GetDexPc(), this); - CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); - Primitive::Type type = instruction_->GetType(); - arm64_codegen->MoveLocation(locations->Out(), calling_convention.GetReturnLocation(type), type); - - RestoreLiveRegisters(codegen, locations); - __ B(GetExitLabel()); - } - - const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathARM64"; } - - private: - DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathARM64); -}; - class NullCheckSlowPathARM64 : public SlowPathCodeARM64 { public: explicit NullCheckSlowPathARM64(HNullCheck* instr) : SlowPathCodeARM64(instr) {} @@ -2178,11 +2148,6 @@ void LocationsBuilderARM64::VisitArraySet(HArraySet* instruction) { } else { locations->SetInAt(2, Location::RequiresRegister()); } - if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && (value_type == Primitive::kPrimNot)) { - // Additional temporary registers for a Baker read barrier. - locations->AddTemp(Location::RequiresRegister()); - locations->AddTemp(Location::RequiresRegister()); - } } void InstructionCodeGeneratorARM64::VisitArraySet(HArraySet* instruction) { @@ -2269,144 +2234,44 @@ void InstructionCodeGeneratorARM64::VisitArraySet(HArraySet* instruction) { __ Bind(&non_zero); } - if (kEmitCompilerReadBarrier) { - if (!kUseBakerReadBarrier) { - // When (non-Baker) read barriers are enabled, the type - // checking instrumentation requires two read barriers - // generated by CodeGeneratorARM64::GenerateReadBarrierSlow: - // - // __ Mov(temp2, temp); - // // /* HeapReference<Class> */ temp = temp->component_type_ - // __ Ldr(temp, HeapOperand(temp, component_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp_loc, temp_loc, temp2_loc, component_offset); - // - // // /* HeapReference<Class> */ temp2 = value->klass_ - // __ Ldr(temp2, HeapOperand(Register(value), class_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp2_loc, temp2_loc, value_loc, class_offset, temp_loc); - // - // __ Cmp(temp, temp2); - // - // However, the second read barrier may trash `temp`, as it - // is a temporary register, and as such would not be saved - // along with live registers before calling the runtime (nor - // restored afterwards). So in this case, we bail out and - // delegate the work to the array set slow path. - // - // TODO: Extend the register allocator to support a new - // "(locally) live temp" location so as to avoid always - // going into the slow path when read barriers are enabled? - // - // There is no such problem with Baker read barriers (see below). - __ B(slow_path->GetEntryLabel()); - } else { - // Note that we cannot use `temps` (instance of VIXL's - // UseScratchRegisterScope) to allocate `temp2` because - // the Baker read barriers generated by - // GenerateFieldLoadWithBakerReadBarrier below also use - // that facility to allocate a temporary register, thus - // making VIXL's scratch register pool empty. - Location temp2_loc = locations->GetTemp(0); - Register temp2 = WRegisterFrom(temp2_loc); - - // Note: Because it is acquired from VIXL's scratch register - // pool, `temp` might be IP0, and thus cannot be used as - // `ref` argument of GenerateFieldLoadWithBakerReadBarrier - // calls below (see ReadBarrierMarkSlowPathARM64 for more - // details). - - // /* HeapReference<Class> */ temp2 = array->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp2_loc, - array, - class_offset, - temp, - /* needs_null_check */ true, - /* use_load_acquire */ false); - - // /* HeapReference<Class> */ temp2 = temp2->component_type_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp2_loc, - temp2, - component_offset, - temp, - /* needs_null_check */ false, - /* use_load_acquire */ false); - // For the same reason that we request `temp2` from the - // register allocator above, we cannot get `temp3` from - // VIXL's scratch register pool. - Location temp3_loc = locations->GetTemp(1); - Register temp3 = WRegisterFrom(temp3_loc); - // Register `temp2` is not trashed by the read barrier - // emitted by GenerateFieldLoadWithBakerReadBarrier below, - // as that method produces a call to a ReadBarrierMarkRegX - // entry point, which saves all potentially live registers, - // including temporaries such a `temp2`. - // /* HeapReference<Class> */ temp3 = register_value->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction, - temp3_loc, - value.W(), - class_offset, - temp, - /* needs_null_check */ false, - /* use_load_acquire */ false); - // If heap poisoning is enabled, `temp2` and `temp3` have - // been unpoisoned by the the previous calls to - // CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier. - __ Cmp(temp2, temp3); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - vixl::aarch64::Label do_put; - __ B(eq, &do_put); - // We do not need to emit a read barrier for the - // following heap reference load, as `temp2` is only used - // in a comparison with null below, and this reference - // is not kept afterwards. - // /* HeapReference<Class> */ temp = temp2->super_class_ - __ Ldr(temp, HeapOperand(temp2, super_offset)); - // If heap poisoning is enabled, no need to unpoison - // `temp`, as we are comparing against null below. - __ Cbnz(temp, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ B(ne, slow_path->GetEntryLabel()); - } - } - } else { - // Non read barrier code. + // Note that when Baker read barriers are enabled, the type + // checks are performed without read barriers. This is fine, + // even in the case where a class object is in the from-space + // after the flip, as a comparison involving such a type would + // not produce a false positive; it may of course produce a + // false negative, in which case we would take the ArraySet + // slow path. - Register temp2 = temps.AcquireSameSizeAs(array); - // /* HeapReference<Class> */ temp = array->klass_ - __ Ldr(temp, HeapOperand(array, class_offset)); - codegen_->MaybeRecordImplicitNullCheck(instruction); + Register temp2 = temps.AcquireSameSizeAs(array); + // /* HeapReference<Class> */ temp = array->klass_ + __ Ldr(temp, HeapOperand(array, class_offset)); + codegen_->MaybeRecordImplicitNullCheck(instruction); + GetAssembler()->MaybeUnpoisonHeapReference(temp); + + // /* HeapReference<Class> */ temp = temp->component_type_ + __ Ldr(temp, HeapOperand(temp, component_offset)); + // /* HeapReference<Class> */ temp2 = value->klass_ + __ Ldr(temp2, HeapOperand(Register(value), class_offset)); + // If heap poisoning is enabled, no need to unpoison `temp` + // nor `temp2`, as we are comparing two poisoned references. + __ Cmp(temp, temp2); + temps.Release(temp2); + + if (instruction->StaticTypeOfArrayIsObjectArray()) { + vixl::aarch64::Label do_put; + __ B(eq, &do_put); + // If heap poisoning is enabled, the `temp` reference has + // not been unpoisoned yet; unpoison it now. GetAssembler()->MaybeUnpoisonHeapReference(temp); - // /* HeapReference<Class> */ temp = temp->component_type_ - __ Ldr(temp, HeapOperand(temp, component_offset)); - // /* HeapReference<Class> */ temp2 = value->klass_ - __ Ldr(temp2, HeapOperand(Register(value), class_offset)); - // If heap poisoning is enabled, no need to unpoison `temp` - // nor `temp2`, as we are comparing two poisoned references. - __ Cmp(temp, temp2); - temps.Release(temp2); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - vixl::aarch64::Label do_put; - __ B(eq, &do_put); - // If heap poisoning is enabled, the `temp` reference has - // not been unpoisoned yet; unpoison it now. - GetAssembler()->MaybeUnpoisonHeapReference(temp); - - // /* HeapReference<Class> */ temp = temp->super_class_ - __ Ldr(temp, HeapOperand(temp, super_offset)); - // If heap poisoning is enabled, no need to unpoison - // `temp`, as we are comparing against null below. - __ Cbnz(temp, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ B(ne, slow_path->GetEntryLabel()); - } + // /* HeapReference<Class> */ temp = temp->super_class_ + __ Ldr(temp, HeapOperand(temp, super_offset)); + // If heap poisoning is enabled, no need to unpoison + // `temp`, as we are comparing against null below. + __ Cbnz(temp, slow_path->GetEntryLabel()); + __ Bind(&do_put); + } else { + __ B(ne, slow_path->GetEntryLabel()); } } @@ -3385,7 +3250,6 @@ void LocationsBuilderARM64::VisitCheckCast(HCheckCast* instruction) { bool throws_into_catch = instruction->CanThrowIntoCatchBlock(); TypeCheckKind type_check_kind = instruction->GetTypeCheckKind(); - bool baker_read_barrier_slow_path = false; switch (type_check_kind) { case TypeCheckKind::kExactCheck: case TypeCheckKind::kAbstractClassCheck: @@ -3394,7 +3258,6 @@ void LocationsBuilderARM64::VisitCheckCast(HCheckCast* instruction) { call_kind = (throws_into_catch || kEmitCompilerReadBarrier) ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall; // In fact, call on a fatal (non-returning) slow path. - baker_read_barrier_slow_path = kUseBakerReadBarrier && !throws_into_catch; break; case TypeCheckKind::kArrayCheck: case TypeCheckKind::kUnresolvedCheck: @@ -3404,9 +3267,6 @@ void LocationsBuilderARM64::VisitCheckCast(HCheckCast* instruction) { } LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, call_kind); - if (baker_read_barrier_slow_path) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } locations->SetInAt(0, Location::RequiresRegister()); locations->SetInAt(1, Location::RequiresRegister()); // Note that TypeCheckSlowPathARM64 uses this "temp" register too. @@ -4259,17 +4119,6 @@ void InstructionCodeGeneratorARM64::VisitClearException(HClearException* clear A HLoadString::LoadKind CodeGeneratorARM64::GetSupportedLoadStringKind( HLoadString::LoadKind desired_string_load_kind) { - if (kEmitCompilerReadBarrier) { - switch (desired_string_load_kind) { - case HLoadString::LoadKind::kBootImageLinkTimeAddress: - case HLoadString::LoadKind::kBootImageLinkTimePcRelative: - case HLoadString::LoadKind::kBootImageAddress: - // TODO: Implement for read barrier. - return HLoadString::LoadKind::kDexCacheViaMethod; - default: - break; - } - } switch (desired_string_load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: DCHECK(!GetCompilerOptions().GetCompilePic()); @@ -4292,18 +4141,17 @@ HLoadString::LoadKind CodeGeneratorARM64::GetSupportedLoadStringKind( } void LocationsBuilderARM64::VisitLoadString(HLoadString* load) { - LocationSummary::CallKind call_kind = (load->NeedsEnvironment() || kEmitCompilerReadBarrier) - ? LocationSummary::kCallOnSlowPath + LocationSummary::CallKind call_kind = load->NeedsEnvironment() + ? LocationSummary::kCallOnMainOnly : LocationSummary::kNoCall; LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(load, call_kind); - if (kUseBakerReadBarrier && !load->NeedsEnvironment()) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } - if (load->GetLoadKind() == HLoadString::LoadKind::kDexCacheViaMethod) { locations->SetInAt(0, Location::RequiresRegister()); + InvokeRuntimeCallingConvention calling_convention; + locations->SetOut(calling_convention.GetReturnLocation(load->GetType())); + } else { + locations->SetOut(Location::RequiresRegister()); } - locations->SetOut(Location::RequiresRegister()); } void InstructionCodeGeneratorARM64::VisitLoadString(HLoadString* load) { @@ -4311,12 +4159,10 @@ void InstructionCodeGeneratorARM64::VisitLoadString(HLoadString* load) { switch (load->GetLoadKind()) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: - DCHECK(!kEmitCompilerReadBarrier); __ Ldr(out, codegen_->DeduplicateBootImageStringLiteral(load->GetDexFile(), load->GetStringIndex())); return; // No dex cache slow path. case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { - DCHECK(!kEmitCompilerReadBarrier); // Add ADRP with its PC-relative String patch. const DexFile& dex_file = load->GetDexFile(); uint32_t string_index = load->GetStringIndex(); @@ -4337,7 +4183,6 @@ void InstructionCodeGeneratorARM64::VisitLoadString(HLoadString* load) { return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageAddress: { - DCHECK(!kEmitCompilerReadBarrier); DCHECK(load->GetAddress() != 0u && IsUint<32>(load->GetAddress())); __ Ldr(out.W(), codegen_->DeduplicateBootImageAddressLiteral(load->GetAddress())); return; // No dex cache slow path. @@ -4347,10 +4192,10 @@ void InstructionCodeGeneratorARM64::VisitLoadString(HLoadString* load) { } // TODO: Re-add the compiler code to do string dex cache lookup again. - SlowPathCodeARM64* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathARM64(load); - codegen_->AddSlowPath(slow_path); - __ B(slow_path->GetEntryLabel()); - __ Bind(slow_path->GetExitLabel()); + InvokeRuntimeCallingConvention calling_convention; + __ Mov(calling_convention.GetRegisterAt(0).W(), load->GetStringIndex()); + codegen_->InvokeRuntime(kQuickResolveString, load, load->GetDexPc()); + CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); } void LocationsBuilderARM64::VisitLongConstant(HLongConstant* constant) { diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index 92e9cd9067..f07f8a0d91 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -2378,13 +2378,8 @@ void InstructionCodeGeneratorMIPS::HandleCondition(HCondition* instruction) { case Primitive::kPrimFloat: case Primitive::kPrimDouble: - // TODO: don't use branches. - GenerateFpCompareAndBranch(instruction->GetCondition(), - instruction->IsGtBias(), - type, - locations, - &true_label); - break; + GenerateFpCompare(instruction->GetCondition(), instruction->IsGtBias(), type, locations); + return; } // Convert the branches into the result. @@ -3177,6 +3172,230 @@ void InstructionCodeGeneratorMIPS::GenerateLongCompareAndBranch(IfCondition cond } } +void InstructionCodeGeneratorMIPS::GenerateFpCompare(IfCondition cond, + bool gt_bias, + Primitive::Type type, + LocationSummary* locations) { + Register dst = locations->Out().AsRegister<Register>(); + FRegister lhs = locations->InAt(0).AsFpuRegister<FRegister>(); + FRegister rhs = locations->InAt(1).AsFpuRegister<FRegister>(); + bool isR6 = codegen_->GetInstructionSetFeatures().IsR6(); + if (type == Primitive::kPrimFloat) { + if (isR6) { + switch (cond) { + case kCondEQ: + __ CmpEqS(FTMP, lhs, rhs); + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondNE: + __ CmpEqS(FTMP, lhs, rhs); + __ Mfc1(dst, FTMP); + __ Addiu(dst, dst, 1); + break; + case kCondLT: + if (gt_bias) { + __ CmpLtS(FTMP, lhs, rhs); + } else { + __ CmpUltS(FTMP, lhs, rhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondLE: + if (gt_bias) { + __ CmpLeS(FTMP, lhs, rhs); + } else { + __ CmpUleS(FTMP, lhs, rhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondGT: + if (gt_bias) { + __ CmpUltS(FTMP, rhs, lhs); + } else { + __ CmpLtS(FTMP, rhs, lhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondGE: + if (gt_bias) { + __ CmpUleS(FTMP, rhs, lhs); + } else { + __ CmpLeS(FTMP, rhs, lhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + default: + LOG(FATAL) << "Unexpected non-floating-point condition " << cond; + UNREACHABLE(); + } + } else { + switch (cond) { + case kCondEQ: + __ CeqS(0, lhs, rhs); + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondNE: + __ CeqS(0, lhs, rhs); + __ LoadConst32(dst, 1); + __ Movt(dst, ZERO, 0); + break; + case kCondLT: + if (gt_bias) { + __ ColtS(0, lhs, rhs); + } else { + __ CultS(0, lhs, rhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondLE: + if (gt_bias) { + __ ColeS(0, lhs, rhs); + } else { + __ CuleS(0, lhs, rhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondGT: + if (gt_bias) { + __ CultS(0, rhs, lhs); + } else { + __ ColtS(0, rhs, lhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondGE: + if (gt_bias) { + __ CuleS(0, rhs, lhs); + } else { + __ ColeS(0, rhs, lhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + default: + LOG(FATAL) << "Unexpected non-floating-point condition " << cond; + UNREACHABLE(); + } + } + } else { + DCHECK_EQ(type, Primitive::kPrimDouble); + if (isR6) { + switch (cond) { + case kCondEQ: + __ CmpEqD(FTMP, lhs, rhs); + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondNE: + __ CmpEqD(FTMP, lhs, rhs); + __ Mfc1(dst, FTMP); + __ Addiu(dst, dst, 1); + break; + case kCondLT: + if (gt_bias) { + __ CmpLtD(FTMP, lhs, rhs); + } else { + __ CmpUltD(FTMP, lhs, rhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondLE: + if (gt_bias) { + __ CmpLeD(FTMP, lhs, rhs); + } else { + __ CmpUleD(FTMP, lhs, rhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondGT: + if (gt_bias) { + __ CmpUltD(FTMP, rhs, lhs); + } else { + __ CmpLtD(FTMP, rhs, lhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + case kCondGE: + if (gt_bias) { + __ CmpUleD(FTMP, rhs, lhs); + } else { + __ CmpLeD(FTMP, rhs, lhs); + } + __ Mfc1(dst, FTMP); + __ Andi(dst, dst, 1); + break; + default: + LOG(FATAL) << "Unexpected non-floating-point condition " << cond; + UNREACHABLE(); + } + } else { + switch (cond) { + case kCondEQ: + __ CeqD(0, lhs, rhs); + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondNE: + __ CeqD(0, lhs, rhs); + __ LoadConst32(dst, 1); + __ Movt(dst, ZERO, 0); + break; + case kCondLT: + if (gt_bias) { + __ ColtD(0, lhs, rhs); + } else { + __ CultD(0, lhs, rhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondLE: + if (gt_bias) { + __ ColeD(0, lhs, rhs); + } else { + __ CuleD(0, lhs, rhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondGT: + if (gt_bias) { + __ CultD(0, rhs, lhs); + } else { + __ ColtD(0, rhs, lhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + case kCondGE: + if (gt_bias) { + __ CuleD(0, rhs, lhs); + } else { + __ ColeD(0, rhs, lhs); + } + __ LoadConst32(dst, 1); + __ Movf(dst, ZERO, 0); + break; + default: + LOG(FATAL) << "Unexpected non-floating-point condition " << cond; + UNREACHABLE(); + } + } + } +} + void InstructionCodeGeneratorMIPS::GenerateFpCompareAndBranch(IfCondition cond, bool gt_bias, Primitive::Type type, diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index 7ba6c0da0c..003998129e 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -243,6 +243,10 @@ class InstructionCodeGeneratorMIPS : public InstructionCodeGenerator { void GenerateLongCompareAndBranch(IfCondition cond, LocationSummary* locations, MipsLabel* label); + void GenerateFpCompare(IfCondition cond, + bool gt_bias, + Primitive::Type type, + LocationSummary* locations); void GenerateFpCompareAndBranch(IfCondition cond, bool gt_bias, Primitive::Type type, diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 4689ccb05c..e18b366411 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -213,35 +213,6 @@ class SuspendCheckSlowPathX86 : public SlowPathCode { DISALLOW_COPY_AND_ASSIGN(SuspendCheckSlowPathX86); }; -class LoadStringSlowPathX86 : public SlowPathCode { - public: - explicit LoadStringSlowPathX86(HLoadString* instruction): SlowPathCode(instruction) {} - - void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { - LocationSummary* locations = instruction_->GetLocations(); - DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); - - CodeGeneratorX86* x86_codegen = down_cast<CodeGeneratorX86*>(codegen); - __ Bind(GetEntryLabel()); - SaveLiveRegisters(codegen, locations); - - InvokeRuntimeCallingConvention calling_convention; - const uint32_t string_index = instruction_->AsLoadString()->GetStringIndex(); - __ movl(calling_convention.GetRegisterAt(0), Immediate(string_index)); - x86_codegen->InvokeRuntime(kQuickResolveString, instruction_, instruction_->GetDexPc(), this); - CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); - x86_codegen->Move32(locations->Out(), Location::RegisterLocation(EAX)); - RestoreLiveRegisters(codegen, locations); - - __ jmp(GetExitLabel()); - } - - const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathX86"; } - - private: - DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathX86); -}; - class LoadClassSlowPathX86 : public SlowPathCode { public: LoadClassSlowPathX86(HLoadClass* cls, @@ -5238,7 +5209,6 @@ void LocationsBuilderX86::VisitArraySet(HArraySet* instruction) { } if (needs_write_barrier) { // Temporary registers for the write barrier. - // These registers may be used for Baker read barriers too. locations->AddTemp(Location::RequiresRegister()); // Possibly used for ref. poisoning too. // Ensure the card is in a byte register. locations->AddTemp(Location::RegisterLocation(ECX)); @@ -5328,105 +5298,40 @@ void InstructionCodeGeneratorX86::VisitArraySet(HArraySet* instruction) { __ Bind(¬_null); } - if (kEmitCompilerReadBarrier) { - if (!kUseBakerReadBarrier) { - // When (non-Baker) read barriers are enabled, the type - // checking instrumentation requires two read barriers - // generated by CodeGeneratorX86::GenerateReadBarrierSlow: - // - // __ movl(temp2, temp); - // // /* HeapReference<Class> */ temp = temp->component_type_ - // __ movl(temp, Address(temp, component_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp_loc, temp_loc, temp2_loc, component_offset); - // - // // /* HeapReference<Class> */ temp2 = register_value->klass_ - // __ movl(temp2, Address(register_value, class_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp2_loc, temp2_loc, value, class_offset, temp_loc); - // - // __ cmpl(temp, temp2); - // - // However, the second read barrier may trash `temp`, as it - // is a temporary register, and as such would not be saved - // along with live registers before calling the runtime (nor - // restored afterwards). So in this case, we bail out and - // delegate the work to the array set slow path. - // - // TODO: Extend the register allocator to support a new - // "(locally) live temp" location so as to avoid always - // going into the slow path when read barriers are enabled? - // - // There is no such problem with Baker read barriers (see below). - __ jmp(slow_path->GetEntryLabel()); - } else { - Location temp2_loc = locations->GetTemp(1); - Register temp2 = temp2_loc.AsRegister<Register>(); - // /* HeapReference<Class> */ temp = array->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp_loc, array, class_offset, /* needs_null_check */ true); - - // /* HeapReference<Class> */ temp = temp->component_type_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp_loc, temp, component_offset, /* needs_null_check */ false); - // Register `temp` is not trashed by the read barrier - // emitted by GenerateFieldLoadWithBakerReadBarrier below, - // as that method produces a call to a ReadBarrierMarkRegX - // entry point, which saves all potentially live registers, - // including temporaries such a `temp`. - // /* HeapReference<Class> */ temp2 = register_value->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp2_loc, register_value, class_offset, /* needs_null_check */ false); - // If heap poisoning is enabled, `temp` and `temp2` have - // been unpoisoned by the the previous calls to - // CodeGeneratorX86::GenerateFieldLoadWithBakerReadBarrier. - __ cmpl(temp, temp2); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - __ j(kEqual, &do_put); - // We do not need to emit a read barrier for the - // following heap reference load, as `temp` is only used - // in a comparison with null below, and this reference - // is not kept afterwards. Also, if heap poisoning is - // enabled, there is no need to unpoison that heap - // reference for the same reason (comparison with null). - __ cmpl(Address(temp, super_offset), Immediate(0)); - __ j(kNotEqual, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ j(kNotEqual, slow_path->GetEntryLabel()); - } - } - } else { - // Non read barrier code. + // Note that when Baker read barriers are enabled, the type + // checks are performed without read barriers. This is fine, + // even in the case where a class object is in the from-space + // after the flip, as a comparison involving such a type would + // not produce a false positive; it may of course produce a + // false negative, in which case we would take the ArraySet + // slow path. - // /* HeapReference<Class> */ temp = array->klass_ - __ movl(temp, Address(array, class_offset)); - codegen_->MaybeRecordImplicitNullCheck(instruction); + // /* HeapReference<Class> */ temp = array->klass_ + __ movl(temp, Address(array, class_offset)); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ MaybeUnpoisonHeapReference(temp); + + // /* HeapReference<Class> */ temp = temp->component_type_ + __ movl(temp, Address(temp, component_offset)); + // If heap poisoning is enabled, no need to unpoison `temp` + // nor the object reference in `register_value->klass`, as + // we are comparing two poisoned references. + __ cmpl(temp, Address(register_value, class_offset)); + + if (instruction->StaticTypeOfArrayIsObjectArray()) { + __ j(kEqual, &do_put); + // If heap poisoning is enabled, the `temp` reference has + // not been unpoisoned yet; unpoison it now. __ MaybeUnpoisonHeapReference(temp); - // /* HeapReference<Class> */ temp = temp->component_type_ - __ movl(temp, Address(temp, component_offset)); - // If heap poisoning is enabled, no need to unpoison `temp` - // nor the object reference in `register_value->klass`, as - // we are comparing two poisoned references. - __ cmpl(temp, Address(register_value, class_offset)); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - __ j(kEqual, &do_put); - // If heap poisoning is enabled, the `temp` reference has - // not been unpoisoned yet; unpoison it now. - __ MaybeUnpoisonHeapReference(temp); - - // If heap poisoning is enabled, no need to unpoison the - // heap reference loaded below, as it is only used for a - // comparison with null. - __ cmpl(Address(temp, super_offset), Immediate(0)); - __ j(kNotEqual, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ j(kNotEqual, slow_path->GetEntryLabel()); - } + // If heap poisoning is enabled, no need to unpoison the + // heap reference loaded below, as it is only used for a + // comparison with null. + __ cmpl(Address(temp, super_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + __ Bind(&do_put); + } else { + __ j(kNotEqual, slow_path->GetEntryLabel()); } } @@ -6160,17 +6065,6 @@ void InstructionCodeGeneratorX86::GenerateClassInitializationCheck( HLoadString::LoadKind CodeGeneratorX86::GetSupportedLoadStringKind( HLoadString::LoadKind desired_string_load_kind) { - if (kEmitCompilerReadBarrier) { - switch (desired_string_load_kind) { - case HLoadString::LoadKind::kBootImageLinkTimeAddress: - case HLoadString::LoadKind::kBootImageLinkTimePcRelative: - case HLoadString::LoadKind::kBootImageAddress: - // TODO: Implement for read barrier. - return HLoadString::LoadKind::kDexCacheViaMethod; - default: - break; - } - } switch (desired_string_load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: DCHECK(!GetCompilerOptions().GetCompilePic()); @@ -6201,20 +6095,20 @@ HLoadString::LoadKind CodeGeneratorX86::GetSupportedLoadStringKind( void LocationsBuilderX86::VisitLoadString(HLoadString* load) { LocationSummary::CallKind call_kind = (load->NeedsEnvironment() || kEmitCompilerReadBarrier) - ? LocationSummary::kCallOnSlowPath + ? LocationSummary::kCallOnMainOnly : LocationSummary::kNoCall; LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(load, call_kind); - if (kUseBakerReadBarrier && !load->NeedsEnvironment()) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } - HLoadString::LoadKind load_kind = load->GetLoadKind(); if (load_kind == HLoadString::LoadKind::kDexCacheViaMethod || load_kind == HLoadString::LoadKind::kBootImageLinkTimePcRelative || load_kind == HLoadString::LoadKind::kDexCachePcRelative) { locations->SetInAt(0, Location::RequiresRegister()); } - locations->SetOut(Location::RequiresRegister()); + if (load_kind == HLoadString::LoadKind::kDexCacheViaMethod) { + locations->SetOut(Location::RegisterLocation(EAX)); + } else { + locations->SetOut(Location::RequiresRegister()); + } } void InstructionCodeGeneratorX86::VisitLoadString(HLoadString* load) { @@ -6224,20 +6118,17 @@ void InstructionCodeGeneratorX86::VisitLoadString(HLoadString* load) { switch (load->GetLoadKind()) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: { - DCHECK(!kEmitCompilerReadBarrier); __ movl(out, Immediate(/* placeholder */ 0)); codegen_->RecordStringPatch(load); return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { - DCHECK(!kEmitCompilerReadBarrier); Register method_address = locations->InAt(0).AsRegister<Register>(); __ leal(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset)); codegen_->RecordStringPatch(load); return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageAddress: { - DCHECK(!kEmitCompilerReadBarrier); DCHECK_NE(load->GetAddress(), 0u); uint32_t address = dchecked_integral_cast<uint32_t>(load->GetAddress()); __ movl(out, Immediate(address)); @@ -6249,10 +6140,10 @@ void InstructionCodeGeneratorX86::VisitLoadString(HLoadString* load) { } // TODO: Re-add the compiler code to do string dex cache lookup again. - SlowPathCode* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathX86(load); - codegen_->AddSlowPath(slow_path); - __ jmp(slow_path->GetEntryLabel()); - __ Bind(slow_path->GetExitLabel()); + InvokeRuntimeCallingConvention calling_convention; + __ movl(calling_convention.GetRegisterAt(0), Immediate(load->GetStringIndex())); + codegen_->InvokeRuntime(kQuickResolveString, load, load->GetDexPc()); + CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); } static Address GetExceptionTlsAddress() { @@ -6518,7 +6409,6 @@ void LocationsBuilderX86::VisitCheckCast(HCheckCast* instruction) { LocationSummary::CallKind call_kind = LocationSummary::kNoCall; bool throws_into_catch = instruction->CanThrowIntoCatchBlock(); TypeCheckKind type_check_kind = instruction->GetTypeCheckKind(); - bool baker_read_barrier_slow_path = false; switch (type_check_kind) { case TypeCheckKind::kExactCheck: case TypeCheckKind::kAbstractClassCheck: @@ -6527,7 +6417,6 @@ void LocationsBuilderX86::VisitCheckCast(HCheckCast* instruction) { call_kind = (throws_into_catch || kEmitCompilerReadBarrier) ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall; // In fact, call on a fatal (non-returning) slow path. - baker_read_barrier_slow_path = kUseBakerReadBarrier && !throws_into_catch; break; case TypeCheckKind::kArrayCheck: case TypeCheckKind::kUnresolvedCheck: @@ -6536,9 +6425,6 @@ void LocationsBuilderX86::VisitCheckCast(HCheckCast* instruction) { break; } LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, call_kind); - if (baker_read_barrier_slow_path) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } locations->SetInAt(0, Location::RequiresRegister()); locations->SetInAt(1, Location::Any()); // Note that TypeCheckSlowPathX86 uses this "temp" register too. diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index a21a09ee8a..15307fe50c 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -288,37 +288,6 @@ class LoadClassSlowPathX86_64 : public SlowPathCode { DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathX86_64); }; -class LoadStringSlowPathX86_64 : public SlowPathCode { - public: - explicit LoadStringSlowPathX86_64(HLoadString* instruction) : SlowPathCode(instruction) {} - - void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { - LocationSummary* locations = instruction_->GetLocations(); - DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); - - CodeGeneratorX86_64* x86_64_codegen = down_cast<CodeGeneratorX86_64*>(codegen); - __ Bind(GetEntryLabel()); - SaveLiveRegisters(codegen, locations); - - InvokeRuntimeCallingConvention calling_convention; - const uint32_t string_index = instruction_->AsLoadString()->GetStringIndex(); - __ movl(CpuRegister(calling_convention.GetRegisterAt(0)), Immediate(string_index)); - x86_64_codegen->InvokeRuntime(kQuickResolveString, - instruction_, - instruction_->GetDexPc(), - this); - CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); - x86_64_codegen->Move(locations->Out(), Location::RegisterLocation(RAX)); - RestoreLiveRegisters(codegen, locations); - __ jmp(GetExitLabel()); - } - - const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathX86_64"; } - - private: - DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathX86_64); -}; - class TypeCheckSlowPathX86_64 : public SlowPathCode { public: TypeCheckSlowPathX86_64(HInstruction* instruction, bool is_fatal) @@ -4732,7 +4701,6 @@ void LocationsBuilderX86_64::VisitArraySet(HArraySet* instruction) { if (needs_write_barrier) { // Temporary registers for the write barrier. - // These registers may be used for Baker read barriers too. locations->AddTemp(Location::RequiresRegister()); // Possibly used for ref. poisoning too. locations->AddTemp(Location::RequiresRegister()); } @@ -4822,105 +4790,40 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { __ Bind(¬_null); } - if (kEmitCompilerReadBarrier) { - if (!kUseBakerReadBarrier) { - // When (non-Baker) read barriers are enabled, the type - // checking instrumentation requires two read barriers - // generated by CodeGeneratorX86_64::GenerateReadBarrierSlow: - // - // __ movl(temp2, temp); - // // /* HeapReference<Class> */ temp = temp->component_type_ - // __ movl(temp, Address(temp, component_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp_loc, temp_loc, temp2_loc, component_offset); - // - // // /* HeapReference<Class> */ temp2 = register_value->klass_ - // __ movl(temp2, Address(register_value, class_offset)); - // codegen_->GenerateReadBarrierSlow( - // instruction, temp2_loc, temp2_loc, value, class_offset, temp_loc); - // - // __ cmpl(temp, temp2); - // - // However, the second read barrier may trash `temp`, as it - // is a temporary register, and as such would not be saved - // along with live registers before calling the runtime (nor - // restored afterwards). So in this case, we bail out and - // delegate the work to the array set slow path. - // - // TODO: Extend the register allocator to support a new - // "(locally) live temp" location so as to avoid always - // going into the slow path when read barriers are enabled? - // - // There is no such problem with Baker read barriers (see below). - __ jmp(slow_path->GetEntryLabel()); - } else { - Location temp2_loc = locations->GetTemp(1); - CpuRegister temp2 = temp2_loc.AsRegister<CpuRegister>(); - // /* HeapReference<Class> */ temp = array->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp_loc, array, class_offset, /* needs_null_check */ true); - - // /* HeapReference<Class> */ temp = temp->component_type_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp_loc, temp, component_offset, /* needs_null_check */ false); - // Register `temp` is not trashed by the read barrier - // emitted by GenerateFieldLoadWithBakerReadBarrier below, - // as that method produces a call to a ReadBarrierMarkRegX - // entry point, which saves all potentially live registers, - // including temporaries such a `temp`. - // /* HeapReference<Class> */ temp2 = register_value->klass_ - codegen_->GenerateFieldLoadWithBakerReadBarrier( - instruction, temp2_loc, register_value, class_offset, /* needs_null_check */ false); - // If heap poisoning is enabled, `temp` and `temp2` have - // been unpoisoned by the the previous calls to - // CodeGeneratorX86_64::GenerateFieldLoadWithBakerReadBarrier. - __ cmpl(temp, temp2); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - __ j(kEqual, &do_put); - // We do not need to emit a read barrier for the - // following heap reference load, as `temp` is only used - // in a comparison with null below, and this reference - // is not kept afterwards. Also, if heap poisoning is - // enabled, there is no need to unpoison that heap - // reference for the same reason (comparison with null). - __ cmpl(Address(temp, super_offset), Immediate(0)); - __ j(kNotEqual, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ j(kNotEqual, slow_path->GetEntryLabel()); - } - } - } else { - // Non read barrier code. + // Note that when Baker read barriers are enabled, the type + // checks are performed without read barriers. This is fine, + // even in the case where a class object is in the from-space + // after the flip, as a comparison involving such a type would + // not produce a false positive; it may of course produce a + // false negative, in which case we would take the ArraySet + // slow path. - // /* HeapReference<Class> */ temp = array->klass_ - __ movl(temp, Address(array, class_offset)); - codegen_->MaybeRecordImplicitNullCheck(instruction); + // /* HeapReference<Class> */ temp = array->klass_ + __ movl(temp, Address(array, class_offset)); + codegen_->MaybeRecordImplicitNullCheck(instruction); + __ MaybeUnpoisonHeapReference(temp); + + // /* HeapReference<Class> */ temp = temp->component_type_ + __ movl(temp, Address(temp, component_offset)); + // If heap poisoning is enabled, no need to unpoison `temp` + // nor the object reference in `register_value->klass`, as + // we are comparing two poisoned references. + __ cmpl(temp, Address(register_value, class_offset)); + + if (instruction->StaticTypeOfArrayIsObjectArray()) { + __ j(kEqual, &do_put); + // If heap poisoning is enabled, the `temp` reference has + // not been unpoisoned yet; unpoison it now. __ MaybeUnpoisonHeapReference(temp); - // /* HeapReference<Class> */ temp = temp->component_type_ - __ movl(temp, Address(temp, component_offset)); - // If heap poisoning is enabled, no need to unpoison `temp` - // nor the object reference in `register_value->klass`, as - // we are comparing two poisoned references. - __ cmpl(temp, Address(register_value, class_offset)); - - if (instruction->StaticTypeOfArrayIsObjectArray()) { - __ j(kEqual, &do_put); - // If heap poisoning is enabled, the `temp` reference has - // not been unpoisoned yet; unpoison it now. - __ MaybeUnpoisonHeapReference(temp); - - // If heap poisoning is enabled, no need to unpoison the - // heap reference loaded below, as it is only used for a - // comparison with null. - __ cmpl(Address(temp, super_offset), Immediate(0)); - __ j(kNotEqual, slow_path->GetEntryLabel()); - __ Bind(&do_put); - } else { - __ j(kNotEqual, slow_path->GetEntryLabel()); - } + // If heap poisoning is enabled, no need to unpoison the + // heap reference loaded below, as it is only used for a + // comparison with null. + __ cmpl(Address(temp, super_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + __ Bind(&do_put); + } else { + __ j(kNotEqual, slow_path->GetEntryLabel()); } } @@ -5592,17 +5495,6 @@ void InstructionCodeGeneratorX86_64::VisitClinitCheck(HClinitCheck* check) { HLoadString::LoadKind CodeGeneratorX86_64::GetSupportedLoadStringKind( HLoadString::LoadKind desired_string_load_kind) { - if (kEmitCompilerReadBarrier) { - switch (desired_string_load_kind) { - case HLoadString::LoadKind::kBootImageLinkTimeAddress: - case HLoadString::LoadKind::kBootImageLinkTimePcRelative: - case HLoadString::LoadKind::kBootImageAddress: - // TODO: Implement for read barrier. - return HLoadString::LoadKind::kDexCacheViaMethod; - default: - break; - } - } switch (desired_string_load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: DCHECK(!GetCompilerOptions().GetCompilePic()); @@ -5626,18 +5518,16 @@ HLoadString::LoadKind CodeGeneratorX86_64::GetSupportedLoadStringKind( } void LocationsBuilderX86_64::VisitLoadString(HLoadString* load) { - LocationSummary::CallKind call_kind = (load->NeedsEnvironment() || kEmitCompilerReadBarrier) - ? LocationSummary::kCallOnSlowPath + LocationSummary::CallKind call_kind = load->NeedsEnvironment() + ? LocationSummary::kCallOnMainOnly : LocationSummary::kNoCall; LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(load, call_kind); - if (kUseBakerReadBarrier && !load->NeedsEnvironment()) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } - if (load->GetLoadKind() == HLoadString::LoadKind::kDexCacheViaMethod) { locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::RegisterLocation(RAX)); + } else { + locations->SetOut(Location::RequiresRegister()); } - locations->SetOut(Location::RequiresRegister()); } void InstructionCodeGeneratorX86_64::VisitLoadString(HLoadString* load) { @@ -5647,13 +5537,11 @@ void InstructionCodeGeneratorX86_64::VisitLoadString(HLoadString* load) { switch (load->GetLoadKind()) { case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { - DCHECK(!kEmitCompilerReadBarrier); __ leal(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip */ false)); codegen_->RecordStringPatch(load); return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageAddress: { - DCHECK(!kEmitCompilerReadBarrier); DCHECK_NE(load->GetAddress(), 0u); uint32_t address = dchecked_integral_cast<uint32_t>(load->GetAddress()); __ movl(out, Immediate(address)); // Zero-extended. @@ -5665,10 +5553,13 @@ void InstructionCodeGeneratorX86_64::VisitLoadString(HLoadString* load) { } // TODO: Re-add the compiler code to do string dex cache lookup again. - SlowPathCode* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathX86_64(load); - codegen_->AddSlowPath(slow_path); - __ jmp(slow_path->GetEntryLabel()); - __ Bind(slow_path->GetExitLabel()); + InvokeRuntimeCallingConvention calling_convention; + __ movl(CpuRegister(calling_convention.GetRegisterAt(0)), + Immediate(load->GetStringIndex())); + codegen_->InvokeRuntime(kQuickResolveString, + load, + load->GetDexPc()); + CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); } static Address GetExceptionTlsAddress() { @@ -5940,7 +5831,6 @@ void LocationsBuilderX86_64::VisitCheckCast(HCheckCast* instruction) { LocationSummary::CallKind call_kind = LocationSummary::kNoCall; bool throws_into_catch = instruction->CanThrowIntoCatchBlock(); TypeCheckKind type_check_kind = instruction->GetTypeCheckKind(); - bool baker_read_barrier_slow_path = false; switch (type_check_kind) { case TypeCheckKind::kExactCheck: case TypeCheckKind::kAbstractClassCheck: @@ -5949,7 +5839,6 @@ void LocationsBuilderX86_64::VisitCheckCast(HCheckCast* instruction) { call_kind = (throws_into_catch || kEmitCompilerReadBarrier) ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall; // In fact, call on a fatal (non-returning) slow path. - baker_read_barrier_slow_path = kUseBakerReadBarrier && !throws_into_catch; break; case TypeCheckKind::kArrayCheck: case TypeCheckKind::kUnresolvedCheck: @@ -5958,9 +5847,6 @@ void LocationsBuilderX86_64::VisitCheckCast(HCheckCast* instruction) { break; } LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, call_kind); - if (baker_read_barrier_slow_path) { - locations->SetCustomSlowPathCallerSaves(RegisterSet()); // No caller-save registers. - } locations->SetInAt(0, Location::RequiresRegister()); locations->SetInAt(1, Location::Any()); // Note that TypeCheckSlowPathX86_64 uses this "temp" register too. diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h index cea4a7e1a6..eda0971ecc 100644 --- a/compiler/optimizing/common_arm64.h +++ b/compiler/optimizing/common_arm64.h @@ -38,7 +38,7 @@ namespace helpers { static_assert((SP == 31) && (WSP == 31) && (XZR == 32) && (WZR == 32), "Unexpected values for register codes."); -static inline int VIXLRegCodeFromART(int code) { +inline int VIXLRegCodeFromART(int code) { if (code == SP) { return vixl::aarch64::kSPRegInternalCode; } @@ -48,7 +48,7 @@ static inline int VIXLRegCodeFromART(int code) { return code; } -static inline int ARTRegCodeFromVIXL(int code) { +inline int ARTRegCodeFromVIXL(int code) { if (code == vixl::aarch64::kSPRegInternalCode) { return SP; } @@ -58,85 +58,85 @@ static inline int ARTRegCodeFromVIXL(int code) { return code; } -static inline vixl::aarch64::Register XRegisterFrom(Location location) { +inline vixl::aarch64::Register XRegisterFrom(Location location) { DCHECK(location.IsRegister()) << location; return vixl::aarch64::Register::GetXRegFromCode(VIXLRegCodeFromART(location.reg())); } -static inline vixl::aarch64::Register WRegisterFrom(Location location) { +inline vixl::aarch64::Register WRegisterFrom(Location location) { DCHECK(location.IsRegister()) << location; return vixl::aarch64::Register::GetWRegFromCode(VIXLRegCodeFromART(location.reg())); } -static inline vixl::aarch64::Register RegisterFrom(Location location, Primitive::Type type) { +inline vixl::aarch64::Register RegisterFrom(Location location, Primitive::Type type) { DCHECK(type != Primitive::kPrimVoid && !Primitive::IsFloatingPointType(type)) << type; return type == Primitive::kPrimLong ? XRegisterFrom(location) : WRegisterFrom(location); } -static inline vixl::aarch64::Register OutputRegister(HInstruction* instr) { +inline vixl::aarch64::Register OutputRegister(HInstruction* instr) { return RegisterFrom(instr->GetLocations()->Out(), instr->GetType()); } -static inline vixl::aarch64::Register InputRegisterAt(HInstruction* instr, int input_index) { +inline vixl::aarch64::Register InputRegisterAt(HInstruction* instr, int input_index) { return RegisterFrom(instr->GetLocations()->InAt(input_index), instr->InputAt(input_index)->GetType()); } -static inline vixl::aarch64::FPRegister DRegisterFrom(Location location) { +inline vixl::aarch64::FPRegister DRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; return vixl::aarch64::FPRegister::GetDRegFromCode(location.reg()); } -static inline vixl::aarch64::FPRegister SRegisterFrom(Location location) { +inline vixl::aarch64::FPRegister SRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; return vixl::aarch64::FPRegister::GetSRegFromCode(location.reg()); } -static inline vixl::aarch64::FPRegister FPRegisterFrom(Location location, Primitive::Type type) { +inline vixl::aarch64::FPRegister FPRegisterFrom(Location location, Primitive::Type type) { DCHECK(Primitive::IsFloatingPointType(type)) << type; return type == Primitive::kPrimDouble ? DRegisterFrom(location) : SRegisterFrom(location); } -static inline vixl::aarch64::FPRegister OutputFPRegister(HInstruction* instr) { +inline vixl::aarch64::FPRegister OutputFPRegister(HInstruction* instr) { return FPRegisterFrom(instr->GetLocations()->Out(), instr->GetType()); } -static inline vixl::aarch64::FPRegister InputFPRegisterAt(HInstruction* instr, int input_index) { +inline vixl::aarch64::FPRegister InputFPRegisterAt(HInstruction* instr, int input_index) { return FPRegisterFrom(instr->GetLocations()->InAt(input_index), instr->InputAt(input_index)->GetType()); } -static inline vixl::aarch64::CPURegister CPURegisterFrom(Location location, Primitive::Type type) { +inline vixl::aarch64::CPURegister CPURegisterFrom(Location location, Primitive::Type type) { return Primitive::IsFloatingPointType(type) ? vixl::aarch64::CPURegister(FPRegisterFrom(location, type)) : vixl::aarch64::CPURegister(RegisterFrom(location, type)); } -static inline vixl::aarch64::CPURegister OutputCPURegister(HInstruction* instr) { +inline vixl::aarch64::CPURegister OutputCPURegister(HInstruction* instr) { return Primitive::IsFloatingPointType(instr->GetType()) ? static_cast<vixl::aarch64::CPURegister>(OutputFPRegister(instr)) : static_cast<vixl::aarch64::CPURegister>(OutputRegister(instr)); } -static inline vixl::aarch64::CPURegister InputCPURegisterAt(HInstruction* instr, int index) { +inline vixl::aarch64::CPURegister InputCPURegisterAt(HInstruction* instr, int index) { return Primitive::IsFloatingPointType(instr->InputAt(index)->GetType()) ? static_cast<vixl::aarch64::CPURegister>(InputFPRegisterAt(instr, index)) : static_cast<vixl::aarch64::CPURegister>(InputRegisterAt(instr, index)); } -static inline vixl::aarch64::CPURegister InputCPURegisterOrZeroRegAt(HInstruction* instr, +inline vixl::aarch64::CPURegister InputCPURegisterOrZeroRegAt(HInstruction* instr, int index) { HInstruction* input = instr->InputAt(index); Primitive::Type input_type = input->GetType(); if (input->IsConstant() && input->AsConstant()->IsZeroBitPattern()) { return (Primitive::ComponentSize(input_type) >= vixl::aarch64::kXRegSizeInBytes) - ? vixl::aarch64::xzr + ? vixl::aarch64::xzr : vixl::aarch64::wzr; } return InputCPURegisterAt(instr, index); } -static inline int64_t Int64ConstantFrom(Location location) { +inline int64_t Int64ConstantFrom(Location location) { HConstant* instr = location.GetConstant(); if (instr->IsIntConstant()) { return instr->AsIntConstant()->GetValue(); @@ -148,7 +148,7 @@ static inline int64_t Int64ConstantFrom(Location location) { } } -static inline vixl::aarch64::Operand OperandFrom(Location location, Primitive::Type type) { +inline vixl::aarch64::Operand OperandFrom(Location location, Primitive::Type type) { if (location.IsRegister()) { return vixl::aarch64::Operand(RegisterFrom(location, type)); } else { @@ -156,23 +156,23 @@ static inline vixl::aarch64::Operand OperandFrom(Location location, Primitive::T } } -static inline vixl::aarch64::Operand InputOperandAt(HInstruction* instr, int input_index) { +inline vixl::aarch64::Operand InputOperandAt(HInstruction* instr, int input_index) { return OperandFrom(instr->GetLocations()->InAt(input_index), instr->InputAt(input_index)->GetType()); } -static inline vixl::aarch64::MemOperand StackOperandFrom(Location location) { +inline vixl::aarch64::MemOperand StackOperandFrom(Location location) { return vixl::aarch64::MemOperand(vixl::aarch64::sp, location.GetStackIndex()); } -static inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, +inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, size_t offset = 0) { // A heap reference must be 32bit, so fit in a W register. DCHECK(base.IsW()); return vixl::aarch64::MemOperand(base.X(), offset); } -static inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, +inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, const vixl::aarch64::Register& regoffset, vixl::aarch64::Shift shift = vixl::aarch64::LSL, unsigned shift_amount = 0) { @@ -181,24 +181,24 @@ static inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Registe return vixl::aarch64::MemOperand(base.X(), regoffset, shift, shift_amount); } -static inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, +inline vixl::aarch64::MemOperand HeapOperand(const vixl::aarch64::Register& base, Offset offset) { return HeapOperand(base, offset.SizeValue()); } -static inline vixl::aarch64::MemOperand HeapOperandFrom(Location location, Offset offset) { +inline vixl::aarch64::MemOperand HeapOperandFrom(Location location, Offset offset) { return HeapOperand(RegisterFrom(location, Primitive::kPrimNot), offset); } -static inline Location LocationFrom(const vixl::aarch64::Register& reg) { +inline Location LocationFrom(const vixl::aarch64::Register& reg) { return Location::RegisterLocation(ARTRegCodeFromVIXL(reg.GetCode())); } -static inline Location LocationFrom(const vixl::aarch64::FPRegister& fpreg) { +inline Location LocationFrom(const vixl::aarch64::FPRegister& fpreg) { return Location::FpuRegisterLocation(fpreg.GetCode()); } -static inline vixl::aarch64::Operand OperandFromMemOperand( +inline vixl::aarch64::Operand OperandFromMemOperand( const vixl::aarch64::MemOperand& mem_op) { if (mem_op.IsImmediateOffset()) { return vixl::aarch64::Operand(mem_op.GetOffset()); @@ -219,7 +219,7 @@ static inline vixl::aarch64::Operand OperandFromMemOperand( } } -static bool CanEncodeConstantAsImmediate(HConstant* constant, HInstruction* instr) { +inline bool CanEncodeConstantAsImmediate(HConstant* constant, HInstruction* instr) { DCHECK(constant->IsIntConstant() || constant->IsLongConstant() || constant->IsNullConstant()) << constant->DebugName(); @@ -258,7 +258,7 @@ static bool CanEncodeConstantAsImmediate(HConstant* constant, HInstruction* inst } } -static inline Location ARM64EncodableConstantOrRegister(HInstruction* constant, +inline Location ARM64EncodableConstantOrRegister(HInstruction* constant, HInstruction* instr) { if (constant->IsConstant() && CanEncodeConstantAsImmediate(constant->AsConstant(), instr)) { @@ -272,7 +272,7 @@ static inline Location ARM64EncodableConstantOrRegister(HInstruction* constant, // codes are same, we can initialize vixl register list simply by the register masks. Currently, // only SP/WSP and ZXR/WZR codes are different between art and vixl. // Note: This function is only used for debug checks. -static inline bool ArtVixlRegCodeCoherentForRegSet(uint32_t art_core_registers, +inline bool ArtVixlRegCodeCoherentForRegSet(uint32_t art_core_registers, size_t num_core, uint32_t art_fpu_registers, size_t num_fpu) { @@ -290,7 +290,7 @@ static inline bool ArtVixlRegCodeCoherentForRegSet(uint32_t art_core_registers, return true; } -static inline vixl::aarch64::Shift ShiftFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) { +inline vixl::aarch64::Shift ShiftFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) { switch (op_kind) { case HArm64DataProcWithShifterOp::kASR: return vixl::aarch64::ASR; case HArm64DataProcWithShifterOp::kLSL: return vixl::aarch64::LSL; @@ -302,7 +302,7 @@ static inline vixl::aarch64::Shift ShiftFromOpKind(HArm64DataProcWithShifterOp:: } } -static inline vixl::aarch64::Extend ExtendFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) { +inline vixl::aarch64::Extend ExtendFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) { switch (op_kind) { case HArm64DataProcWithShifterOp::kUXTB: return vixl::aarch64::UXTB; case HArm64DataProcWithShifterOp::kUXTH: return vixl::aarch64::UXTH; @@ -317,7 +317,7 @@ static inline vixl::aarch64::Extend ExtendFromOpKind(HArm64DataProcWithShifterOp } } -static inline bool CanFitInShifterOperand(HInstruction* instruction) { +inline bool CanFitInShifterOperand(HInstruction* instruction) { if (instruction->IsTypeConversion()) { HTypeConversion* conversion = instruction->AsTypeConversion(); Primitive::Type result_type = conversion->GetResultType(); @@ -332,7 +332,7 @@ static inline bool CanFitInShifterOperand(HInstruction* instruction) { } } -static inline bool HasShifterOperand(HInstruction* instr) { +inline bool HasShifterOperand(HInstruction* instr) { // `neg` instructions are an alias of `sub` using the zero register as the // first register input. bool res = instr->IsAdd() || instr->IsAnd() || instr->IsNeg() || @@ -340,7 +340,7 @@ static inline bool HasShifterOperand(HInstruction* instr) { return res; } -static inline bool ShifterOperandSupportsExtension(HInstruction* instruction) { +inline bool ShifterOperandSupportsExtension(HInstruction* instruction) { DCHECK(HasShifterOperand(instruction)); // Although the `neg` instruction is an alias of the `sub` instruction, `HNeg` // does *not* support extension. This is because the `extended register` form @@ -351,7 +351,7 @@ static inline bool ShifterOperandSupportsExtension(HInstruction* instruction) { return instruction->IsAdd() || instruction->IsSub(); } -static inline bool IsConstantZeroBitPattern(const HInstruction* instruction) { +inline bool IsConstantZeroBitPattern(const HInstruction* instruction) { return instruction->IsConstant() && instruction->AsConstant()->IsZeroBitPattern(); } diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index 8d4d143696..b8e1379ef9 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -297,7 +297,15 @@ void HSharpening::ProcessLoadString(HLoadString* load_string) { DCHECK(!runtime->UseJitCompilation()); mirror::String* string = class_linker->ResolveString(dex_file, string_index, dex_cache); CHECK(string != nullptr); - // TODO: In follow up CL, add PcRelative and Address back in. + if (compiler_driver_->GetSupportBootImageFixup()) { + DCHECK(ContainsElement(compiler_driver_->GetDexFilesForOatFile(), &dex_file)); + desired_load_kind = codegen_->GetCompilerOptions().GetCompilePic() + ? HLoadString::LoadKind::kBootImageLinkTimePcRelative + : HLoadString::LoadKind::kBootImageLinkTimeAddress; + } else { + // MIPS64 or compiler_driver_test. Do not sharpen. + DCHECK_EQ(desired_load_kind, HLoadString::LoadKind::kDexCacheViaMethod); + } } else if (runtime->UseJitCompilation()) { // TODO: Make sure we don't set the "compile PIC" flag for JIT as that's bogus. // DCHECK(!codegen_->GetCompilerOptions().GetCompilePic()); diff --git a/compiler/utils/arm/jni_macro_assembler_arm.cc b/compiler/utils/arm/jni_macro_assembler_arm.cc index e0bfa12b2a..cf7a4d1b72 100644 --- a/compiler/utils/arm/jni_macro_assembler_arm.cc +++ b/compiler/utils/arm/jni_macro_assembler_arm.cc @@ -367,11 +367,21 @@ void ArmJNIMacroAssembler::Move(ManagedRegister m_dst, ManagedRegister m_src, si CHECK(src.IsCoreRegister()) << src; __ mov(dst.AsCoreRegister(), ShifterOperand(src.AsCoreRegister())); } else if (dst.IsDRegister()) { - CHECK(src.IsDRegister()) << src; - __ vmovd(dst.AsDRegister(), src.AsDRegister()); + if (src.IsDRegister()) { + __ vmovd(dst.AsDRegister(), src.AsDRegister()); + } else { + // VMOV Dn, Rlo, Rhi (Dn = {Rlo, Rhi}) + CHECK(src.IsRegisterPair()) << src; + __ vmovdrr(dst.AsDRegister(), src.AsRegisterPairLow(), src.AsRegisterPairHigh()); + } } else if (dst.IsSRegister()) { - CHECK(src.IsSRegister()) << src; - __ vmovs(dst.AsSRegister(), src.AsSRegister()); + if (src.IsSRegister()) { + __ vmovs(dst.AsSRegister(), src.AsSRegister()); + } else { + // VMOV Sn, Rn (Sn = Rn) + CHECK(src.IsCoreRegister()) << src; + __ vmovsr(dst.AsSRegister(), src.AsCoreRegister()); + } } else { CHECK(dst.IsRegisterPair()) << dst; CHECK(src.IsRegisterPair()) << src; diff --git a/compiler/utils/assembler_thumb_test.cc b/compiler/utils/assembler_thumb_test.cc index 367ed97325..3b05173d88 100644 --- a/compiler/utils/assembler_thumb_test.cc +++ b/compiler/utils/assembler_thumb_test.cc @@ -1661,13 +1661,19 @@ void EmitAndCheck(JniAssemblerType* assembler, const char* testname) { TEST_F(ArmVIXLAssemblerTest, VixlJniHelpers) { const bool is_static = true; const bool is_synchronized = false; + const bool is_critical_native = false; const char* shorty = "IIFII"; ArenaPool pool; ArenaAllocator arena(&pool); std::unique_ptr<JniCallingConvention> jni_conv( - JniCallingConvention::Create(&arena, is_static, is_synchronized, shorty, kThumb2)); + JniCallingConvention::Create(&arena, + is_static, + is_synchronized, + is_critical_native, + shorty, + kThumb2)); std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv( ManagedRuntimeCallingConvention::Create(&arena, is_static, is_synchronized, shorty, kThumb2)); const int frame_size(jni_conv->FrameSize()); diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index f2ef41f400..cd30872986 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -1708,6 +1708,13 @@ void X86Assembler::jmp(NearLabel* label) { } +void X86Assembler::repne_scasb() { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0xF2); + EmitUint8(0xAE); +} + + void X86Assembler::repne_scasw() { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -1716,6 +1723,13 @@ void X86Assembler::repne_scasw() { } +void X86Assembler::repe_cmpsb() { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0xF2); + EmitUint8(0xA6); +} + + void X86Assembler::repe_cmpsw() { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -1731,6 +1745,13 @@ void X86Assembler::repe_cmpsl() { } +void X86Assembler::rep_movsb() { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0xF3); + EmitUint8(0xA4); +} + + void X86Assembler::rep_movsw() { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index 2ddcd760dd..9738784d45 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -592,9 +592,12 @@ class X86Assembler FINAL : public Assembler { void jmp(Label* label); void jmp(NearLabel* label); + void repne_scasb(); void repne_scasw(); + void repe_cmpsb(); void repe_cmpsw(); void repe_cmpsl(); + void rep_movsb(); void rep_movsw(); X86Assembler* lock(); diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index 61d70d714a..9bae6c20bd 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -207,12 +207,24 @@ TEST_F(AssemblerX86Test, FPUIntegerStore) { DriverStr(expected, "FPUIntegerStore"); } +TEST_F(AssemblerX86Test, Repnescasb) { + GetAssembler()->repne_scasb(); + const char* expected = "repne scasb\n"; + DriverStr(expected, "Repnescasb"); +} + TEST_F(AssemblerX86Test, Repnescasw) { GetAssembler()->repne_scasw(); const char* expected = "repne scasw\n"; DriverStr(expected, "Repnescasw"); } +TEST_F(AssemblerX86Test, Repecmpsb) { + GetAssembler()->repe_cmpsb(); + const char* expected = "repe cmpsb\n"; + DriverStr(expected, "Repecmpsb"); +} + TEST_F(AssemblerX86Test, Repecmpsw) { GetAssembler()->repe_cmpsw(); const char* expected = "repe cmpsw\n"; @@ -225,10 +237,10 @@ TEST_F(AssemblerX86Test, Repecmpsl) { DriverStr(expected, "Repecmpsl"); } -TEST_F(AssemblerX86Test, RepneScasw) { - GetAssembler()->repne_scasw(); - const char* expected = "repne scasw\n"; - DriverStr(expected, "repne_scasw"); +TEST_F(AssemblerX86Test, RepMovsb) { + GetAssembler()->rep_movsb(); + const char* expected = "rep movsb\n"; + DriverStr(expected, "rep_movsb"); } TEST_F(AssemblerX86Test, RepMovsw) { diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc index 1f73aa7374..e9a0607290 100644 --- a/compiler/utils/x86_64/assembler_x86_64.cc +++ b/compiler/utils/x86_64/assembler_x86_64.cc @@ -2325,6 +2325,12 @@ void X86_64Assembler::popcntq(CpuRegister dst, const Address& src) { EmitOperand(dst.LowBits(), src); } +void X86_64Assembler::repne_scasb() { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0xF2); + EmitUint8(0xAE); +} + void X86_64Assembler::repne_scasw() { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -2332,7 +2338,6 @@ void X86_64Assembler::repne_scasw() { EmitUint8(0xAF); } - void X86_64Assembler::repe_cmpsw() { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h index 3a4bfca6b0..fdd3aa9317 100644 --- a/compiler/utils/x86_64/assembler_x86_64.h +++ b/compiler/utils/x86_64/assembler_x86_64.h @@ -670,6 +670,7 @@ class X86_64Assembler FINAL : public Assembler { void rolq(CpuRegister reg, const Immediate& imm); void rolq(CpuRegister operand, CpuRegister shifter); + void repne_scasb(); void repne_scasw(); void repe_cmpsw(); void repe_cmpsl(); diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index 48a18760f1..ff01429058 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -956,6 +956,12 @@ TEST_F(AssemblerX86_64Test, Xorq) { DriverStr(expected, "xorq"); } +TEST_F(AssemblerX86_64Test, RepneScasb) { + GetAssembler()->repne_scasb(); + const char* expected = "repne scasb\n"; + DriverStr(expected, "repne_scasb"); +} + TEST_F(AssemblerX86_64Test, RepneScasw) { GetAssembler()->repne_scasw(); const char* expected = "repne scasw\n"; |