From 70bef0d8f6aa30b0da5c6ca56e1bc5729f74654b Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Wed, 15 Apr 2015 02:37:28 -0700 Subject: [PATCH] ART: Add compiled-methods Add a dex2oat option for compiled-methods, a more granular filter than compiled-classes. Add compiler-driver support for it. Refactor dex2oat to reuse file reading. Add a test to oat_test. Change-Id: I78d0d040bce7738b4bb7aabe7768b5788d2587ac --- build/Android.gtest.mk | 2 +- compiler/common_compiler_test.cc | 26 ++++++- compiler/common_compiler_test.h | 13 ++++ compiler/dex/quick/quick_cfi_test.cc | 2 +- compiler/driver/compiler_driver.cc | 23 +++++- compiler/driver/compiler_driver.h | 9 +++ compiler/driver/compiler_driver_test.cc | 54 +++++++++++++ compiler/jit/jit_compiler.cc | 2 +- compiler/linker/relative_patcher_test.h | 2 +- compiler/oat_test.cc | 4 +- dex2oat/dex2oat.cc | 133 +++++++++++++++++++++++--------- 11 files changed, 222 insertions(+), 48 deletions(-) diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 8c618719e..e0f0ae581 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -61,7 +61,7 @@ $(ART_TEST_TARGET_GTEST_MainStripped_DEX): $(ART_TEST_TARGET_GTEST_Main_DEX) # Dex file dependencies for each gtest. ART_GTEST_class_linker_test_DEX_DEPS := Interfaces MultiDex MyClass Nested Statics StaticsFromCode -ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod +ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index 8ffc86ea3..05cb8b458 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -140,6 +140,27 @@ void CommonCompilerTest::MakeExecutable(mirror::ClassLoader* class_loader, const } } +// Get the set of image classes given to the compiler-driver in SetUp. Note: the compiler +// driver assumes ownership of the set, so the test should properly release the set. +std::unordered_set* CommonCompilerTest::GetImageClasses() { + // Empty set: by default no classes are retained in the image. + return new std::unordered_set(); +} + +// Get the set of compiled classes given to the compiler-driver in SetUp. Note: the compiler +// driver assumes ownership of the set, so the test should properly release the set. +std::unordered_set* CommonCompilerTest::GetCompiledClasses() { + // Null, no selection of compiled-classes. + return nullptr; +} + +// Get the set of compiled methods given to the compiler-driver in SetUp. Note: the compiler +// driver assumes ownership of the set, so the test should properly release the set. +std::unordered_set* CommonCompilerTest::GetCompiledMethods() { + // Null, no selection of compiled-methods. + return nullptr; +} + void CommonCompilerTest::SetUp() { CommonRuntimeTest::SetUp(); { @@ -165,7 +186,10 @@ void CommonCompilerTest::SetUp() { method_inliner_map_.get(), compiler_kind, instruction_set, instruction_set_features_.get(), - true, new std::unordered_set, nullptr, + true, + GetImageClasses(), + GetCompiledClasses(), + GetCompiledMethods(), 2, true, true, "", timer_.get(), -1, "")); } // We typically don't generate an image in unit tests, disable this optimization by default. diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index d7b210d57..8d80a2da5 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -18,6 +18,7 @@ #define ART_COMPILER_COMMON_COMPILER_TEST_H_ #include +#include #include #include "common_runtime_test.h" @@ -56,6 +57,18 @@ class CommonCompilerTest : public CommonRuntimeTest { virtual void SetUpRuntimeOptions(RuntimeOptions *options); + // Get the set of image classes given to the compiler-driver in SetUp. Note: the compiler + // driver assumes ownership of the set, so the test should properly release the set. + virtual std::unordered_set* GetImageClasses(); + + // Get the set of compiled classes given to the compiler-driver in SetUp. Note: the compiler + // driver assumes ownership of the set, so the test should properly release the set. + virtual std::unordered_set* GetCompiledClasses(); + + // Get the set of compiled methods given to the compiler-driver in SetUp. Note: the compiler + // driver assumes ownership of the set, so the test should properly release the set. + virtual std::unordered_set* GetCompiledMethods(); + virtual void TearDown(); void CompileClass(mirror::ClassLoader* class_loader, const char* class_name) diff --git a/compiler/dex/quick/quick_cfi_test.cc b/compiler/dex/quick/quick_cfi_test.cc index d276457d0..555d5b9cf 100644 --- a/compiler/dex/quick/quick_cfi_test.cc +++ b/compiler/dex/quick/quick_cfi_test.cc @@ -76,7 +76,7 @@ class QuickCFITest : public CFITest { isa_features.reset(InstructionSetFeatures::FromVariant(isa, "default", &error)); CompilerDriver driver(&compiler_options, &verification_results, &method_inliner_map, Compiler::kQuick, isa, isa_features.get(), - false, 0, 0, 0, false, false, "", 0, -1, ""); + false, nullptr, nullptr, nullptr, 0, false, false, "", 0, -1, ""); ClassLinker* linker = nullptr; CompilationUnit cu(&pool, isa, &driver, linker); DexFile::CodeItem code_item { 0, 0, 0, 0, 0, 0, { 0 } }; // NOLINT diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 1832647f4..e665e1d4e 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -76,8 +76,8 @@ static constexpr bool kTimeCompileMethod = !kIsDebugBuild; // Whether to produce 64-bit ELF files for 64-bit targets. Leave this off for now. static constexpr bool kProduce64BitELFFiles = false; -// Whether classes-to-compile is only applied to the boot image, or, when given, too all -// compilations. +// Whether classes-to-compile and methods-to-compile are only applied to the boot image, or, when +// given, too all compilations. static constexpr bool kRestrictCompilationFiltersToImage = true; static double Percentage(size_t x, size_t y) { @@ -349,6 +349,7 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, const InstructionSetFeatures* instruction_set_features, bool image, std::unordered_set* image_classes, std::unordered_set* compiled_classes, + std::unordered_set* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, const std::string& dump_cfg_file_name, CumulativeLogger* timer, int swap_fd, const std::string& profile_file) @@ -369,6 +370,7 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, image_(image), image_classes_(image_classes), classes_to_compile_(compiled_classes), + methods_to_compile_(compiled_methods), had_hard_verifier_failure_(false), thread_count_(thread_count), stats_(new AOTCompilationStats), @@ -670,6 +672,19 @@ bool CompilerDriver::IsClassToCompile(const char* descriptor) const { return classes_to_compile_->find(descriptor) != classes_to_compile_->end(); } +bool CompilerDriver::IsMethodToCompile(const MethodReference& method_ref) const { + if (kRestrictCompilationFiltersToImage && !IsImage()) { + return true; + } + + if (methods_to_compile_ == nullptr) { + return true; + } + + std::string tmp = PrettyMethod(method_ref.dex_method_index, *method_ref.dex_file, true); + return methods_to_compile_->find(tmp.c_str()) != methods_to_compile_->end(); +} + static void ResolveExceptionsForMethod(MutableHandle method_handle, std::set>& exceptions_to_resolve) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { @@ -2232,7 +2247,9 @@ void CompilerDriver::CompileMethod(Thread* self, const DexFile::CodeItem* code_i // Basic checks, e.g., not . verification_results_->IsCandidateForCompilation(method_ref, access_flags) && // Did not fail to create VerifiedMethod metadata. - has_verified_method; + has_verified_method && + // Is eligable for compilation by methods-to-compile filter. + IsMethodToCompile(method_ref); if (compile) { // NOTE: if compiler declines to compile this method, it will return nullptr. compiled_method = compiler_->Compile(code_item, access_flags, invoke_type, class_def_idx, diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index ce13a1779..50e1fb14e 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -104,6 +104,7 @@ class CompilerDriver { const InstructionSetFeatures* instruction_set_features, bool image, std::unordered_set* image_classes, std::unordered_set* compiled_classes, + std::unordered_set* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, const std::string& dump_cfg_file_name, CumulativeLogger* timer, int swap_fd, @@ -428,6 +429,9 @@ class CompilerDriver { // Checks whether the provided class should be compiled, i.e., is in classes_to_compile_. bool IsClassToCompile(const char* descriptor) const; + // Checks whether the provided method should be compiled, i.e., is in method_to_compile_. + bool IsMethodToCompile(const MethodReference& method_ref) const; + void RecordClassStatus(ClassReference ref, mirror::Class::Status status) LOCKS_EXCLUDED(compiled_classes_lock_); @@ -597,6 +601,11 @@ class CompilerDriver { // This option may be restricted to the boot image, depending on a flag in the implementation. std::unique_ptr> classes_to_compile_; + // Specifies the methods that will be compiled. Note that if methods_to_compile_ is nullptr, + // all methods are eligible for compilation (compilation filters etc. will still apply). + // This option may be restricted to the boot image, depending on a flag in the implementation. + std::unique_ptr> methods_to_compile_; + bool had_hard_verifier_failure_; size_t thread_count_; diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index e78ff9078..ded50ca10 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -175,6 +175,60 @@ TEST_F(CompilerDriverTest, AbstractMethodErrorStub) { } } +class CompilerDriverMethodsTest : public CompilerDriverTest { + protected: + std::unordered_set* GetCompiledMethods() OVERRIDE { + return new std::unordered_set({ + "byte StaticLeafMethods.identity(byte)", + "int StaticLeafMethods.sum(int, int, int)", + "double StaticLeafMethods.sum(double, double, double, double)" + }); + } +}; + +TEST_F(CompilerDriverMethodsTest, Selection) { + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("StaticLeafMethods"); + } + ASSERT_NE(class_loader, nullptr); + + // Need to enable dex-file writability. Methods rejected to be compiled will run through the + // dex-to-dex compiler. + for (const DexFile* dex_file : GetDexFiles(class_loader)) { + ASSERT_TRUE(dex_file->EnableWrite()); + } + + CompileAll(class_loader); + + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + StackHandleScope<1> hs(self); + ScopedObjectAccess soa(self); + Handle h_loader(hs.NewHandle( + reinterpret_cast(self->DecodeJObject(class_loader)))); + mirror::Class* klass = class_linker->FindClass(self, "LStaticLeafMethods;", h_loader); + ASSERT_NE(klass, nullptr); + + std::unique_ptr> expected(GetCompiledMethods()); + + for (int32_t i = 0; static_cast(i) < klass->NumDirectMethods(); i++) { + mirror::ArtMethod* m = klass->GetDirectMethod(i); + std::string name = PrettyMethod(m, true); + const void* code = + m->GetEntryPointFromQuickCompiledCodePtrSize(InstructionSetPointerSize(kRuntimeISA)); + ASSERT_NE(code, nullptr); + if (expected->find(name) != expected->end()) { + expected->erase(name); + EXPECT_FALSE(class_linker->IsQuickToInterpreterBridge(code)); + } else { + EXPECT_TRUE(class_linker->IsQuickToInterpreterBridge(code)); + } + } + EXPECT_TRUE(expected->empty()); +} + // TODO: need check-cast test (when stub complete & we can throw/catch } // namespace art diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 9ff7ab80e..6a085482f 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -94,7 +94,7 @@ JitCompiler::JitCompiler() : total_time_(0) { compiler_driver_.reset(new CompilerDriver( compiler_options_.get(), verification_results_.get(), method_inliner_map_.get(), Compiler::kQuick, instruction_set, instruction_set_features_.get(), false, - nullptr, nullptr, 1, false, true, + nullptr, nullptr, nullptr, 1, false, true, std::string(), cumulative_logger_.get(), -1, std::string())); // Disable dedupe so we can remove compiled methods. compiler_driver_->SetDedupeEnabled(false); diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h index 70630f366..1f7500a6e 100644 --- a/compiler/linker/relative_patcher_test.h +++ b/compiler/linker/relative_patcher_test.h @@ -45,7 +45,7 @@ class RelativePatcherTest : public testing::Test { inliner_map_(), driver_(&compiler_options_, &verification_results_, &inliner_map_, Compiler::kQuick, instruction_set, nullptr, - false, nullptr, nullptr, 1u, + false, nullptr, nullptr, nullptr, 1u, false, false, "", nullptr, -1, ""), error_msg_(), instruction_set_(instruction_set), diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 989b04fa3..925b507e0 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -93,8 +93,8 @@ TEST_F(OatTest, WriteRead) { verification_results_.get(), method_inliner_map_.get(), compiler_kind, insn_set, - insn_features.get(), false, nullptr, nullptr, 2, true, - true, "", timer_.get(), -1, "")); + insn_features.get(), false, nullptr, nullptr, nullptr, + 2, true, true, "", timer_.get(), -1, "")); jobject class_loader = nullptr; if (kCompile) { TimingLogger timings2("OatTest::WriteRead", false, false); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 70b4213d5..9c01a0f31 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -445,6 +445,8 @@ class Dex2Oat FINAL { image_classes_filename_(nullptr), compiled_classes_zip_filename_(nullptr), compiled_classes_filename_(nullptr), + compiled_methods_zip_filename_(nullptr), + compiled_methods_filename_(nullptr), image_(false), is_host_(false), dump_stats_(false), @@ -564,6 +566,10 @@ class Dex2Oat FINAL { compiled_classes_filename_ = option.substr(strlen("--compiled-classes=")).data(); } else if (option.starts_with("--compiled-classes-zip=")) { compiled_classes_zip_filename_ = option.substr(strlen("--compiled-classes-zip=")).data(); + } else if (option.starts_with("--compiled-methods=")) { + compiled_methods_filename_ = option.substr(strlen("--compiled-methods=")).data(); + } else if (option.starts_with("--compiled-methods-zip=")) { + compiled_methods_zip_filename_ = option.substr(strlen("--compiled-methods-zip=")).data(); } else if (option.starts_with("--base=")) { const char* image_base_str = option.substr(strlen("--base=")).data(); char* end; @@ -1092,8 +1098,8 @@ class Dex2Oat FINAL { std::string error_msg; if (image_classes_zip_filename_ != nullptr) { image_classes_.reset(ReadImageClassesFromZip(image_classes_zip_filename_, - image_classes_filename_, - &error_msg)); + image_classes_filename_, + &error_msg)); } else { image_classes_.reset(ReadImageClassesFromFile(image_classes_filename_)); } @@ -1121,9 +1127,29 @@ class Dex2Oat FINAL { << compiled_classes_filename_ << "': " << error_msg; return false; } - } else if (image_) { + } else { compiled_classes_.reset(nullptr); // By default compile everything. } + // If --compiled-methods was specified, read the methods to compile from the given file(s). + if (compiled_methods_filename_ != nullptr) { + std::string error_msg; + if (compiled_methods_zip_filename_ != nullptr) { + compiled_methods_.reset(ReadCommentedInputFromZip(compiled_methods_zip_filename_, + compiled_methods_filename_, + nullptr, // No post-processing. + &error_msg)); + } else { + compiled_methods_.reset(ReadCommentedInputFromFile(compiled_methods_filename_, + nullptr)); // No post-processing. + } + if (compiled_methods_.get() == nullptr) { + LOG(ERROR) << "Failed to create list of compiled methods from '" + << compiled_methods_filename_ << "': " << error_msg; + return false; + } + } else { + compiled_methods_.reset(nullptr); // By default compile everything. + } if (boot_image_option_.empty()) { dex_files_ = Runtime::Current()->GetClassLinker()->GetBootClassPath(); @@ -1258,6 +1284,7 @@ class Dex2Oat FINAL { image_, image_classes_.release(), compiled_classes_.release(), + nullptr, thread_count_, dump_stats_, dump_passes_, @@ -1618,59 +1645,86 @@ class Dex2Oat FINAL { // Reads the class names (java.lang.Object) and returns a set of descriptors (Ljava/lang/Object;) static std::unordered_set* ReadImageClassesFromFile( const char* image_classes_filename) { - std::unique_ptr image_classes_file(new std::ifstream(image_classes_filename, - std::ifstream::in)); - if (image_classes_file.get() == nullptr) { - LOG(ERROR) << "Failed to open image classes file " << image_classes_filename; - return nullptr; - } - std::unique_ptr> result(ReadImageClasses(*image_classes_file)); - image_classes_file->close(); - return result.release(); + std::function process = DotToDescriptor; + return ReadCommentedInputFromFile(image_classes_filename, &process); } - static std::unordered_set* ReadImageClasses(std::istream& image_classes_stream) { - std::unique_ptr> image_classes( - new std::unordered_set); - while (image_classes_stream.good()) { - std::string dot; - std::getline(image_classes_stream, dot); - if (StartsWith(dot, "#") || dot.empty()) { - continue; - } - std::string descriptor(DotToDescriptor(dot.c_str())); - image_classes->insert(descriptor); + // Reads the class names (java.lang.Object) and returns a set of descriptors (Ljava/lang/Object;) + static std::unordered_set* ReadImageClassesFromZip( + const char* zip_filename, + const char* image_classes_filename, + std::string* error_msg) { + std::function process = DotToDescriptor; + return ReadCommentedInputFromZip(zip_filename, image_classes_filename, &process, error_msg); + } + + // Read lines from the given file, dropping comments and empty lines. Post-process each line with + // the given function. + static std::unordered_set* ReadCommentedInputFromFile( + const char* input_filename, std::function* process) { + std::unique_ptr input_file(new std::ifstream(input_filename, std::ifstream::in)); + if (input_file.get() == nullptr) { + LOG(ERROR) << "Failed to open input file " << input_filename; + return nullptr; } - return image_classes.release(); + std::unique_ptr> result( + ReadCommentedInputStream(*input_file, process)); + input_file->close(); + return result.release(); } - // Reads the class names (java.lang.Object) and returns a set of descriptors (Ljava/lang/Object;) - static std::unordered_set* ReadImageClassesFromZip( + // Read lines from the given file from the given zip file, dropping comments and empty lines. + // Post-process each line with the given function. + static std::unordered_set* ReadCommentedInputFromZip( const char* zip_filename, - const char* image_classes_filename, + const char* input_filename, + std::function* process, std::string* error_msg) { std::unique_ptr zip_archive(ZipArchive::Open(zip_filename, error_msg)); if (zip_archive.get() == nullptr) { return nullptr; } - std::unique_ptr zip_entry(zip_archive->Find(image_classes_filename, error_msg)); + std::unique_ptr zip_entry(zip_archive->Find(input_filename, error_msg)); if (zip_entry.get() == nullptr) { - *error_msg = StringPrintf("Failed to find '%s' within '%s': %s", image_classes_filename, + *error_msg = StringPrintf("Failed to find '%s' within '%s': %s", input_filename, zip_filename, error_msg->c_str()); return nullptr; } - std::unique_ptr image_classes_file(zip_entry->ExtractToMemMap(zip_filename, - image_classes_filename, - error_msg)); - if (image_classes_file.get() == nullptr) { - *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", image_classes_filename, + std::unique_ptr input_file(zip_entry->ExtractToMemMap(zip_filename, + input_filename, + error_msg)); + if (input_file.get() == nullptr) { + *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", input_filename, zip_filename, error_msg->c_str()); return nullptr; } - const std::string image_classes_string(reinterpret_cast(image_classes_file->Begin()), - image_classes_file->Size()); - std::istringstream image_classes_stream(image_classes_string); - return ReadImageClasses(image_classes_stream); + const std::string input_string(reinterpret_cast(input_file->Begin()), + input_file->Size()); + std::istringstream input_stream(input_string); + return ReadCommentedInputStream(input_stream, process); + } + + // Read lines from the given stream, dropping comments and empty lines. Post-process each line + // with the given function. + static std::unordered_set* ReadCommentedInputStream( + std::istream& in_stream, + std::function* process) { + std::unique_ptr> image_classes( + new std::unordered_set); + while (in_stream.good()) { + std::string dot; + std::getline(in_stream, dot); + if (StartsWith(dot, "#") || dot.empty()) { + continue; + } + if (process != nullptr) { + std::string descriptor((*process)(dot.c_str())); + image_classes->insert(descriptor); + } else { + image_classes->insert(dot); + } + } + return image_classes.release(); } void LogCompletionTime() { @@ -1724,8 +1778,11 @@ class Dex2Oat FINAL { const char* image_classes_filename_; const char* compiled_classes_zip_filename_; const char* compiled_classes_filename_; + const char* compiled_methods_zip_filename_; + const char* compiled_methods_filename_; std::unique_ptr> image_classes_; std::unique_ptr> compiled_classes_; + std::unique_ptr> compiled_methods_; bool image_; std::unique_ptr image_writer_; bool is_host_; -- 2.11.0