diff options
136 files changed, 2669 insertions, 2147 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index af64470a17..e3f0c24414 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -39,6 +39,7 @@ GTEST_DEX_DIRECTORIES := \ NonStaticLeafMethods \ ProtoCompare \ ProtoCompare2 \ + ProfileTestMultiDex \ StaticLeafMethods \ Statics \ StaticsFromCode \ @@ -65,7 +66,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 StaticLeafMethods +ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle @@ -78,6 +79,8 @@ ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods +ART_GTEST_profile_assistant_test_DEX_DEPS := ProfileTestMultiDex +ART_GTEST_profile_compilation_info_test_DEX_DEPS := ProfileTestMultiDex ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup @@ -191,13 +194,12 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/gc/collector/immune_spaces_test.cc \ runtime/gc/heap_test.cc \ runtime/gc/reference_queue_test.cc \ - runtime/gc/space/dlmalloc_space_base_test.cc \ runtime/gc/space/dlmalloc_space_static_test.cc \ runtime/gc/space/dlmalloc_space_random_test.cc \ - runtime/gc/space/rosalloc_space_base_test.cc \ + runtime/gc/space/large_object_space_test.cc \ runtime/gc/space/rosalloc_space_static_test.cc \ runtime/gc/space/rosalloc_space_random_test.cc \ - runtime/gc/space/large_object_space_test.cc \ + runtime/gc/space/space_create_test.cc \ runtime/gc/task_processor_test.cc \ runtime/gtest_test.cc \ runtime/handle_scope_test.cc \ @@ -208,6 +210,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/interpreter/safe_math_test.cc \ runtime/interpreter/unstarted_runtime_test.cc \ runtime/java_vm_ext_test.cc \ + runtime/jit/profile_compilation_info_test.cc \ runtime/lambda/closure_test.cc \ runtime/lambda/shorty_field_type_test.cc \ runtime/leb128_test.cc \ @@ -267,6 +270,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/optimizing/ssa_test.cc \ compiler/optimizing/stack_map_test.cc \ compiler/optimizing/suspend_check_test.cc \ + compiler/profile_assistant_test.cc \ compiler/utils/arena_allocator_test.cc \ compiler/utils/dedupe_set_test.cc \ compiler/utils/swap_space_test.cc \ diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index b5fd1e074f..afc8463878 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -168,6 +168,12 @@ std::unordered_set<std::string>* CommonCompilerTest::GetCompiledMethods() { return nullptr; } +// Get ProfileCompilationInfo that should be passed to the driver. +ProfileCompilationInfo* CommonCompilerTest::GetProfileCompilationInfo() { + // Null, profile information will not be taken into account. + return nullptr; +} + void CommonCompilerTest::SetUp() { CommonRuntimeTest::SetUp(); { @@ -204,12 +210,10 @@ void CommonCompilerTest::CreateCompilerDriver(Compiler::Kind kind, InstructionSe 2, true, true, - "", - false, timer_.get(), -1, /* dex_to_oat_map */ nullptr, - /* profile_compilation_info */ nullptr)); + GetProfileCompilationInfo())); // We typically don't generate an image in unit tests, disable this optimization by default. compiler_driver_->SetSupportBootImageFixup(false); } diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index b491946dc3..7e0fbabff8 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -23,6 +23,7 @@ #include "common_runtime_test.h" #include "compiler.h" +#include "jit/offline_profiling_info.h" #include "oat_file.h" namespace art { @@ -75,6 +76,8 @@ class CommonCompilerTest : public CommonRuntimeTest { // driver assumes ownership of the set, so the test should properly release the set. virtual std::unordered_set<std::string>* GetCompiledMethods(); + virtual ProfileCompilationInfo* GetProfileCompilationInfo(); + virtual void TearDown(); void CompileClass(mirror::ClassLoader* class_loader, const char* class_name) diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc index 32d751861a..4617668ee8 100644 --- a/compiler/dex/quick/dex_file_method_inliner.cc +++ b/compiler/dex/quick/dex_file_method_inliner.cc @@ -110,9 +110,9 @@ static_assert(kIntrinsicIsStatic[kIntrinsicAbsLong], "AbsLong must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicAbsFloat], "AbsFloat must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicAbsDouble], "AbsDouble must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxInt], "MinMaxInt must be static"); -static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxLong], "MinMaxLong_must_be_static"); -static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxFloat], "MinMaxFloat_must_be_static"); -static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxDouble], "MinMaxDouble_must_be_static"); +static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxLong], "MinMaxLong must be static"); +static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxFloat], "MinMaxFloat must be static"); +static_assert(kIntrinsicIsStatic[kIntrinsicMinMaxDouble], "MinMaxDouble must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicCos], "Cos must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicSin], "Sin must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicAcos], "Acos must be static"); @@ -153,7 +153,7 @@ static_assert(kIntrinsicIsStatic[kIntrinsicCurrentThread], "CurrentThread must b static_assert(kIntrinsicIsStatic[kIntrinsicPeek], "Peek must be static"); static_assert(kIntrinsicIsStatic[kIntrinsicPoke], "Poke must be static"); static_assert(!kIntrinsicIsStatic[kIntrinsicCas], "Cas must not be static"); -static_assert(!kIntrinsicIsStatic[kIntrinsicUnsafeGet], "UnsafeGet_must_not_be_static"); +static_assert(!kIntrinsicIsStatic[kIntrinsicUnsafeGet], "UnsafeGet must not be static"); static_assert(!kIntrinsicIsStatic[kIntrinsicUnsafePut], "UnsafePut must not be static"); static_assert(kIntrinsicIsStatic[kIntrinsicSystemArrayCopyCharArray], "SystemArrayCopyCharArray must be static"); diff --git a/compiler/dex/quick/quick_cfi_test.cc b/compiler/dex/quick/quick_cfi_test.cc index 12568a4ad4..c5df134493 100644 --- a/compiler/dex/quick/quick_cfi_test.cc +++ b/compiler/dex/quick/quick_cfi_test.cc @@ -69,6 +69,8 @@ class QuickCFITest : public CFITest { false, nullptr, nullptr, + false, + "", false); VerificationResults verification_results(&compiler_options); DexFileToMethodInlinerMap method_inliner_map; @@ -88,8 +90,6 @@ class QuickCFITest : public CFITest { 0, false, false, - "", - false, 0, -1, nullptr, diff --git a/compiler/dex/quick/x86/quick_assemble_x86_test.cc b/compiler/dex/quick/x86/quick_assemble_x86_test.cc index b39fe4da4f..d63878d6b9 100644 --- a/compiler/dex/quick/x86/quick_assemble_x86_test.cc +++ b/compiler/dex/quick/x86/quick_assemble_x86_test.cc @@ -52,6 +52,8 @@ class QuickAssembleX86TestBase : public testing::Test { false, nullptr, nullptr, + false, + "", false)); verification_results_.reset(new VerificationResults(compiler_options_.get())); method_inliner_map_.reset(new DexFileToMethodInlinerMap()); @@ -69,8 +71,6 @@ class QuickAssembleX86TestBase : public testing::Test { 0, false, false, - "", - false, 0, -1, nullptr, diff --git a/compiler/driver/compiled_method_storage_test.cc b/compiler/driver/compiled_method_storage_test.cc index f18fa67ea5..2e2d1f99f3 100644 --- a/compiler/driver/compiled_method_storage_test.cc +++ b/compiler/driver/compiled_method_storage_test.cc @@ -41,8 +41,6 @@ TEST(CompiledMethodStorage, Deduplicate) { 1u, false, false, - "", - false, nullptr, -1, nullptr, diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 043bd93bd7..d0215255e8 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -345,7 +345,6 @@ CompilerDriver::CompilerDriver( std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, - const std::string& dump_cfg_file_name, bool dump_cfg_append, CumulativeLogger* timer, int swap_fd, const std::unordered_map<const DexFile*, const char*>* dex_to_oat_map, const ProfileCompilationInfo* profile_compilation_info) @@ -370,8 +369,6 @@ CompilerDriver::CompilerDriver( stats_(new AOTCompilationStats), dump_stats_(dump_stats), dump_passes_(dump_passes), - dump_cfg_file_name_(dump_cfg_file_name), - dump_cfg_append_(dump_cfg_append), timings_logger_(timer), compiler_context_(nullptr), support_boot_image_fixup_(instruction_set != kMips && instruction_set != kMips64), @@ -1197,15 +1194,18 @@ bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const Dex if (equals_referrers_class != nullptr) { *equals_referrers_class = (method_id.class_idx_ == type_idx); } - mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); - if (referrer_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - return false; // Incomplete referrer knowledge needs access check. + bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. + if (!is_accessible) { + mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); + if (referrer_class == nullptr) { + stats_->TypeNeedsAccessCheck(); + return false; // Incomplete referrer knowledge needs access check. + } + // Perform access check, will return true if access is ok or false if we're going to have to + // check this at runtime (for example for class loaders). + is_accessible = referrer_class->CanAccess(resolved_class); } - // Perform access check, will return true if access is ok or false if we're going to have to - // check this at runtime (for example for class loaders). - bool result = referrer_class->CanAccess(resolved_class); - if (result) { + if (is_accessible) { stats_->TypeDoesntNeedAccessCheck(); if (type_known_final != nullptr) { *type_known_final = resolved_class->IsFinal() && !resolved_class->IsArrayClass(); @@ -1216,7 +1216,7 @@ bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const Dex } else { stats_->TypeNeedsAccessCheck(); } - return result; + return is_accessible; } bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx, @@ -1236,14 +1236,18 @@ bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_id } *finalizable = resolved_class->IsFinalizable(); const DexFile::MethodId& method_id = dex_file.GetMethodId(referrer_idx); - mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); - if (referrer_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - return false; // Incomplete referrer knowledge needs access check. - } - // Perform access and instantiable checks, will return true if access is ok or false if we're - // going to have to check this at runtime (for example for class loaders). - bool result = referrer_class->CanAccess(resolved_class) && resolved_class->IsInstantiable(); + bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. + if (!is_accessible) { + mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); + if (referrer_class == nullptr) { + stats_->TypeNeedsAccessCheck(); + return false; // Incomplete referrer knowledge needs access check. + } + // Perform access and instantiable checks, will return true if access is ok or false if we're + // going to have to check this at runtime (for example for class loaders). + is_accessible = referrer_class->CanAccess(resolved_class); + } + bool result = is_accessible && resolved_class->IsInstantiable(); if (result) { stats_->TypeDoesntNeedAccessCheck(); } else { diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 17b2f5e98d..6a2f7bfd4e 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -95,7 +95,6 @@ class CompilerDriver { std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, size_t thread_count, bool dump_stats, bool dump_passes, - const std::string& dump_cfg_file_name, bool dump_cfg_append, CumulativeLogger* timer, int swap_fd, const std::unordered_map<const DexFile*, const char*>* dex_to_oat_map, const ProfileCompilationInfo* profile_compilation_info); @@ -423,14 +422,6 @@ class CompilerDriver { return dump_passes_; } - const std::string& GetDumpCfgFileName() const { - return dump_cfg_file_name_; - } - - bool GetDumpCfgAppend() const { - return dump_cfg_append_; - } - CumulativeLogger* GetTimingsLogger() const { return timings_logger_; } @@ -668,8 +659,6 @@ class CompilerDriver { bool dump_stats_; const bool dump_passes_; - const std::string dump_cfg_file_name_; - const bool dump_cfg_append_; CumulativeLogger* const timings_logger_; diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index 82c0e86b25..4c03e5ddfe 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -31,6 +31,7 @@ #include "mirror/object_array-inl.h" #include "mirror/object-inl.h" #include "handle_scope-inl.h" +#include "jit/offline_profiling_info.h" #include "scoped_thread_state_change.h" namespace art { @@ -240,6 +241,94 @@ TEST_F(CompilerDriverMethodsTest, Selection) { EXPECT_TRUE(expected->empty()); } +class CompilerDriverProfileTest : public CompilerDriverTest { + protected: + ProfileCompilationInfo* GetProfileCompilationInfo() OVERRIDE { + ScopedObjectAccess soa(Thread::Current()); + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); + + ProfileCompilationInfo info; + for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { + std::cout << std::string(dex_file->GetLocation()); + profile_info_.AddData(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 1); + profile_info_.AddData(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 2); + } + return &profile_info_; + } + + std::unordered_set<std::string> GetExpectedMethodsForClass(const std::string& clazz) { + if (clazz == "Main") { + return std::unordered_set<std::string>({ + "java.lang.String Main.getA()", + "java.lang.String Main.getB()"}); + } else if (clazz == "Second") { + return std::unordered_set<std::string>({ + "java.lang.String Second.getX()", + "java.lang.String Second.getY()"}); + } else { + return std::unordered_set<std::string>(); + } + } + + void CheckCompiledMethods(jobject class_loader, + const std::string& clazz, + const std::unordered_set<std::string>& expected_methods) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader(hs.NewHandle( + reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader)))); + mirror::Class* klass = class_linker->FindClass(self, clazz.c_str(), h_loader); + ASSERT_NE(klass, nullptr); + + const auto pointer_size = class_linker->GetImagePointerSize(); + size_t number_of_compiled_methods = 0; + for (auto& m : klass->GetVirtualMethods(pointer_size)) { + std::string name = PrettyMethod(&m, true); + const void* code = m.GetEntryPointFromQuickCompiledCodePtrSize(pointer_size); + ASSERT_NE(code, nullptr); + if (expected_methods.find(name) != expected_methods.end()) { + number_of_compiled_methods++; + EXPECT_FALSE(class_linker->IsQuickToInterpreterBridge(code)); + } else { + EXPECT_TRUE(class_linker->IsQuickToInterpreterBridge(code)); + } + } + EXPECT_EQ(expected_methods.size(), number_of_compiled_methods); + } + + private: + ProfileCompilationInfo profile_info_; +}; + +TEST_F(CompilerDriverProfileTest, ProfileGuidedCompilation) { + TEST_DISABLED_FOR_HEAP_REFERENCE_POISONING_WITH_QUICK(); + TEST_DISABLED_FOR_READ_BARRIER_WITH_QUICK(); + TEST_DISABLED_FOR_READ_BARRIER_WITH_OPTIMIZING_FOR_UNSUPPORTED_INSTRUCTION_SETS(); + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("ProfileTestMultiDex"); + } + ASSERT_NE(class_loader, nullptr); + + // Need to enable dex-file writability. Methods rejected to be compiled will run through the + // dex-to-dex compiler. + ProfileCompilationInfo info; + for (const DexFile* dex_file : GetDexFiles(class_loader)) { + ASSERT_TRUE(dex_file->EnableWrite()); + } + + CompileAll(class_loader); + + std::unordered_set<std::string> m = GetExpectedMethodsForClass("Main"); + std::unordered_set<std::string> s = GetExpectedMethodsForClass("Second"); + CheckCompiledMethods(class_loader, "LMain;", m); + CheckCompiledMethods(class_loader, "LSecond;", s); +} + // TODO: need check-cast test (when stub complete & we can throw/catch } // namespace art diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 385f34a9f9..2644528e56 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -44,7 +44,9 @@ CompilerOptions::CompilerOptions() verbose_methods_(nullptr), pass_manager_options_(), abort_on_hard_verifier_failure_(false), - init_failure_output_(nullptr) { + init_failure_output_(nullptr), + dump_cfg_file_name_(""), + dump_cfg_append_(false) { } CompilerOptions::~CompilerOptions() { @@ -71,7 +73,9 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, bool compile_pic, const std::vector<std::string>* verbose_methods, std::ostream* init_failure_output, - bool abort_on_hard_verifier_failure + bool abort_on_hard_verifier_failure, + const std::string& dump_cfg_file_name, + bool dump_cfg_append ) : // NOLINT(whitespace/parens) compiler_filter_(compiler_filter), huge_method_threshold_(huge_method_threshold), @@ -94,7 +98,9 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, verbose_methods_(verbose_methods), pass_manager_options_(), abort_on_hard_verifier_failure_(abort_on_hard_verifier_failure), - init_failure_output_(init_failure_output) { + init_failure_output_(init_failure_output), + dump_cfg_file_name_(dump_cfg_file_name), + dump_cfg_append_(dump_cfg_append) { } void CompilerOptions::ParseHugeMethodMax(const StringPiece& option, UsageFn Usage) { @@ -238,6 +244,10 @@ bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usa ParsePassOptions(option, Usage); } else if (option.starts_with("--dump-init-failures=")) { ParseDumpInitFailures(option, Usage); + } else if (option.starts_with("--dump-cfg=")) { + dump_cfg_file_name_ = option.substr(strlen("--dump-cfg=")).data(); + } else if (option.starts_with("--dump-cfg-append")) { + dump_cfg_append_ = true; } else { // Option not recognized. return false; diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index f14bdc4a2f..d47fc2ad4b 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -83,7 +83,9 @@ class CompilerOptions FINAL { bool compile_pic, const std::vector<std::string>* verbose_methods, std::ostream* init_failure_output, - bool abort_on_hard_verifier_failure); + bool abort_on_hard_verifier_failure, + const std::string& dump_cfg_file_name, + bool dump_cfg_append); CompilerFilter GetCompilerFilter() const { return compiler_filter_; @@ -224,6 +226,14 @@ class CompilerOptions FINAL { bool ParseCompilerOption(const StringPiece& option, UsageFn Usage); + const std::string& GetDumpCfgFileName() const { + return dump_cfg_file_name_; + } + + bool GetDumpCfgAppend() const { + return dump_cfg_append_; + } + private: void ParseDumpInitFailures(const StringPiece& option, UsageFn Usage); void ParsePassOptions(const StringPiece& option, UsageFn Usage); @@ -273,6 +283,9 @@ class CompilerOptions FINAL { // Log initialization of initialization failures to this stream if not null. std::unique_ptr<std::ostream> init_failure_output_; + std::string dump_cfg_file_name_; + bool dump_cfg_append_; + friend class Dex2Oat; DISALLOW_COPY_AND_ASSIGN(CompilerOptions); diff --git a/compiler/dwarf/register.h b/compiler/dwarf/register.h index b67e8ddc9d..35b3e15d83 100644 --- a/compiler/dwarf/register.h +++ b/compiler/dwarf/register.h @@ -29,7 +29,7 @@ class Reg { // TODO: Arm S0–S31 register mapping is obsolescent. // We should use VFP-v3/Neon D0-D31 mapping instead. // However, D0 is aliased to pair of S0 and S1, so using that - // mapping we can not easily say S0 is spilled and S1 is not. + // mapping we cannot easily say S0 is spilled and S1 is not. // There are ways around this in DWARF but they are complex. // It would be much simpler to always spill whole D registers. // Arm64 mapping is correct since we already do this there. diff --git a/compiler/elf_writer_debug.cc b/compiler/elf_writer_debug.cc index dd50f69b71..e03614f090 100644 --- a/compiler/elf_writer_debug.cc +++ b/compiler/elf_writer_debug.cc @@ -212,7 +212,7 @@ static void WriteCIE(InstructionSet isa, case kNone: break; } - LOG(FATAL) << "Can not write CIE frame for ISA " << isa; + LOG(FATAL) << "Cannot write CIE frame for ISA " << isa; UNREACHABLE(); } @@ -653,6 +653,21 @@ class DebugInfoWriter { info_.EndTag(); // DW_TAG_member. } + if (type->IsStringClass()) { + // Emit debug info about an artifical class member for java.lang.String which represents + // the first element of the data stored in a string instance. Consumers of the debug + // info will be able to read the content of java.lang.String based on the count (real + // field) and based on the location of this data member. + info_.StartTag(DW_TAG_member); + WriteName("value"); + // We don't support fields with C like array types so we just say its type is java char. + WriteLazyType("C"); // char. + info_.WriteUdata(DW_AT_data_member_location, + mirror::String::ValueOffset().Uint32Value()); + info_.WriteSdata(DW_AT_accessibility, DW_ACCESS_private); + info_.EndTag(); // DW_TAG_member. + } + EndClassTag(desc); } } @@ -883,6 +898,8 @@ class DebugInfoWriter { info_.EndTag(); } else { // Primitive types. + DCHECK_EQ(desc.size(), 1u); + const char* name; uint32_t encoding; uint32_t byte_size; @@ -1226,26 +1243,8 @@ class DebugLineWriter { std::vector<uintptr_t> debug_line_patches; }; -// Get all types loaded by the runtime. -static std::vector<mirror::Class*> GetLoadedRuntimeTypes() SHARED_REQUIRES(Locks::mutator_lock_) { - std::vector<mirror::Class*> result; - class CollectClasses : public ClassVisitor { - public: - virtual bool Visit(mirror::Class* klass) { - classes_->push_back(klass); - return true; - } - std::vector<mirror::Class*>* classes_; - }; - CollectClasses visitor; - visitor.classes_ = &result; - Runtime::Current()->GetClassLinker()->VisitClasses(&visitor); - return result; -} - template<typename ElfTypes> static void WriteDebugSections(ElfBuilder<ElfTypes>* builder, - bool write_loaded_runtime_types, const ArrayRef<const MethodDebugInfo>& method_infos) { // Group the methods into compilation units based on source file. std::vector<CompilationUnit> compilation_units; @@ -1274,19 +1273,12 @@ static void WriteDebugSections(ElfBuilder<ElfTypes>* builder, } // Write .debug_info section. - if (!compilation_units.empty() || write_loaded_runtime_types) { + if (!compilation_units.empty()) { DebugInfoWriter<ElfTypes> info_writer(builder); info_writer.Start(); for (const auto& compilation_unit : compilation_units) { info_writer.WriteCompilationUnit(compilation_unit); } - if (write_loaded_runtime_types) { - Thread* self = Thread::Current(); - // The lock prevents the classes being moved by the GC. - ReaderMutexLock mu(self, *Locks::mutator_lock_); - std::vector<mirror::Class*> types = GetLoadedRuntimeTypes(); - info_writer.WriteTypes(ArrayRef<mirror::Class*>(types.data(), types.size())); - } info_writer.End(); } } @@ -1353,7 +1345,6 @@ void WriteDebugSymbols(ElfBuilder<ElfTypes>* builder, template <typename ElfTypes> void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, - bool write_loaded_runtime_types, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format) { // Add methods to .symtab. @@ -1361,7 +1352,7 @@ void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, // Generate CFI (stack unwinding information). WriteCFISection(builder, method_infos, cfi_format); // Write DWARF .debug_* sections. - WriteDebugSections(builder, write_loaded_runtime_types, method_infos); + WriteDebugSections(builder, method_infos); } template <typename ElfTypes> @@ -1374,7 +1365,6 @@ static ArrayRef<const uint8_t> WriteDebugElfFileForMethodInternal( std::unique_ptr<ElfBuilder<ElfTypes>> builder(new ElfBuilder<ElfTypes>(isa, &out)); builder->Start(); WriteDebugInfo(builder.get(), - false, ArrayRef<const MethodDebugInfo>(&method_info, 1), DW_DEBUG_FRAME_FORMAT); builder->End(); @@ -1396,8 +1386,8 @@ ArrayRef<const uint8_t> WriteDebugElfFileForMethod(const dwarf::MethodDebugInfo& } template <typename ElfTypes> -static ArrayRef<const uint8_t> WriteDebugElfFileForClassInternal(const InstructionSet isa, - mirror::Class* type) +static ArrayRef<const uint8_t> WriteDebugElfFileForClassesInternal( + const InstructionSet isa, const ArrayRef<mirror::Class*>& types) SHARED_REQUIRES(Locks::mutator_lock_) { std::vector<uint8_t> buffer; buffer.reserve(KB); @@ -1407,7 +1397,7 @@ static ArrayRef<const uint8_t> WriteDebugElfFileForClassInternal(const Instructi DebugInfoWriter<ElfTypes> info_writer(builder.get()); info_writer.Start(); - info_writer.WriteTypes(ArrayRef<mirror::Class*>(&type, 1)); + info_writer.WriteTypes(types); info_writer.End(); builder->End(); @@ -1419,23 +1409,22 @@ static ArrayRef<const uint8_t> WriteDebugElfFileForClassInternal(const Instructi return ArrayRef<const uint8_t>(result, buffer.size()); } -ArrayRef<const uint8_t> WriteDebugElfFileForClass(const InstructionSet isa, mirror::Class* type) { +ArrayRef<const uint8_t> WriteDebugElfFileForClasses(const InstructionSet isa, + const ArrayRef<mirror::Class*>& types) { if (Is64BitInstructionSet(isa)) { - return WriteDebugElfFileForClassInternal<ElfTypes64>(isa, type); + return WriteDebugElfFileForClassesInternal<ElfTypes64>(isa, types); } else { - return WriteDebugElfFileForClassInternal<ElfTypes32>(isa, type); + return WriteDebugElfFileForClassesInternal<ElfTypes32>(isa, types); } } // Explicit instantiations template void WriteDebugInfo<ElfTypes32>( ElfBuilder<ElfTypes32>* builder, - bool write_loaded_runtime_types, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format); template void WriteDebugInfo<ElfTypes64>( ElfBuilder<ElfTypes64>* builder, - bool write_loaded_runtime_types, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format); diff --git a/compiler/elf_writer_debug.h b/compiler/elf_writer_debug.h index 91da00f97a..e4bc856c5e 100644 --- a/compiler/elf_writer_debug.h +++ b/compiler/elf_writer_debug.h @@ -32,13 +32,13 @@ struct MethodDebugInfo; template <typename ElfTypes> void WriteDebugInfo(ElfBuilder<ElfTypes>* builder, - bool write_loaded_runtime_types, const ArrayRef<const MethodDebugInfo>& method_infos, CFIFormat cfi_format); ArrayRef<const uint8_t> WriteDebugElfFileForMethod(const dwarf::MethodDebugInfo& method_info); -ArrayRef<const uint8_t> WriteDebugElfFileForClass(const InstructionSet isa, mirror::Class* type) +ArrayRef<const uint8_t> WriteDebugElfFileForClasses(const InstructionSet isa, + const ArrayRef<mirror::Class*>& types) SHARED_REQUIRES(Locks::mutator_lock_); } // namespace dwarf diff --git a/compiler/elf_writer_quick.cc b/compiler/elf_writer_quick.cc index a67f3bd1a9..7b1bdd72e5 100644 --- a/compiler/elf_writer_quick.cc +++ b/compiler/elf_writer_quick.cc @@ -152,7 +152,7 @@ template <typename ElfTypes> void ElfWriterQuick<ElfTypes>::WriteDebugInfo( const ArrayRef<const dwarf::MethodDebugInfo>& method_infos) { if (compiler_options_->GetGenerateDebugInfo()) { - dwarf::WriteDebugInfo(builder_.get(), /* write_types */ true, method_infos, kCFIFormat); + dwarf::WriteDebugInfo(builder_.get(), method_infos, kCFIFormat); } } diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index bc51ed6e6a..3a3275a5f4 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -28,6 +28,8 @@ #include "dex/quick_compiler_callbacks.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" +#include "elf_writer_debug.h" +#include "jit/debugger_interface.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "oat_file-inl.h" @@ -65,6 +67,17 @@ extern "C" bool jit_compile_method(void* handle, ArtMethod* method, Thread* self return jit_compiler->CompileMethod(self, method); } +extern "C" void jit_types_loaded(void* handle, mirror::Class** types, size_t count) + SHARED_REQUIRES(Locks::mutator_lock_) { + auto* jit_compiler = reinterpret_cast<JitCompiler*>(handle); + DCHECK(jit_compiler != nullptr); + if (jit_compiler->GetCompilerOptions()->GetGenerateDebugInfo()) { + const ArrayRef<mirror::Class*> types_array(types, count); + ArrayRef<const uint8_t> elf_file = dwarf::WriteDebugElfFileForClasses(kRuntimeISA, types_array); + CreateJITCodeEntry(std::unique_ptr<const uint8_t[]>(elf_file.data()), elf_file.size()); + } +} + // Callers of this method assume it has NO_RETURN. NO_RETURN static void Usage(const char* fmt, ...) { va_list ap; @@ -97,7 +110,9 @@ JitCompiler::JitCompiler() : total_time_(0) { /* pic */ true, // TODO: Support non-PIC in optimizing. /* verbose_methods */ nullptr, /* init_failure_output */ nullptr, - /* abort_on_hard_verifier_failure */ false)); + /* abort_on_hard_verifier_failure */ false, + /* dump_cfg_file_name */ "", + /* dump_cfg_append */ false)); for (const std::string& argument : Runtime::Current()->GetCompilerOptions()) { compiler_options_->ParseCompilerOption(argument, Usage); } @@ -153,8 +168,6 @@ JitCompiler::JitCompiler() : total_time_(0) { /* thread_count */ 1, /* dump_stats */ false, /* dump_passes */ false, - /* dump_cfg_file_name */ "", - /* dump_cfg_append */ false, cumulative_logger_.get(), /* swap_fd */ -1, /* dex to oat map */ nullptr, diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h index b10cc3534c..bf8e786f64 100644 --- a/compiler/linker/relative_patcher_test.h +++ b/compiler/linker/relative_patcher_test.h @@ -47,7 +47,7 @@ class RelativePatcherTest : public testing::Test { driver_(&compiler_options_, &verification_results_, &inliner_map_, Compiler::kQuick, instruction_set, nullptr, false, nullptr, nullptr, nullptr, 1u, - false, false, "", false, nullptr, -1, nullptr, nullptr), + false, false, nullptr, -1, nullptr, nullptr), error_msg_(), instruction_set_(instruction_set), features_(InstructionSetFeatures::FromVariant(instruction_set, variant, &error_msg_)), diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 9f7ffa5ace..7a2b74ed88 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -117,8 +117,6 @@ class OatTest : public CommonCompilerTest { 2, true, true, - "", - false, timer_.get(), -1, nullptr, diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 26bf1cbc75..1d604e7135 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -56,7 +56,6 @@ class HGraphBuilder : public ValueObject { return_type_(Primitive::GetType(dex_compilation_unit_->GetShorty()[0])), code_start_(nullptr), latest_result_(nullptr), - can_use_baseline_for_string_init_(true), compilation_stats_(compiler_stats), interpreter_metadata_(interpreter_metadata), dex_cache_(dex_cache) {} @@ -77,7 +76,6 @@ class HGraphBuilder : public ValueObject { return_type_(return_type), code_start_(nullptr), latest_result_(nullptr), - can_use_baseline_for_string_init_(true), compilation_stats_(nullptr), interpreter_metadata_(nullptr), null_dex_cache_(), @@ -85,10 +83,6 @@ class HGraphBuilder : public ValueObject { bool BuildGraph(const DexFile::CodeItem& code); - bool CanUseBaselineForStringInit() const { - return can_use_baseline_for_string_init_; - } - static constexpr const char* kBuilderPassName = "builder"; // The number of entries in a packed switch before we use a jump table or specified @@ -363,11 +357,6 @@ class HGraphBuilder : public ValueObject { // used by move-result instructions. HInstruction* latest_result_; - // We need to know whether we have built a graph that has calls to StringFactory - // and hasn't gone through the verifier. If the following flag is `false`, then - // we cannot compile with baseline. - bool can_use_baseline_for_string_init_; - OptimizingCompilerStats* compilation_stats_; const uint8_t* interpreter_metadata_; diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index ea0b9eca9a..a3bbfdbd27 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -142,23 +142,6 @@ size_t CodeGenerator::GetCachePointerOffset(uint32_t index) { return pointer_size * index; } -void CodeGenerator::CompileBaseline(CodeAllocator* allocator, bool is_leaf) { - Initialize(); - if (!is_leaf) { - MarkNotLeaf(); - } - const bool is_64_bit = Is64BitInstructionSet(GetInstructionSet()); - InitializeCodeGeneration(GetGraph()->GetNumberOfLocalVRegs() - + GetGraph()->GetTemporariesVRegSlots() - + 1 /* filler */, - 0, /* the baseline compiler does not have live registers at slow path */ - 0, /* the baseline compiler does not have live registers at slow path */ - GetGraph()->GetMaximumNumberOfOutVRegs() - + (is_64_bit ? 2 : 1) /* current method */, - GetGraph()->GetBlocks()); - CompileInternal(allocator, /* is_baseline */ true); -} - bool CodeGenerator::GoesToNextBlock(HBasicBlock* current, HBasicBlock* next) const { DCHECK_EQ((*block_order_)[current_block_index_], current); return GetNextBlockToEmit() == FirstNonEmptyBlock(next); @@ -220,8 +203,12 @@ void CodeGenerator::GenerateSlowPaths() { current_slow_path_ = nullptr; } -void CodeGenerator::CompileInternal(CodeAllocator* allocator, bool is_baseline) { - is_baseline_ = is_baseline; +void CodeGenerator::Compile(CodeAllocator* allocator) { + // The register allocator already called `InitializeCodeGeneration`, + // where the frame size has been computed. + DCHECK(block_order_ != nullptr); + Initialize(); + HGraphVisitor* instruction_visitor = GetInstructionVisitor(); DCHECK_EQ(current_block_index_, 0u); @@ -242,9 +229,6 @@ void CodeGenerator::CompileInternal(CodeAllocator* allocator, bool is_baseline) for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* current = it.Current(); DisassemblyScope disassembly_scope(current, *this); - if (is_baseline) { - InitLocationsBaseline(current); - } DCHECK(CheckTypeConsistency(current)); current->Accept(instruction_visitor); } @@ -254,7 +238,7 @@ void CodeGenerator::CompileInternal(CodeAllocator* allocator, bool is_baseline) // Emit catch stack maps at the end of the stack map stream as expected by the // runtime exception handler. - if (!is_baseline && graph_->HasTryCatch()) { + if (graph_->HasTryCatch()) { RecordCatchBlockInfo(); } @@ -262,14 +246,6 @@ void CodeGenerator::CompileInternal(CodeAllocator* allocator, bool is_baseline) Finalize(allocator); } -void CodeGenerator::CompileOptimized(CodeAllocator* allocator) { - // The register allocator already called `InitializeCodeGeneration`, - // where the frame size has been computed. - DCHECK(block_order_ != nullptr); - Initialize(); - CompileInternal(allocator, /* is_baseline */ false); -} - void CodeGenerator::Finalize(CodeAllocator* allocator) { size_t code_size = GetAssembler()->CodeSize(); uint8_t* buffer = allocator->Allocate(code_size); @@ -282,29 +258,6 @@ void CodeGenerator::EmitLinkerPatches(ArenaVector<LinkerPatch>* linker_patches A // No linker patches by default. } -size_t CodeGenerator::FindFreeEntry(bool* array, size_t length) { - for (size_t i = 0; i < length; ++i) { - if (!array[i]) { - array[i] = true; - return i; - } - } - LOG(FATAL) << "Could not find a register in baseline register allocator"; - UNREACHABLE(); -} - -size_t CodeGenerator::FindTwoFreeConsecutiveAlignedEntries(bool* array, size_t length) { - for (size_t i = 0; i < length - 1; i += 2) { - if (!array[i] && !array[i + 1]) { - array[i] = true; - array[i + 1] = true; - return i; - } - } - LOG(FATAL) << "Could not find a register in baseline register allocator"; - UNREACHABLE(); -} - void CodeGenerator::InitializeCodeGeneration(size_t number_of_spill_slots, size_t maximum_number_of_live_core_registers, size_t maximum_number_of_live_fpu_registers, @@ -592,123 +545,6 @@ void CodeGenerator::BlockIfInRegister(Location location, bool is_out) const { } } -void CodeGenerator::AllocateRegistersLocally(HInstruction* instruction) const { - LocationSummary* locations = instruction->GetLocations(); - if (locations == nullptr) return; - - for (size_t i = 0, e = GetNumberOfCoreRegisters(); i < e; ++i) { - blocked_core_registers_[i] = false; - } - - for (size_t i = 0, e = GetNumberOfFloatingPointRegisters(); i < e; ++i) { - blocked_fpu_registers_[i] = false; - } - - for (size_t i = 0, e = number_of_register_pairs_; i < e; ++i) { - blocked_register_pairs_[i] = false; - } - - // Mark all fixed input, temp and output registers as used. - for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { - BlockIfInRegister(locations->InAt(i)); - } - - for (size_t i = 0, e = locations->GetTempCount(); i < e; ++i) { - Location loc = locations->GetTemp(i); - BlockIfInRegister(loc); - } - Location result_location = locations->Out(); - if (locations->OutputCanOverlapWithInputs()) { - BlockIfInRegister(result_location, /* is_out */ true); - } - - SetupBlockedRegisters(/* is_baseline */ true); - - // Allocate all unallocated input locations. - for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { - Location loc = locations->InAt(i); - HInstruction* input = instruction->InputAt(i); - if (loc.IsUnallocated()) { - if ((loc.GetPolicy() == Location::kRequiresRegister) - || (loc.GetPolicy() == Location::kRequiresFpuRegister)) { - loc = AllocateFreeRegister(input->GetType()); - } else { - DCHECK_EQ(loc.GetPolicy(), Location::kAny); - HLoadLocal* load = input->AsLoadLocal(); - if (load != nullptr) { - loc = GetStackLocation(load); - } else { - loc = AllocateFreeRegister(input->GetType()); - } - } - locations->SetInAt(i, loc); - } - } - - // Allocate all unallocated temp locations. - for (size_t i = 0, e = locations->GetTempCount(); i < e; ++i) { - Location loc = locations->GetTemp(i); - if (loc.IsUnallocated()) { - switch (loc.GetPolicy()) { - case Location::kRequiresRegister: - // Allocate a core register (large enough to fit a 32-bit integer). - loc = AllocateFreeRegister(Primitive::kPrimInt); - break; - - case Location::kRequiresFpuRegister: - // Allocate a core register (large enough to fit a 64-bit double). - loc = AllocateFreeRegister(Primitive::kPrimDouble); - break; - - default: - LOG(FATAL) << "Unexpected policy for temporary location " - << loc.GetPolicy(); - } - locations->SetTempAt(i, loc); - } - } - if (result_location.IsUnallocated()) { - switch (result_location.GetPolicy()) { - case Location::kAny: - case Location::kRequiresRegister: - case Location::kRequiresFpuRegister: - result_location = AllocateFreeRegister(instruction->GetType()); - break; - case Location::kSameAsFirstInput: - result_location = locations->InAt(0); - break; - } - locations->UpdateOut(result_location); - } -} - -void CodeGenerator::InitLocationsBaseline(HInstruction* instruction) { - AllocateLocations(instruction); - if (instruction->GetLocations() == nullptr) { - if (instruction->IsTemporary()) { - HInstruction* previous = instruction->GetPrevious(); - Location temp_location = GetTemporaryLocation(instruction->AsTemporary()); - Move(previous, temp_location, instruction); - } - return; - } - AllocateRegistersLocally(instruction); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { - Location location = instruction->GetLocations()->InAt(i); - HInstruction* input = instruction->InputAt(i); - if (location.IsValid()) { - // Move the input to the desired location. - if (input->GetNext()->IsTemporary()) { - // If the input was stored in a temporary, use that temporary to - // perform the move. - Move(input->GetNext(), location, instruction); - } else { - Move(input, location, instruction); - } - } - } -} - void CodeGenerator::AllocateLocations(HInstruction* instruction) { instruction->Accept(GetLocationBuilder()); DCHECK(CheckTypeConsistency(instruction)); @@ -789,132 +625,6 @@ CodeGenerator* CodeGenerator::Create(HGraph* graph, } } -void CodeGenerator::BuildNativeGCMap( - ArenaVector<uint8_t>* data, const CompilerDriver& compiler_driver) const { - const std::vector<uint8_t>& gc_map_raw = - compiler_driver.GetVerifiedMethod(&GetGraph()->GetDexFile(), GetGraph()->GetMethodIdx()) - ->GetDexGcMap(); - verifier::DexPcToReferenceMap dex_gc_map(&(gc_map_raw)[0]); - - uint32_t max_native_offset = stack_map_stream_.ComputeMaxNativePcOffset(); - - size_t num_stack_maps = stack_map_stream_.GetNumberOfStackMaps(); - GcMapBuilder builder(data, num_stack_maps, max_native_offset, dex_gc_map.RegWidth()); - for (size_t i = 0; i != num_stack_maps; ++i) { - const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); - uint32_t native_offset = stack_map_entry.native_pc_offset; - uint32_t dex_pc = stack_map_entry.dex_pc; - const uint8_t* references = dex_gc_map.FindBitMap(dex_pc, false); - CHECK(references != nullptr) << "Missing ref for dex pc 0x" << std::hex << dex_pc; - builder.AddEntry(native_offset, references); - } -} - -void CodeGenerator::BuildMappingTable(ArenaVector<uint8_t>* data) const { - uint32_t pc2dex_data_size = 0u; - uint32_t pc2dex_entries = stack_map_stream_.GetNumberOfStackMaps(); - uint32_t pc2dex_offset = 0u; - int32_t pc2dex_dalvik_offset = 0; - uint32_t dex2pc_data_size = 0u; - uint32_t dex2pc_entries = 0u; - uint32_t dex2pc_offset = 0u; - int32_t dex2pc_dalvik_offset = 0; - - for (size_t i = 0; i < pc2dex_entries; i++) { - const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); - pc2dex_data_size += UnsignedLeb128Size(stack_map_entry.native_pc_offset - pc2dex_offset); - pc2dex_data_size += SignedLeb128Size(stack_map_entry.dex_pc - pc2dex_dalvik_offset); - pc2dex_offset = stack_map_entry.native_pc_offset; - pc2dex_dalvik_offset = stack_map_entry.dex_pc; - } - - // Walk over the blocks and find which ones correspond to catch block entries. - for (HBasicBlock* block : graph_->GetBlocks()) { - if (block->IsCatchBlock()) { - intptr_t native_pc = GetAddressOf(block); - ++dex2pc_entries; - dex2pc_data_size += UnsignedLeb128Size(native_pc - dex2pc_offset); - dex2pc_data_size += SignedLeb128Size(block->GetDexPc() - dex2pc_dalvik_offset); - dex2pc_offset = native_pc; - dex2pc_dalvik_offset = block->GetDexPc(); - } - } - - uint32_t total_entries = pc2dex_entries + dex2pc_entries; - uint32_t hdr_data_size = UnsignedLeb128Size(total_entries) + UnsignedLeb128Size(pc2dex_entries); - uint32_t data_size = hdr_data_size + pc2dex_data_size + dex2pc_data_size; - data->resize(data_size); - - uint8_t* data_ptr = &(*data)[0]; - uint8_t* write_pos = data_ptr; - - write_pos = EncodeUnsignedLeb128(write_pos, total_entries); - write_pos = EncodeUnsignedLeb128(write_pos, pc2dex_entries); - DCHECK_EQ(static_cast<size_t>(write_pos - data_ptr), hdr_data_size); - uint8_t* write_pos2 = write_pos + pc2dex_data_size; - - pc2dex_offset = 0u; - pc2dex_dalvik_offset = 0u; - dex2pc_offset = 0u; - dex2pc_dalvik_offset = 0u; - - for (size_t i = 0; i < pc2dex_entries; i++) { - const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); - DCHECK(pc2dex_offset <= stack_map_entry.native_pc_offset); - write_pos = EncodeUnsignedLeb128(write_pos, stack_map_entry.native_pc_offset - pc2dex_offset); - write_pos = EncodeSignedLeb128(write_pos, stack_map_entry.dex_pc - pc2dex_dalvik_offset); - pc2dex_offset = stack_map_entry.native_pc_offset; - pc2dex_dalvik_offset = stack_map_entry.dex_pc; - } - - for (HBasicBlock* block : graph_->GetBlocks()) { - if (block->IsCatchBlock()) { - intptr_t native_pc = GetAddressOf(block); - write_pos2 = EncodeUnsignedLeb128(write_pos2, native_pc - dex2pc_offset); - write_pos2 = EncodeSignedLeb128(write_pos2, block->GetDexPc() - dex2pc_dalvik_offset); - dex2pc_offset = native_pc; - dex2pc_dalvik_offset = block->GetDexPc(); - } - } - - - DCHECK_EQ(static_cast<size_t>(write_pos - data_ptr), hdr_data_size + pc2dex_data_size); - DCHECK_EQ(static_cast<size_t>(write_pos2 - data_ptr), data_size); - - if (kIsDebugBuild) { - // Verify the encoded table holds the expected data. - MappingTable table(data_ptr); - CHECK_EQ(table.TotalSize(), total_entries); - CHECK_EQ(table.PcToDexSize(), pc2dex_entries); - auto it = table.PcToDexBegin(); - auto it2 = table.DexToPcBegin(); - for (size_t i = 0; i < pc2dex_entries; i++) { - const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); - CHECK_EQ(stack_map_entry.native_pc_offset, it.NativePcOffset()); - CHECK_EQ(stack_map_entry.dex_pc, it.DexPc()); - ++it; - } - for (HBasicBlock* block : graph_->GetBlocks()) { - if (block->IsCatchBlock()) { - CHECK_EQ(GetAddressOf(block), it2.NativePcOffset()); - CHECK_EQ(block->GetDexPc(), it2.DexPc()); - ++it2; - } - } - CHECK(it == table.PcToDexEnd()); - CHECK(it2 == table.DexToPcEnd()); - } -} - -void CodeGenerator::BuildVMapTable(ArenaVector<uint8_t>* data) const { - Leb128Encoder<ArenaVector<uint8_t>> vmap_encoder(data); - // We currently don't use callee-saved registers. - size_t size = 0 + 1 /* marker */ + 0; - vmap_encoder.Reserve(size + 1u); // All values are likely to be one byte in ULEB128 (<128). - vmap_encoder.PushBackUnsigned(size); - vmap_encoder.PushBackUnsigned(VmapTable::kAdjustedFpMarker); -} - size_t CodeGenerator::ComputeStackMapsSize() { return stack_map_stream_.PrepareForFillIn(); } diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index 5958cd89bc..4f8f146753 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -158,10 +158,8 @@ class FieldAccessCallingConvention { class CodeGenerator { public: - // Compiles the graph to executable instructions. Returns whether the compilation - // succeeded. - void CompileBaseline(CodeAllocator* allocator, bool is_leaf = false); - void CompileOptimized(CodeAllocator* allocator); + // Compiles the graph to executable instructions. + void Compile(CodeAllocator* allocator); static CodeGenerator* Create(HGraph* graph, InstructionSet instruction_set, const InstructionSetFeatures& isa_features, @@ -214,7 +212,7 @@ class CodeGenerator { size_t GetNumberOfCoreRegisters() const { return number_of_core_registers_; } size_t GetNumberOfFloatingPointRegisters() const { return number_of_fpu_registers_; } - virtual void SetupBlockedRegisters(bool is_baseline) const = 0; + virtual void SetupBlockedRegisters() const = 0; virtual void ComputeSpillMask() { core_spill_mask_ = allocated_registers_.GetCoreRegisters() & core_callee_save_mask_; @@ -290,17 +288,9 @@ class CodeGenerator { slow_paths_.push_back(slow_path); } - void BuildMappingTable(ArenaVector<uint8_t>* vector) const; - void BuildVMapTable(ArenaVector<uint8_t>* vector) const; - void BuildNativeGCMap( - ArenaVector<uint8_t>* vector, const CompilerDriver& compiler_driver) const; void BuildStackMaps(MemoryRegion region); size_t ComputeStackMapsSize(); - bool IsBaseline() const { - return is_baseline_; - } - bool IsLeafMethod() const { return is_leaf_; } @@ -489,7 +479,6 @@ class CodeGenerator { fpu_callee_save_mask_(fpu_callee_save_mask), stack_map_stream_(graph->GetArena()), block_order_(nullptr), - is_baseline_(false), disasm_info_(nullptr), stats_(stats), graph_(graph), @@ -502,15 +491,6 @@ class CodeGenerator { slow_paths_.reserve(8); } - // Register allocation logic. - void AllocateRegistersLocally(HInstruction* instruction) const; - - // Backend specific implementation for allocating a register. - virtual Location AllocateFreeRegister(Primitive::Type type) const = 0; - - static size_t FindFreeEntry(bool* array, size_t length); - static size_t FindTwoFreeConsecutiveAlignedEntries(bool* array, size_t length); - virtual Location GetStackLocation(HLoadLocal* load) const = 0; virtual HGraphVisitor* GetLocationBuilder() = 0; @@ -593,16 +573,11 @@ class CodeGenerator { // The order to use for code generation. const ArenaVector<HBasicBlock*>* block_order_; - // Whether we are using baseline. - bool is_baseline_; - DisassemblyInformation* disasm_info_; private: - void InitLocationsBaseline(HInstruction* instruction); size_t GetStackOffsetOfSavedRegister(size_t index); void GenerateSlowPaths(); - void CompileInternal(CodeAllocator* allocator, bool is_baseline); void BlockIfInRegister(Location location, bool is_out = false) const; void EmitEnvironment(HEnvironment* environment, SlowPathCode* slow_path); diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index a11ceb9bd9..272579219f 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -47,9 +47,7 @@ static bool ExpectedPairLayout(Location location) { static constexpr int kCurrentMethodStackOffset = 0; static constexpr Register kMethodRegisterArgument = R0; -// We unconditionally allocate R5 to ensure we can do long operations -// with baseline. -static constexpr Register kCoreSavedRegisterForBaseline = R5; +static constexpr Register kCoreAlwaysSpillRegister = R5; static constexpr Register kCoreCalleeSaves[] = { R5, R6, R7, R8, R10, R11, LR }; static constexpr SRegister kFpuCalleeSaves[] = @@ -728,6 +726,24 @@ inline Condition ARMUnsignedCondition(IfCondition cond) { UNREACHABLE(); } +inline Condition ARMFPCondition(IfCondition cond, bool gt_bias) { + // The ARM condition codes can express all the necessary branches, see the + // "Meaning (floating-point)" column in the table A8-1 of the ARMv7 reference manual. + // There is no dex instruction or HIR that would need the missing conditions + // "equal or unordered" or "not equal". + switch (cond) { + case kCondEQ: return EQ; + case kCondNE: return NE /* unordered */; + case kCondLT: return gt_bias ? CC : LT /* unordered */; + case kCondLE: return gt_bias ? LS : LE /* unordered */; + case kCondGT: return gt_bias ? HI /* unordered */ : GT; + case kCondGE: return gt_bias ? CS /* unordered */ : GE; + default: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); + } +} + void CodeGeneratorARM::DumpCoreRegister(std::ostream& stream, int reg) const { stream << Register(reg); } @@ -815,58 +831,7 @@ void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { CodeGenerator::Finalize(allocator); } -Location CodeGeneratorARM::AllocateFreeRegister(Primitive::Type type) const { - switch (type) { - case Primitive::kPrimLong: { - size_t reg = FindFreeEntry(blocked_register_pairs_, kNumberOfRegisterPairs); - ArmManagedRegister pair = - ArmManagedRegister::FromRegisterPair(static_cast<RegisterPair>(reg)); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairLow()]); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairHigh()]); - - blocked_core_registers_[pair.AsRegisterPairLow()] = true; - blocked_core_registers_[pair.AsRegisterPairHigh()] = true; - UpdateBlockedPairRegisters(); - return Location::RegisterPairLocation(pair.AsRegisterPairLow(), pair.AsRegisterPairHigh()); - } - - case Primitive::kPrimByte: - case Primitive::kPrimBoolean: - case Primitive::kPrimChar: - case Primitive::kPrimShort: - case Primitive::kPrimInt: - case Primitive::kPrimNot: { - int reg = FindFreeEntry(blocked_core_registers_, kNumberOfCoreRegisters); - // Block all register pairs that contain `reg`. - for (int i = 0; i < kNumberOfRegisterPairs; i++) { - ArmManagedRegister current = - ArmManagedRegister::FromRegisterPair(static_cast<RegisterPair>(i)); - if (current.AsRegisterPairLow() == reg || current.AsRegisterPairHigh() == reg) { - blocked_register_pairs_[i] = true; - } - } - return Location::RegisterLocation(reg); - } - - case Primitive::kPrimFloat: { - int reg = FindFreeEntry(blocked_fpu_registers_, kNumberOfSRegisters); - return Location::FpuRegisterLocation(reg); - } - - case Primitive::kPrimDouble: { - int reg = FindTwoFreeConsecutiveAlignedEntries(blocked_fpu_registers_, kNumberOfSRegisters); - DCHECK_EQ(reg % 2, 0); - return Location::FpuRegisterPairLocation(reg, reg + 1); - } - - case Primitive::kPrimVoid: - LOG(FATAL) << "Unreachable type " << type; - } - - return Location::NoLocation(); -} - -void CodeGeneratorARM::SetupBlockedRegisters(bool is_baseline) const { +void CodeGeneratorARM::SetupBlockedRegisters() const { // Don't allocate the dalvik style register pair passing. blocked_register_pairs_[R1_R2] = true; @@ -881,15 +846,7 @@ void CodeGeneratorARM::SetupBlockedRegisters(bool is_baseline) const { // Reserve temp register. blocked_core_registers_[IP] = true; - if (is_baseline) { - for (size_t i = 0; i < arraysize(kCoreCalleeSaves); ++i) { - blocked_core_registers_[kCoreCalleeSaves[i]] = true; - } - - blocked_core_registers_[kCoreSavedRegisterForBaseline] = false; - } - - if (is_baseline || GetGraph()->IsDebuggable()) { + if (GetGraph()->IsDebuggable()) { // Stubs do not save callee-save floating point registers. If the graph // is debuggable, we need to deal with these registers differently. For // now, just block them. @@ -919,11 +876,10 @@ InstructionCodeGeneratorARM::InstructionCodeGeneratorARM(HGraph* graph, CodeGene void CodeGeneratorARM::ComputeSpillMask() { core_spill_mask_ = allocated_registers_.GetCoreRegisters() & core_callee_save_mask_; - // Save one extra register for baseline. Note that on thumb2, there is no easy - // instruction to restore just the PC, so this actually helps both baseline - // and non-baseline to save and restore at least two registers at entry and exit. - core_spill_mask_ |= (1 << kCoreSavedRegisterForBaseline); DCHECK_NE(core_spill_mask_, 0u) << "At least the return address register must be saved"; + // There is no easy instruction to restore just the PC on thumb2. We spill and + // restore another arbitrary register. + core_spill_mask_ |= (1 << kCoreAlwaysSpillRegister); fpu_spill_mask_ = allocated_registers_.GetFloatingPointRegisters() & fpu_callee_save_mask_; // We use vpush and vpop for saving and restoring floating point registers, which take // a SRegister and the number of registers to save/restore after that SRegister. We @@ -1416,15 +1372,9 @@ void InstructionCodeGeneratorARM::VisitExit(HExit* exit ATTRIBUTE_UNUSED) { void InstructionCodeGeneratorARM::GenerateFPJumps(HCondition* cond, Label* true_label, - Label* false_label) { + Label* false_label ATTRIBUTE_UNUSED) { __ vmstat(); // transfer FP status register to ARM APSR. - // TODO: merge into a single branch (except "equal or unordered" and "not equal") - if (cond->IsFPConditionTrueIfNaN()) { - __ b(true_label, VS); // VS for unordered. - } else if (cond->IsFPConditionFalseIfNaN()) { - __ b(false_label, VS); // VS for unordered. - } - __ b(true_label, ARMCondition(cond->GetCondition())); + __ b(true_label, ARMFPCondition(cond->GetCondition(), cond->IsGtBias())); } void InstructionCodeGeneratorARM::GenerateLongComparesAndJumps(HCondition* cond, @@ -1972,9 +1922,9 @@ void InstructionCodeGeneratorARM::VisitInvokeUnresolved(HInvokeUnresolved* invok } void LocationsBuilderARM::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderARM intrinsic(GetGraph()->GetArena(), codegen_->GetAssembler(), @@ -2004,9 +1954,9 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorARM* codegen) } void InstructionCodeGeneratorARM::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; @@ -3803,6 +3753,7 @@ void InstructionCodeGeneratorARM::VisitCompare(HCompare* compare) { Label less, greater, done; Primitive::Type type = compare->InputAt(0)->GetType(); + Condition less_cond; switch (type) { case Primitive::kPrimLong: { __ cmp(left.AsRegisterPairHigh<Register>(), @@ -3813,6 +3764,7 @@ void InstructionCodeGeneratorARM::VisitCompare(HCompare* compare) { __ LoadImmediate(out, 0); __ cmp(left.AsRegisterPairLow<Register>(), ShifterOperand(right.AsRegisterPairLow<Register>())); // Unsigned compare. + less_cond = LO; break; } case Primitive::kPrimFloat: @@ -3825,14 +3777,15 @@ void InstructionCodeGeneratorARM::VisitCompare(HCompare* compare) { FromLowSToD(right.AsFpuRegisterPairLow<SRegister>())); } __ vmstat(); // transfer FP status register to ARM APSR. - __ b(compare->IsGtBias() ? &greater : &less, VS); // VS for unordered. + less_cond = ARMFPCondition(kCondLT, compare->IsGtBias()); break; } default: LOG(FATAL) << "Unexpected compare type " << type; + UNREACHABLE(); } __ b(&done, EQ); - __ b(&less, LO); // LO is for both: unsigned compare for longs and 'less than' for floats. + __ b(&less, less_cond); __ Bind(&greater); __ LoadImmediate(out, 1); @@ -5530,7 +5483,7 @@ void InstructionCodeGeneratorARM::VisitInstanceOf(HInstanceOf* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: { // Note that we indeed only call on slow path, but we always go - // into the slow path for the unresolved & interface check + // into the slow path for the unresolved and interface check // cases. // // We cannot directly call the InstanceofNonTrivial runtime @@ -5740,8 +5693,8 @@ void InstructionCodeGeneratorARM::VisitCheckCast(HCheckCast* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: - // We always go into the type check slow path for the unresolved & - // interface check cases. + // We always go into the type check slow path for the unresolved + // and interface check cases. // // We cannot directly call the CheckCast runtime entry point // without resorting to a type checking slow path here (i.e. by @@ -6027,6 +5980,7 @@ void InstructionCodeGeneratorARM::GenerateGcRootFieldLoad(HInstruction* instruct new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathARM(instruction, root, root); codegen_->AddSlowPath(slow_path); + // IP = Thread::Current()->GetIsGcMarking() __ LoadFromOffset( kLoadWord, IP, TR, Thread::IsGcMarkingOffset<kArmWordSize>().Int32Value()); __ CompareAndBranchIfNonZero(IP, slow_path->GetEntryLabel()); @@ -6105,11 +6059,8 @@ void CodeGeneratorARM::GenerateReferenceLoadWithBakerReadBarrier(HInstruction* i // } // // Note: the original implementation in ReadBarrier::Barrier is - // slightly more complex as: - // - it implements the load-load fence using a data dependency on - // the high-bits of rb_state, which are expected to be all zeroes; - // - it performs additional checks that we do not do here for - // performance reasons. + // slightly more complex as it performs additional checks that we do + // not do here for performance reasons. Register ref_reg = ref.AsRegister<Register>(); Register temp_reg = temp.AsRegister<Register>(); diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index 26d6d63b31..d45ea973f9 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -340,9 +340,7 @@ class CodeGeneratorARM : public CodeGenerator { return GetLabelOf(block)->Position(); } - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; Location GetStackLocation(HLoadLocal* load) const OVERRIDE; @@ -444,7 +442,7 @@ class CodeGeneratorARM : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference field load when Baker's read barriers are used. void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, Register obj, uint32_t offset, Location temp, @@ -452,7 +450,7 @@ class CodeGeneratorARM : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference array load when Baker's read barriers are used. void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, Register obj, uint32_t data_offset, Location index, diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 5e905fc9aa..2cb2741b17 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -93,6 +93,24 @@ inline Condition ARM64Condition(IfCondition cond) { UNREACHABLE(); } +inline Condition ARM64FPCondition(IfCondition cond, bool gt_bias) { + // The ARM64 condition codes can express all the necessary branches, see the + // "Meaning (floating-point)" column in the table C1-1 in the ARMv8 reference manual. + // There is no dex instruction or HIR that would need the missing conditions + // "equal or unordered" or "not equal". + switch (cond) { + case kCondEQ: return eq; + case kCondNE: return ne /* unordered */; + case kCondLT: return gt_bias ? cc : lt /* unordered */; + case kCondLE: return gt_bias ? ls : le /* unordered */; + case kCondGT: return gt_bias ? hi /* unordered */ : gt; + case kCondGE: return gt_bias ? cs /* unordered */ : ge; + default: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); + } +} + Location ARM64ReturnLocation(Primitive::Type return_type) { // Note that in practice, `LocationFrom(x0)` and `LocationFrom(w0)` create the // same Location object, and so do `LocationFrom(d0)` and `LocationFrom(s0)`, @@ -604,30 +622,13 @@ class ReadBarrierForHeapReferenceSlowPathARM64 : public SlowPathCodeARM64 { DCHECK(!instruction_->IsInvoke() || (instruction_->IsInvokeStaticOrDirect() && instruction_->GetLocations()->Intrinsified())); + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + DCHECK(!(instruction_->IsArrayGet() && + instruction_->AsArrayGet()->GetArray()->IsArm64IntermediateAddress())); __ Bind(GetEntryLabel()); - // Note: In the case of a HArrayGet instruction, when the base - // address is a HArm64IntermediateAddress instruction, it does not - // point to the array object itself, but to an offset within this - // object. However, the read barrier entry point needs the array - // object address to be passed as first argument. So we - // temporarily set back `obj_` to that address, and restore its - // initial value later. - if (instruction_->IsArrayGet() && - instruction_->AsArrayGet()->GetArray()->IsArm64IntermediateAddress()) { - if (kIsDebugBuild) { - HArm64IntermediateAddress* intermediate_address = - instruction_->AsArrayGet()->GetArray()->AsArm64IntermediateAddress(); - uint32_t intermediate_address_offset = - intermediate_address->GetOffset()->AsIntConstant()->GetValueAsUint64(); - DCHECK_EQ(intermediate_address_offset, offset_); - DCHECK_EQ(mirror::Array::DataOffset(Primitive::ComponentSize(type)).Uint32Value(), offset_); - } - Register obj_reg = RegisterFrom(obj_, Primitive::kPrimInt); - __ Sub(obj_reg, obj_reg, offset_); - } - SaveLiveRegisters(codegen, locations); // We may have to change the index's value, but as `index_` is a @@ -728,22 +729,6 @@ class ReadBarrierForHeapReferenceSlowPathARM64 : public SlowPathCodeARM64 { RestoreLiveRegisters(codegen, locations); - // Restore the value of `obj_` when it corresponds to a - // HArm64IntermediateAddress instruction. - if (instruction_->IsArrayGet() && - instruction_->AsArrayGet()->GetArray()->IsArm64IntermediateAddress()) { - if (kIsDebugBuild) { - HArm64IntermediateAddress* intermediate_address = - instruction_->AsArrayGet()->GetArray()->AsArm64IntermediateAddress(); - uint32_t intermediate_address_offset = - intermediate_address->GetOffset()->AsIntConstant()->GetValueAsUint64(); - DCHECK_EQ(intermediate_address_offset, offset_); - DCHECK_EQ(mirror::Array::DataOffset(Primitive::ComponentSize(type)).Uint32Value(), offset_); - } - Register obj_reg = RegisterFrom(obj_, Primitive::kPrimInt); - __ Add(obj_reg, obj_reg, offset_); - } - __ B(GetExitLabel()); } @@ -1127,7 +1112,7 @@ void CodeGeneratorARM64::MarkGCCard(Register object, Register value, bool value_ } } -void CodeGeneratorARM64::SetupBlockedRegisters(bool is_baseline) const { +void CodeGeneratorARM64::SetupBlockedRegisters() const { // Blocked core registers: // lr : Runtime reserved. // tr : Runtime reserved. @@ -1148,40 +1133,17 @@ void CodeGeneratorARM64::SetupBlockedRegisters(bool is_baseline) const { blocked_fpu_registers_[reserved_fp_registers.PopLowestIndex().code()] = true; } - if (is_baseline) { - CPURegList reserved_core_baseline_registers = callee_saved_core_registers; - while (!reserved_core_baseline_registers.IsEmpty()) { - blocked_core_registers_[reserved_core_baseline_registers.PopLowestIndex().code()] = true; - } - } - - if (is_baseline || GetGraph()->IsDebuggable()) { + if (GetGraph()->IsDebuggable()) { // Stubs do not save callee-save floating point registers. If the graph // is debuggable, we need to deal with these registers differently. For // now, just block them. - CPURegList reserved_fp_baseline_registers = callee_saved_fp_registers; - while (!reserved_fp_baseline_registers.IsEmpty()) { - blocked_fpu_registers_[reserved_fp_baseline_registers.PopLowestIndex().code()] = true; + CPURegList reserved_fp_registers_debuggable = callee_saved_fp_registers; + while (!reserved_fp_registers_debuggable.IsEmpty()) { + blocked_fpu_registers_[reserved_fp_registers_debuggable.PopLowestIndex().code()] = true; } } } -Location CodeGeneratorARM64::AllocateFreeRegister(Primitive::Type type) const { - if (type == Primitive::kPrimVoid) { - LOG(FATAL) << "Unreachable type " << type; - } - - if (Primitive::IsFloatingPointType(type)) { - ssize_t reg = FindFreeEntry(blocked_fpu_registers_, kNumberOfAllocatableFPRegisters); - DCHECK_NE(reg, -1); - return Location::FpuRegisterLocation(reg); - } else { - ssize_t reg = FindFreeEntry(blocked_core_registers_, kNumberOfAllocatableRegisters); - DCHECK_NE(reg, -1); - return Location::RegisterLocation(reg); - } -} - size_t CodeGeneratorARM64::SaveCoreRegister(size_t stack_index, uint32_t reg_id) { Register reg = Register(VIXLRegCodeFromART(reg_id), kXRegSize); __ Str(reg, MemOperand(sp, stack_index)); @@ -1970,6 +1932,9 @@ void InstructionCodeGeneratorARM64::VisitArm64DataProcWithShifterOp( } void LocationsBuilderARM64::VisitArm64IntermediateAddress(HArm64IntermediateAddress* instruction) { + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + DCHECK(!kEmitCompilerReadBarrier); LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall); locations->SetInAt(0, Location::RequiresRegister()); @@ -1979,6 +1944,9 @@ void LocationsBuilderARM64::VisitArm64IntermediateAddress(HArm64IntermediateAddr void InstructionCodeGeneratorARM64::VisitArm64IntermediateAddress( HArm64IntermediateAddress* instruction) { + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + DCHECK(!kEmitCompilerReadBarrier); __ Add(OutputRegister(instruction), InputRegisterAt(instruction, 0), Operand(InputOperandAt(instruction, 1))); @@ -2067,6 +2035,9 @@ void InstructionCodeGeneratorARM64::VisitArrayGet(HArrayGet* instruction) { } else { Register temp = temps.AcquireSameSizeAs(obj); if (instruction->GetArray()->IsArm64IntermediateAddress()) { + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + DCHECK(!kEmitCompilerReadBarrier); // We do not need to compute the intermediate address from the array: the // input instruction has done it already. See the comment in // `InstructionSimplifierArm64::TryExtractArrayAccessAddress()`. @@ -2093,11 +2064,6 @@ void InstructionCodeGeneratorARM64::VisitArrayGet(HArrayGet* instruction) { if (index.IsConstant()) { codegen_->MaybeGenerateReadBarrier(instruction, out, out, obj_loc, offset); } else { - // Note: when `obj_loc` is a HArm64IntermediateAddress, it does - // not contain the base address of the array object, which is - // needed by the read barrier entry point. So the read barrier - // slow path will temporarily set back `obj_loc` to the right - // address (see ReadBarrierForHeapReferenceSlowPathARM64::EmitNativeCode). codegen_->MaybeGenerateReadBarrier(instruction, out, out, obj_loc, offset, index); } } @@ -2161,6 +2127,9 @@ void InstructionCodeGeneratorARM64::VisitArraySet(HArraySet* instruction) { UseScratchRegisterScope temps(masm); Register temp = temps.AcquireSameSizeAs(array); if (instruction->GetArray()->IsArm64IntermediateAddress()) { + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + DCHECK(!kEmitCompilerReadBarrier); // We do not need to compute the intermediate address from the array: the // input instruction has done it already. See the comment in // `InstructionSimplifierArm64::TryExtractArrayAccessAddress()`. @@ -2407,12 +2376,8 @@ void InstructionCodeGeneratorARM64::VisitCompare(HCompare* compare) { } else { __ Fcmp(left, InputFPRegisterAt(compare, 1)); } - if (compare->IsGtBias()) { - __ Cset(result, ne); - } else { - __ Csetm(result, ne); - } - __ Cneg(result, result, compare->IsGtBias() ? mi : gt); + __ Cset(result, ne); + __ Cneg(result, result, ARM64FPCondition(kCondLT, compare->IsGtBias())); break; } default: @@ -2448,7 +2413,6 @@ void InstructionCodeGeneratorARM64::HandleCondition(HCondition* instruction) { LocationSummary* locations = instruction->GetLocations(); Register res = RegisterFrom(locations->Out(), instruction->GetType()); IfCondition if_cond = instruction->GetCondition(); - Condition arm64_cond = ARM64Condition(if_cond); if (Primitive::IsFloatingPointType(instruction->InputAt(0)->GetType())) { FPRegister lhs = InputFPRegisterAt(instruction, 0); @@ -2459,20 +2423,13 @@ void InstructionCodeGeneratorARM64::HandleCondition(HCondition* instruction) { } else { __ Fcmp(lhs, InputFPRegisterAt(instruction, 1)); } - __ Cset(res, arm64_cond); - if (instruction->IsFPConditionTrueIfNaN()) { - // res = IsUnordered(arm64_cond) ? 1 : res <=> res = IsNotUnordered(arm64_cond) ? res : 1 - __ Csel(res, res, Operand(1), vc); // VC for "not unordered". - } else if (instruction->IsFPConditionFalseIfNaN()) { - // res = IsUnordered(arm64_cond) ? 0 : res <=> res = IsNotUnordered(arm64_cond) ? res : 0 - __ Csel(res, res, Operand(0), vc); // VC for "not unordered". - } + __ Cset(res, ARM64FPCondition(if_cond, instruction->IsGtBias())); } else { // Integer cases. Register lhs = InputRegisterAt(instruction, 0); Operand rhs = InputOperandAt(instruction, 1); __ Cmp(lhs, rhs); - __ Cset(res, arm64_cond); + __ Cset(res, ARM64Condition(if_cond)); } } @@ -2842,15 +2799,11 @@ void InstructionCodeGeneratorARM64::GenerateTestAndBranch(HInstruction* instruct } else { __ Fcmp(lhs, InputFPRegisterAt(condition, 1)); } - if (condition->IsFPConditionTrueIfNaN()) { - __ B(vs, true_target == nullptr ? &fallthrough_target : true_target); - } else if (condition->IsFPConditionFalseIfNaN()) { - __ B(vs, false_target == nullptr ? &fallthrough_target : false_target); - } if (true_target == nullptr) { - __ B(ARM64Condition(condition->GetOppositeCondition()), false_target); + IfCondition opposite_condition = condition->GetOppositeCondition(); + __ B(ARM64FPCondition(opposite_condition, condition->IsGtBias()), false_target); } else { - __ B(ARM64Condition(condition->GetCondition()), true_target); + __ B(ARM64FPCondition(condition->GetCondition(), condition->IsGtBias()), true_target); } } else { // Integer cases. @@ -3488,9 +3441,9 @@ void LocationsBuilderARM64::VisitInvokeVirtual(HInvokeVirtual* invoke) { } void LocationsBuilderARM64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderARM64 intrinsic(GetGraph()->GetArena()); if (intrinsic.TryDispatch(invoke)) { @@ -3738,9 +3691,9 @@ vixl::Literal<uint64_t>* CodeGeneratorARM64::DeduplicateMethodCodeLiteral( void InstructionCodeGeneratorARM64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index f2ff89488e..8eb9fcc558 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -339,10 +339,7 @@ class CodeGeneratorARM64 : public CodeGenerator { // Register allocation. - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - // AllocateFreeRegister() is only used when allocating registers locally - // during CompileBaseline(). - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; Location GetStackLocation(HLoadLocal* load) const OVERRIDE; diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index e34767cecd..5bd136a3f0 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -1042,7 +1042,7 @@ void CodeGeneratorMIPS::MarkGCCard(Register object, Register value) { __ Bind(&done); } -void CodeGeneratorMIPS::SetupBlockedRegisters(bool is_baseline) const { +void CodeGeneratorMIPS::SetupBlockedRegisters() const { // Don't allocate the dalvik style register pair passing. blocked_register_pairs_[A1_A2] = true; @@ -1072,16 +1072,6 @@ void CodeGeneratorMIPS::SetupBlockedRegisters(bool is_baseline) const { blocked_fpu_registers_[i] = true; } - if (is_baseline) { - for (size_t i = 0; i < arraysize(kCoreCalleeSaves); ++i) { - blocked_core_registers_[kCoreCalleeSaves[i]] = true; - } - - for (size_t i = 0; i < arraysize(kFpuCalleeSaves); ++i) { - blocked_fpu_registers_[kFpuCalleeSaves[i]] = true; - } - } - UpdateBlockedPairRegisters(); } @@ -1096,52 +1086,6 @@ void CodeGeneratorMIPS::UpdateBlockedPairRegisters() const { } } -Location CodeGeneratorMIPS::AllocateFreeRegister(Primitive::Type type) const { - switch (type) { - case Primitive::kPrimLong: { - size_t reg = FindFreeEntry(blocked_register_pairs_, kNumberOfRegisterPairs); - MipsManagedRegister pair = - MipsManagedRegister::FromRegisterPair(static_cast<RegisterPair>(reg)); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairLow()]); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairHigh()]); - - blocked_core_registers_[pair.AsRegisterPairLow()] = true; - blocked_core_registers_[pair.AsRegisterPairHigh()] = true; - UpdateBlockedPairRegisters(); - return Location::RegisterPairLocation(pair.AsRegisterPairLow(), pair.AsRegisterPairHigh()); - } - - case Primitive::kPrimByte: - case Primitive::kPrimBoolean: - case Primitive::kPrimChar: - case Primitive::kPrimShort: - case Primitive::kPrimInt: - case Primitive::kPrimNot: { - int reg = FindFreeEntry(blocked_core_registers_, kNumberOfCoreRegisters); - // Block all register pairs that contain `reg`. - for (int i = 0; i < kNumberOfRegisterPairs; i++) { - MipsManagedRegister current = - MipsManagedRegister::FromRegisterPair(static_cast<RegisterPair>(i)); - if (current.AsRegisterPairLow() == reg || current.AsRegisterPairHigh() == reg) { - blocked_register_pairs_[i] = true; - } - } - return Location::RegisterLocation(reg); - } - - case Primitive::kPrimFloat: - case Primitive::kPrimDouble: { - int reg = FindFreeEntry(blocked_fpu_registers_, kNumberOfFRegisters); - return Location::FpuRegisterLocation(reg); - } - - case Primitive::kPrimVoid: - LOG(FATAL) << "Unreachable type " << type; - } - - UNREACHABLE(); -} - size_t CodeGeneratorMIPS::SaveCoreRegister(size_t stack_index, uint32_t reg_id) { __ StoreToOffset(kStoreWord, Register(reg_id), SP, stack_index); return kMipsWordSize; @@ -3835,9 +3779,9 @@ void LocationsBuilderMIPS::VisitInvokeVirtual(HInvokeVirtual* invoke) { } void LocationsBuilderMIPS::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderMIPS intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { @@ -3973,9 +3917,9 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke } void InstructionCodeGeneratorMIPS::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index c3d4851ee9..2cde0ed90b 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -290,10 +290,7 @@ class CodeGeneratorMIPS : public CodeGenerator { // Register allocation. - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - // AllocateFreeRegister() is only used when allocating registers locally - // during CompileBaseline(). - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; Location GetStackLocation(HLoadLocal* load) const OVERRIDE; diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 79cd56d698..05054867fe 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -979,7 +979,7 @@ void CodeGeneratorMIPS64::MarkGCCard(GpuRegister object, GpuRegister value) { __ Bind(&done); } -void CodeGeneratorMIPS64::SetupBlockedRegisters(bool is_baseline ATTRIBUTE_UNUSED) const { +void CodeGeneratorMIPS64::SetupBlockedRegisters() const { // ZERO, K0, K1, GP, SP, RA are always reserved and can't be allocated. blocked_core_registers_[ZERO] = true; blocked_core_registers_[K0] = true; @@ -1003,8 +1003,7 @@ void CodeGeneratorMIPS64::SetupBlockedRegisters(bool is_baseline ATTRIBUTE_UNUSE // TODO: review; anything else? - // TODO: make these two for's conditional on is_baseline once - // all the issues with register saving/restoring are sorted out. + // TODO: remove once all the issues with register saving/restoring are sorted out. for (size_t i = 0; i < arraysize(kCoreCalleeSaves); ++i) { blocked_core_registers_[kCoreCalleeSaves[i]] = true; } @@ -1014,20 +1013,6 @@ void CodeGeneratorMIPS64::SetupBlockedRegisters(bool is_baseline ATTRIBUTE_UNUSE } } -Location CodeGeneratorMIPS64::AllocateFreeRegister(Primitive::Type type) const { - if (type == Primitive::kPrimVoid) { - LOG(FATAL) << "Unreachable type " << type; - } - - if (Primitive::IsFloatingPointType(type)) { - size_t reg = FindFreeEntry(blocked_fpu_registers_, kNumberOfFpuRegisters); - return Location::FpuRegisterLocation(reg); - } else { - size_t reg = FindFreeEntry(blocked_core_registers_, kNumberOfGpuRegisters); - return Location::RegisterLocation(reg); - } -} - size_t CodeGeneratorMIPS64::SaveCoreRegister(size_t stack_index, uint32_t reg_id) { __ StoreToOffset(kStoreDoubleword, GpuRegister(reg_id), SP, stack_index); return kMips64WordSize; @@ -3031,9 +3016,9 @@ void LocationsBuilderMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) { } void LocationsBuilderMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderMIPS64 intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { @@ -3182,9 +3167,9 @@ void CodeGeneratorMIPS64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invo } void InstructionCodeGeneratorMIPS64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 7182e8e987..140ff95f14 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -289,10 +289,7 @@ class CodeGeneratorMIPS64 : public CodeGenerator { // Register allocation. - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - // AllocateFreeRegister() is only used when allocating registers locally - // during CompileBaseline(). - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; Location GetStackLocation(HLoadLocal* load) const OVERRIDE; diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 6259acded3..f7ccdd8b8f 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -817,65 +817,13 @@ CodeGeneratorX86::CodeGeneratorX86(HGraph* graph, AddAllocatedRegister(Location::RegisterLocation(kFakeReturnRegister)); } -Location CodeGeneratorX86::AllocateFreeRegister(Primitive::Type type) const { - switch (type) { - case Primitive::kPrimLong: { - size_t reg = FindFreeEntry(blocked_register_pairs_, kNumberOfRegisterPairs); - X86ManagedRegister pair = - X86ManagedRegister::FromRegisterPair(static_cast<RegisterPair>(reg)); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairLow()]); - DCHECK(!blocked_core_registers_[pair.AsRegisterPairHigh()]); - blocked_core_registers_[pair.AsRegisterPairLow()] = true; - blocked_core_registers_[pair.AsRegisterPairHigh()] = true; - UpdateBlockedPairRegisters(); - return Location::RegisterPairLocation(pair.AsRegisterPairLow(), pair.AsRegisterPairHigh()); - } - - case Primitive::kPrimByte: - case Primitive::kPrimBoolean: - case Primitive::kPrimChar: - case Primitive::kPrimShort: - case Primitive::kPrimInt: - case Primitive::kPrimNot: { - Register reg = static_cast<Register>( - FindFreeEntry(blocked_core_registers_, kNumberOfCpuRegisters)); - // Block all register pairs that contain `reg`. - for (int i = 0; i < kNumberOfRegisterPairs; i++) { - X86ManagedRegister current = - X86ManagedRegister::FromRegisterPair(static_cast<RegisterPair>(i)); - if (current.AsRegisterPairLow() == reg || current.AsRegisterPairHigh() == reg) { - blocked_register_pairs_[i] = true; - } - } - return Location::RegisterLocation(reg); - } - - case Primitive::kPrimFloat: - case Primitive::kPrimDouble: { - return Location::FpuRegisterLocation( - FindFreeEntry(blocked_fpu_registers_, kNumberOfXmmRegisters)); - } - - case Primitive::kPrimVoid: - LOG(FATAL) << "Unreachable type " << type; - } - - return Location::NoLocation(); -} - -void CodeGeneratorX86::SetupBlockedRegisters(bool is_baseline) const { +void CodeGeneratorX86::SetupBlockedRegisters() const { // Don't allocate the dalvik style register pair passing. blocked_register_pairs_[ECX_EDX] = true; // Stack register is always reserved. blocked_core_registers_[ESP] = true; - if (is_baseline) { - blocked_core_registers_[EBP] = true; - blocked_core_registers_[ESI] = true; - blocked_core_registers_[EDI] = true; - } - UpdateBlockedPairRegisters(); } @@ -1981,9 +1929,9 @@ void InstructionCodeGeneratorX86::VisitInvokeUnresolved(HInvokeUnresolved* invok } void LocationsBuilderX86::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderX86 intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { @@ -1999,17 +1947,6 @@ void LocationsBuilderX86::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invok if (invoke->HasPcRelativeDexCache()) { invoke->GetLocations()->SetInAt(invoke->GetSpecialInputIndex(), Location::RequiresRegister()); } - - if (codegen_->IsBaseline()) { - // Baseline does not have enough registers if the current method also - // needs a register. We therefore do not require a register for it, and let - // the code generation of the invoke handle it. - LocationSummary* locations = invoke->GetLocations(); - Location location = locations->InAt(invoke->GetSpecialInputIndex()); - if (location.IsUnallocated() && location.GetPolicy() == Location::kRequiresRegister) { - locations->SetInAt(invoke->GetSpecialInputIndex(), Location::NoLocation()); - } - } } static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorX86* codegen) { @@ -2022,9 +1959,9 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorX86* codegen) } void InstructionCodeGeneratorX86::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; @@ -4286,7 +4223,7 @@ void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, if (current_method.IsRegister()) { method_reg = current_method.AsRegister<Register>(); } else { - DCHECK(IsBaseline() || invoke->GetLocations()->Intrinsified()); + DCHECK(invoke->GetLocations()->Intrinsified()); DCHECK(!current_method.IsValid()); method_reg = reg; __ movl(reg, Address(ESP, kCurrentMethodStackOffset)); @@ -5076,11 +5013,6 @@ void InstructionCodeGeneratorX86::VisitArrayGet(HArrayGet* instruction) { } void LocationsBuilderX86::VisitArraySet(HArraySet* instruction) { - // This location builder might end up asking to up to four registers, which is - // not currently possible for baseline. The situation in which we need four - // registers cannot be met by baseline though, because it has not run any - // optimization. - Primitive::Type value_type = instruction->GetComponentType(); bool needs_write_barrier = @@ -6077,7 +6009,7 @@ void InstructionCodeGeneratorX86::VisitInstanceOf(HInstanceOf* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: { // Note that we indeed only call on slow path, but we always go - // into the slow path for the unresolved & interface check + // into the slow path for the unresolved and interface check // cases. // // We cannot directly call the InstanceofNonTrivial runtime @@ -6308,8 +6240,8 @@ void InstructionCodeGeneratorX86::VisitCheckCast(HCheckCast* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: - // We always go into the type check slow path for the unresolved & - // interface check cases. + // We always go into the type check slow path for the unresolved + // and interface check cases. // // We cannot directly call the CheckCast runtime entry point // without resorting to a type checking slow path here (i.e. by @@ -6588,6 +6520,8 @@ void InstructionCodeGeneratorX86::GenerateGcRootFieldLoad(HInstruction* instruct // Plain GC root load with no read barrier. // /* GcRoot<mirror::Object> */ root = *(obj + offset) __ movl(root_reg, Address(obj, offset)); + // Note that GC roots are not affected by heap poisoning, thus we + // do not have to unpoison `root_reg` here. } } @@ -6650,7 +6584,9 @@ void CodeGeneratorX86::GenerateReferenceLoadWithBakerReadBarrier(HInstruction* i // Note: the original implementation in ReadBarrier::Barrier is // slightly more complex as: // - it implements the load-load fence using a data dependency on - // the high-bits of rb_state, which are expected to be all zeroes; + // the high-bits of rb_state, which are expected to be all zeroes + // (we use CodeGeneratorX86::GenerateMemoryBarrier instead here, + // which is a no-op thanks to the x86 memory model); // - it performs additional checks that we do not do here for // performance reasons. diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index c65c423eae..43e9543e41 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -359,9 +359,7 @@ class CodeGeneratorX86 : public CodeGenerator { return GetLabelOf(block)->Position(); } - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; Location GetStackLocation(HLoadLocal* load) const OVERRIDE; @@ -453,7 +451,7 @@ class CodeGeneratorX86 : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference field load when Baker's read barriers are used. void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, Register obj, uint32_t offset, Location temp, @@ -461,7 +459,7 @@ class CodeGeneratorX86 : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference array load when Baker's read barriers are used. void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, Register obj, uint32_t data_offset, Location index, diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index e024ce2b6c..2ce2d91502 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1002,47 +1002,12 @@ InstructionCodeGeneratorX86_64::InstructionCodeGeneratorX86_64(HGraph* graph, assembler_(codegen->GetAssembler()), codegen_(codegen) {} -Location CodeGeneratorX86_64::AllocateFreeRegister(Primitive::Type type) const { - switch (type) { - case Primitive::kPrimLong: - case Primitive::kPrimByte: - case Primitive::kPrimBoolean: - case Primitive::kPrimChar: - case Primitive::kPrimShort: - case Primitive::kPrimInt: - case Primitive::kPrimNot: { - size_t reg = FindFreeEntry(blocked_core_registers_, kNumberOfCpuRegisters); - return Location::RegisterLocation(reg); - } - - case Primitive::kPrimFloat: - case Primitive::kPrimDouble: { - size_t reg = FindFreeEntry(blocked_fpu_registers_, kNumberOfFloatRegisters); - return Location::FpuRegisterLocation(reg); - } - - case Primitive::kPrimVoid: - LOG(FATAL) << "Unreachable type " << type; - } - - return Location::NoLocation(); -} - -void CodeGeneratorX86_64::SetupBlockedRegisters(bool is_baseline) const { +void CodeGeneratorX86_64::SetupBlockedRegisters() const { // Stack register is always reserved. blocked_core_registers_[RSP] = true; // Block the register used as TMP. blocked_core_registers_[TMP] = true; - - if (is_baseline) { - for (size_t i = 0; i < arraysize(kCoreCalleeSaves); ++i) { - blocked_core_registers_[kCoreCalleeSaves[i]] = true; - } - for (size_t i = 0; i < arraysize(kFpuCalleeSaves); ++i) { - blocked_fpu_registers_[kFpuCalleeSaves[i]] = true; - } - } } static dwarf::Reg DWARFReg(Register reg) { @@ -2161,9 +2126,9 @@ void InstructionCodeGeneratorX86_64::VisitInvokeUnresolved(HInvokeUnresolved* in } void LocationsBuilderX86_64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); IntrinsicLocationsBuilderX86_64 intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { @@ -2183,9 +2148,9 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorX86_64* codeg } void InstructionCodeGeneratorX86_64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been pruned by + // art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; @@ -4698,13 +4663,13 @@ void LocationsBuilderX86_64::VisitArraySet(HArraySet* instruction) { bool needs_write_barrier = CodeGenerator::StoreNeedsWriteBarrier(value_type, instruction->GetValue()); - bool may_need_runtime_call = instruction->NeedsTypeCheck(); + bool may_need_runtime_call_for_type_check = instruction->NeedsTypeCheck(); bool object_array_set_with_read_barrier = kEmitCompilerReadBarrier && (value_type == Primitive::kPrimNot); LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary( instruction, - (may_need_runtime_call || object_array_set_with_read_barrier) ? + (may_need_runtime_call_for_type_check || object_array_set_with_read_barrier) ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall); @@ -4733,7 +4698,7 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { Location index = locations->InAt(1); Location value = locations->InAt(2); Primitive::Type value_type = instruction->GetComponentType(); - bool may_need_runtime_call = instruction->NeedsTypeCheck(); + bool may_need_runtime_call_for_type_check = instruction->NeedsTypeCheck(); bool needs_write_barrier = CodeGenerator::StoreNeedsWriteBarrier(value_type, instruction->GetValue()); uint32_t class_offset = mirror::Object::ClassOffset().Int32Value(); @@ -4785,7 +4750,7 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { __ movl(address, Immediate(0)); codegen_->MaybeRecordImplicitNullCheck(instruction); DCHECK(!needs_write_barrier); - DCHECK(!may_need_runtime_call); + DCHECK(!may_need_runtime_call_for_type_check); break; } @@ -4794,7 +4759,7 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { NearLabel done, not_null, do_put; SlowPathCode* slow_path = nullptr; CpuRegister temp = locations->GetTemp(0).AsRegister<CpuRegister>(); - if (may_need_runtime_call) { + if (may_need_runtime_call_for_type_check) { slow_path = new (GetGraph()->GetArena()) ArraySetSlowPathX86_64(instruction); codegen_->AddSlowPath(slow_path); if (instruction->GetValueCanBeNull()) { @@ -4872,7 +4837,7 @@ void InstructionCodeGeneratorX86_64::VisitArraySet(HArraySet* instruction) { } else { __ movl(address, register_value); } - if (!may_need_runtime_call) { + if (!may_need_runtime_call_for_type_check) { codegen_->MaybeRecordImplicitNullCheck(instruction); } @@ -5661,7 +5626,7 @@ void InstructionCodeGeneratorX86_64::VisitInstanceOf(HInstanceOf* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: { // Note that we indeed only call on slow path, but we always go - // into the slow path for the unresolved & interface check + // into the slow path for the unresolved and interface check // cases. // // We cannot directly call the InstanceofNonTrivial runtime @@ -5892,8 +5857,8 @@ void InstructionCodeGeneratorX86_64::VisitCheckCast(HCheckCast* instruction) { case TypeCheckKind::kUnresolvedCheck: case TypeCheckKind::kInterfaceCheck: - // We always go into the type check slow path for the unresolved & - // interface check cases. + // We always go into the type check slow path for the unresolved + // and interface check cases. // // We cannot directly call the CheckCast runtime entry point // without resorting to a type checking slow path here (i.e. by @@ -6155,6 +6120,8 @@ void InstructionCodeGeneratorX86_64::GenerateGcRootFieldLoad(HInstruction* instr // Plain GC root load with no read barrier. // /* GcRoot<mirror::Object> */ root = *(obj + offset) __ movl(root_reg, Address(obj, offset)); + // Note that GC roots are not affected by heap poisoning, thus we + // do not have to unpoison `root_reg` here. } } @@ -6217,7 +6184,9 @@ void CodeGeneratorX86_64::GenerateReferenceLoadWithBakerReadBarrier(HInstruction // Note: the original implementation in ReadBarrier::Barrier is // slightly more complex as: // - it implements the load-load fence using a data dependency on - // the high-bits of rb_state, which are expected to be all zeroes; + // the high-bits of rb_state, which are expected to be all zeroes + // (we use CodeGeneratorX86_64::GenerateMemoryBarrier instead + // here, which is a no-op thanks to the x86-64 memory model); // - it performs additional checks that we do not do here for // performance reasons. diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index 505c9dcdad..82aabb04d3 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -347,8 +347,7 @@ class CodeGeneratorX86_64 : public CodeGenerator { Location GetStackLocation(HLoadLocal* load) const OVERRIDE; - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE; - Location AllocateFreeRegister(Primitive::Type type) const OVERRIDE; + void SetupBlockedRegisters() const OVERRIDE; void DumpCoreRegister(std::ostream& stream, int reg) const OVERRIDE; void DumpFloatingPointRegister(std::ostream& stream, int reg) const OVERRIDE; void Finalize(CodeAllocator* allocator) OVERRIDE; @@ -401,7 +400,7 @@ class CodeGeneratorX86_64 : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference field load when Baker's read barriers are used. void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, CpuRegister obj, uint32_t offset, Location temp, @@ -409,7 +408,7 @@ class CodeGeneratorX86_64 : public CodeGenerator { // Fast path implementation of ReadBarrier::Barrier for a heap // reference array load when Baker's read barriers are used. void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction, - Location out, + Location ref, CpuRegister obj, uint32_t data_offset, Location index, diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc index d970704368..19d63de499 100644 --- a/compiler/optimizing/codegen_test.cc +++ b/compiler/optimizing/codegen_test.cc @@ -40,6 +40,7 @@ #include "dex_file.h" #include "dex_instruction.h" #include "driver/compiler_options.h" +#include "graph_checker.h" #include "nodes.h" #include "optimizing_unit_test.h" #include "prepare_for_register_allocation.h" @@ -70,8 +71,8 @@ class TestCodeGeneratorARM : public arm::CodeGeneratorARM { AddAllocatedRegister(Location::RegisterLocation(arm::R7)); } - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE { - arm::CodeGeneratorARM::SetupBlockedRegisters(is_baseline); + void SetupBlockedRegisters() const OVERRIDE { + arm::CodeGeneratorARM::SetupBlockedRegisters(); blocked_core_registers_[arm::R4] = true; blocked_core_registers_[arm::R6] = false; blocked_core_registers_[arm::R7] = false; @@ -90,8 +91,8 @@ class TestCodeGeneratorX86 : public x86::CodeGeneratorX86 { AddAllocatedRegister(Location::RegisterLocation(x86::EDI)); } - void SetupBlockedRegisters(bool is_baseline) const OVERRIDE { - x86::CodeGeneratorX86::SetupBlockedRegisters(is_baseline); + void SetupBlockedRegisters() const OVERRIDE { + x86::CodeGeneratorX86::SetupBlockedRegisters(); // ebx is a callee-save register in C, but caller-save for ART. blocked_core_registers_[x86::EBX] = true; blocked_register_pairs_[x86::EAX_EBX] = true; @@ -200,259 +201,228 @@ static void Run(const InternalCodeAllocator& allocator, } template <typename Expected> -static void RunCodeBaseline(InstructionSet target_isa, - HGraph* graph, - bool has_result, - Expected expected) { - InternalCodeAllocator allocator; - - CompilerOptions compiler_options; - std::unique_ptr<const X86InstructionSetFeatures> features_x86( - X86InstructionSetFeatures::FromCppDefines()); - TestCodeGeneratorX86 codegenX86(graph, *features_x86.get(), compiler_options); - // We avoid doing a stack overflow check that requires the runtime being setup, - // by making sure the compiler knows the methods we are running are leaf methods. - codegenX86.CompileBaseline(&allocator, true); - if (target_isa == kX86) { - Run(allocator, codegenX86, has_result, expected); - } +static void RunCode(CodeGenerator* codegen, + HGraph* graph, + std::function<void(HGraph*)> hook_before_codegen, + bool has_result, + Expected expected) { + ASSERT_TRUE(graph->IsInSsaForm()); - std::unique_ptr<const ArmInstructionSetFeatures> features_arm( - ArmInstructionSetFeatures::FromCppDefines()); - TestCodeGeneratorARM codegenARM(graph, *features_arm.get(), compiler_options); - codegenARM.CompileBaseline(&allocator, true); - if (target_isa == kArm || target_isa == kThumb2) { - Run(allocator, codegenARM, has_result, expected); - } - - std::unique_ptr<const X86_64InstructionSetFeatures> features_x86_64( - X86_64InstructionSetFeatures::FromCppDefines()); - x86_64::CodeGeneratorX86_64 codegenX86_64(graph, *features_x86_64.get(), compiler_options); - codegenX86_64.CompileBaseline(&allocator, true); - if (target_isa == kX86_64) { - Run(allocator, codegenX86_64, has_result, expected); - } - - std::unique_ptr<const Arm64InstructionSetFeatures> features_arm64( - Arm64InstructionSetFeatures::FromCppDefines()); - arm64::CodeGeneratorARM64 codegenARM64(graph, *features_arm64.get(), compiler_options); - codegenARM64.CompileBaseline(&allocator, true); - if (target_isa == kArm64) { - Run(allocator, codegenARM64, has_result, expected); - } - - std::unique_ptr<const MipsInstructionSetFeatures> features_mips( - MipsInstructionSetFeatures::FromCppDefines()); - mips::CodeGeneratorMIPS codegenMIPS(graph, *features_mips.get(), compiler_options); - codegenMIPS.CompileBaseline(&allocator, true); - if (kRuntimeISA == kMips) { - Run(allocator, codegenMIPS, has_result, expected); - } - - std::unique_ptr<const Mips64InstructionSetFeatures> features_mips64( - Mips64InstructionSetFeatures::FromCppDefines()); - mips64::CodeGeneratorMIPS64 codegenMIPS64(graph, *features_mips64.get(), compiler_options); - codegenMIPS64.CompileBaseline(&allocator, true); - if (target_isa == kMips64) { - Run(allocator, codegenMIPS64, has_result, expected); - } -} + SSAChecker graph_checker(graph); + graph_checker.Run(); + ASSERT_TRUE(graph_checker.IsValid()); -template <typename Expected> -static void RunCodeOptimized(CodeGenerator* codegen, - HGraph* graph, - std::function<void(HGraph*)> hook_before_codegen, - bool has_result, - Expected expected) { - // Tests may have already computed it. - if (graph->GetReversePostOrder().empty()) { - graph->BuildDominatorTree(); - } SsaLivenessAnalysis liveness(graph, codegen); - liveness.Analyze(); - RegisterAllocator register_allocator(graph->GetArena(), codegen, liveness); - register_allocator.AllocateRegisters(); + PrepareForRegisterAllocation(graph).Run(); + liveness.Analyze(); + RegisterAllocator(graph->GetArena(), codegen, liveness).AllocateRegisters(); hook_before_codegen(graph); InternalCodeAllocator allocator; - codegen->CompileOptimized(&allocator); + codegen->Compile(&allocator); Run(allocator, *codegen, has_result, expected); } template <typename Expected> -static void RunCodeOptimized(InstructionSet target_isa, - HGraph* graph, - std::function<void(HGraph*)> hook_before_codegen, - bool has_result, - Expected expected) { +static void RunCode(InstructionSet target_isa, + HGraph* graph, + std::function<void(HGraph*)> hook_before_codegen, + bool has_result, + Expected expected) { CompilerOptions compiler_options; if (target_isa == kArm || target_isa == kThumb2) { std::unique_ptr<const ArmInstructionSetFeatures> features_arm( ArmInstructionSetFeatures::FromCppDefines()); TestCodeGeneratorARM codegenARM(graph, *features_arm.get(), compiler_options); - RunCodeOptimized(&codegenARM, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenARM, graph, hook_before_codegen, has_result, expected); } else if (target_isa == kArm64) { std::unique_ptr<const Arm64InstructionSetFeatures> features_arm64( Arm64InstructionSetFeatures::FromCppDefines()); arm64::CodeGeneratorARM64 codegenARM64(graph, *features_arm64.get(), compiler_options); - RunCodeOptimized(&codegenARM64, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenARM64, graph, hook_before_codegen, has_result, expected); } else if (target_isa == kX86) { std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegenX86(graph, *features_x86.get(), compiler_options); - RunCodeOptimized(&codegenX86, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenX86, graph, hook_before_codegen, has_result, expected); } else if (target_isa == kX86_64) { std::unique_ptr<const X86_64InstructionSetFeatures> features_x86_64( X86_64InstructionSetFeatures::FromCppDefines()); x86_64::CodeGeneratorX86_64 codegenX86_64(graph, *features_x86_64.get(), compiler_options); - RunCodeOptimized(&codegenX86_64, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenX86_64, graph, hook_before_codegen, has_result, expected); } else if (target_isa == kMips) { std::unique_ptr<const MipsInstructionSetFeatures> features_mips( MipsInstructionSetFeatures::FromCppDefines()); mips::CodeGeneratorMIPS codegenMIPS(graph, *features_mips.get(), compiler_options); - RunCodeOptimized(&codegenMIPS, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenMIPS, graph, hook_before_codegen, has_result, expected); } else if (target_isa == kMips64) { std::unique_ptr<const Mips64InstructionSetFeatures> features_mips64( Mips64InstructionSetFeatures::FromCppDefines()); mips64::CodeGeneratorMIPS64 codegenMIPS64(graph, *features_mips64.get(), compiler_options); - RunCodeOptimized(&codegenMIPS64, graph, hook_before_codegen, has_result, expected); + RunCode(&codegenMIPS64, graph, hook_before_codegen, has_result, expected); } } -static void TestCode(InstructionSet target_isa, - const uint16_t* data, +static ::std::vector<InstructionSet> GetTargetISAs() { + ::std::vector<InstructionSet> v; + // Add all ISAs that are executable on hardware or on simulator. + const ::std::vector<InstructionSet> executable_isa_candidates = { + kArm, + kArm64, + kThumb2, + kX86, + kX86_64, + kMips, + kMips64 + }; + + for (auto target_isa : executable_isa_candidates) { + if (CanExecute(target_isa)) { + v.push_back(target_isa); + } + } + + return v; +} + +static void TestCode(const uint16_t* data, bool has_result = false, int32_t expected = 0) { - ArenaPool pool; - ArenaAllocator arena(&pool); - HGraph* graph = CreateGraph(&arena); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - // Remove suspend checks, they cannot be executed in this context. - RemoveSuspendChecks(graph); - RunCodeBaseline(target_isa, graph, has_result, expected); -} - -static void TestCodeLong(InstructionSet target_isa, - const uint16_t* data, + for (InstructionSet target_isa : GetTargetISAs()) { + ArenaPool pool; + ArenaAllocator arena(&pool); + HGraph* graph = CreateGraph(&arena); + HGraphBuilder builder(graph); + const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); + bool graph_built = builder.BuildGraph(*item); + ASSERT_TRUE(graph_built); + // Remove suspend checks, they cannot be executed in this context. + RemoveSuspendChecks(graph); + TransformToSsa(graph); + RunCode(target_isa, graph, [](HGraph*) {}, has_result, expected); + } +} + +static void TestCodeLong(const uint16_t* data, bool has_result, int64_t expected) { - ArenaPool pool; - ArenaAllocator arena(&pool); - HGraph* graph = CreateGraph(&arena); - HGraphBuilder builder(graph, Primitive::kPrimLong); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - // Remove suspend checks, they cannot be executed in this context. - RemoveSuspendChecks(graph); - RunCodeBaseline(target_isa, graph, has_result, expected); + for (InstructionSet target_isa : GetTargetISAs()) { + ArenaPool pool; + ArenaAllocator arena(&pool); + HGraph* graph = CreateGraph(&arena); + HGraphBuilder builder(graph, Primitive::kPrimLong); + const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); + bool graph_built = builder.BuildGraph(*item); + ASSERT_TRUE(graph_built); + // Remove suspend checks, they cannot be executed in this context. + RemoveSuspendChecks(graph); + TransformToSsa(graph); + RunCode(target_isa, graph, [](HGraph*) {}, has_result, expected); + } } -class CodegenTest: public ::testing::TestWithParam<InstructionSet> {}; +class CodegenTest : public CommonCompilerTest {}; -TEST_P(CodegenTest, ReturnVoid) { +TEST_F(CodegenTest, ReturnVoid) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM(Instruction::RETURN_VOID); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, CFG1) { +TEST_F(CodegenTest, CFG1) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x100, Instruction::RETURN_VOID); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, CFG2) { +TEST_F(CodegenTest, CFG2) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x100, Instruction::GOTO | 0x100, Instruction::RETURN_VOID); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, CFG3) { +TEST_F(CodegenTest, CFG3) { const uint16_t data1[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x200, Instruction::RETURN_VOID, Instruction::GOTO | 0xFF00); - TestCode(GetParam(), data1); + TestCode(data1); const uint16_t data2[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO_16, 3, Instruction::RETURN_VOID, Instruction::GOTO_16, 0xFFFF); - TestCode(GetParam(), data2); + TestCode(data2); const uint16_t data3[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO_32, 4, 0, Instruction::RETURN_VOID, Instruction::GOTO_32, 0xFFFF, 0xFFFF); - TestCode(GetParam(), data3); + TestCode(data3); } -TEST_P(CodegenTest, CFG4) { +TEST_F(CodegenTest, CFG4) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::RETURN_VOID, Instruction::GOTO | 0x100, Instruction::GOTO | 0xFE00); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, CFG5) { +TEST_F(CodegenTest, CFG5) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, Instruction::GOTO | 0x100, Instruction::RETURN_VOID); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, IntConstant) { +TEST_F(CodegenTest, IntConstant) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::RETURN_VOID); - TestCode(GetParam(), data); + TestCode(data); } -TEST_P(CodegenTest, Return1) { +TEST_F(CodegenTest, Return1) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::RETURN | 0); - TestCode(GetParam(), data, true, 0); + TestCode(data, true, 0); } -TEST_P(CodegenTest, Return2) { +TEST_F(CodegenTest, Return2) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::CONST_4 | 0 | 1 << 8, Instruction::RETURN | 1 << 8); - TestCode(GetParam(), data, true, 0); + TestCode(data, true, 0); } -TEST_P(CodegenTest, Return3) { +TEST_F(CodegenTest, Return3) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::CONST_4 | 1 << 8 | 1 << 12, Instruction::RETURN | 1 << 8); - TestCode(GetParam(), data, true, 1); + TestCode(data, true, 1); } -TEST_P(CodegenTest, ReturnIf1) { +TEST_F(CodegenTest, ReturnIf1) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::CONST_4 | 1 << 8 | 1 << 12, @@ -460,10 +430,10 @@ TEST_P(CodegenTest, ReturnIf1) { Instruction::RETURN | 0 << 8, Instruction::RETURN | 1 << 8); - TestCode(GetParam(), data, true, 1); + TestCode(data, true, 1); } -TEST_P(CodegenTest, ReturnIf2) { +TEST_F(CodegenTest, ReturnIf2) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::CONST_4 | 1 << 8 | 1 << 12, @@ -471,12 +441,12 @@ TEST_P(CodegenTest, ReturnIf2) { Instruction::RETURN | 0 << 8, Instruction::RETURN | 1 << 8); - TestCode(GetParam(), data, true, 0); + TestCode(data, true, 0); } // Exercise bit-wise (one's complement) not-int instruction. #define NOT_INT_TEST(TEST_NAME, INPUT, EXPECTED_OUTPUT) \ -TEST_P(CodegenTest, TEST_NAME) { \ +TEST_F(CodegenTest, TEST_NAME) { \ const int32_t input = INPUT; \ const uint16_t input_lo = Low16Bits(input); \ const uint16_t input_hi = High16Bits(input); \ @@ -485,7 +455,7 @@ TEST_P(CodegenTest, TEST_NAME) { \ Instruction::NOT_INT | 1 << 8 | 0 << 12 , \ Instruction::RETURN | 1 << 8); \ \ - TestCode(GetParam(), data, true, EXPECTED_OUTPUT); \ + TestCode(data, true, EXPECTED_OUTPUT); \ } NOT_INT_TEST(ReturnNotIntMinus2, -2, 1) @@ -501,7 +471,7 @@ NOT_INT_TEST(ReturnNotIntINT32_MAX, 2147483647, -2147483648) // -(2^31) // Exercise bit-wise (one's complement) not-long instruction. #define NOT_LONG_TEST(TEST_NAME, INPUT, EXPECTED_OUTPUT) \ -TEST_P(CodegenTest, TEST_NAME) { \ +TEST_F(CodegenTest, TEST_NAME) { \ const int64_t input = INPUT; \ const uint16_t word0 = Low16Bits(Low32Bits(input)); /* LSW. */ \ const uint16_t word1 = High16Bits(Low32Bits(input)); \ @@ -512,7 +482,7 @@ TEST_P(CodegenTest, TEST_NAME) { \ Instruction::NOT_LONG | 2 << 8 | 0 << 12, \ Instruction::RETURN_WIDE | 2 << 8); \ \ - TestCodeLong(GetParam(), data, true, EXPECTED_OUTPUT); \ + TestCodeLong(data, true, EXPECTED_OUTPUT); \ } NOT_LONG_TEST(ReturnNotLongMinus2, INT64_C(-2), INT64_C(1)) @@ -551,7 +521,7 @@ NOT_LONG_TEST(ReturnNotLongINT64_MAX, #undef NOT_LONG_TEST -TEST_P(CodegenTest, IntToLongOfLongToInt) { +TEST_F(CodegenTest, IntToLongOfLongToInt) { const int64_t input = INT64_C(4294967296); // 2^32 const uint16_t word0 = Low16Bits(Low32Bits(input)); // LSW. const uint16_t word1 = High16Bits(Low32Bits(input)); @@ -565,192 +535,146 @@ TEST_P(CodegenTest, IntToLongOfLongToInt) { Instruction::INT_TO_LONG | 2 << 8 | 4 << 12, Instruction::RETURN_WIDE | 2 << 8); - TestCodeLong(GetParam(), data, true, 1); + TestCodeLong(data, true, 1); } -TEST_P(CodegenTest, ReturnAdd1) { +TEST_F(CodegenTest, ReturnAdd1) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, Instruction::ADD_INT, 1 << 8 | 0, Instruction::RETURN); - TestCode(GetParam(), data, true, 7); + TestCode(data, true, 7); } -TEST_P(CodegenTest, ReturnAdd2) { +TEST_F(CodegenTest, ReturnAdd2) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, Instruction::ADD_INT_2ADDR | 1 << 12, Instruction::RETURN); - TestCode(GetParam(), data, true, 7); + TestCode(data, true, 7); } -TEST_P(CodegenTest, ReturnAdd3) { +TEST_F(CodegenTest, ReturnAdd3) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0 << 8, Instruction::ADD_INT_LIT8, 3 << 8 | 0, Instruction::RETURN); - TestCode(GetParam(), data, true, 7); + TestCode(data, true, 7); } -TEST_P(CodegenTest, ReturnAdd4) { +TEST_F(CodegenTest, ReturnAdd4) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0 << 8, Instruction::ADD_INT_LIT16, 3, Instruction::RETURN); - TestCode(GetParam(), data, true, 7); -} - -TEST_P(CodegenTest, NonMaterializedCondition) { - ArenaPool pool; - ArenaAllocator allocator(&pool); - - HGraph* graph = CreateGraph(&allocator); - HBasicBlock* entry = new (&allocator) HBasicBlock(graph); - graph->AddBlock(entry); - graph->SetEntryBlock(entry); - entry->AddInstruction(new (&allocator) HGoto()); - - HBasicBlock* first_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(first_block); - entry->AddSuccessor(first_block); - HIntConstant* constant0 = graph->GetIntConstant(0); - HIntConstant* constant1 = graph->GetIntConstant(1); - HEqual* equal = new (&allocator) HEqual(constant0, constant0); - first_block->AddInstruction(equal); - first_block->AddInstruction(new (&allocator) HIf(equal)); - - HBasicBlock* then = new (&allocator) HBasicBlock(graph); - HBasicBlock* else_ = new (&allocator) HBasicBlock(graph); - HBasicBlock* exit = new (&allocator) HBasicBlock(graph); - - graph->AddBlock(then); - graph->AddBlock(else_); - graph->AddBlock(exit); - first_block->AddSuccessor(then); - first_block->AddSuccessor(else_); - then->AddSuccessor(exit); - else_->AddSuccessor(exit); - - exit->AddInstruction(new (&allocator) HExit()); - then->AddInstruction(new (&allocator) HReturn(constant0)); - else_->AddInstruction(new (&allocator) HReturn(constant1)); - - ASSERT_TRUE(equal->NeedsMaterialization()); - graph->BuildDominatorTree(); - PrepareForRegisterAllocation(graph).Run(); - ASSERT_FALSE(equal->NeedsMaterialization()); - - auto hook_before_codegen = [](HGraph* graph_in) { - HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; - HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); - block->InsertInstructionBefore(move, block->GetLastInstruction()); - }; - - RunCodeOptimized(GetParam(), graph, hook_before_codegen, true, 0); + TestCode(data, true, 7); } -TEST_P(CodegenTest, ReturnMulInt) { +TEST_F(CodegenTest, ReturnMulInt) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, Instruction::MUL_INT, 1 << 8 | 0, Instruction::RETURN); - TestCode(GetParam(), data, true, 12); + TestCode(data, true, 12); } -TEST_P(CodegenTest, ReturnMulInt2addr) { +TEST_F(CodegenTest, ReturnMulInt2addr) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, Instruction::MUL_INT_2ADDR | 1 << 12, Instruction::RETURN); - TestCode(GetParam(), data, true, 12); + TestCode(data, true, 12); } -TEST_P(CodegenTest, ReturnMulLong) { +TEST_F(CodegenTest, ReturnMulLong) { const uint16_t data[] = FOUR_REGISTERS_CODE_ITEM( - Instruction::CONST_4 | 3 << 12 | 0, - Instruction::CONST_4 | 0 << 12 | 1 << 8, - Instruction::CONST_4 | 4 << 12 | 2 << 8, - Instruction::CONST_4 | 0 << 12 | 3 << 8, + Instruction::CONST_WIDE | 0 << 8, 3, 0, 0, 0, + Instruction::CONST_WIDE | 2 << 8, 4, 0, 0, 0, Instruction::MUL_LONG, 2 << 8 | 0, Instruction::RETURN_WIDE); - TestCodeLong(GetParam(), data, true, 12); + TestCodeLong(data, true, 12); } -TEST_P(CodegenTest, ReturnMulLong2addr) { +TEST_F(CodegenTest, ReturnMulLong2addr) { const uint16_t data[] = FOUR_REGISTERS_CODE_ITEM( - Instruction::CONST_4 | 3 << 12 | 0 << 8, - Instruction::CONST_4 | 0 << 12 | 1 << 8, - Instruction::CONST_4 | 4 << 12 | 2 << 8, - Instruction::CONST_4 | 0 << 12 | 3 << 8, + Instruction::CONST_WIDE | 0 << 8, 3, 0, 0, 0, + Instruction::CONST_WIDE | 2 << 8, 4, 0, 0, 0, Instruction::MUL_LONG_2ADDR | 2 << 12, Instruction::RETURN_WIDE); - TestCodeLong(GetParam(), data, true, 12); + TestCodeLong(data, true, 12); } -TEST_P(CodegenTest, ReturnMulIntLit8) { +TEST_F(CodegenTest, ReturnMulIntLit8) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0 << 8, Instruction::MUL_INT_LIT8, 3 << 8 | 0, Instruction::RETURN); - TestCode(GetParam(), data, true, 12); + TestCode(data, true, 12); } -TEST_P(CodegenTest, ReturnMulIntLit16) { +TEST_F(CodegenTest, ReturnMulIntLit16) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0 << 8, Instruction::MUL_INT_LIT16, 3, Instruction::RETURN); - TestCode(GetParam(), data, true, 12); + TestCode(data, true, 12); } -TEST_P(CodegenTest, MaterializedCondition1) { - // Check that condition are materialized correctly. A materialized condition - // should yield `1` if it evaluated to true, and `0` otherwise. - // We force the materialization of comparisons for different combinations of - // inputs and check the results. - - int lhs[] = {1, 2, -1, 2, 0xabc}; - int rhs[] = {2, 1, 2, -1, 0xabc}; - - for (size_t i = 0; i < arraysize(lhs); i++) { +TEST_F(CodegenTest, NonMaterializedCondition) { + for (InstructionSet target_isa : GetTargetISAs()) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HBasicBlock* entry_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(entry_block); - graph->SetEntryBlock(entry_block); - entry_block->AddInstruction(new (&allocator) HGoto()); - HBasicBlock* code_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(code_block); + HGraph* graph = CreateGraph(&allocator); + HBasicBlock* entry = new (&allocator) HBasicBlock(graph); + graph->AddBlock(entry); + graph->SetEntryBlock(entry); + entry->AddInstruction(new (&allocator) HGoto()); + + HBasicBlock* first_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(first_block); + entry->AddSuccessor(first_block); + HIntConstant* constant0 = graph->GetIntConstant(0); + HIntConstant* constant1 = graph->GetIntConstant(1); + HEqual* equal = new (&allocator) HEqual(constant0, constant0); + first_block->AddInstruction(equal); + first_block->AddInstruction(new (&allocator) HIf(equal)); + + HBasicBlock* then_block = new (&allocator) HBasicBlock(graph); + HBasicBlock* else_block = new (&allocator) HBasicBlock(graph); HBasicBlock* exit_block = new (&allocator) HBasicBlock(graph); + graph->SetExitBlock(exit_block); + + graph->AddBlock(then_block); + graph->AddBlock(else_block); graph->AddBlock(exit_block); - exit_block->AddInstruction(new (&allocator) HExit()); + first_block->AddSuccessor(then_block); + first_block->AddSuccessor(else_block); + then_block->AddSuccessor(exit_block); + else_block->AddSuccessor(exit_block); - entry_block->AddSuccessor(code_block); - code_block->AddSuccessor(exit_block); - graph->SetExitBlock(exit_block); + exit_block->AddInstruction(new (&allocator) HExit()); + then_block->AddInstruction(new (&allocator) HReturn(constant0)); + else_block->AddInstruction(new (&allocator) HReturn(constant1)); - HIntConstant* cst_lhs = graph->GetIntConstant(lhs[i]); - HIntConstant* cst_rhs = graph->GetIntConstant(rhs[i]); - HLessThan cmp_lt(cst_lhs, cst_rhs); - code_block->AddInstruction(&cmp_lt); - HReturn ret(&cmp_lt); - code_block->AddInstruction(&ret); + ASSERT_TRUE(equal->NeedsMaterialization()); + TransformToSsa(graph); + PrepareForRegisterAllocation(graph).Run(); + ASSERT_FALSE(equal->NeedsMaterialization()); auto hook_before_codegen = [](HGraph* graph_in) { HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; @@ -758,93 +682,143 @@ TEST_P(CodegenTest, MaterializedCondition1) { block->InsertInstructionBefore(move, block->GetLastInstruction()); }; - RunCodeOptimized(GetParam(), graph, hook_before_codegen, true, lhs[i] < rhs[i]); + RunCode(target_isa, graph, hook_before_codegen, true, 0); } } -TEST_P(CodegenTest, MaterializedCondition2) { - // Check that HIf correctly interprets a materialized condition. - // We force the materialization of comparisons for different combinations of - // inputs. An HIf takes the materialized combination as input and returns a - // value that we verify. - - int lhs[] = {1, 2, -1, 2, 0xabc}; - int rhs[] = {2, 1, 2, -1, 0xabc}; - - - for (size_t i = 0; i < arraysize(lhs); i++) { - ArenaPool pool; - ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - - HBasicBlock* entry_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(entry_block); - graph->SetEntryBlock(entry_block); - entry_block->AddInstruction(new (&allocator) HGoto()); - - HBasicBlock* if_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(if_block); - HBasicBlock* if_true_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(if_true_block); - HBasicBlock* if_false_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(if_false_block); - HBasicBlock* exit_block = new (&allocator) HBasicBlock(graph); - graph->AddBlock(exit_block); - exit_block->AddInstruction(new (&allocator) HExit()); - - graph->SetEntryBlock(entry_block); - entry_block->AddSuccessor(if_block); - if_block->AddSuccessor(if_true_block); - if_block->AddSuccessor(if_false_block); - if_true_block->AddSuccessor(exit_block); - if_false_block->AddSuccessor(exit_block); - graph->SetExitBlock(exit_block); - - HIntConstant* cst_lhs = graph->GetIntConstant(lhs[i]); - HIntConstant* cst_rhs = graph->GetIntConstant(rhs[i]); - HLessThan cmp_lt(cst_lhs, cst_rhs); - if_block->AddInstruction(&cmp_lt); - // We insert a temporary to separate the HIf from the HLessThan and force - // the materialization of the condition. - HTemporary force_materialization(0); - if_block->AddInstruction(&force_materialization); - HIf if_lt(&cmp_lt); - if_block->AddInstruction(&if_lt); - - HIntConstant* cst_lt = graph->GetIntConstant(1); - HReturn ret_lt(cst_lt); - if_true_block->AddInstruction(&ret_lt); - HIntConstant* cst_ge = graph->GetIntConstant(0); - HReturn ret_ge(cst_ge); - if_false_block->AddInstruction(&ret_ge); - - auto hook_before_codegen = [](HGraph* graph_in) { - HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; - HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); - block->InsertInstructionBefore(move, block->GetLastInstruction()); - }; +TEST_F(CodegenTest, MaterializedCondition1) { + for (InstructionSet target_isa : GetTargetISAs()) { + // Check that condition are materialized correctly. A materialized condition + // should yield `1` if it evaluated to true, and `0` otherwise. + // We force the materialization of comparisons for different combinations of + + // inputs and check the results. + + int lhs[] = {1, 2, -1, 2, 0xabc}; + int rhs[] = {2, 1, 2, -1, 0xabc}; + + for (size_t i = 0; i < arraysize(lhs); i++) { + ArenaPool pool; + ArenaAllocator allocator(&pool); + HGraph* graph = CreateGraph(&allocator); + + HBasicBlock* entry_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(entry_block); + graph->SetEntryBlock(entry_block); + entry_block->AddInstruction(new (&allocator) HGoto()); + HBasicBlock* code_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(code_block); + HBasicBlock* exit_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(exit_block); + exit_block->AddInstruction(new (&allocator) HExit()); + + entry_block->AddSuccessor(code_block); + code_block->AddSuccessor(exit_block); + graph->SetExitBlock(exit_block); + + HIntConstant* cst_lhs = graph->GetIntConstant(lhs[i]); + HIntConstant* cst_rhs = graph->GetIntConstant(rhs[i]); + HLessThan cmp_lt(cst_lhs, cst_rhs); + code_block->AddInstruction(&cmp_lt); + HReturn ret(&cmp_lt); + code_block->AddInstruction(&ret); + + TransformToSsa(graph); + auto hook_before_codegen = [](HGraph* graph_in) { + HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; + HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); + block->InsertInstructionBefore(move, block->GetLastInstruction()); + }; + RunCode(target_isa, graph, hook_before_codegen, true, lhs[i] < rhs[i]); + } + } +} - RunCodeOptimized(GetParam(), graph, hook_before_codegen, true, lhs[i] < rhs[i]); +TEST_F(CodegenTest, MaterializedCondition2) { + for (InstructionSet target_isa : GetTargetISAs()) { + // Check that HIf correctly interprets a materialized condition. + // We force the materialization of comparisons for different combinations of + // inputs. An HIf takes the materialized combination as input and returns a + // value that we verify. + + int lhs[] = {1, 2, -1, 2, 0xabc}; + int rhs[] = {2, 1, 2, -1, 0xabc}; + + + for (size_t i = 0; i < arraysize(lhs); i++) { + ArenaPool pool; + ArenaAllocator allocator(&pool); + HGraph* graph = CreateGraph(&allocator); + + HBasicBlock* entry_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(entry_block); + graph->SetEntryBlock(entry_block); + entry_block->AddInstruction(new (&allocator) HGoto()); + + HBasicBlock* if_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(if_block); + HBasicBlock* if_true_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(if_true_block); + HBasicBlock* if_false_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(if_false_block); + HBasicBlock* exit_block = new (&allocator) HBasicBlock(graph); + graph->AddBlock(exit_block); + exit_block->AddInstruction(new (&allocator) HExit()); + + graph->SetEntryBlock(entry_block); + entry_block->AddSuccessor(if_block); + if_block->AddSuccessor(if_true_block); + if_block->AddSuccessor(if_false_block); + if_true_block->AddSuccessor(exit_block); + if_false_block->AddSuccessor(exit_block); + graph->SetExitBlock(exit_block); + + HIntConstant* cst_lhs = graph->GetIntConstant(lhs[i]); + HIntConstant* cst_rhs = graph->GetIntConstant(rhs[i]); + HLessThan cmp_lt(cst_lhs, cst_rhs); + if_block->AddInstruction(&cmp_lt); + // We insert a temporary to separate the HIf from the HLessThan and force + // the materialization of the condition. + HTemporary force_materialization(0); + if_block->AddInstruction(&force_materialization); + HIf if_lt(&cmp_lt); + if_block->AddInstruction(&if_lt); + + HIntConstant* cst_lt = graph->GetIntConstant(1); + HReturn ret_lt(cst_lt); + if_true_block->AddInstruction(&ret_lt); + HIntConstant* cst_ge = graph->GetIntConstant(0); + HReturn ret_ge(cst_ge); + if_false_block->AddInstruction(&ret_ge); + + TransformToSsa(graph); + auto hook_before_codegen = [](HGraph* graph_in) { + HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; + HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); + block->InsertInstructionBefore(move, block->GetLastInstruction()); + }; + RunCode(target_isa, graph, hook_before_codegen, true, lhs[i] < rhs[i]); + } } } -TEST_P(CodegenTest, ReturnDivIntLit8) { +TEST_F(CodegenTest, ReturnDivIntLit8) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0 << 8, Instruction::DIV_INT_LIT8, 3 << 8 | 0, Instruction::RETURN); - TestCode(GetParam(), data, true, 1); + TestCode(data, true, 1); } -TEST_P(CodegenTest, ReturnDivInt2Addr) { +TEST_F(CodegenTest, ReturnDivInt2Addr) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 4 << 12 | 0, Instruction::CONST_4 | 2 << 12 | 1 << 8, Instruction::DIV_INT_2ADDR | 1 << 12, Instruction::RETURN); - TestCode(GetParam(), data, true, 2); + TestCode(data, true, 2); } // Helper method. @@ -933,80 +907,55 @@ static void TestComparison(IfCondition condition, block->AddInstruction(comparison); block->AddInstruction(new (&allocator) HReturn(comparison)); - auto hook_before_codegen = [](HGraph*) { - }; - RunCodeOptimized(target_isa, graph, hook_before_codegen, true, expected_result); -} - -TEST_P(CodegenTest, ComparisonsInt) { - const InstructionSet target_isa = GetParam(); - for (int64_t i = -1; i <= 1; i++) { - for (int64_t j = -1; j <= 1; j++) { - TestComparison(kCondEQ, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondNE, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondLT, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondLE, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondGT, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondGE, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondB, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondBE, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondA, i, j, Primitive::kPrimInt, target_isa); - TestComparison(kCondAE, i, j, Primitive::kPrimInt, target_isa); + TransformToSsa(graph); + RunCode(target_isa, graph, [](HGraph*) {}, true, expected_result); +} + +TEST_F(CodegenTest, ComparisonsInt) { + for (InstructionSet target_isa : GetTargetISAs()) { + for (int64_t i = -1; i <= 1; i++) { + for (int64_t j = -1; j <= 1; j++) { + TestComparison(kCondEQ, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondNE, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondLT, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondLE, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondGT, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondGE, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondB, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondBE, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondA, i, j, Primitive::kPrimInt, target_isa); + TestComparison(kCondAE, i, j, Primitive::kPrimInt, target_isa); + } } } } -TEST_P(CodegenTest, ComparisonsLong) { +TEST_F(CodegenTest, ComparisonsLong) { // TODO: make MIPS work for long if (kRuntimeISA == kMips || kRuntimeISA == kMips64) { return; } - const InstructionSet target_isa = GetParam(); - if (target_isa == kMips || target_isa == kMips64) { - return; - } - - for (int64_t i = -1; i <= 1; i++) { - for (int64_t j = -1; j <= 1; j++) { - TestComparison(kCondEQ, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondNE, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondLT, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondLE, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondGT, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondGE, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondB, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondBE, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondA, i, j, Primitive::kPrimLong, target_isa); - TestComparison(kCondAE, i, j, Primitive::kPrimLong, target_isa); + for (InstructionSet target_isa : GetTargetISAs()) { + if (target_isa == kMips || target_isa == kMips64) { + continue; } - } -} -static ::std::vector<InstructionSet> GetTargetISAs() { - ::std::vector<InstructionSet> v; - // Add all ISAs that are executable on hardware or on simulator. - const ::std::vector<InstructionSet> executable_isa_candidates = { - kArm, - kArm64, - kThumb2, - kX86, - kX86_64, - kMips, - kMips64 - }; - - for (auto target_isa : executable_isa_candidates) { - if (CanExecute(target_isa)) { - v.push_back(target_isa); + for (int64_t i = -1; i <= 1; i++) { + for (int64_t j = -1; j <= 1; j++) { + TestComparison(kCondEQ, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondNE, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondLT, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondLE, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondGT, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondGE, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondB, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondBE, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondA, i, j, Primitive::kPrimLong, target_isa); + TestComparison(kCondAE, i, j, Primitive::kPrimLong, target_isa); + } } } - - return v; } -INSTANTIATE_TEST_CASE_P(MultipleTargets, - CodegenTest, - ::testing::ValuesIn(GetTargetISAs())); - } // namespace art diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 86a695b152..e170e37bdd 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -89,15 +89,18 @@ void HDeadCodeElimination::MaybeRecordDeadBlock(HBasicBlock* block) { } void HDeadCodeElimination::RemoveDeadBlocks() { + if (graph_->HasIrreducibleLoops()) { + // Do not eliminate dead blocks if the graph has irreducible loops. We could + // support it, but that would require changes in our loop representation to handle + // multiple entry points. We decided it was not worth the complexity. + return; + } // Classify blocks as reachable/unreachable. ArenaAllocator* allocator = graph_->GetArena(); ArenaBitVector live_blocks(allocator, graph_->GetBlocks().size(), false); MarkReachableBlocks(graph_, &live_blocks); bool removed_one_or_more_blocks = false; - // If the graph has irreducible loops we need to reset all graph analysis we have done - // before: the irreducible loop can be turned into a reducible one. - // For simplicity, we do the full computation regardless of the type of the loops. bool rerun_dominance_and_loop_analysis = false; // Remove all dead blocks. Iterate in post order because removal needs the @@ -105,9 +108,6 @@ void HDeadCodeElimination::RemoveDeadBlocks() { // inside out. for (HPostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); - if (block->IsLoopHeader() && block->GetLoopInformation()->IsIrreducible()) { - rerun_dominance_and_loop_analysis = true; - } int id = block->GetBlockId(); if (!live_blocks.IsBitSet(id)) { MaybeRecordDeadBlock(block); diff --git a/compiler/optimizing/dominator_test.cc b/compiler/optimizing/dominator_test.cc index 91e4a997fd..feb8b2092a 100644 --- a/compiler/optimizing/dominator_test.cc +++ b/compiler/optimizing/dominator_test.cc @@ -133,8 +133,9 @@ TEST(OptimizerTest, CFG4) { const uint32_t dominators[] = { kInvalidBlockId, - 0, - kInvalidBlockId + 3, + kInvalidBlockId, + 0 }; TestCode(data1, dominators, sizeof(dominators) / sizeof(int)); diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 9439ba0c8d..31136772c7 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -484,6 +484,18 @@ void SSAChecker::CheckLoop(HBasicBlock* loop_header) { loop_information->GetPreHeader()->GetSuccessors().size())); } + if (loop_information->GetSuspendCheck() == nullptr) { + AddError(StringPrintf( + "Loop with header %d does not have a suspend check.", + loop_header->GetBlockId())); + } + + if (loop_information->GetSuspendCheck() != loop_header->GetFirstInstructionDisregardMoves()) { + AddError(StringPrintf( + "Loop header %d does not have the loop suspend check as the first instruction.", + loop_header->GetBlockId())); + } + // Ensure the loop header has only one incoming branch and the remaining // predecessors are back edges. size_t num_preds = loop_header->GetPredecessors().size(); @@ -589,6 +601,14 @@ void SSAChecker::VisitInstruction(HInstruction* instruction) { } } + if (instruction->NeedsEnvironment() && !instruction->HasEnvironment()) { + AddError(StringPrintf("Instruction %s:%d in block %d requires an environment " + "but does not have one.", + instruction->DebugName(), + instruction->GetId(), + current_block_->GetBlockId())); + } + // Ensure an instruction having an environment is dominated by the // instructions contained in the environment. for (HEnvironment* environment = instruction->GetEnvironment(); diff --git a/compiler/optimizing/graph_test.cc b/compiler/optimizing/graph_test.cc index d4b9b71952..d5305646a8 100644 --- a/compiler/optimizing/graph_test.cc +++ b/compiler/optimizing/graph_test.cc @@ -164,7 +164,7 @@ TEST(GraphTest, IfSuccessorMultipleBackEdges1) { // Ensure there is only one back edge. ASSERT_EQ(if_block->GetPredecessors().size(), 2u); - ASSERT_EQ(if_block->GetPredecessors()[0], entry_block); + ASSERT_EQ(if_block->GetPredecessors()[0], entry_block->GetSingleSuccessor()); ASSERT_NE(if_block->GetPredecessors()[1], if_block); // Ensure the new block is the back edge. @@ -199,7 +199,7 @@ TEST(GraphTest, IfSuccessorMultipleBackEdges2) { // Ensure there is only one back edge. ASSERT_EQ(if_block->GetPredecessors().size(), 2u); - ASSERT_EQ(if_block->GetPredecessors()[0], entry_block); + ASSERT_EQ(if_block->GetPredecessors()[0], entry_block->GetSingleSuccessor()); ASSERT_NE(if_block->GetPredecessors()[1], if_block); // Ensure the new block is the back edge. diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 293282edbb..2e79df1b84 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -356,12 +356,12 @@ bool HInliner::TryInlineMonomorphicCall(HInvoke* invoke_instruction, compare, invoke_instruction->GetDexPc()); // TODO: Extend reference type propagation to understand the guard. if (cursor != nullptr) { - bb_cursor->InsertInstructionAfter(load_class, cursor); + bb_cursor->InsertInstructionAfter(field_get, cursor); } else { - bb_cursor->InsertInstructionBefore(load_class, bb_cursor->GetFirstInstruction()); + bb_cursor->InsertInstructionBefore(field_get, bb_cursor->GetFirstInstruction()); } - bb_cursor->InsertInstructionAfter(field_get, load_class); - bb_cursor->InsertInstructionAfter(compare, field_get); + bb_cursor->InsertInstructionAfter(load_class, field_get); + bb_cursor->InsertInstructionAfter(compare, load_class); bb_cursor->InsertInstructionAfter(deoptimize, compare); deoptimize->CopyEnvironmentFrom(invoke_instruction->GetEnvironment()); @@ -419,7 +419,10 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, ArtMethod* method, bool do size_t inline_max_code_units = compiler_driver_->GetCompilerOptions().GetInlineMaxCodeUnits(); if (code_item->insns_size_in_code_units_ > inline_max_code_units) { VLOG(compiler) << "Method " << PrettyMethod(method) - << " is too big to inline"; + << " is too big to inline: " + << code_item->insns_size_in_code_units_ + << " > " + << inline_max_code_units; return false; } @@ -639,9 +642,12 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, for (; !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); - if (block->IsLoopHeader()) { + + if (block->IsLoopHeader() && block->GetLoopInformation()->IsIrreducible()) { + // Don't inline methods with irreducible loops, they could prevent some + // optimizations to run. VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) - << " could not be inlined because it contains a loop"; + << " could not be inlined because it contains an irreducible loop"; return false; } diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc index 6bbc751bee..4bcfc54791 100644 --- a/compiler/optimizing/instruction_simplifier_arm64.cc +++ b/compiler/optimizing/instruction_simplifier_arm64.cc @@ -30,6 +30,15 @@ void InstructionSimplifierArm64Visitor::TryExtractArrayAccessAddress(HInstructio HInstruction* array, HInstruction* index, int access_size) { + if (kEmitCompilerReadBarrier) { + // The read barrier instrumentation does not support the + // HArm64IntermediateAddress instruction yet. + // + // TODO: Handle this case properly in the ARM64 code generator and + // re-enable this optimization; otherwise, remove this TODO. + // b/26601270 + return; + } if (index->IsConstant() || (index->IsBoundsCheck() && index->AsBoundsCheck()->GetIndex()->IsConstant())) { // When the index is a constant all the addressing can be fitted in the diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h index 9f50d1814e..3bf3f7ffae 100644 --- a/compiler/optimizing/intrinsics.h +++ b/compiler/optimizing/intrinsics.h @@ -85,9 +85,9 @@ INTRINSICS_LIST(OPTIMIZING_INTRINSICS) InvokeDexCallingConventionVisitor* calling_convention_visitor) { if (kIsDebugBuild && invoke->IsInvokeStaticOrDirect()) { HInvokeStaticOrDirect* invoke_static_or_direct = invoke->AsInvokeStaticOrDirect(); - // When we do not run baseline, explicit clinit checks triggered by static - // invokes must have been pruned by art::PrepareForRegisterAllocation. - DCHECK(codegen->IsBaseline() || !invoke_static_or_direct->IsStaticWithExplicitClinitCheck()); + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation. + DCHECK(!invoke_static_or_direct->IsStaticWithExplicitClinitCheck()); } if (invoke->GetNumberOfArguments() == 0) { diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 854d92a409..adf8734214 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -167,11 +167,7 @@ void HGraph::ClearDominanceInformation() { void HGraph::ClearLoopInformation() { SetHasIrreducibleLoops(false); for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { - HBasicBlock* current = it.Current(); - if (current->IsLoopHeader()) { - current->RemoveInstruction(current->GetLoopInformation()->GetSuspendCheck()); - } - current->SetLoopInformation(nullptr); + it.Current()->SetLoopInformation(nullptr); } } @@ -180,6 +176,14 @@ void HBasicBlock::ClearDominanceInformation() { dominator_ = nullptr; } +HInstruction* HBasicBlock::GetFirstInstructionDisregardMoves() const { + HInstruction* instruction = GetFirstInstruction(); + while (instruction->IsParallelMove()) { + instruction = instruction->GetNext(); + } + return instruction; +} + void HGraph::ComputeDominanceInformation() { DCHECK(reverse_post_order_.empty()); reverse_post_order_.reserve(blocks_.size()); @@ -284,9 +288,10 @@ void HGraph::SimplifyLoop(HBasicBlock* header) { // Make sure the loop has only one pre header. This simplifies SSA building by having // to just look at the pre header to know which locals are initialized at entry of the - // loop. + // loop. Also, don't allow the entry block to be a pre header: this simplifies inlining + // this graph. size_t number_of_incomings = header->GetPredecessors().size() - info->NumberOfBackEdges(); - if (number_of_incomings != 1) { + if (number_of_incomings != 1 || (GetEntryBlock()->GetSingleSuccessor() == header)) { HBasicBlock* pre_header = new (arena_) HBasicBlock(this, header->GetDexPc()); AddBlock(pre_header); pre_header->AddInstruction(new (arena_) HGoto(header->GetDexPc())); @@ -457,6 +462,10 @@ void HGraph::SimplifyCFG() { } if (block->IsLoopHeader()) { SimplifyLoop(block); + } else if (!block->IsEntryBlock() && block->GetFirstInstruction()->IsSuspendCheck()) { + // We are being called by the dead code elimiation pass, and what used to be + // a loop got dismantled. Just remove the suspend check. + block->RemoveInstruction(block->GetFirstInstruction()); } } } @@ -1829,6 +1838,7 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { DCHECK(GetBlocks()[0]->IsEntryBlock()); DCHECK(GetBlocks()[2]->IsExitBlock()); DCHECK(!body->IsExitBlock()); + DCHECK(!body->IsInLoop()); HInstruction* last = body->GetLastInstruction(); invoke->GetBlock()->instructions_.AddAfter(invoke, body->GetInstructions()); @@ -1887,7 +1897,7 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // Update the meta information surrounding blocks: // (1) the graph they are now in, // (2) the reverse post order of that graph, - // (3) the potential loop information they are now in, + // (3) their potential loop information, inner and outer, // (4) try block membership. // Note that we do not need to update catch phi inputs because they // correspond to the register file of the outer method which the inlinee @@ -1916,15 +1926,24 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { HBasicBlock* current = it.Current(); if (current != exit_block_ && current != entry_block_ && current != first) { - DCHECK(!current->IsInLoop()); DCHECK(current->GetTryCatchInformation() == nullptr); DCHECK(current->GetGraph() == this); current->SetGraph(outer_graph); outer_graph->AddBlock(current); outer_graph->reverse_post_order_[++index_of_at] = current; - if (loop_info != nullptr) { + if (!current->IsInLoop()) { current->SetLoopInformation(loop_info); - for (HLoopInformationOutwardIterator loop_it(*at); !loop_it.Done(); loop_it.Advance()) { + } else if (current->IsLoopHeader()) { + // Clear the information of which blocks are contained in that loop. Since the + // information is stored as a bit vector based on block ids, we have to update + // it, as those block ids were specific to the callee graph and we are now adding + // these blocks to the caller graph. + current->GetLoopInformation()->ClearAllBlocks(); + } + if (current->IsInLoop()) { + for (HLoopInformationOutwardIterator loop_it(*current); + !loop_it.Done(); + loop_it.Advance()) { loop_it.Current()->Add(current); } } @@ -1937,7 +1956,9 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { outer_graph->AddBlock(to); outer_graph->reverse_post_order_[++index_of_at] = to; if (loop_info != nullptr) { - to->SetLoopInformation(loop_info); + if (!to->IsInLoop()) { + to->SetLoopInformation(loop_info); + } for (HLoopInformationOutwardIterator loop_it(*at); !loop_it.Done(); loop_it.Advance()) { loop_it.Current()->Add(to); } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 859d570b29..5246fd1f05 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -689,6 +689,10 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { void Add(HBasicBlock* block); void Remove(HBasicBlock* block); + void ClearAllBlocks() { + blocks_.ClearAllBits(); + } + private: // Internal recursive implementation of `Populate`. void PopulateRecursive(HBasicBlock* block); @@ -860,6 +864,8 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { HInstruction* GetLastPhi() const { return phis_.last_instruction_; } const HInstructionList& GetPhis() const { return phis_; } + HInstruction* GetFirstInstructionDisregardMoves() const; + void AddSuccessor(HBasicBlock* block) { successors_.push_back(block); block->predecessors_.push_back(this); @@ -3687,19 +3693,13 @@ class HInvokeStaticOrDirect : public HInvoke { DCHECK(!IsStaticWithExplicitClinitCheck()); } - HNewInstance* GetThisArgumentOfStringInit() const { - DCHECK(IsStringInit()); - size_t index = InputCount() - 1; - DCHECK(InputAt(index)->IsNewInstance()); - return InputAt(index)->AsNewInstance(); - } - - void RemoveThisArgumentOfStringInit() { + HInstruction* GetAndRemoveThisArgumentOfStringInit() { DCHECK(IsStringInit()); size_t index = InputCount() - 1; - DCHECK(InputAt(index)->IsNewInstance()); + HInstruction* input = InputAt(index); RemoveAsUserOfInput(index); inputs_.pop_back(); + return input; } // Is this a call to a static method whose declaring class has an diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index bb840eabdd..fffd00535c 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -127,7 +127,7 @@ class PassObserver : public ValueObject { timing_logger_enabled_(compiler_driver->GetDumpPasses()), timing_logger_(timing_logger_enabled_ ? GetMethodName() : "", true, true), disasm_info_(graph->GetArena()), - visualizer_enabled_(!compiler_driver->GetDumpCfgFileName().empty()), + visualizer_enabled_(!compiler_driver->GetCompilerOptions().GetDumpCfgFileName().empty()), visualizer_(visualizer_output, graph, *codegen), graph_in_bad_state_(false) { if (timing_logger_enabled_ || visualizer_enabled_) { @@ -305,30 +305,19 @@ class OptimizingCompiler FINAL : public Compiler { SHARED_REQUIRES(Locks::mutator_lock_); private: - // Whether we should run any optimization or register allocation. If false, will - // just run the code generation after the graph was built. - const bool run_optimizations_; - // Create a 'CompiledMethod' for an optimized graph. - CompiledMethod* EmitOptimized(ArenaAllocator* arena, - CodeVectorAllocator* code_allocator, - CodeGenerator* codegen, - CompilerDriver* driver) const; - - // Create a 'CompiledMethod' for a non-optimized graph. - CompiledMethod* EmitBaseline(ArenaAllocator* arena, - CodeVectorAllocator* code_allocator, - CodeGenerator* codegen, - CompilerDriver* driver) const; + CompiledMethod* Emit(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + CodeGenerator* codegen, + CompilerDriver* driver) const; // Try compiling a method and return the code generator used for // compiling it. // This method: // 1) Builds the graph. Returns null if it failed to build it. - // 2) If `run_optimizations_` is set: - // 2.1) Transform the graph to SSA. Returns null if it failed. - // 2.2) Run optimizations on the graph, including register allocator. - // 3) Generate code with the `code_allocator` provided. + // 2) Transforms the graph to SSA. Returns null if it failed. + // 3) Runs optimizations on the graph, including register allocator. + // 4) Generates code with the `code_allocator` provided. CodeGenerator* TryCompile(ArenaAllocator* arena, CodeVectorAllocator* code_allocator, const DexFile::CodeItem* code_item, @@ -350,21 +339,19 @@ class OptimizingCompiler FINAL : public Compiler { static const int kMaximumCompilationTimeBeforeWarning = 100; /* ms */ OptimizingCompiler::OptimizingCompiler(CompilerDriver* driver) - : Compiler(driver, kMaximumCompilationTimeBeforeWarning), - run_optimizations_( - driver->GetCompilerOptions().GetCompilerFilter() != CompilerOptions::kTime) {} + : Compiler(driver, kMaximumCompilationTimeBeforeWarning) {} void OptimizingCompiler::Init() { // Enable C1visualizer output. Must be done in Init() because the compiler // driver is not fully initialized when passed to the compiler's constructor. CompilerDriver* driver = GetCompilerDriver(); - const std::string cfg_file_name = driver->GetDumpCfgFileName(); + const std::string cfg_file_name = driver->GetCompilerOptions().GetDumpCfgFileName(); if (!cfg_file_name.empty()) { CHECK_EQ(driver->GetThreadCount(), 1U) << "Graph visualizer requires the compiler to run single-threaded. " << "Invoke the compiler with '-j1'."; std::ios_base::openmode cfg_file_mode = - driver->GetDumpCfgAppend() ? std::ofstream::app : std::ofstream::out; + driver->GetCompilerOptions().GetDumpCfgAppend() ? std::ofstream::app : std::ofstream::out; visualizer_output_.reset(new std::ofstream(cfg_file_name, cfg_file_mode)); } if (driver->GetDumpStats()) { @@ -577,17 +564,6 @@ static void RunOptimizations(HGraph* graph, AllocateRegisters(graph, codegen, pass_observer); } -// The stack map we generate must be 4-byte aligned on ARM. Since existing -// maps are generated alongside these stack maps, we must also align them. -static ArrayRef<const uint8_t> AlignVectorSize(ArenaVector<uint8_t>& vector) { - size_t size = vector.size(); - size_t aligned_size = RoundUp(size, 4); - for (; size < aligned_size; ++size) { - vector.push_back(0); - } - return ArrayRef<const uint8_t>(vector); -} - static ArenaVector<LinkerPatch> EmitAndSortLinkerPatches(CodeGenerator* codegen) { ArenaVector<LinkerPatch> linker_patches(codegen->GetGraph()->GetArena()->Adapter()); codegen->EmitLinkerPatches(&linker_patches); @@ -601,10 +577,10 @@ static ArenaVector<LinkerPatch> EmitAndSortLinkerPatches(CodeGenerator* codegen) return linker_patches; } -CompiledMethod* OptimizingCompiler::EmitOptimized(ArenaAllocator* arena, - CodeVectorAllocator* code_allocator, - CodeGenerator* codegen, - CompilerDriver* compiler_driver) const { +CompiledMethod* OptimizingCompiler::Emit(ArenaAllocator* arena, + CodeVectorAllocator* code_allocator, + CodeGenerator* codegen, + CompilerDriver* compiler_driver) const { ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen); ArenaVector<uint8_t> stack_map(arena->Adapter(kArenaAllocStackMaps)); stack_map.resize(codegen->ComputeStackMapsSize()); @@ -630,39 +606,6 @@ CompiledMethod* OptimizingCompiler::EmitOptimized(ArenaAllocator* arena, return compiled_method; } -CompiledMethod* OptimizingCompiler::EmitBaseline( - ArenaAllocator* arena, - CodeVectorAllocator* code_allocator, - CodeGenerator* codegen, - CompilerDriver* compiler_driver) const { - ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen); - - ArenaVector<uint8_t> mapping_table(arena->Adapter(kArenaAllocBaselineMaps)); - codegen->BuildMappingTable(&mapping_table); - ArenaVector<uint8_t> vmap_table(arena->Adapter(kArenaAllocBaselineMaps)); - codegen->BuildVMapTable(&vmap_table); - ArenaVector<uint8_t> gc_map(arena->Adapter(kArenaAllocBaselineMaps)); - codegen->BuildNativeGCMap(&gc_map, *compiler_driver); - - CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod( - compiler_driver, - codegen->GetInstructionSet(), - ArrayRef<const uint8_t>(code_allocator->GetMemory()), - // Follow Quick's behavior and set the frame size to zero if it is - // considered "empty" (see the definition of - // art::CodeGenerator::HasEmptyFrame). - codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), - codegen->GetCoreSpillMask(), - codegen->GetFpuSpillMask(), - ArrayRef<const SrcMapElem>(), - AlignVectorSize(mapping_table), - AlignVectorSize(vmap_table), - AlignVectorSize(gc_map), - ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()), - ArrayRef<const LinkerPatch>(linker_patches)); - return compiled_method; -} - CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, CodeVectorAllocator* code_allocator, const DexFile::CodeItem* code_item, @@ -775,41 +718,37 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, VLOG(compiler) << "Optimizing " << pass_observer.GetMethodName(); - if (run_optimizations_) { - ScopedObjectAccess soa(Thread::Current()); - StackHandleScopeCollection handles(soa.Self()); - ScopedThreadSuspension sts(soa.Self(), kNative); - - { - PassScope scope(SsaBuilder::kSsaBuilderPassName, &pass_observer); - GraphAnalysisResult result = graph->TryBuildingSsa(&handles); - if (result != kAnalysisSuccess) { - switch (result) { - case kAnalysisFailThrowCatchLoop: - MaybeRecordStat(MethodCompilationStat::kNotCompiledThrowCatchLoop); - break; - case kAnalysisFailAmbiguousArrayOp: - MaybeRecordStat(MethodCompilationStat::kNotCompiledAmbiguousArrayOp); - break; - case kAnalysisSuccess: - UNREACHABLE(); - } - pass_observer.SetGraphInBadState(); - return nullptr; + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + ScopedThreadSuspension sts(soa.Self(), kNative); + + { + PassScope scope(SsaBuilder::kSsaBuilderPassName, &pass_observer); + GraphAnalysisResult result = graph->TryBuildingSsa(&handles); + if (result != kAnalysisSuccess) { + switch (result) { + case kAnalysisFailThrowCatchLoop: + MaybeRecordStat(MethodCompilationStat::kNotCompiledThrowCatchLoop); + break; + case kAnalysisFailAmbiguousArrayOp: + MaybeRecordStat(MethodCompilationStat::kNotCompiledAmbiguousArrayOp); + break; + case kAnalysisSuccess: + UNREACHABLE(); } + pass_observer.SetGraphInBadState(); + return nullptr; } - - RunOptimizations(graph, - codegen.get(), - compiler_driver, - compilation_stats_.get(), - dex_compilation_unit, - &pass_observer, - &handles); - codegen->CompileOptimized(code_allocator); - } else { - codegen->CompileBaseline(code_allocator); } + + RunOptimizations(graph, + codegen.get(), + compiler_driver, + compilation_stats_.get(), + dex_compilation_unit, + &pass_observer, + &handles); + codegen->Compile(code_allocator); pass_observer.DumpDisassembly(); if (kArenaAllocatorCountAllocations) { @@ -861,11 +800,7 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, dex_cache)); if (codegen.get() != nullptr) { MaybeRecordStat(MethodCompilationStat::kCompiled); - if (run_optimizations_) { - method = EmitOptimized(&arena, &code_allocator, codegen.get(), compiler_driver); - } else { - method = EmitBaseline(&arena, &code_allocator, codegen.get(), compiler_driver); - } + method = Emit(&arena, &code_allocator, codegen.get(), compiler_driver); } } else { if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) { @@ -928,8 +863,6 @@ bool OptimizingCompiler::JitCompile(Thread* self, { // Go to native so that we don't block GC during compilation. ScopedThreadSuspension sts(self, kNative); - - DCHECK(run_optimizations_); codegen.reset( TryCompile(&arena, &code_allocator, diff --git a/compiler/optimizing/parallel_move_resolver.cc b/compiler/optimizing/parallel_move_resolver.cc index 9d136f3ae6..be470ccb7d 100644 --- a/compiler/optimizing/parallel_move_resolver.cc +++ b/compiler/optimizing/parallel_move_resolver.cc @@ -504,7 +504,7 @@ void ParallelMoveResolverNoSwap::PerformMove(size_t index) { void ParallelMoveResolverNoSwap::UpdateMoveSource(Location from, Location to) { // This function is used to reduce the dependencies in the graph after // (from -> to) has been performed. Since we ensure there is no move with the same - // destination, (to -> X) can not be blocked while (from -> X) might still be + // destination, (to -> X) cannot be blocked while (from -> X) might still be // blocked. Consider for example the moves (0 -> 1) (1 -> 2) (1 -> 3). After // (1 -> 2) has been performed, the moves left are (0 -> 1) and (1 -> 3). There is // a dependency between the two. If we update the source location from 1 to 2, we diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index 2bae4bc5c8..a966b62b4f 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -72,8 +72,7 @@ RegisterAllocator::RegisterAllocator(ArenaAllocator* allocator, float_spill_slots_.reserve(kDefaultNumberOfSpillSlots); double_spill_slots_.reserve(kDefaultNumberOfSpillSlots); - static constexpr bool kIsBaseline = false; - codegen->SetupBlockedRegisters(kIsBaseline); + codegen->SetupBlockedRegisters(); physical_core_register_intervals_.resize(codegen->GetNumberOfCoreRegisters(), nullptr); physical_fp_register_intervals_.resize(codegen->GetNumberOfFloatingPointRegisters(), nullptr); // Always reserve for the current method and the graph's max out registers. diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index 7494e336b1..165d09d1a5 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -422,6 +422,34 @@ bool SsaBuilder::FixAmbiguousArrayOps() { return true; } +void SsaBuilder::RemoveRedundantUninitializedStrings() { + if (GetGraph()->IsDebuggable()) { + // Do not perform the optimization for consistency with the interpreter + // which always allocates an object for new-instance of String. + return; + } + + for (HNewInstance* new_instance : uninitialized_strings_) { + DCHECK(new_instance->IsStringAlloc()); + + // Replace NewInstance of String with NullConstant if not used prior to + // calling StringFactory. In case of deoptimization, the interpreter is + // expected to skip null check on the `this` argument of the StringFactory call. + if (!new_instance->HasNonEnvironmentUses()) { + new_instance->ReplaceWith(GetGraph()->GetNullConstant()); + new_instance->GetBlock()->RemoveInstruction(new_instance); + + // Remove LoadClass if not needed any more. + HLoadClass* load_class = new_instance->InputAt(0)->AsLoadClass(); + DCHECK(load_class != nullptr); + DCHECK(!load_class->NeedsAccessCheck()) << "String class is always accessible"; + if (!load_class->HasUses()) { + load_class->GetBlock()->RemoveInstruction(load_class); + } + } + } +} + GraphAnalysisResult SsaBuilder::BuildSsa() { // 1) Visit in reverse post order. We need to have all predecessors of a block // visited (with the exception of loops) in order to create the right environment @@ -487,7 +515,15 @@ GraphAnalysisResult SsaBuilder::BuildSsa() { // input types. dead_phi_elimimation.EliminateDeadPhis(); - // 11) Clear locals. + // 11) Step 1) replaced uses of NewInstances of String with the results of + // their corresponding StringFactory calls. Unless the String objects are used + // before they are initialized, they can be replaced with NullConstant. + // Note that this optimization is valid only if unsimplified code does not use + // the uninitialized value because we assume execution can be deoptimized at + // any safepoint. We must therefore perform it before any other optimizations. + RemoveRedundantUninitializedStrings(); + + // 12) Clear locals. for (HInstructionIterator it(GetGraph()->GetEntryBlock()->GetInstructions()); !it.Done(); it.Advance()) { @@ -891,12 +927,21 @@ void SsaBuilder::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { if (invoke->IsStringInit()) { // This is a StringFactory call which acts as a String constructor. Its // result replaces the empty String pre-allocated by NewInstance. - HNewInstance* new_instance = invoke->GetThisArgumentOfStringInit(); - invoke->RemoveThisArgumentOfStringInit(); + HInstruction* arg_this = invoke->GetAndRemoveThisArgumentOfStringInit(); + + // Replacing the NewInstance might render it redundant. Keep a list of these + // to be visited once it is clear whether it is has remaining uses. + if (arg_this->IsNewInstance()) { + uninitialized_strings_.push_back(arg_this->AsNewInstance()); + } else { + DCHECK(arg_this->IsPhi()); + // NewInstance is not the direct input of the StringFactory call. It might + // be redundant but optimizing this case is not worth the effort. + } - // Walk over all vregs and replace any occurrence of `new_instance` with `invoke`. + // Walk over all vregs and replace any occurrence of `arg_this` with `invoke`. for (size_t vreg = 0, e = current_locals_->size(); vreg < e; ++vreg) { - if ((*current_locals_)[vreg] == new_instance) { + if ((*current_locals_)[vreg] == arg_this) { (*current_locals_)[vreg] = invoke; } } diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h index 28eef6a40c..ccef8ea380 100644 --- a/compiler/optimizing/ssa_builder.h +++ b/compiler/optimizing/ssa_builder.h @@ -57,6 +57,7 @@ class SsaBuilder : public HGraphVisitor { loop_headers_(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)), ambiguous_agets_(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)), ambiguous_asets_(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)), + uninitialized_strings_(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)), locals_for_(graph->GetBlocks().size(), ArenaVector<HInstruction*>(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)), graph->GetArena()->Adapter(kArenaAllocSsaBuilder)) { @@ -105,6 +106,8 @@ class SsaBuilder : public HGraphVisitor { HPhi* GetFloatDoubleOrReferenceEquivalentOfPhi(HPhi* phi, Primitive::Type type); HArrayGet* GetFloatOrDoubleEquivalentOfArrayGet(HArrayGet* aget); + void RemoveRedundantUninitializedStrings(); + StackHandleScopeCollection* const handles_; // True if types of ambiguous ArrayGets have been resolved. @@ -119,6 +122,7 @@ class SsaBuilder : public HGraphVisitor { ArenaVector<HArrayGet*> ambiguous_agets_; ArenaVector<HArraySet*> ambiguous_asets_; + ArenaVector<HNewInstance*> uninitialized_strings_; // HEnvironment for each block. ArenaVector<ArenaVector<HInstruction*>> locals_for_; diff --git a/compiler/profile_assistant.cc b/compiler/profile_assistant.cc index 81f2a5692d..85335efcc4 100644 --- a/compiler/profile_assistant.cc +++ b/compiler/profile_assistant.cc @@ -16,54 +16,154 @@ #include "profile_assistant.h" +#include "base/unix_file/fd_file.h" +#include "os.h" + namespace art { // Minimum number of new methods that profiles must contain to enable recompilation. static constexpr const uint32_t kMinNewMethodsForCompilation = 10; -bool ProfileAssistant::ProcessProfiles( - const std::vector<std::string>& profile_files, - const std::vector<std::string>& reference_profile_files, - /*out*/ ProfileCompilationInfo** profile_compilation_info) { +bool ProfileAssistant::ProcessProfilesInternal( + const std::vector<ScopedFlock>& profile_files, + const std::vector<ScopedFlock>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { DCHECK(!profile_files.empty()); - DCHECK(reference_profile_files.empty() || + DCHECK(!reference_profile_files.empty() || (profile_files.size() == reference_profile_files.size())); std::vector<ProfileCompilationInfo> new_info(profile_files.size()); bool should_compile = false; // Read the main profile files. - for (size_t i = 0; i < profile_files.size(); i++) { - if (!new_info[i].Load(profile_files[i])) { - LOG(WARNING) << "Could not load profile file: " << profile_files[i]; + for (size_t i = 0; i < new_info.size(); i++) { + if (!new_info[i].Load(profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not load profile file at index " << i; return false; } // Do we have enough new profiled methods that will make the compilation worthwhile? should_compile |= (new_info[i].GetNumberOfMethods() > kMinNewMethodsForCompilation); } + if (!should_compile) { - *profile_compilation_info = nullptr; return true; } std::unique_ptr<ProfileCompilationInfo> result(new ProfileCompilationInfo()); + // Merge information. for (size_t i = 0; i < new_info.size(); i++) { + if (!reference_profile_files.empty()) { + if (!new_info[i].Load(reference_profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not load reference profile file at index " << i; + return false; + } + } // Merge all data into a single object. - result->Load(new_info[i]); - // If we have any reference profile information merge their information with - // the current profiles and save them back to disk. + if (!result->Load(new_info[i])) { + LOG(WARNING) << "Could not merge profile data at index " << i; + return false; + } + } + // We were successful in merging all profile information. Update the files. + for (size_t i = 0; i < new_info.size(); i++) { if (!reference_profile_files.empty()) { - if (!new_info[i].Load(reference_profile_files[i])) { - LOG(WARNING) << "Could not load reference profile file: " << reference_profile_files[i]; + if (!reference_profile_files[i].GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear reference profile file at index " << i; + return false; + } + if (!new_info[i].Save(reference_profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not save reference profile file at index " << i; return false; } - if (!new_info[i].Save(reference_profile_files[i])) { - LOG(WARNING) << "Could not save reference profile file: " << reference_profile_files[i]; + if (!profile_files[i].GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear profile file at index " << i; return false; } } } + *profile_compilation_info = result.release(); return true; } +class ScopedCollectionFlock { + public: + explicit ScopedCollectionFlock(size_t size) : flocks_(size) {} + + // Will block until all the locks are acquired. + bool Init(const std::vector<std::string>& filenames, /* out */ std::string* error) { + for (size_t i = 0; i < filenames.size(); i++) { + if (!flocks_[i].Init(filenames[i].c_str(), O_RDWR, /* block */ true, error)) { + *error += " (index=" + std::to_string(i) + ")"; + return false; + } + } + return true; + } + + // Will block until all the locks are acquired. + bool Init(const std::vector<uint32_t>& fds, /* out */ std::string* error) { + for (size_t i = 0; i < fds.size(); i++) { + // We do not own the descriptor, so disable auto-close and don't check usage. + File file(fds[i], false); + file.DisableAutoClose(); + if (!flocks_[i].Init(&file, error)) { + *error += " (index=" + std::to_string(i) + ")"; + return false; + } + } + return true; + } + + const std::vector<ScopedFlock>& Get() const { return flocks_; } + + private: + std::vector<ScopedFlock> flocks_; +}; + +bool ProfileAssistant::ProcessProfiles( + const std::vector<uint32_t>& profile_files_fd, + const std::vector<uint32_t>& reference_profile_files_fd, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { + *profile_compilation_info = nullptr; + + std::string error; + ScopedCollectionFlock profile_files_flocks(profile_files_fd.size()); + if (!profile_files_flocks.Init(profile_files_fd, &error)) { + LOG(WARNING) << "Could not lock profile files: " << error; + return false; + } + ScopedCollectionFlock reference_profile_files_flocks(reference_profile_files_fd.size()); + if (!reference_profile_files_flocks.Init(reference_profile_files_fd, &error)) { + LOG(WARNING) << "Could not lock reference profile files: " << error; + return false; + } + + return ProcessProfilesInternal(profile_files_flocks.Get(), + reference_profile_files_flocks.Get(), + profile_compilation_info); +} + +bool ProfileAssistant::ProcessProfiles( + const std::vector<std::string>& profile_files, + const std::vector<std::string>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { + *profile_compilation_info = nullptr; + + std::string error; + ScopedCollectionFlock profile_files_flocks(profile_files.size()); + if (!profile_files_flocks.Init(profile_files, &error)) { + LOG(WARNING) << "Could not lock profile files: " << error; + return false; + } + ScopedCollectionFlock reference_profile_files_flocks(reference_profile_files.size()); + if (!reference_profile_files_flocks.Init(reference_profile_files, &error)) { + LOG(WARNING) << "Could not lock reference profile files: " << error; + return false; + } + + return ProcessProfilesInternal(profile_files_flocks.Get(), + reference_profile_files_flocks.Get(), + profile_compilation_info); +} + } // namespace art diff --git a/compiler/profile_assistant.h b/compiler/profile_assistant.h index 088c8bd1c7..ad5e2163cf 100644 --- a/compiler/profile_assistant.h +++ b/compiler/profile_assistant.h @@ -20,6 +20,7 @@ #include <string> #include <vector> +#include "base/scoped_flock.h" #include "jit/offline_profiling_info.cc" namespace art { @@ -52,7 +53,17 @@ class ProfileAssistant { const std::vector<std::string>& reference_profile_files, /*out*/ ProfileCompilationInfo** profile_compilation_info); + static bool ProcessProfiles( + const std::vector<uint32_t>& profile_files_fd_, + const std::vector<uint32_t>& reference_profile_files_fd_, + /*out*/ ProfileCompilationInfo** profile_compilation_info); + private: + static bool ProcessProfilesInternal( + const std::vector<ScopedFlock>& profile_files, + const std::vector<ScopedFlock>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info); + DISALLOW_COPY_AND_ASSIGN(ProfileAssistant); }; diff --git a/compiler/profile_assistant_test.cc b/compiler/profile_assistant_test.cc new file mode 100644 index 0000000000..58b7513377 --- /dev/null +++ b/compiler/profile_assistant_test.cc @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "base/unix_file/fd_file.h" +#include "common_runtime_test.h" +#include "compiler/profile_assistant.h" +#include "jit/offline_profiling_info.h" + +namespace art { + +class ProfileAssistantTest : public CommonRuntimeTest { + protected: + void SetupProfile(const std::string& id, + uint32_t checksum, + uint16_t number_of_methods, + const ScratchFile& profile, + ProfileCompilationInfo* info, + uint16_t start_method_index = 0) { + std::string dex_location1 = "location1" + id; + uint32_t dex_location_checksum1 = checksum; + std::string dex_location2 = "location2" + id; + uint32_t dex_location_checksum2 = 10 * checksum; + for (uint16_t i = start_method_index; i < start_method_index + number_of_methods; i++) { + ASSERT_TRUE(info->AddData(dex_location1, dex_location_checksum1, i)); + ASSERT_TRUE(info->AddData(dex_location2, dex_location_checksum2, i)); + } + ASSERT_TRUE(info->Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + } + + uint32_t GetFd(const ScratchFile& file) const { + return static_cast<uint32_t>(file.GetFd()); + } +}; + +TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + // We should advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result != nullptr); + + // The resulting compilation info must be equal to the merge of the inputs. + ProfileCompilationInfo expected; + ASSERT_TRUE(expected.Load(info1)); + ASSERT_TRUE(expected.Load(info2)); + ASSERT_TRUE(expected.Equals(*result)); + + // The information from profiles must be transfered to the reference profiles. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(reference_profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(reference_profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(reference_profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Initial profiles must be cleared. + ASSERT_EQ(0, profile1.GetFile()->GetLength()); + ASSERT_EQ(0, profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + // The new profile info will contain the methods with indices 0-100. + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + + // The reference profile info will contain the methods with indices 50-150. + const uint16_t kNumberOfMethodsAlreadyCompiled = 100; + ProfileCompilationInfo reference_info1; + SetupProfile("p1", 1, kNumberOfMethodsAlreadyCompiled, reference_profile1, + &reference_info1, kNumberOfMethodsToEnableCompilation / 2); + ProfileCompilationInfo reference_info2; + SetupProfile("p2", 2, kNumberOfMethodsAlreadyCompiled, reference_profile2, + &reference_info2, kNumberOfMethodsToEnableCompilation / 2); + + // We should advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result != nullptr); + + // The resulting compilation info must be equal to the merge of the inputs + ProfileCompilationInfo expected; + ASSERT_TRUE(expected.Load(info1)); + ASSERT_TRUE(expected.Load(info2)); + ASSERT_TRUE(expected.Load(reference_info1)); + ASSERT_TRUE(expected.Load(reference_info2)); + ASSERT_TRUE(expected.Equals(*result)); + + // The information from profiles must be transfered to the reference profiles. + ProfileCompilationInfo file_info1; + ProfileCompilationInfo merge1; + ASSERT_TRUE(merge1.Load(info1)); + ASSERT_TRUE(merge1.Load(reference_info1)); + ASSERT_TRUE(reference_profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(reference_profile1))); + ASSERT_TRUE(file_info1.Equals(merge1)); + + ProfileCompilationInfo file_info2; + ProfileCompilationInfo merge2; + ASSERT_TRUE(merge2.Load(info2)); + ASSERT_TRUE(merge2.Load(reference_info2)); + ASSERT_TRUE(reference_profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile2))); + ASSERT_TRUE(file_info2.Equals(merge2)); + + // Initial profiles must be cleared. + ASSERT_EQ(0, profile1.GetFile()->GetLength()); + ASSERT_EQ(0, profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, DoNotAdviseCompilation) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToSkipCompilation = 1; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToSkipCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToSkipCompilation, profile2, &info2); + + // We should not advise compilation. + ProfileCompilationInfo* result = nullptr; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Reference profile files must remain empty. + ASSERT_EQ(0, reference_profile1.GetFile()->GetLength()); + ASSERT_EQ(0, reference_profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, FailProcessingBecauseOfProfiles) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + // Assign different hashes for the same dex file. This will make merging of information to fail. + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + // We should fail processing. + ProfileCompilationInfo* result = nullptr; + ASSERT_FALSE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must still remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Reference profile files must still remain empty. + ASSERT_EQ(0, reference_profile1.GetFile()->GetLength()); + ASSERT_EQ(0, reference_profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, FailProcessingBecauseOfReferenceProfiles) { + ScratchFile profile1; + ScratchFile reference_profile; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + // Assign different hashes for the same dex file. This will make merging of information to fail. + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo reference_info; + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, reference_profile, &reference_info); + + // We should not advise compilation. + ProfileCompilationInfo* result = nullptr; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(reference_profile.GetFile()->ResetOffset()); + ASSERT_FALSE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must still remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(reference_profile.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile))); + ASSERT_TRUE(file_info2.Equals(reference_info)); +} + +} // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index ea5423961c..52c22836b5 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -340,6 +340,12 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --profile-file will be merged into --reference-profile-file. Valid only when"); UsageError(" specified together with --profile-file."); UsageError(""); + UsageError(" --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor."); + UsageError(" Cannot be used together with --profile-file."); + UsageError(""); + UsageError(" --reference-profile-file-fd=<number>: same as --reference-profile-file but"); + UsageError(" accepts a file descriptor. Cannot be used together with"); + UsageError(" --reference-profile-file."); UsageError(" --print-pass-names: print a list of pass names"); UsageError(""); UsageError(" --disable-passes=<pass-names>: disable one or more passes separated by comma."); @@ -497,6 +503,14 @@ static bool UseSwap(bool is_image, std::vector<const DexFile*>& dex_files) { return dex_files_size >= kMinDexFileCumulativeSizeForSwap; } +static void CloseAllFds(const std::vector<uint32_t>& fds, const char* descriptor) { + for (size_t i = 0; i < fds.size(); i++) { + if (close(fds[i]) < 0) { + PLOG(WARNING) << "Failed to close descriptor for " << descriptor << " at index " << i; + } + } +} + class Dex2Oat FINAL { public: explicit Dex2Oat(TimingLogger* timings) : @@ -528,7 +542,6 @@ class Dex2Oat FINAL { dump_passes_(false), dump_timing_(false), dump_slow_timing_(kIsDebugBuild), - dump_cfg_append_(false), swap_fd_(-1), app_image_fd_(kInvalidImageFd), timings_(timings) {} @@ -576,6 +589,14 @@ class Dex2Oat FINAL { ParseUintOption(option, "--oat-fd", &oat_fd_, Usage); } + void ParseFdForCollection(const StringPiece& option, + const char* arg_name, + std::vector<uint32_t>* fds) { + uint32_t fd; + ParseUintOption(option, arg_name, &fd, Usage); + fds->push_back(fd); + } + void ParseJ(const StringPiece& option) { ParseUintOption(option, "-j", &thread_count_, Usage, /* is_long_option */ false); } @@ -779,11 +800,25 @@ class Dex2Oat FINAL { } } + if (!profile_files_.empty() && !profile_files_fd_.empty()) { + Usage("Profile files should not be specified with both --profile-file-fd and --profile-file"); + } if (!profile_files_.empty()) { if (!reference_profile_files_.empty() && (reference_profile_files_.size() != profile_files_.size())) { Usage("If specified, --reference-profile-file should match the number of --profile-file."); } + } else if (!reference_profile_files_.empty()) { + Usage("--reference-profile-file should only be supplied with --profile-file"); + } + if (!profile_files_fd_.empty()) { + if (!reference_profile_files_fd_.empty() && + (reference_profile_files_fd_.size() != profile_files_fd_.size())) { + Usage("If specified, --reference-profile-file-fd should match the number", + " of --profile-file-fd."); + } + } else if (!reference_profile_files_fd_.empty()) { + Usage("--reference-profile-file-fd should only be supplied with --profile-file-fd"); } if (!parser_options->oat_symbols.empty()) { @@ -1077,6 +1112,10 @@ class Dex2Oat FINAL { } else if (option.starts_with("--reference-profile-file=")) { reference_profile_files_.push_back( option.substr(strlen("--reference-profile-file=")).ToString()); + } else if (option.starts_with("--profile-file-fd=")) { + ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_); + } else if (option.starts_with("--reference-profile-file-fd=")) { + ParseFdForCollection(option, "--reference_profile-file-fd", &reference_profile_files_fd_); } else if (option == "--no-profile-file") { // No profile } else if (option == "--host") { @@ -1093,10 +1132,6 @@ class Dex2Oat FINAL { dump_timing_ = true; } else if (option == "--dump-passes") { dump_passes_ = true; - } else if (option.starts_with("--dump-cfg=")) { - dump_cfg_file_name_ = option.substr(strlen("--dump-cfg=")).data(); - } else if (option.starts_with("--dump-cfg-append")) { - dump_cfg_append_ = true; } else if (option == "--dump-stats") { dump_stats_ = true; } else if (option.starts_with("--swap-file=")) { @@ -1509,8 +1544,6 @@ class Dex2Oat FINAL { thread_count_, dump_stats_, dump_passes_, - dump_cfg_file_name_, - dump_cfg_append_, compiler_phases_timings_.get(), swap_fd_, &dex_file_oat_filename_map_, @@ -1822,17 +1855,27 @@ class Dex2Oat FINAL { } bool UseProfileGuidedCompilation() const { - return !profile_files_.empty(); + return !profile_files_.empty() || !profile_files_fd_.empty(); } bool ProcessProfiles() { DCHECK(UseProfileGuidedCompilation()); ProfileCompilationInfo* info = nullptr; - if (ProfileAssistant::ProcessProfiles(profile_files_, reference_profile_files_, &info)) { - profile_compilation_info_.reset(info); - return true; + bool result = false; + if (profile_files_.empty()) { + DCHECK(!profile_files_fd_.empty()); + result = ProfileAssistant::ProcessProfiles( + profile_files_fd_, reference_profile_files_fd_, &info); + CloseAllFds(profile_files_fd_, "profile_files_fd_"); + CloseAllFds(reference_profile_files_fd_, "reference_profile_files_fd_"); + } else { + result = ProfileAssistant::ProcessProfiles( + profile_files_, reference_profile_files_, &info); } - return false; + + profile_compilation_info_.reset(info); + + return result; } bool ShouldCompileBasedOnProfiles() const { @@ -2312,14 +2355,14 @@ class Dex2Oat FINAL { bool dump_passes_; bool dump_timing_; bool dump_slow_timing_; - std::string dump_cfg_file_name_; - bool dump_cfg_append_; std::string swap_file_name_; int swap_fd_; std::string app_image_file_name_; int app_image_fd_; std::vector<std::string> profile_files_; std::vector<std::string> reference_profile_files_; + std::vector<uint32_t> profile_files_fd_; + std::vector<uint32_t> reference_profile_files_fd_; std::unique_ptr<ProfileCompilationInfo> profile_compilation_info_; TimingLogger* timings_; std::unique_ptr<CumulativeLogger> compiler_phases_timings_; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 69e767dbd3..7b9ce5bd1b 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -395,6 +395,9 @@ class OatDumper { os << "MAGIC:\n"; os << oat_header.GetMagic() << "\n\n"; + os << "LOCATION:\n"; + os << oat_file_.GetLocation() << "\n\n"; + os << "CHECKSUM:\n"; os << StringPrintf("0x%08x\n\n", oat_header.GetChecksum()); diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc index 7170f73e10..d4b873e441 100644 --- a/runtime/arch/stub_test.cc +++ b/runtime/arch/stub_test.cc @@ -378,8 +378,8 @@ class StubTest : public CommonRuntimeTest { "memory"); // clobber. #elif defined(__mips__) && defined(__LP64__) __asm__ __volatile__ ( - // Spill a0-a7 and t0-t3 which we say we don't clobber. May contain args. - "daddiu $sp, $sp, -96\n\t" + // Spill a0-a7 which we say we don't clobber. May contain args. + "daddiu $sp, $sp, -64\n\t" "sd $a0, 0($sp)\n\t" "sd $a1, 8($sp)\n\t" "sd $a2, 16($sp)\n\t" @@ -388,10 +388,6 @@ class StubTest : public CommonRuntimeTest { "sd $a5, 40($sp)\n\t" "sd $a6, 48($sp)\n\t" "sd $a7, 56($sp)\n\t" - "sd $t0, 64($sp)\n\t" - "sd $t1, 72($sp)\n\t" - "sd $t2, 80($sp)\n\t" - "sd $t3, 88($sp)\n\t" "daddiu $sp, $sp, -16\n\t" // Reserve stack space, 16B aligned. "sd %[referrer], 0($sp)\n\t" @@ -427,18 +423,16 @@ class StubTest : public CommonRuntimeTest { "ld $a5, 40($sp)\n\t" "ld $a6, 48($sp)\n\t" "ld $a7, 56($sp)\n\t" - "ld $t0, 64($sp)\n\t" - "ld $t1, 72($sp)\n\t" - "ld $t2, 80($sp)\n\t" - "ld $t3, 88($sp)\n\t" - "daddiu $sp, $sp, 96\n\t" + "daddiu $sp, $sp, 64\n\t" "move %[result], $v0\n\t" // Store the call result. : [result] "=r" (result) : [arg0] "r"(arg0), [arg1] "r"(arg1), [arg2] "r"(arg2), [code] "r"(code), [self] "r"(self), [referrer] "r"(referrer), [hidden] "r"(hidden) - : "at", "v0", "v1", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", - "t8", "t9", "k0", "k1", "fp", "ra", + // Instead aliases t0-t3, register names $12-$15 has been used in the clobber list because + // t0-t3 are ambiguous. + : "at", "v0", "v1", "$12", "$13", "$14", "$15", "s0", "s1", "s2", "s3", "s4", "s5", "s6", + "s7", "t8", "t9", "k0", "k1", "fp", "ra", "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", diff --git a/runtime/base/scoped_flock.cc b/runtime/base/scoped_flock.cc index 71e0590272..814cbd093a 100644 --- a/runtime/base/scoped_flock.cc +++ b/runtime/base/scoped_flock.cc @@ -26,16 +26,25 @@ namespace art { bool ScopedFlock::Init(const char* filename, std::string* error_msg) { + return Init(filename, O_CREAT | O_RDWR, true, error_msg); +} + +bool ScopedFlock::Init(const char* filename, int flags, bool block, std::string* error_msg) { while (true) { if (file_.get() != nullptr) { UNUSED(file_->FlushCloseOrErase()); // Ignore result. } - file_.reset(OS::OpenFileWithFlags(filename, O_CREAT | O_RDWR)); + file_.reset(OS::OpenFileWithFlags(filename, flags)); if (file_.get() == nullptr) { *error_msg = StringPrintf("Failed to open file '%s': %s", filename, strerror(errno)); return false; } - int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_EX)); + int operation = block ? LOCK_EX : (LOCK_EX | LOCK_NB); + int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), operation)); + if (flock_result == EWOULDBLOCK) { + // File is locked by someone else and we are required not to block; + return false; + } if (flock_result != 0) { *error_msg = StringPrintf("Failed to lock file '%s': %s", filename, strerror(errno)); return false; @@ -51,11 +60,23 @@ bool ScopedFlock::Init(const char* filename, std::string* error_msg) { if (stat_result != 0) { PLOG(WARNING) << "Failed to stat, will retry: " << filename; // ENOENT can happen if someone racing with us unlinks the file we created so just retry. - continue; + if (block) { + continue; + } else { + // Note that in theory we could race with someone here for a long time and end up retrying + // over and over again. This potential behavior does not fit well in the non-blocking + // semantics. Thus, if we are not require to block return failure when racing. + return false; + } } if (fstat_stat.st_dev != stat_stat.st_dev || fstat_stat.st_ino != stat_stat.st_ino) { LOG(WARNING) << "File changed while locking, will retry: " << filename; - continue; + if (block) { + continue; + } else { + // See comment above. + return false; + } } return true; } @@ -78,7 +99,7 @@ bool ScopedFlock::Init(File* file, std::string* error_msg) { return true; } -File* ScopedFlock::GetFile() { +File* ScopedFlock::GetFile() const { CHECK(file_.get() != nullptr); return file_.get(); } diff --git a/runtime/base/scoped_flock.h b/runtime/base/scoped_flock.h index 08612e3016..cc22056443 100644 --- a/runtime/base/scoped_flock.h +++ b/runtime/base/scoped_flock.h @@ -32,10 +32,15 @@ class ScopedFlock { // Attempts to acquire an exclusive file lock (see flock(2)) on the file // at filename, and blocks until it can do so. // - // Returns true if the lock could be acquired, or false if an error - // occurred. It is an error if the file does not exist, or if its inode - // changed (usually due to a new file being created at the same path) - // between attempts to lock it. + // Returns true if the lock could be acquired, or false if an error occurred. + // It is an error if its inode changed (usually due to a new file being + // created at the same path) between attempts to lock it. In blocking mode, + // locking will be retried if the file changed. In non-blocking mode, false + // is returned and no attempt is made to re-acquire the lock. + // + // The file is opened with the provided flags. + bool Init(const char* filename, int flags, bool block, std::string* error_msg); + // Calls Init(filename, O_CREAT | O_RDWR, true, errror_msg) bool Init(const char* filename, std::string* error_msg); // Attempt to acquire an exclusive file lock (see flock(2)) on 'file'. // Returns true if the lock could be acquired or false if an error @@ -43,7 +48,7 @@ class ScopedFlock { bool Init(File* file, std::string* error_msg); // Returns the (locked) file associated with this instance. - File* GetFile(); + File* GetFile() const; // Returns whether a file is held. bool HasFile(); diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc index 78bc3d5f9f..e17bebb4fb 100644 --- a/runtime/base/unix_file/fd_file.cc +++ b/runtime/base/unix_file/fd_file.cc @@ -316,4 +316,21 @@ void FdFile::MarkUnchecked() { guard_state_ = GuardState::kNoCheck; } +bool FdFile::ClearContent() { + if (SetLength(0) < 0) { + PLOG(art::ERROR) << "Failed to reset the length"; + return false; + } + return ResetOffset(); +} + +bool FdFile::ResetOffset() { + off_t rc = TEMP_FAILURE_RETRY(lseek(fd_, 0, SEEK_SET)); + if (rc == static_cast<off_t>(-1)) { + PLOG(art::ERROR) << "Failed to reset the offset"; + return false; + } + return true; +} + } // namespace unix_file diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h index 231a1ab145..1e2d8af151 100644 --- a/runtime/base/unix_file/fd_file.h +++ b/runtime/base/unix_file/fd_file.h @@ -79,6 +79,11 @@ class FdFile : public RandomAccessFile { // Copy data from another file. bool Copy(FdFile* input_file, int64_t offset, int64_t size); + // Clears the file content and resets the file offset to 0. + // Returns true upon success, false otherwise. + bool ClearContent(); + // Resets the file offset to the beginning of the file. + bool ResetOffset(); // This enum is public so that we can define the << operator over it. enum class GuardState { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index ddd285a4db..ed833c4335 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1880,6 +1880,9 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, */ Dbg::PostClassPrepare(h_new_class.Get()); + // Notify native debugger of the new class and its layout. + jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get()); + return h_new_class.Get(); } @@ -2766,6 +2769,7 @@ mirror::Class* ClassLinker::CreateArrayClass(Thread* self, const char* descripto mirror::Class* existing = InsertClass(descriptor, new_class.Get(), hash); if (existing == nullptr) { + jit::Jit::NewTypeLoadedIfUsingJit(new_class.Get()); return new_class.Get(); } // Another thread must have loaded the class after we diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 2184f0aee2..3df9101613 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -117,14 +117,15 @@ void ScratchFile::Unlink() { static bool unstarted_initialized_ = false; -CommonRuntimeTest::CommonRuntimeTest() {} -CommonRuntimeTest::~CommonRuntimeTest() { +CommonRuntimeTestImpl::CommonRuntimeTestImpl() {} + +CommonRuntimeTestImpl::~CommonRuntimeTestImpl() { // Ensure the dex files are cleaned up before the runtime. loaded_dex_files_.clear(); runtime_.reset(); } -void CommonRuntimeTest::SetUpAndroidRoot() { +void CommonRuntimeTestImpl::SetUpAndroidRoot() { if (IsHost()) { // $ANDROID_ROOT is set on the device, but not necessarily on the host. // But it needs to be set so that icu4c can find its locale data. @@ -166,7 +167,7 @@ void CommonRuntimeTest::SetUpAndroidRoot() { } } -void CommonRuntimeTest::SetUpAndroidData(std::string& android_data) { +void CommonRuntimeTestImpl::SetUpAndroidData(std::string& android_data) { // On target, Cannot use /mnt/sdcard because it is mounted noexec, so use subdir of dalvik-cache if (IsHost()) { const char* tmpdir = getenv("TMPDIR"); @@ -185,7 +186,8 @@ void CommonRuntimeTest::SetUpAndroidData(std::string& android_data) { setenv("ANDROID_DATA", android_data.c_str(), 1); } -void CommonRuntimeTest::TearDownAndroidData(const std::string& android_data, bool fail_on_error) { +void CommonRuntimeTestImpl::TearDownAndroidData(const std::string& android_data, + bool fail_on_error) { if (fail_on_error) { ASSERT_EQ(rmdir(android_data.c_str()), 0); } else { @@ -230,18 +232,18 @@ static std::string GetAndroidToolsDir(const std::string& subdir1, } if (founddir.empty()) { - ADD_FAILURE() << "Can not find Android tools directory."; + ADD_FAILURE() << "Cannot find Android tools directory."; } return founddir; } -std::string CommonRuntimeTest::GetAndroidHostToolsDir() { +std::string CommonRuntimeTestImpl::GetAndroidHostToolsDir() { return GetAndroidToolsDir("prebuilts/gcc/linux-x86/host", "x86_64-linux-glibc2.15", "x86_64-linux"); } -std::string CommonRuntimeTest::GetAndroidTargetToolsDir(InstructionSet isa) { +std::string CommonRuntimeTestImpl::GetAndroidTargetToolsDir(InstructionSet isa) { switch (isa) { case kArm: case kThumb2: @@ -269,15 +271,16 @@ std::string CommonRuntimeTest::GetAndroidTargetToolsDir(InstructionSet isa) { return ""; } -std::string CommonRuntimeTest::GetCoreArtLocation() { +std::string CommonRuntimeTestImpl::GetCoreArtLocation() { return GetCoreFileLocation("art"); } -std::string CommonRuntimeTest::GetCoreOatLocation() { +std::string CommonRuntimeTestImpl::GetCoreOatLocation() { return GetCoreFileLocation("oat"); } -std::unique_ptr<const DexFile> CommonRuntimeTest::LoadExpectSingleDexFile(const char* location) { +std::unique_ptr<const DexFile> CommonRuntimeTestImpl::LoadExpectSingleDexFile( + const char* location) { std::vector<std::unique_ptr<const DexFile>> dex_files; std::string error_msg; MemMap::Init(); @@ -290,7 +293,7 @@ std::unique_ptr<const DexFile> CommonRuntimeTest::LoadExpectSingleDexFile(const } } -void CommonRuntimeTest::SetUp() { +void CommonRuntimeTestImpl::SetUp() { SetUpAndroidRoot(); SetUpAndroidData(android_data_); dalvik_cache_.append(android_data_.c_str()); @@ -345,7 +348,7 @@ void CommonRuntimeTest::SetUp() { FinalizeSetup(); } -void CommonRuntimeTest::FinalizeSetup() { +void CommonRuntimeTestImpl::FinalizeSetup() { // Initialize maps for unstarted runtime. This needs to be here, as running clinits needs this // set up. if (!unstarted_initialized_) { @@ -369,7 +372,7 @@ void CommonRuntimeTest::FinalizeSetup() { runtime_->GetHeap()->SetMinIntervalHomogeneousSpaceCompactionByOom(0U); } -void CommonRuntimeTest::ClearDirectory(const char* dirpath) { +void CommonRuntimeTestImpl::ClearDirectory(const char* dirpath) { ASSERT_TRUE(dirpath != nullptr); DIR* dir = opendir(dirpath); ASSERT_TRUE(dir != nullptr); @@ -396,7 +399,7 @@ void CommonRuntimeTest::ClearDirectory(const char* dirpath) { closedir(dir); } -void CommonRuntimeTest::TearDown() { +void CommonRuntimeTestImpl::TearDown() { const char* android_data = getenv("ANDROID_DATA"); ASSERT_TRUE(android_data != nullptr); ClearDirectory(dalvik_cache_.c_str()); @@ -453,12 +456,12 @@ static std::string GetDexFileName(const std::string& jar_prefix, bool host) { return StringPrintf("%s/framework/%s%s.jar", path.c_str(), jar_prefix.c_str(), suffix.c_str()); } -std::vector<std::string> CommonRuntimeTest::GetLibCoreDexFileNames() { +std::vector<std::string> CommonRuntimeTestImpl::GetLibCoreDexFileNames() { return std::vector<std::string>({GetDexFileName("core-oj", IsHost()), GetDexFileName("core-libart", IsHost())}); } -std::string CommonRuntimeTest::GetTestAndroidRoot() { +std::string CommonRuntimeTestImpl::GetTestAndroidRoot() { if (IsHost()) { const char* host_dir = getenv("ANDROID_HOST_OUT"); CHECK(host_dir != nullptr); @@ -478,7 +481,7 @@ std::string CommonRuntimeTest::GetTestAndroidRoot() { #define ART_TARGET_NATIVETEST_DIR_STRING "" #endif -std::string CommonRuntimeTest::GetTestDexFileName(const char* name) { +std::string CommonRuntimeTestImpl::GetTestDexFileName(const char* name) { CHECK(name != nullptr); std::string filename; if (IsHost()) { @@ -493,7 +496,8 @@ std::string CommonRuntimeTest::GetTestDexFileName(const char* name) { return filename; } -std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTest::OpenTestDexFiles(const char* name) { +std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTestImpl::OpenTestDexFiles( + const char* name) { std::string filename = GetTestDexFileName(name); std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; @@ -506,13 +510,13 @@ std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTest::OpenTestDexFiles( return dex_files; } -std::unique_ptr<const DexFile> CommonRuntimeTest::OpenTestDexFile(const char* name) { +std::unique_ptr<const DexFile> CommonRuntimeTestImpl::OpenTestDexFile(const char* name) { std::vector<std::unique_ptr<const DexFile>> vector = OpenTestDexFiles(name); EXPECT_EQ(1U, vector.size()); return std::move(vector[0]); } -std::vector<const DexFile*> CommonRuntimeTest::GetDexFiles(jobject jclass_loader) { +std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(jobject jclass_loader) { std::vector<const DexFile*> ret; ScopedObjectAccess soa(Thread::Current()); @@ -572,7 +576,7 @@ std::vector<const DexFile*> CommonRuntimeTest::GetDexFiles(jobject jclass_loader return ret; } -const DexFile* CommonRuntimeTest::GetFirstDexFile(jobject jclass_loader) { +const DexFile* CommonRuntimeTestImpl::GetFirstDexFile(jobject jclass_loader) { std::vector<const DexFile*> tmp(GetDexFiles(jclass_loader)); DCHECK(!tmp.empty()); const DexFile* ret = tmp[0]; @@ -580,7 +584,7 @@ const DexFile* CommonRuntimeTest::GetFirstDexFile(jobject jclass_loader) { return ret; } -jobject CommonRuntimeTest::LoadDex(const char* dex_name) { +jobject CommonRuntimeTestImpl::LoadDex(const char* dex_name) { std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(dex_name); std::vector<const DexFile*> class_path; CHECK_NE(0U, dex_files.size()); @@ -596,7 +600,7 @@ jobject CommonRuntimeTest::LoadDex(const char* dex_name) { return class_loader; } -std::string CommonRuntimeTest::GetCoreFileLocation(const char* suffix) { +std::string CommonRuntimeTestImpl::GetCoreFileLocation(const char* suffix) { CHECK(suffix != nullptr); std::string location; diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 7223b6ec6b..0ce40e8e22 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -64,8 +64,10 @@ class ScratchFile { std::unique_ptr<File> file_; }; -class CommonRuntimeTest : public testing::Test { +class CommonRuntimeTestImpl { public: + CommonRuntimeTestImpl(); + virtual ~CommonRuntimeTestImpl(); static void SetUpAndroidRoot(); // Note: setting up ANDROID_DATA may create a temporary directory. If this is used in a @@ -74,19 +76,25 @@ class CommonRuntimeTest : public testing::Test { static void TearDownAndroidData(const std::string& android_data, bool fail_on_error); - CommonRuntimeTest(); - ~CommonRuntimeTest(); - // Gets the paths of the libcore dex files. static std::vector<std::string> GetLibCoreDexFileNames(); // Returns bin directory which contains host's prebuild tools. static std::string GetAndroidHostToolsDir(); - // Returns bin directory which contains target's prebuild tools. + // Returns bin directory wahich contains target's prebuild tools. static std::string GetAndroidTargetToolsDir(InstructionSet isa); protected: + // Allow subclases such as CommonCompilerTest to add extra options. + virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) {} + + // Called before the runtime is created. + virtual void PreRuntimeCreate() {} + + // Called after the runtime is created. + virtual void PostRuntimeCreate() {} + static bool IsHost() { return !kIsTargetBuild; } @@ -99,25 +107,8 @@ class CommonRuntimeTest : public testing::Test { std::unique_ptr<const DexFile> LoadExpectSingleDexFile(const char* location); - virtual void SetUp(); - - // Allow subclases such as CommonCompilerTest to add extra options. - virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) {} - void ClearDirectory(const char* dirpath); - virtual void TearDown(); - - // Called before the runtime is created. - virtual void PreRuntimeCreate() {} - - // Called after the runtime is created. - virtual void PostRuntimeCreate() {} - - // Called to finish up runtime creation and filling test fields. By default runs root - // initializers, initialize well-known classes, and creates the heap thread pool. - virtual void FinalizeSetup(); - std::string GetTestAndroidRoot(); std::string GetTestDexFileName(const char* name); @@ -150,12 +141,45 @@ class CommonRuntimeTest : public testing::Test { std::unique_ptr<CompilerCallbacks> callbacks_; + void SetUp(); + + void TearDown(); + + void FinalizeSetup(); + private: static std::string GetCoreFileLocation(const char* suffix); std::vector<std::unique_ptr<const DexFile>> loaded_dex_files_; }; +template <typename TestType> +class CommonRuntimeTestBase : public TestType, public CommonRuntimeTestImpl { + public: + CommonRuntimeTestBase() {} + virtual ~CommonRuntimeTestBase() {} + + protected: + virtual void SetUp() { + CommonRuntimeTestImpl::SetUp(); + } + + virtual void TearDown() { + CommonRuntimeTestImpl::TearDown(); + } + + // Called to finish up runtime creation and filling test fields. By default runs root + // initializers, initialize well-known classes, and creates the heap thread pool. + virtual void FinalizeSetup() { + CommonRuntimeTestImpl::FinalizeSetup(); + } +}; + +using CommonRuntimeTest = CommonRuntimeTestBase<testing::Test>; + +template <typename Param> +using CommonRuntimeTestWithParam = CommonRuntimeTestBase<testing::TestWithParam<Param>>; + // Sets a CheckJni abort hook to catch failures. Note that this will cause CheckJNI to carry on // rather than aborting, so be careful! class CheckJniAbortCatcher { diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 8a3db6ccf3..108b8d2441 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -1094,11 +1094,11 @@ class DexFile { int32_t GetLineNumFromPC(ArtMethod* method, uint32_t rel_pc) const SHARED_REQUIRES(Locks::mutator_lock_); - // Returns false if there is no debugging information or if it can not be decoded. + // Returns false if there is no debugging information or if it cannot be decoded. bool DecodeDebugLocalInfo(const CodeItem* code_item, bool is_static, uint32_t method_idx, DexDebugNewLocalCb local_cb, void* context) const; - // Returns false if there is no debugging information or if it can not be decoded. + // Returns false if there is no debugging information or if it cannot be decoded. bool DecodeDebugPositionInfo(const CodeItem* code_item, DexDebugNewPositionCb position_cb, void* context) const; diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc index 272249c23a..b67af53458 100644 --- a/runtime/dex_file_verifier_test.cc +++ b/runtime/dex_file_verifier_test.cc @@ -686,31 +686,6 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsIgnoredOK) { // Set of dex files for interface method tests. As it's not as easy to mutate method names, it's // just easier to break up bad cases. -// Interface with an instance constructor. -// -// .class public interface LInterfaceMethodFlags; -// .super Ljava/lang/Object; -// -// .method public static constructor <clinit>()V -// .registers 1 -// return-void -// .end method -// -// .method public constructor <init>()V -// .registers 1 -// return-void -// .end method -static const char kMethodFlagsInterfaceWithInit[] = - "ZGV4CjAzNQDRNt+hZ6X3I+xe66iVlCW7h9I38HmN4SvUAQAAcAAAAHhWNBIAAAAAAAAAAEwBAAAF" - "AAAAcAAAAAMAAACEAAAAAQAAAJAAAAAAAAAAAAAAAAIAAACcAAAAAQAAAKwAAAAIAQAAzAAAAMwA" - "AADWAAAA3gAAAPYAAAAKAQAAAgAAAAMAAAAEAAAABAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAQAA" - "AAAAAAABAgAAAQAAAAAAAAD/////AAAAADoBAAAAAAAACDxjbGluaXQ+AAY8aW5pdD4AFkxJbnRl" - "cmZhY2VNZXRob2RGbGFnczsAEkxqYXZhL2xhbmcvT2JqZWN0OwABVgAAAAAAAAAAAQAAAAAAAAAA" - "AAAAAQAAAA4AAAABAAEAAAAAAAAAAAABAAAADgAAAAIAAImABJQCAYGABKgCAAALAAAAAAAAAAEA" - "AAAAAAAAAQAAAAUAAABwAAAAAgAAAAMAAACEAAAAAwAAAAEAAACQAAAABQAAAAIAAACcAAAABgAA" - "AAEAAACsAAAAAiAAAAUAAADMAAAAAxAAAAEAAAAQAQAAASAAAAIAAAAUAQAAACAAAAEAAAA6AQAA" - "ABAAAAEAAABMAQAA"; - // Standard interface. Use declared-synchronized again for 3B encoding. // // .class public interface LInterfaceMethodFlags; @@ -751,13 +726,6 @@ static uint32_t ApplyMaskShifted(uint32_t src_value, uint32_t mask) { } TEST_F(DexFileVerifierTest, MethodAccessFlagsInterfaces) { - // Reject interface with <init>. - VerifyModification( - kMethodFlagsInterfaceWithInit, - "method_flags_interface_with_init", - [](DexFile* dex_file ATTRIBUTE_UNUSED) {}, - "Non-clinit interface method 1 should not have code"); - VerifyModification( kMethodFlagsInterface, "method_flags_interface_ok", diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 9a9f42b976..0663b7ef78 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -193,10 +193,10 @@ inline mirror::Object* AllocObjectFromCodeResolved(mirror::Class* klass, return nullptr; } gc::Heap* heap = Runtime::Current()->GetHeap(); - // Pass in false since the object can not be finalizable. + // Pass in false since the object cannot be finalizable. return klass->Alloc<kInstrumented, false>(self, heap->GetCurrentAllocator()); } - // Pass in false since the object can not be finalizable. + // Pass in false since the object cannot be finalizable. return klass->Alloc<kInstrumented, false>(self, allocator_type); } @@ -207,7 +207,7 @@ inline mirror::Object* AllocObjectFromCodeInitialized(mirror::Class* klass, Thread* self, gc::AllocatorType allocator_type) { DCHECK(klass != nullptr); - // Pass in false since the object can not be finalizable. + // Pass in false since the object cannot be finalizable. return klass->Alloc<kInstrumented, false>(self, allocator_type); } @@ -410,10 +410,19 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, mirror::Object** this_ DCHECK(self->IsExceptionPending()); // Throw exception and unwind. return nullptr; // Failure. } else if (UNLIKELY(*this_object == nullptr && type != kStatic)) { - // Maintain interpreter-like semantics where NullPointerException is thrown - // after potential NoSuchMethodError from class linker. - ThrowNullPointerExceptionForMethodAccess(method_idx, type); - return nullptr; // Failure. + if (UNLIKELY(resolved_method->GetDeclaringClass()->IsStringClass() && + resolved_method->IsConstructor())) { + // Hack for String init: + // + // We assume that the input of String.<init> in verified code is always + // an unitialized reference. If it is a null constant, it must have been + // optimized out by the compiler. Do not throw NullPointerException. + } else { + // Maintain interpreter-like semantics where NullPointerException is thrown + // after potential NoSuchMethodError from class linker. + ThrowNullPointerExceptionForMethodAccess(method_idx, type); + return nullptr; // Failure. + } } else if (access_check) { mirror::Class* methods_class = resolved_method->GetDeclaringClass(); bool can_access_resolved_method = diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc index ce6467a6cf..7727b2da18 100644 --- a/runtime/gc/collector/mark_compact.cc +++ b/runtime/gc/collector/mark_compact.cc @@ -180,7 +180,7 @@ void MarkCompact::MarkingPhase() { t.NewTiming("ProcessCards"); // Process dirty cards and add dirty cards to mod-union tables. heap_->ProcessCards(GetTimings(), false, false, true); - // Clear the whole card table since we can not Get any additional dirty cards during the + // Clear the whole card table since we cannot get any additional dirty cards during the // paused GC. This saves memory but only works for pause the world collectors. t.NewTiming("ClearCardTable"); heap_->GetCardTable()->ClearCardTable(); diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc index 99e98bb56a..278469329f 100644 --- a/runtime/gc/collector/semi_space.cc +++ b/runtime/gc/collector/semi_space.cc @@ -227,7 +227,7 @@ void SemiSpace::MarkingPhase() { BindBitmaps(); // Process dirty cards and add dirty cards to mod-union tables. heap_->ProcessCards(GetTimings(), kUseRememberedSet && generational_, false, true); - // Clear the whole card table since we can not Get any additional dirty cards during the + // Clear the whole card table since we cannot get any additional dirty cards during the // paused GC. This saves memory but only works for pause the world collectors. t.NewTiming("ClearCardTable"); heap_->GetCardTable()->ClearCardTable(); diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h index 416510d73b..c8e913c0d4 100644 --- a/runtime/gc/collector_type.h +++ b/runtime/gc/collector_type.h @@ -34,7 +34,7 @@ enum CollectorType { kCollectorTypeSS, // A generational variant of kCollectorTypeSS. kCollectorTypeGSS, - // Mark compact colector. + // Mark compact collector. kCollectorTypeMC, // Heap trimming collector, doesn't do any actual collecting. kCollectorTypeHeapTrim, diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index e7ea983410..7b531ba322 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -89,7 +89,6 @@ namespace space { class RegionSpace; class RosAllocSpace; class Space; - class SpaceTest; class ZygoteSpace; } // namespace space @@ -1335,7 +1334,6 @@ class Heap { friend class VerifyReferenceCardVisitor; friend class VerifyReferenceVisitor; friend class VerifyObjectVisitor; - friend class space::SpaceTest; DISALLOW_IMPLICIT_CONSTRUCTORS(Heap); }; diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc index 39ba7432a1..5e7f1a20fd 100644 --- a/runtime/gc/reference_processor.cc +++ b/runtime/gc/reference_processor.cc @@ -86,7 +86,7 @@ mirror::Object* ReferenceProcessor::GetReferent(Thread* self, mirror::Reference* // it to the mutator as long as the GC is not preserving references. if (LIKELY(collector_ != nullptr)) { // If it's null it means not marked, but it could become marked if the referent is reachable - // by finalizer referents. So we can not return in this case and must block. Otherwise, we + // by finalizer referents. So we cannot return in this case and must block. Otherwise, we // can return it to the mutator as long as the GC is not preserving references, in which // case only black nodes can be safely returned. If the GC is preserving references, the // mutator could take a white field from a grey or white node and move it somewhere else diff --git a/runtime/gc/space/dlmalloc_space_base_test.cc b/runtime/gc/space/dlmalloc_space_base_test.cc deleted file mode 100644 index 93fe1559a0..0000000000 --- a/runtime/gc/space/dlmalloc_space_base_test.cc +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "space_test.h" - -#include "dlmalloc_space.h" -#include "scoped_thread_state_change.h" - -namespace art { -namespace gc { -namespace space { - -MallocSpace* CreateDlMallocSpace(const std::string& name, size_t initial_size, size_t growth_limit, - size_t capacity, uint8_t* requested_begin) { - return DlMallocSpace::Create(name, initial_size, growth_limit, capacity, requested_begin, false); -} - -TEST_SPACE_CREATE_FN_BASE(DlMallocSpace, CreateDlMallocSpace) - - -} // namespace space -} // namespace gc -} // namespace art diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc index 2798b21f94..e70fe215ab 100644 --- a/runtime/gc/space/large_object_space.cc +++ b/runtime/gc/space/large_object_space.cc @@ -521,7 +521,7 @@ mirror::Object* FreeListSpace::Alloc(Thread* self, size_t num_bytes, size_t* byt num_bytes_allocated_ += allocation_size; total_bytes_allocated_ += allocation_size; mirror::Object* obj = reinterpret_cast<mirror::Object*>(GetAddressForAllocationInfo(new_info)); - // We always put our object at the start of the free block, there can not be another free block + // We always put our object at the start of the free block, there cannot be another free block // before it. if (kIsDebugBuild) { mprotect(obj, allocation_size, PROT_READ | PROT_WRITE); diff --git a/runtime/gc/space/large_object_space_test.cc b/runtime/gc/space/large_object_space_test.cc index 05b484af07..ad38724e7d 100644 --- a/runtime/gc/space/large_object_space_test.cc +++ b/runtime/gc/space/large_object_space_test.cc @@ -22,7 +22,7 @@ namespace art { namespace gc { namespace space { -class LargeObjectSpaceTest : public SpaceTest { +class LargeObjectSpaceTest : public SpaceTest<CommonRuntimeTest> { public: void LargeObjectTest(); diff --git a/runtime/gc/space/rosalloc_space_base_test.cc b/runtime/gc/space/rosalloc_space_base_test.cc deleted file mode 100644 index 0c5be03180..0000000000 --- a/runtime/gc/space/rosalloc_space_base_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "space_test.h" - -namespace art { -namespace gc { -namespace space { - -MallocSpace* CreateRosAllocSpace(const std::string& name, size_t initial_size, size_t growth_limit, - size_t capacity, uint8_t* requested_begin) { - return RosAllocSpace::Create(name, initial_size, growth_limit, capacity, requested_begin, - Runtime::Current()->GetHeap()->IsLowMemoryMode(), false); -} - -TEST_SPACE_CREATE_FN_BASE(RosAllocSpace, CreateRosAllocSpace) - - -} // namespace space -} // namespace gc -} // namespace art diff --git a/runtime/gc/space/space_create_test.cc b/runtime/gc/space/space_create_test.cc new file mode 100644 index 0000000000..aea2d9f895 --- /dev/null +++ b/runtime/gc/space/space_create_test.cc @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "space_test.h" + +#include "dlmalloc_space.h" +#include "rosalloc_space.h" +#include "scoped_thread_state_change.h" + +namespace art { +namespace gc { +namespace space { + +enum MallocSpaceType { + kMallocSpaceDlMalloc, + kMallocSpaceRosAlloc, +}; + +class SpaceCreateTest : public SpaceTest<CommonRuntimeTestWithParam<MallocSpaceType>> { + public: + MallocSpace* CreateSpace(const std::string& name, + size_t initial_size, + size_t growth_limit, + size_t capacity, + uint8_t* requested_begin) { + const MallocSpaceType type = GetParam(); + if (type == kMallocSpaceDlMalloc) { + return DlMallocSpace::Create(name, + initial_size, + growth_limit, + capacity, + requested_begin, + false); + } + DCHECK_EQ(static_cast<uint32_t>(type), static_cast<uint32_t>(kMallocSpaceRosAlloc)); + return RosAllocSpace::Create(name, + initial_size, + growth_limit, + capacity, + requested_begin, + Runtime::Current()->GetHeap()->IsLowMemoryMode(), + false); + } +}; + +TEST_P(SpaceCreateTest, InitTestBody) { + // This will lead to error messages in the log. + ScopedLogSeverity sls(LogSeverity::FATAL); + + { + // Init < max == growth + std::unique_ptr<Space> space(CreateSpace("test", 16 * MB, 32 * MB, 32 * MB, nullptr)); + EXPECT_TRUE(space != nullptr); + // Init == max == growth + space.reset(CreateSpace("test", 16 * MB, 16 * MB, 16 * MB, nullptr)); + EXPECT_TRUE(space != nullptr); + // Init > max == growth + space.reset(CreateSpace("test", 32 * MB, 16 * MB, 16 * MB, nullptr)); + EXPECT_TRUE(space == nullptr); + // Growth == init < max + space.reset(CreateSpace("test", 16 * MB, 16 * MB, 32 * MB, nullptr)); + EXPECT_TRUE(space != nullptr); + // Growth < init < max + space.reset(CreateSpace("test", 16 * MB, 8 * MB, 32 * MB, nullptr)); + EXPECT_TRUE(space == nullptr); + // Init < growth < max + space.reset(CreateSpace("test", 8 * MB, 16 * MB, 32 * MB, nullptr)); + EXPECT_TRUE(space != nullptr); + // Init < max < growth + space.reset(CreateSpace("test", 8 * MB, 32 * MB, 16 * MB, nullptr)); + EXPECT_TRUE(space == nullptr); + } +} + +// TODO: This test is not very good, we should improve it. +// The test should do more allocations before the creation of the ZygoteSpace, and then do +// allocations after the ZygoteSpace is created. The test should also do some GCs to ensure that +// the GC works with the ZygoteSpace. +TEST_P(SpaceCreateTest, ZygoteSpaceTestBody) { + size_t dummy; + MallocSpace* space(CreateSpace("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); + ASSERT_TRUE(space != nullptr); + + // Make space findable to the heap, will also delete space when runtime is cleaned up + AddSpace(space); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + // Succeeds, fits without adjusting the footprint limit. + size_t ptr1_bytes_allocated, ptr1_usable_size, ptr1_bytes_tl_bulk_allocated; + StackHandleScope<3> hs(soa.Self()); + MutableHandle<mirror::Object> ptr1(hs.NewHandle(Alloc(space, + self, + 1 * MB, + &ptr1_bytes_allocated, + &ptr1_usable_size, + &ptr1_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr1.Get() != nullptr); + EXPECT_LE(1U * MB, ptr1_bytes_allocated); + EXPECT_LE(1U * MB, ptr1_usable_size); + EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); + EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); + + // Fails, requires a higher footprint limit. + mirror::Object* ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr2 == nullptr); + + // Succeeds, adjusts the footprint. + size_t ptr3_bytes_allocated, ptr3_usable_size, ptr3_bytes_tl_bulk_allocated; + MutableHandle<mirror::Object> ptr3(hs.NewHandle(AllocWithGrowth(space, + self, + 8 * MB, + &ptr3_bytes_allocated, + &ptr3_usable_size, + &ptr3_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr3.Get() != nullptr); + EXPECT_LE(8U * MB, ptr3_bytes_allocated); + EXPECT_LE(8U * MB, ptr3_usable_size); + EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); + EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); + + // Fails, requires a higher footprint limit. + mirror::Object* ptr4 = space->Alloc(self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr4 == nullptr); + + // Also fails, requires a higher allowed footprint. + mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr5 == nullptr); + + // Release some memory. + size_t free3 = space->AllocationSize(ptr3.Get(), nullptr); + EXPECT_EQ(free3, ptr3_bytes_allocated); + EXPECT_EQ(free3, space->Free(self, ptr3.Assign(nullptr))); + EXPECT_LE(8U * MB, free3); + + // Succeeds, now that memory has been freed. + size_t ptr6_bytes_allocated, ptr6_usable_size, ptr6_bytes_tl_bulk_allocated; + Handle<mirror::Object> ptr6(hs.NewHandle(AllocWithGrowth(space, + self, + 9 * MB, + &ptr6_bytes_allocated, + &ptr6_usable_size, + &ptr6_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr6.Get() != nullptr); + EXPECT_LE(9U * MB, ptr6_bytes_allocated); + EXPECT_LE(9U * MB, ptr6_usable_size); + EXPECT_LE(ptr6_usable_size, ptr6_bytes_allocated); + EXPECT_EQ(ptr6_bytes_tl_bulk_allocated, ptr6_bytes_allocated); + + // Final clean up. + size_t free1 = space->AllocationSize(ptr1.Get(), nullptr); + space->Free(self, ptr1.Assign(nullptr)); + EXPECT_LE(1U * MB, free1); + + // Make sure that the zygote space isn't directly at the start of the space. + EXPECT_TRUE(space->Alloc(self, 1U * MB, &dummy, nullptr, &dummy) != nullptr); + + gc::Heap* heap = Runtime::Current()->GetHeap(); + space::Space* old_space = space; + heap->RemoveSpace(old_space); + heap->RevokeAllThreadLocalBuffers(); + space::ZygoteSpace* zygote_space = space->CreateZygoteSpace("alloc space", + heap->IsLowMemoryMode(), + &space); + delete old_space; + // Add the zygote space. + AddSpace(zygote_space, false); + + // Make space findable to the heap, will also delete space when runtime is cleaned up + AddSpace(space, false); + + // Succeeds, fits without adjusting the footprint limit. + ptr1.Assign(Alloc(space, + self, + 1 * MB, + &ptr1_bytes_allocated, + &ptr1_usable_size, + &ptr1_bytes_tl_bulk_allocated)); + EXPECT_TRUE(ptr1.Get() != nullptr); + EXPECT_LE(1U * MB, ptr1_bytes_allocated); + EXPECT_LE(1U * MB, ptr1_usable_size); + EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); + EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); + + // Fails, requires a higher footprint limit. + ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr2 == nullptr); + + // Succeeds, adjusts the footprint. + ptr3.Assign(AllocWithGrowth(space, + self, + 2 * MB, + &ptr3_bytes_allocated, + &ptr3_usable_size, + &ptr3_bytes_tl_bulk_allocated)); + EXPECT_TRUE(ptr3.Get() != nullptr); + EXPECT_LE(2U * MB, ptr3_bytes_allocated); + EXPECT_LE(2U * MB, ptr3_usable_size); + EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); + EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); + space->Free(self, ptr3.Assign(nullptr)); + + // Final clean up. + free1 = space->AllocationSize(ptr1.Get(), nullptr); + space->Free(self, ptr1.Assign(nullptr)); + EXPECT_LE(1U * MB, free1); +} + +TEST_P(SpaceCreateTest, AllocAndFreeTestBody) { + size_t dummy = 0; + MallocSpace* space(CreateSpace("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); + ASSERT_TRUE(space != nullptr); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + // Make space findable to the heap, will also delete space when runtime is cleaned up + AddSpace(space); + + // Succeeds, fits without adjusting the footprint limit. + size_t ptr1_bytes_allocated, ptr1_usable_size, ptr1_bytes_tl_bulk_allocated; + StackHandleScope<3> hs(soa.Self()); + MutableHandle<mirror::Object> ptr1(hs.NewHandle(Alloc(space, + self, + 1 * MB, + &ptr1_bytes_allocated, + &ptr1_usable_size, + &ptr1_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr1.Get() != nullptr); + EXPECT_LE(1U * MB, ptr1_bytes_allocated); + EXPECT_LE(1U * MB, ptr1_usable_size); + EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); + EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); + + // Fails, requires a higher footprint limit. + mirror::Object* ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr2 == nullptr); + + // Succeeds, adjusts the footprint. + size_t ptr3_bytes_allocated, ptr3_usable_size, ptr3_bytes_tl_bulk_allocated; + MutableHandle<mirror::Object> ptr3(hs.NewHandle(AllocWithGrowth(space, + self, + 8 * MB, + &ptr3_bytes_allocated, + &ptr3_usable_size, + &ptr3_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr3.Get() != nullptr); + EXPECT_LE(8U * MB, ptr3_bytes_allocated); + EXPECT_LE(8U * MB, ptr3_usable_size); + EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); + EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); + + // Fails, requires a higher footprint limit. + mirror::Object* ptr4 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr4 == nullptr); + + // Also fails, requires a higher allowed footprint. + mirror::Object* ptr5 = AllocWithGrowth(space, self, 8 * MB, &dummy, nullptr, &dummy); + EXPECT_TRUE(ptr5 == nullptr); + + // Release some memory. + size_t free3 = space->AllocationSize(ptr3.Get(), nullptr); + EXPECT_EQ(free3, ptr3_bytes_allocated); + space->Free(self, ptr3.Assign(nullptr)); + EXPECT_LE(8U * MB, free3); + + // Succeeds, now that memory has been freed. + size_t ptr6_bytes_allocated, ptr6_usable_size, ptr6_bytes_tl_bulk_allocated; + Handle<mirror::Object> ptr6(hs.NewHandle(AllocWithGrowth(space, + self, + 9 * MB, + &ptr6_bytes_allocated, + &ptr6_usable_size, + &ptr6_bytes_tl_bulk_allocated))); + EXPECT_TRUE(ptr6.Get() != nullptr); + EXPECT_LE(9U * MB, ptr6_bytes_allocated); + EXPECT_LE(9U * MB, ptr6_usable_size); + EXPECT_LE(ptr6_usable_size, ptr6_bytes_allocated); + EXPECT_EQ(ptr6_bytes_tl_bulk_allocated, ptr6_bytes_allocated); + + // Final clean up. + size_t free1 = space->AllocationSize(ptr1.Get(), nullptr); + space->Free(self, ptr1.Assign(nullptr)); + EXPECT_LE(1U * MB, free1); +} + +TEST_P(SpaceCreateTest, AllocAndFreeListTestBody) { + MallocSpace* space(CreateSpace("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); + ASSERT_TRUE(space != nullptr); + + // Make space findable to the heap, will also delete space when runtime is cleaned up + AddSpace(space); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + // Succeeds, fits without adjusting the max allowed footprint. + mirror::Object* lots_of_objects[1024]; + for (size_t i = 0; i < arraysize(lots_of_objects); i++) { + size_t allocation_size, usable_size, bytes_tl_bulk_allocated; + size_t size_of_zero_length_byte_array = SizeOfZeroLengthByteArray(); + lots_of_objects[i] = Alloc(space, + self, + size_of_zero_length_byte_array, + &allocation_size, + &usable_size, + &bytes_tl_bulk_allocated); + EXPECT_TRUE(lots_of_objects[i] != nullptr); + size_t computed_usable_size; + EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i], &computed_usable_size)); + EXPECT_EQ(usable_size, computed_usable_size); + EXPECT_TRUE(bytes_tl_bulk_allocated == 0 || + bytes_tl_bulk_allocated >= allocation_size); + } + + // Release memory. + space->FreeList(self, arraysize(lots_of_objects), lots_of_objects); + + // Succeeds, fits by adjusting the max allowed footprint. + for (size_t i = 0; i < arraysize(lots_of_objects); i++) { + size_t allocation_size, usable_size, bytes_tl_bulk_allocated; + lots_of_objects[i] = AllocWithGrowth(space, + self, + 1024, + &allocation_size, + &usable_size, + &bytes_tl_bulk_allocated); + EXPECT_TRUE(lots_of_objects[i] != nullptr); + size_t computed_usable_size; + EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i], &computed_usable_size)); + EXPECT_EQ(usable_size, computed_usable_size); + EXPECT_TRUE(bytes_tl_bulk_allocated == 0 || + bytes_tl_bulk_allocated >= allocation_size); + } + + // Release memory. + space->FreeList(self, arraysize(lots_of_objects), lots_of_objects); +} + +INSTANTIATE_TEST_CASE_P(CreateRosAllocSpace, + SpaceCreateTest, + testing::Values(kMallocSpaceRosAlloc)); +INSTANTIATE_TEST_CASE_P(CreateDlMallocSpace, + SpaceCreateTest, + testing::Values(kMallocSpaceDlMalloc)); + +} // namespace space +} // namespace gc +} // namespace art diff --git a/runtime/gc/space/space_test.h b/runtime/gc/space/space_test.h index 4d2db11ac2..e588eb3efa 100644 --- a/runtime/gc/space/space_test.h +++ b/runtime/gc/space/space_test.h @@ -33,12 +33,10 @@ namespace art { namespace gc { namespace space { -class SpaceTest : public CommonRuntimeTest { +template <class Super> +class SpaceTest : public Super { public: - jobject byte_array_class_; - - SpaceTest() : byte_array_class_(nullptr) { - } + jobject byte_array_class_ = nullptr; void AddSpace(ContinuousSpace* space, bool revoke = true) { Heap* heap = Runtime::Current()->GetHeap(); @@ -62,13 +60,19 @@ class SpaceTest : public CommonRuntimeTest { return reinterpret_cast<mirror::Class*>(self->DecodeJObject(byte_array_class_)); } - mirror::Object* Alloc(space::MallocSpace* alloc_space, Thread* self, size_t bytes, - size_t* bytes_allocated, size_t* usable_size, + mirror::Object* Alloc(space::MallocSpace* alloc_space, + Thread* self, + size_t bytes, + size_t* bytes_allocated, + size_t* usable_size, size_t* bytes_tl_bulk_allocated) SHARED_REQUIRES(Locks::mutator_lock_) { StackHandleScope<1> hs(self); Handle<mirror::Class> byte_array_class(hs.NewHandle(GetByteArrayClass(self))); - mirror::Object* obj = alloc_space->Alloc(self, bytes, bytes_allocated, usable_size, + mirror::Object* obj = alloc_space->Alloc(self, + bytes, + bytes_allocated, + usable_size, bytes_tl_bulk_allocated); if (obj != nullptr) { InstallClass(obj, byte_array_class.Get(), bytes); @@ -76,8 +80,11 @@ class SpaceTest : public CommonRuntimeTest { return obj; } - mirror::Object* AllocWithGrowth(space::MallocSpace* alloc_space, Thread* self, size_t bytes, - size_t* bytes_allocated, size_t* usable_size, + mirror::Object* AllocWithGrowth(space::MallocSpace* alloc_space, + Thread* self, + size_t bytes, + size_t* bytes_allocated, + size_t* usable_size, size_t* bytes_tl_bulk_allocated) SHARED_REQUIRES(Locks::mutator_lock_) { StackHandleScope<1> hs(self); @@ -117,10 +124,6 @@ class SpaceTest : public CommonRuntimeTest { typedef MallocSpace* (*CreateSpaceFn)(const std::string& name, size_t initial_size, size_t growth_limit, size_t capacity, uint8_t* requested_begin); - void InitTestBody(CreateSpaceFn create_space); - void ZygoteSpaceTestBody(CreateSpaceFn create_space); - void AllocAndFreeTestBody(CreateSpaceFn create_space); - void AllocAndFreeListTestBody(CreateSpaceFn create_space); void SizeFootPrintGrowthLimitAndTrimBody(MallocSpace* space, intptr_t object_size, int round, size_t growth_limit); @@ -132,278 +135,11 @@ static inline size_t test_rand(size_t* seed) { return *seed; } -void SpaceTest::InitTestBody(CreateSpaceFn create_space) { - // This will lead to error messages in the log. - ScopedLogSeverity sls(LogSeverity::FATAL); - - { - // Init < max == growth - std::unique_ptr<Space> space(create_space("test", 16 * MB, 32 * MB, 32 * MB, nullptr)); - EXPECT_TRUE(space.get() != nullptr); - } - { - // Init == max == growth - std::unique_ptr<Space> space(create_space("test", 16 * MB, 16 * MB, 16 * MB, nullptr)); - EXPECT_TRUE(space.get() != nullptr); - } - { - // Init > max == growth - std::unique_ptr<Space> space(create_space("test", 32 * MB, 16 * MB, 16 * MB, nullptr)); - EXPECT_TRUE(space.get() == nullptr); - } - { - // Growth == init < max - std::unique_ptr<Space> space(create_space("test", 16 * MB, 16 * MB, 32 * MB, nullptr)); - EXPECT_TRUE(space.get() != nullptr); - } - { - // Growth < init < max - std::unique_ptr<Space> space(create_space("test", 16 * MB, 8 * MB, 32 * MB, nullptr)); - EXPECT_TRUE(space.get() == nullptr); - } - { - // Init < growth < max - std::unique_ptr<Space> space(create_space("test", 8 * MB, 16 * MB, 32 * MB, nullptr)); - EXPECT_TRUE(space.get() != nullptr); - } - { - // Init < max < growth - std::unique_ptr<Space> space(create_space("test", 8 * MB, 32 * MB, 16 * MB, nullptr)); - EXPECT_TRUE(space.get() == nullptr); - } -} - -// TODO: This test is not very good, we should improve it. -// The test should do more allocations before the creation of the ZygoteSpace, and then do -// allocations after the ZygoteSpace is created. The test should also do some GCs to ensure that -// the GC works with the ZygoteSpace. -void SpaceTest::ZygoteSpaceTestBody(CreateSpaceFn create_space) { - size_t dummy; - MallocSpace* space(create_space("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); - ASSERT_TRUE(space != nullptr); - - // Make space findable to the heap, will also delete space when runtime is cleaned up - AddSpace(space); - Thread* self = Thread::Current(); - ScopedObjectAccess soa(self); - - // Succeeds, fits without adjusting the footprint limit. - size_t ptr1_bytes_allocated, ptr1_usable_size, ptr1_bytes_tl_bulk_allocated; - StackHandleScope<3> hs(soa.Self()); - MutableHandle<mirror::Object> ptr1( - hs.NewHandle(Alloc(space, self, 1 * MB, &ptr1_bytes_allocated, &ptr1_usable_size, - &ptr1_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr1.Get() != nullptr); - EXPECT_LE(1U * MB, ptr1_bytes_allocated); - EXPECT_LE(1U * MB, ptr1_usable_size); - EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); - EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); - - // Fails, requires a higher footprint limit. - mirror::Object* ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr2 == nullptr); - - // Succeeds, adjusts the footprint. - size_t ptr3_bytes_allocated, ptr3_usable_size, ptr3_bytes_tl_bulk_allocated; - MutableHandle<mirror::Object> ptr3( - hs.NewHandle(AllocWithGrowth(space, self, 8 * MB, &ptr3_bytes_allocated, &ptr3_usable_size, - &ptr3_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr3.Get() != nullptr); - EXPECT_LE(8U * MB, ptr3_bytes_allocated); - EXPECT_LE(8U * MB, ptr3_usable_size); - EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); - EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); - - // Fails, requires a higher footprint limit. - mirror::Object* ptr4 = space->Alloc(self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr4 == nullptr); - - // Also fails, requires a higher allowed footprint. - mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr5 == nullptr); - - // Release some memory. - size_t free3 = space->AllocationSize(ptr3.Get(), nullptr); - EXPECT_EQ(free3, ptr3_bytes_allocated); - EXPECT_EQ(free3, space->Free(self, ptr3.Assign(nullptr))); - EXPECT_LE(8U * MB, free3); - - // Succeeds, now that memory has been freed. - size_t ptr6_bytes_allocated, ptr6_usable_size, ptr6_bytes_tl_bulk_allocated; - Handle<mirror::Object> ptr6( - hs.NewHandle(AllocWithGrowth(space, self, 9 * MB, &ptr6_bytes_allocated, &ptr6_usable_size, - &ptr6_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr6.Get() != nullptr); - EXPECT_LE(9U * MB, ptr6_bytes_allocated); - EXPECT_LE(9U * MB, ptr6_usable_size); - EXPECT_LE(ptr6_usable_size, ptr6_bytes_allocated); - EXPECT_EQ(ptr6_bytes_tl_bulk_allocated, ptr6_bytes_allocated); - - // Final clean up. - size_t free1 = space->AllocationSize(ptr1.Get(), nullptr); - space->Free(self, ptr1.Assign(nullptr)); - EXPECT_LE(1U * MB, free1); - - // Make sure that the zygote space isn't directly at the start of the space. - EXPECT_TRUE(space->Alloc(self, 1U * MB, &dummy, nullptr, &dummy) != nullptr); - - gc::Heap* heap = Runtime::Current()->GetHeap(); - space::Space* old_space = space; - heap->RemoveSpace(old_space); - heap->RevokeAllThreadLocalBuffers(); - space::ZygoteSpace* zygote_space = space->CreateZygoteSpace("alloc space", - heap->IsLowMemoryMode(), - &space); - delete old_space; - // Add the zygote space. - AddSpace(zygote_space, false); - - // Make space findable to the heap, will also delete space when runtime is cleaned up - AddSpace(space, false); - - // Succeeds, fits without adjusting the footprint limit. - ptr1.Assign(Alloc(space, self, 1 * MB, &ptr1_bytes_allocated, &ptr1_usable_size, - &ptr1_bytes_tl_bulk_allocated)); - EXPECT_TRUE(ptr1.Get() != nullptr); - EXPECT_LE(1U * MB, ptr1_bytes_allocated); - EXPECT_LE(1U * MB, ptr1_usable_size); - EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); - EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); - - // Fails, requires a higher footprint limit. - ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr2 == nullptr); - - // Succeeds, adjusts the footprint. - ptr3.Assign(AllocWithGrowth(space, self, 2 * MB, &ptr3_bytes_allocated, &ptr3_usable_size, - &ptr3_bytes_tl_bulk_allocated)); - EXPECT_TRUE(ptr3.Get() != nullptr); - EXPECT_LE(2U * MB, ptr3_bytes_allocated); - EXPECT_LE(2U * MB, ptr3_usable_size); - EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); - EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); - space->Free(self, ptr3.Assign(nullptr)); - - // Final clean up. - free1 = space->AllocationSize(ptr1.Get(), nullptr); - space->Free(self, ptr1.Assign(nullptr)); - EXPECT_LE(1U * MB, free1); -} - -void SpaceTest::AllocAndFreeTestBody(CreateSpaceFn create_space) { - size_t dummy = 0; - MallocSpace* space(create_space("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); - ASSERT_TRUE(space != nullptr); - Thread* self = Thread::Current(); - ScopedObjectAccess soa(self); - - // Make space findable to the heap, will also delete space when runtime is cleaned up - AddSpace(space); - - // Succeeds, fits without adjusting the footprint limit. - size_t ptr1_bytes_allocated, ptr1_usable_size, ptr1_bytes_tl_bulk_allocated; - StackHandleScope<3> hs(soa.Self()); - MutableHandle<mirror::Object> ptr1( - hs.NewHandle(Alloc(space, self, 1 * MB, &ptr1_bytes_allocated, &ptr1_usable_size, - &ptr1_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr1.Get() != nullptr); - EXPECT_LE(1U * MB, ptr1_bytes_allocated); - EXPECT_LE(1U * MB, ptr1_usable_size); - EXPECT_LE(ptr1_usable_size, ptr1_bytes_allocated); - EXPECT_EQ(ptr1_bytes_tl_bulk_allocated, ptr1_bytes_allocated); - - // Fails, requires a higher footprint limit. - mirror::Object* ptr2 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr2 == nullptr); - - // Succeeds, adjusts the footprint. - size_t ptr3_bytes_allocated, ptr3_usable_size, ptr3_bytes_tl_bulk_allocated; - MutableHandle<mirror::Object> ptr3( - hs.NewHandle(AllocWithGrowth(space, self, 8 * MB, &ptr3_bytes_allocated, &ptr3_usable_size, - &ptr3_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr3.Get() != nullptr); - EXPECT_LE(8U * MB, ptr3_bytes_allocated); - EXPECT_LE(8U * MB, ptr3_usable_size); - EXPECT_LE(ptr3_usable_size, ptr3_bytes_allocated); - EXPECT_EQ(ptr3_bytes_tl_bulk_allocated, ptr3_bytes_allocated); - - // Fails, requires a higher footprint limit. - mirror::Object* ptr4 = Alloc(space, self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr4 == nullptr); - - // Also fails, requires a higher allowed footprint. - mirror::Object* ptr5 = AllocWithGrowth(space, self, 8 * MB, &dummy, nullptr, &dummy); - EXPECT_TRUE(ptr5 == nullptr); - - // Release some memory. - size_t free3 = space->AllocationSize(ptr3.Get(), nullptr); - EXPECT_EQ(free3, ptr3_bytes_allocated); - space->Free(self, ptr3.Assign(nullptr)); - EXPECT_LE(8U * MB, free3); - - // Succeeds, now that memory has been freed. - size_t ptr6_bytes_allocated, ptr6_usable_size, ptr6_bytes_tl_bulk_allocated; - Handle<mirror::Object> ptr6( - hs.NewHandle(AllocWithGrowth(space, self, 9 * MB, &ptr6_bytes_allocated, &ptr6_usable_size, - &ptr6_bytes_tl_bulk_allocated))); - EXPECT_TRUE(ptr6.Get() != nullptr); - EXPECT_LE(9U * MB, ptr6_bytes_allocated); - EXPECT_LE(9U * MB, ptr6_usable_size); - EXPECT_LE(ptr6_usable_size, ptr6_bytes_allocated); - EXPECT_EQ(ptr6_bytes_tl_bulk_allocated, ptr6_bytes_allocated); - - // Final clean up. - size_t free1 = space->AllocationSize(ptr1.Get(), nullptr); - space->Free(self, ptr1.Assign(nullptr)); - EXPECT_LE(1U * MB, free1); -} - -void SpaceTest::AllocAndFreeListTestBody(CreateSpaceFn create_space) { - MallocSpace* space(create_space("test", 4 * MB, 16 * MB, 16 * MB, nullptr)); - ASSERT_TRUE(space != nullptr); - - // Make space findable to the heap, will also delete space when runtime is cleaned up - AddSpace(space); - Thread* self = Thread::Current(); - ScopedObjectAccess soa(self); - - // Succeeds, fits without adjusting the max allowed footprint. - mirror::Object* lots_of_objects[1024]; - for (size_t i = 0; i < arraysize(lots_of_objects); i++) { - size_t allocation_size, usable_size, bytes_tl_bulk_allocated; - size_t size_of_zero_length_byte_array = SizeOfZeroLengthByteArray(); - lots_of_objects[i] = Alloc(space, self, size_of_zero_length_byte_array, &allocation_size, - &usable_size, &bytes_tl_bulk_allocated); - EXPECT_TRUE(lots_of_objects[i] != nullptr); - size_t computed_usable_size; - EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i], &computed_usable_size)); - EXPECT_EQ(usable_size, computed_usable_size); - EXPECT_TRUE(bytes_tl_bulk_allocated == 0 || - bytes_tl_bulk_allocated >= allocation_size); - } - - // Release memory. - space->FreeList(self, arraysize(lots_of_objects), lots_of_objects); - - // Succeeds, fits by adjusting the max allowed footprint. - for (size_t i = 0; i < arraysize(lots_of_objects); i++) { - size_t allocation_size, usable_size, bytes_tl_bulk_allocated; - lots_of_objects[i] = AllocWithGrowth(space, self, 1024, &allocation_size, &usable_size, - &bytes_tl_bulk_allocated); - EXPECT_TRUE(lots_of_objects[i] != nullptr); - size_t computed_usable_size; - EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i], &computed_usable_size)); - EXPECT_EQ(usable_size, computed_usable_size); - EXPECT_TRUE(bytes_tl_bulk_allocated == 0 || - bytes_tl_bulk_allocated >= allocation_size); - } - - // Release memory. - space->FreeList(self, arraysize(lots_of_objects), lots_of_objects); -} - -void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(MallocSpace* space, intptr_t object_size, - int round, size_t growth_limit) { +template <class Super> +void SpaceTest<Super>::SizeFootPrintGrowthLimitAndTrimBody(MallocSpace* space, + intptr_t object_size, + int round, + size_t growth_limit) { if (((object_size > 0 && object_size >= static_cast<intptr_t>(growth_limit))) || ((object_size < 0 && -object_size >= static_cast<intptr_t>(growth_limit)))) { // No allocation can succeed @@ -576,7 +312,9 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(MallocSpace* space, intptr_t EXPECT_LE(space->Size(), growth_limit); } -void SpaceTest::SizeFootPrintGrowthLimitAndTrimDriver(size_t object_size, CreateSpaceFn create_space) { +template <class Super> +void SpaceTest<Super>::SizeFootPrintGrowthLimitAndTrimDriver(size_t object_size, + CreateSpaceFn create_space) { if (object_size < SizeOfZeroLengthByteArray()) { // Too small for the object layout/model. return; @@ -614,25 +352,8 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimDriver(size_t object_size, Create SizeFootPrintGrowthLimitAndTrimDriver(-size, spaceFn); \ } -#define TEST_SPACE_CREATE_FN_BASE(spaceName, spaceFn) \ - class spaceName##BaseTest : public SpaceTest { \ - }; \ - \ - TEST_F(spaceName##BaseTest, Init) { \ - InitTestBody(spaceFn); \ - } \ - TEST_F(spaceName##BaseTest, ZygoteSpace) { \ - ZygoteSpaceTestBody(spaceFn); \ - } \ - TEST_F(spaceName##BaseTest, AllocAndFree) { \ - AllocAndFreeTestBody(spaceFn); \ - } \ - TEST_F(spaceName##BaseTest, AllocAndFreeList) { \ - AllocAndFreeListTestBody(spaceFn); \ - } - #define TEST_SPACE_CREATE_FN_STATIC(spaceName, spaceFn) \ - class spaceName##StaticTest : public SpaceTest { \ + class spaceName##StaticTest : public SpaceTest<CommonRuntimeTest> { \ }; \ \ TEST_SizeFootPrintGrowthLimitAndTrimStatic(12B, spaceName, spaceFn, 12) \ @@ -648,7 +369,7 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimDriver(size_t object_size, Create TEST_SizeFootPrintGrowthLimitAndTrimStatic(8MB, spaceName, spaceFn, 8 * MB) #define TEST_SPACE_CREATE_FN_RANDOM(spaceName, spaceFn) \ - class spaceName##RandomTest : public SpaceTest { \ + class spaceName##RandomTest : public SpaceTest<CommonRuntimeTest> { \ }; \ \ TEST_SizeFootPrintGrowthLimitAndTrimRandom(16B, spaceName, spaceFn, 16) \ diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 7d60264579..7d55e8c20a 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -931,7 +931,7 @@ void Instrumentation::InvokeVirtualOrInterfaceImpl(Thread* thread, ArtMethod* caller, uint32_t dex_pc, ArtMethod* callee) const { - // We can not have thread suspension since that would cause the this_object parameter to + // We cannot have thread suspension since that would cause the this_object parameter to // potentially become a dangling pointer. An alternative could be to put it in a handle instead. ScopedAssertNoThreadSuspension ants(thread, __FUNCTION__); for (InstrumentationListener* listener : invoke_virtual_or_interface_listeners_) { diff --git a/runtime/intern_table.h b/runtime/intern_table.h index 8f715a3dc3..2b2176efe1 100644 --- a/runtime/intern_table.h +++ b/runtime/intern_table.h @@ -61,7 +61,7 @@ class InternTable { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); // Only used by image writer. Special version that may not cause thread suspension since the GC - // can not be running while we are doing image writing. Maybe be called while while holding a + // cannot be running while we are doing image writing. Maybe be called while while holding a // lock since there will not be thread suspension. mirror::String* InternStrongImageString(mirror::String* s) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index e7b4731ef8..6b5218dff0 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -239,7 +239,7 @@ static std::ostream& operator<<(std::ostream& os, const InterpreterImplKind& rhs } #if !defined(__clang__) -#if defined(__arm__) && !defined(ART_USE_READ_BARRIER) +#if defined(__arm__) // TODO: remove when all targets implemented. static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind; #else @@ -247,7 +247,7 @@ static constexpr InterpreterImplKind kInterpreterImplKind = kComputedGotoImplKin #endif #else // Clang 3.4 fails to build the goto interpreter implementation. -#if defined(__arm__) && !defined(ART_USE_READ_BARRIER) +#if defined(__arm__) static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind; #else static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind; diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 18fb0d8518..ecd4de9bd0 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -592,6 +592,10 @@ static inline bool DoCallCommon(ArtMethod* called_method, // // (at this point the ArtMethod has already been replaced, // so we just need to fix-up the arguments) + // + // Note that FindMethodFromCode in entrypoint_utils-inl.h was also special-cased + // to handle the compiler optimization of replacing `this` with null without + // throwing NullPointerException. uint32_t string_init_vreg_this = is_range ? vregC : arg[0]; if (UNLIKELY(string_init)) { DCHECK_GT(num_regs, 0u); // As the method is an instance method, there should be at least 1. diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 05668a97b3..8f4d24f385 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -56,7 +56,8 @@ void Jit::DumpInfo(std::ostream& os) { os << "JIT code cache size=" << PrettySize(code_cache_->CodeCacheSize()) << "\n" << "JIT data cache size=" << PrettySize(code_cache_->DataCacheSize()) << "\n" << "JIT current capacity=" << PrettySize(code_cache_->GetCurrentCapacity()) << "\n" - << "JIT number of compiled code=" << code_cache_->NumberOfCompiledCode() << "\n"; + << "JIT number of compiled code=" << code_cache_->NumberOfCompiledCode() << "\n" + << "JIT total number of compilations=" << code_cache_->NumberOfCompilations() << "\n"; cumulative_timings_.Dump(os); } @@ -127,6 +128,13 @@ bool Jit::LoadCompiler(std::string* error_msg) { *error_msg = "JIT couldn't find jit_compile_method entry point"; return false; } + jit_types_loaded_ = reinterpret_cast<void (*)(void*, mirror::Class**, size_t)>( + dlsym(jit_library_handle_, "jit_types_loaded")); + if (jit_types_loaded_ == nullptr) { + dlclose(jit_library_handle_); + *error_msg = "JIT couldn't find jit_types_loaded entry point"; + return false; + } CompilerCallbacks* callbacks = nullptr; bool will_generate_debug_symbols = false; VLOG(jit) << "Calling JitLoad interpreter_only=" @@ -214,5 +222,31 @@ void Jit::CreateInstrumentationCache(size_t compile_threshold, size_t warmup_thr new jit::JitInstrumentationCache(compile_threshold, warmup_threshold)); } +void Jit::NewTypeLoadedIfUsingJit(mirror::Class* type) { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr && jit->generate_debug_info_) { + DCHECK(jit->jit_types_loaded_ != nullptr); + jit->jit_types_loaded_(jit->jit_compiler_handle_, &type, 1); + } +} + +void Jit::DumpTypeInfoForLoadedTypes(ClassLinker* linker) { + struct CollectClasses : public ClassVisitor { + bool Visit(mirror::Class* klass) override { + classes_.push_back(klass); + return true; + } + std::vector<mirror::Class*> classes_; + }; + + if (generate_debug_info_) { + ScopedObjectAccess so(Thread::Current()); + + CollectClasses visitor; + linker->VisitClasses(&visitor); + jit_types_loaded_(jit_compiler_handle_, visitor.classes_.data(), visitor.classes_.size()); + } +} + } // namespace jit } // namespace art diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 42bbbe73c7..429edf65a6 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -79,6 +79,13 @@ class Jit { DumpInfo(os); } + static void NewTypeLoadedIfUsingJit(mirror::Class* type) + SHARED_REQUIRES(Locks::mutator_lock_); + + // If debug info generation is turned on then write the type information for types already loaded + // into the specified class linker to the jit debug interface, + void DumpTypeInfoForLoadedTypes(ClassLinker* linker); + private: Jit(); bool LoadCompiler(std::string* error_msg); @@ -89,6 +96,7 @@ class Jit { void* (*jit_load_)(CompilerCallbacks**, bool*); void (*jit_unload_)(void*); bool (*jit_compile_method_)(void*, ArtMethod*, Thread*); + void (*jit_types_loaded_)(void*, mirror::Class**, size_t count); // Performance monitoring. bool dump_info_on_shutdown_; diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 2d575bdb31..64b2c899aa 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -125,7 +125,8 @@ JitCodeCache::JitCodeCache(MemMap* code_map, data_end_(initial_data_capacity), has_done_one_collection_(false), last_update_time_ns_(0), - garbage_collect_code_(garbage_collect_code) { + garbage_collect_code_(garbage_collect_code), + number_of_compilations_(0) { DCHECK_GE(max_capacity, initial_code_capacity + initial_data_capacity); code_mspace_ = create_mspace_with_base(code_map_->Begin(), code_end_, false /*locked*/); @@ -322,6 +323,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, __builtin___clear_cache(reinterpret_cast<char*>(code_ptr), reinterpret_cast<char*>(code_ptr + code_size)); + number_of_compilations_++; } // We need to update the entry point in the runnable state for the instrumentation. { @@ -347,6 +349,11 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, return reinterpret_cast<uint8_t*>(method_header); } +size_t JitCodeCache::NumberOfCompilations() { + MutexLock mu(Thread::Current(), lock_); + return number_of_compilations_; +} + size_t JitCodeCache::CodeCacheSize() { MutexLock mu(Thread::Current(), lock_); return CodeCacheSizeLocked(); diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index a152bcd2d4..67fa928f61 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -68,6 +68,9 @@ class JitCodeCache { // of methods that got JIT compiled, as we might have collected some. size_t NumberOfCompiledCode() REQUIRES(!lock_); + // Number of compilations done throughout the lifetime of the JIT. + size_t NumberOfCompilations() REQUIRES(!lock_); + bool NotifyCompilationOf(ArtMethod* method, Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); @@ -261,6 +264,9 @@ class JitCodeCache { // Whether we can do garbage collection. const bool garbage_collect_code_; + // Number of compilations done throughout the lifetime of the JIT. + size_t number_of_compilations_ GUARDED_BY(lock_); + DISALLOW_IMPLICIT_CONSTRUCTORS(JitCodeCache); }; diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc index a132701796..b4b872ff50 100644 --- a/runtime/jit/offline_profiling_info.cc +++ b/runtime/jit/offline_profiling_info.cc @@ -24,8 +24,11 @@ #include "art_method-inl.h" #include "base/mutex.h" +#include "base/scoped_flock.h" #include "base/stl_util.h" +#include "base/unix_file/fd_file.h" #include "jit/profiling_info.h" +#include "os.h" #include "safe_map.h" namespace art { @@ -37,8 +40,17 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, return true; } + ScopedFlock flock; + std::string error; + if (!flock.Init(filename.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC, /* block */ false, &error)) { + LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error; + return false; + } + + int fd = flock.GetFile()->Fd(); + ProfileCompilationInfo info; - if (!info.Load(filename)) { + if (!info.Load(fd)) { LOG(WARNING) << "Could not load previous profile data from file " << filename; return false; } @@ -54,9 +66,14 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, } } + if (!flock.GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear profile file: " << filename; + return false; + } + // This doesn't need locking because we are trying to lock the file for exclusive // access and fail immediately if we can't. - bool result = info.Save(filename); + bool result = info.Save(fd); if (result) { VLOG(profiler) << "Successfully saved profile info to " << filename << " Size: " << GetFileSizeBytes(filename); @@ -66,64 +83,20 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, return result; } -enum OpenMode { - READ, - READ_WRITE -}; - -static int OpenFile(const std::string& filename, OpenMode open_mode) { - int fd = -1; - switch (open_mode) { - case READ: - fd = open(filename.c_str(), O_RDONLY); - break; - case READ_WRITE: - // TODO(calin) allow the shared uid of the app to access the file. - fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC); - break; - } - - if (fd < 0) { - PLOG(WARNING) << "Failed to open profile file " << filename; - return -1; - } - - // Lock the file for exclusive access but don't wait if we can't lock it. - int err = flock(fd, LOCK_EX | LOCK_NB); - if (err < 0) { - PLOG(WARNING) << "Failed to lock profile file " << filename; - return -1; - } - return fd; -} - -static bool CloseDescriptorForFile(int fd, const std::string& filename) { - // Now unlock the file, allowing another process in. - int err = flock(fd, LOCK_UN); - if (err < 0) { - PLOG(WARNING) << "Failed to unlock profile file " << filename; - return false; - } - - // Done, close the file. - err = ::close(fd); - if (err < 0) { - PLOG(WARNING) << "Failed to close descriptor for profile file" << filename; - return false; - } - - return true; -} - -static void WriteToFile(int fd, const std::ostringstream& os) { +static bool WriteToFile(int fd, const std::ostringstream& os) { std::string data(os.str()); const char *p = data.c_str(); size_t length = data.length(); do { - int n = ::write(fd, p, length); + int n = TEMP_FAILURE_RETRY(write(fd, p, length)); + if (n < 0) { + PLOG(WARNING) << "Failed to write to descriptor: " << fd; + return false; + } p += n; length -= n; } while (length > 0); + return true; } static constexpr const char kFieldSeparator = ','; @@ -137,13 +110,8 @@ static constexpr const char kLineSeparator = '\n'; * /system/priv-app/app/app.apk,131232145,11,23,454,54 * /system/priv-app/app/app.apk:classes5.dex,218490184,39,13,49,1 **/ -bool ProfileCompilationInfo::Save(const std::string& filename) { - int fd = OpenFile(filename, READ_WRITE); - if (fd == -1) { - return false; - } - - // TODO(calin): Merge with a previous existing profile. +bool ProfileCompilationInfo::Save(uint32_t fd) { + DCHECK_GE(fd, 0u); // TODO(calin): Profile this and see how much memory it takes. If too much, // write to file directly. std::ostringstream os; @@ -158,9 +126,7 @@ bool ProfileCompilationInfo::Save(const std::string& filename) { os << kLineSeparator; } - WriteToFile(fd, os); - - return CloseDescriptorForFile(fd, filename); + return WriteToFile(fd, os); } // TODO(calin): This a duplicate of Utils::Split fixing the case where the first character @@ -222,7 +188,9 @@ bool ProfileCompilationInfo::ProcessLine(const std::string& line) { LOG(WARNING) << "Cannot parse method_idx " << parts[i]; return false; } - AddData(dex_location, checksum, method_idx); + if (!AddData(dex_location, checksum, method_idx)) { + return false; + } } return true; } @@ -249,23 +217,18 @@ static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& l return new_line_pos == -1 ? new_line_pos : new_line_pos + 1; } -bool ProfileCompilationInfo::Load(const std::string& filename) { - int fd = OpenFile(filename, READ); - if (fd == -1) { - return false; - } +bool ProfileCompilationInfo::Load(uint32_t fd) { + DCHECK_GE(fd, 0u); std::string current_line; const int kBufferSize = 1024; char buffer[kBufferSize]; - bool success = true; - while (success) { - int n = read(fd, buffer, kBufferSize); + while (true) { + int n = TEMP_FAILURE_RETRY(read(fd, buffer, kBufferSize)); if (n < 0) { - PLOG(WARNING) << "Error when reading profile file " << filename; - success = false; - break; + PLOG(WARNING) << "Error when reading profile file"; + return false; } else if (n == 0) { break; } @@ -278,17 +241,13 @@ bool ProfileCompilationInfo::Load(const std::string& filename) { break; } if (!ProcessLine(current_line)) { - success = false; - break; + return false; } // Reset the current line (we just processed it). current_line.clear(); } } - if (!success) { - info_.clear(); - } - return CloseDescriptorForFile(fd, filename) && success; + return true; } bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) { @@ -369,4 +328,8 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* return os.str(); } +bool ProfileCompilationInfo::Equals(ProfileCompilationInfo& other) { + return info_.Equals(other.info_); +} + } // namespace art diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h index 26e1ac385f..ffd14335d7 100644 --- a/runtime/jit/offline_profiling_info.h +++ b/runtime/jit/offline_profiling_info.h @@ -39,15 +39,18 @@ class ArtMethod; */ class ProfileCompilationInfo { public: + // Saves profile information about the given methods in the given file. + // Note that the saving proceeds only if the file can be locked for exclusive access. + // If not (the locking is not blocking), the function does not save and returns false. static bool SaveProfilingInfo(const std::string& filename, const std::vector<ArtMethod*>& methods); - // Loads profile information from the given file. - bool Load(const std::string& profile_filename); + // Loads profile information from the given file descriptor. + bool Load(uint32_t fd); // Loads the data from another ProfileCompilationInfo object. bool Load(const ProfileCompilationInfo& info); - // Saves the profile data to the given file. - bool Save(const std::string& profile_filename); + // Saves the profile data to the given file descriptor. + bool Save(uint32_t fd); // Returns the number of methods that were profiled. uint32_t GetNumberOfMethods() const; @@ -61,6 +64,9 @@ class ProfileCompilationInfo { std::string DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location = true) const; + // For testing purposes. + bool Equals(ProfileCompilationInfo& other); + private: bool AddData(const std::string& dex_location, uint32_t checksum, uint16_t method_idx); bool ProcessLine(const std::string& line); @@ -69,10 +75,18 @@ class ProfileCompilationInfo { explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {} uint32_t checksum; std::set<uint16_t> method_set; + + bool operator==(const DexFileData& other) const { + return checksum == other.checksum && method_set == other.method_set; + } }; using DexFileToProfileInfoMap = SafeMap<const std::string, DexFileData>; + friend class ProfileCompilationInfoTest; + friend class CompilerDriverProfileTest; + friend class ProfileAssistantTest; + DexFileToProfileInfoMap info_; }; diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc new file mode 100644 index 0000000000..482ea06395 --- /dev/null +++ b/runtime/jit/profile_compilation_info_test.cc @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "base/unix_file/fd_file.h" +#include "art_method-inl.h" +#include "class_linker-inl.h" +#include "common_runtime_test.h" +#include "dex_file.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "handle_scope-inl.h" +#include "jit/offline_profiling_info.h" +#include "scoped_thread_state_change.h" + +namespace art { + +class ProfileCompilationInfoTest : public CommonRuntimeTest { + protected: + std::vector<ArtMethod*> GetVirtualMethods(jobject class_loader, + const std::string& clazz) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader(hs.NewHandle( + reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader)))); + mirror::Class* klass = class_linker->FindClass(self, clazz.c_str(), h_loader); + + const auto pointer_size = class_linker->GetImagePointerSize(); + std::vector<ArtMethod*> methods; + for (auto& m : klass->GetVirtualMethods(pointer_size)) { + methods.push_back(&m); + } + return methods; + } + + bool AddData(const std::string& dex_location, + uint32_t checksum, + uint16_t method_index, + ProfileCompilationInfo* info) { + return info->AddData(dex_location, checksum, method_index); + } + + uint32_t GetFd(const ScratchFile& file) { + return static_cast<uint32_t>(file.GetFd()); + } +}; + +TEST_F(ProfileCompilationInfoTest, SaveArtMethods) { + ScratchFile profile; + + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("ProfileTestMultiDex"); + } + ASSERT_NE(class_loader, nullptr); + + // Save virtual methods from Main. + std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;"); + ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), main_methods)); + + // Check that what we saved is in the profile. + ProfileCompilationInfo info1; + ASSERT_TRUE(info1.Load(GetFd(profile))); + ASSERT_EQ(info1.GetNumberOfMethods(), main_methods.size()); + { + ScopedObjectAccess soa(self); + for (ArtMethod* m : main_methods) { + ASSERT_TRUE(info1.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + } + + // Save virtual methods from Second. + std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;"); + ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), second_methods)); + + // Check that what we saved is in the profile (methods form Main and Second). + ProfileCompilationInfo info2; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(info2.Load(GetFd(profile))); + ASSERT_EQ(info2.GetNumberOfMethods(), main_methods.size() + second_methods.size()); + { + ScopedObjectAccess soa(self); + for (ArtMethod* m : main_methods) { + ASSERT_TRUE(info2.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + for (ArtMethod* m : second_methods) { + ASSERT_TRUE(info2.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + } +} + +TEST_F(ProfileCompilationInfoTest, SaveFd) { + ScratchFile profile; + + ProfileCompilationInfo saved_info; + // Save a few methods. + for (uint16_t i = 0; i < 10; i++) { + ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + } + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back what we saved. + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info.Equals(saved_info)); + + // Save more methods. + for (uint16_t i = 0; i < 100; i++) { + ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info)); + } + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back everything we saved. + ProfileCompilationInfo loaded_info2; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info2.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info2.Equals(saved_info)); +} + +TEST_F(ProfileCompilationInfoTest, AddDataFail) { + ScratchFile profile; + + ProfileCompilationInfo info; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info)); + // Trying to add info for an existing file but with a different checksum. + ASSERT_FALSE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info)); +} + +TEST_F(ProfileCompilationInfoTest, LoadFail) { + ScratchFile profile; + + ProfileCompilationInfo info1; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1)); + // Use the same file, change the checksum. + ProfileCompilationInfo info2; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2)); + + ASSERT_FALSE(info1.Load(info2)); +} + +} // namespace art diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index ec289ea2b5..fc257c0441 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -86,12 +86,14 @@ void ProfileSaver::Run() { } bool ProfileSaver::ProcessProfilingInfo() { - VLOG(profiler) << "Initiating save profiling information to: " << output_filename_; + VLOG(profiler) << "Save profiling information to: " << output_filename_; uint64_t last_update_time_ns = jit_code_cache_->GetLastUpdateTimeNs(); if (last_update_time_ns - code_cache_last_update_time_ns_ - > kMinimumTimeBetweenCodeCacheUpdatesNs) { - VLOG(profiler) << "Not enough time has passed since the last code cache update."; + < kMinimumTimeBetweenCodeCacheUpdatesNs) { + VLOG(profiler) << "Not enough time has passed since the last code cache update." + << "Last update: " << last_update_time_ns + << " Last save: " << code_cache_last_update_time_ns_; return false; } diff --git a/runtime/mem_map_test.cc b/runtime/mem_map_test.cc index edcbcf2dca..81c855e736 100644 --- a/runtime/mem_map_test.cc +++ b/runtime/mem_map_test.cc @@ -251,6 +251,10 @@ TEST_F(MemMapTest, RemapAtEnd32bit) { #endif TEST_F(MemMapTest, MapAnonymousExactAddr32bitHighAddr) { + // Some MIPS32 hardware (namely the Creator Ci20 development board) + // cannot allocate in the 2GB-4GB region. + TEST_DISABLED_FOR_MIPS(); + CommonInit(); // This test may not work under valgrind. if (RUNNING_ON_MEMORY_TOOL == 0) { @@ -271,8 +275,8 @@ TEST_F(MemMapTest, MapAnonymousExactAddr32bitHighAddr) { break; } } - ASSERT_GE(reinterpret_cast<uintptr_t>(map->End()), 2u * GB); ASSERT_TRUE(map.get() != nullptr) << error_msg; + ASSERT_GE(reinterpret_cast<uintptr_t>(map->End()), 2u * GB); ASSERT_TRUE(error_msg.empty()); ASSERT_EQ(BaseBegin(map.get()), reinterpret_cast<void*>(start_addr)); } diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index e89c74da23..0ddd4a280c 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -16,6 +16,8 @@ #include "java_lang_Class.h" +#include <iostream> + #include "art_field-inl.h" #include "class_linker.h" #include "common_throws.h" @@ -303,7 +305,10 @@ static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring nam // We log the error for this specific case, as the user might just swallow the exception. // This helps diagnose crashes when applications rely on the String#value field being // there. - LOG(ERROR) << "The String#value field is not present on Android versions >= 6.0"; + // Also print on the error stream to test it through run-test. + std::string message("The String#value field is not present on Android versions >= 6.0"); + LOG(ERROR) << message; + std::cerr << message << std::endl; } // We may have a pending exception if we failed to resolve. if (!soa.Self()->IsExceptionPending()) { diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 83e594b169..d2f563be7a 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -627,7 +627,7 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { if (dl_iterate_phdr(dl_iterate_context::callback, &context) == 0) { PrintFileToLog("/proc/self/maps", LogSeverity::WARNING); - LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but can not find its mmaps."; + LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but cannot find its mmaps."; } #endif } diff --git a/runtime/prebuilt_tools_test.cc b/runtime/prebuilt_tools_test.cc index a7f7bcd134..eb226d496a 100644 --- a/runtime/prebuilt_tools_test.cc +++ b/runtime/prebuilt_tools_test.cc @@ -34,7 +34,7 @@ static void CheckToolsExist(const std::string& tools_dir) { struct stat exec_st; std::string exec_path = tools_dir + tool; if (stat(exec_path.c_str(), &exec_st) != 0) { - ADD_FAILURE() << "Can not find " << tool << " in " << tools_dir; + ADD_FAILURE() << "Cannot find " << tool << " in " << tools_dir; } } } @@ -42,7 +42,7 @@ static void CheckToolsExist(const std::string& tools_dir) { TEST_F(PrebuiltToolsTest, CheckHostTools) { std::string tools_dir = GetAndroidHostToolsDir(); if (tools_dir.empty()) { - ADD_FAILURE() << "Can not find Android tools directory for host"; + ADD_FAILURE() << "Cannot find Android tools directory for host"; } else { CheckToolsExist(tools_dir); } @@ -54,7 +54,7 @@ TEST_F(PrebuiltToolsTest, CheckTargetTools) { for (InstructionSet isa : isas) { std::string tools_dir = GetAndroidTargetToolsDir(isa); if (tools_dir.empty()) { - ADD_FAILURE() << "Can not find Android tools directory for " << isa; + ADD_FAILURE() << "Cannot find Android tools directory for " << isa; } else { CheckToolsExist(tools_dir); } diff --git a/runtime/runtime.cc b/runtime/runtime.cc index c4694ee291..1101acdf61 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1881,6 +1881,9 @@ void Runtime::CreateJit() { jit_->CreateInstrumentationCache(jit_options_->GetCompileThreshold(), jit_options_->GetWarmupThreshold()); jit_->CreateThreadPool(); + + // Notify native debugger about the classes already loaded before the creation of the jit. + jit_->DumpTypeInfoForLoadedTypes(GetClassLinker()); } else { LOG(WARNING) << "Failed to create JIT " << error_msg; } diff --git a/runtime/type_lookup_table_test.cc b/runtime/type_lookup_table_test.cc index 7f500cc9a1..ea4d8b5ee0 100644 --- a/runtime/type_lookup_table_test.cc +++ b/runtime/type_lookup_table_test.cc @@ -25,10 +25,10 @@ namespace art { -class TypeLookupTableTest : public CommonRuntimeTest { - public: - size_t kDexNoIndex = DexFile::kDexNoIndex; // Make copy to prevent linking errors. -}; +static const size_t kDexNoIndex = DexFile::kDexNoIndex; // Make copy to prevent linking errors. + +using DescriptorClassDefIdxPair = std::pair<const char*, uint32_t>; +class TypeLookupTableTest : public CommonRuntimeTestWithParam<DescriptorClassDefIdxPair> {}; TEST_F(TypeLookupTableTest, CreateLookupTable) { ScopedObjectAccess soa(Thread::Current()); @@ -39,48 +39,28 @@ TEST_F(TypeLookupTableTest, CreateLookupTable) { ASSERT_EQ(32U, table->RawDataLength()); } -TEST_F(TypeLookupTableTest, FindNonExistingClassWithoutCollisions) { - ScopedObjectAccess soa(Thread::Current()); - std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); - std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); - ASSERT_NE(nullptr, table.get()); - const char* descriptor = "LBA;"; - size_t hash = ComputeModifiedUtf8Hash(descriptor); - uint32_t class_def_idx = table->Lookup(descriptor, hash); - ASSERT_EQ(kDexNoIndex, class_def_idx); -} - -TEST_F(TypeLookupTableTest, FindNonExistingClassWithCollisions) { - ScopedObjectAccess soa(Thread::Current()); - std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); - std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); - ASSERT_NE(nullptr, table.get()); - const char* descriptor = "LDA;"; - size_t hash = ComputeModifiedUtf8Hash(descriptor); - uint32_t class_def_idx = table->Lookup(descriptor, hash); - ASSERT_EQ(kDexNoIndex, class_def_idx); -} - -TEST_F(TypeLookupTableTest, FindClassNoCollisions) { - ScopedObjectAccess soa(Thread::Current()); - std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); - std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); - ASSERT_NE(nullptr, table.get()); - const char* descriptor = "LC;"; - size_t hash = ComputeModifiedUtf8Hash(descriptor); - uint32_t class_def_idx = table->Lookup(descriptor, hash); - ASSERT_EQ(2U, class_def_idx); -} - -TEST_F(TypeLookupTableTest, FindClassWithCollisions) { +TEST_P(TypeLookupTableTest, Find) { ScopedObjectAccess soa(Thread::Current()); std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); ASSERT_NE(nullptr, table.get()); - const char* descriptor = "LAB;"; + auto pair = GetParam(); + const char* descriptor = pair.first; size_t hash = ComputeModifiedUtf8Hash(descriptor); uint32_t class_def_idx = table->Lookup(descriptor, hash); - ASSERT_EQ(1U, class_def_idx); + ASSERT_EQ(pair.second, class_def_idx); } +INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithoutCollisions, + TypeLookupTableTest, + testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); +INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithCollisions, + TypeLookupTableTest, + testing::Values(DescriptorClassDefIdxPair("LDA;", kDexNoIndex))); +INSTANTIATE_TEST_CASE_P(FindClassNoCollisions, + TypeLookupTableTest, + testing::Values(DescriptorClassDefIdxPair("LC;", 2U))); +INSTANTIATE_TEST_CASE_P(FindClassWithCollisions, + TypeLookupTableTest, + testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); } // namespace art diff --git a/runtime/utf_test.cc b/runtime/utf_test.cc index 5239e40540..c67879b427 100644 --- a/runtime/utf_test.cc +++ b/runtime/utf_test.cc @@ -353,7 +353,7 @@ TEST_F(UtfTest, ExhaustiveBidirectionalCodePointCheck) { if (codePoint <= 0xffff) { if (codePoint >= 0xd800 && codePoint <= 0xdfff) { // According to the Unicode standard, no character will ever - // be assigned to these code points, and they can not be encoded + // be assigned to these code points, and they cannot be encoded // into either utf-16 or utf-8. continue; } diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc index 16cab033d4..0894f5d1ee 100644 --- a/runtime/verifier/reg_type.cc +++ b/runtime/verifier/reg_type.cc @@ -667,13 +667,13 @@ const RegType& RegType::Merge(const RegType& incoming_type, RegTypeCache* reg_ty // float/long/double MERGE float/long/double_constant => float/long/double return SelectNonConstant(*this, incoming_type); } else if (IsReferenceTypes() && incoming_type.IsReferenceTypes()) { - if (IsZero() || incoming_type.IsZero()) { - return SelectNonConstant(*this, incoming_type); // 0 MERGE ref => ref - } else if (IsUninitializedTypes() || incoming_type.IsUninitializedTypes()) { + if (IsUninitializedTypes() || incoming_type.IsUninitializedTypes()) { // Something that is uninitialized hasn't had its constructor called. Unitialized types are // special. They may only ever be merged with themselves (must be taken care of by the // caller of Merge(), see the DCHECK on entry). So mark any other merge as conflicting here. return conflict; + } else if (IsZero() || incoming_type.IsZero()) { + return SelectNonConstant(*this, incoming_type); // 0 MERGE ref => ref } else if (IsJavaLangObject() || incoming_type.IsJavaLangObject()) { return reg_types->JavaLangObject(false); // Object MERGE ref => Object } else if (IsUnresolvedTypes() || incoming_type.IsUnresolvedTypes()) { diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java index 883ce2c9fe..072b1d0c4d 100644 --- a/test/004-StackWalk/src/Main.java +++ b/test/004-StackWalk/src/Main.java @@ -2,14 +2,14 @@ public class Main { public Main() { } + boolean doThrow = false; + int $noinline$f() throws Exception { g(1); g(2); - // This loop currently defeats inlining of `f`. - for (int i = 0; i < 10; i++) { - Thread.sleep(0); - } + // This currently defeats inlining of `f`. + if (doThrow) { throw new Error(); } return 0; } diff --git a/test/048-reflect-v8/build b/test/048-reflect-v8/build new file mode 100644 index 0000000000..4ea1838465 --- /dev/null +++ b/test/048-reflect-v8/build @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Make us exit on a failure. +set -e + +# Hard-wired use of experimental jack. +# TODO: fix this temporary work-around for lambdas, see b/19467889 +export USE_JACK=true +export JACK_SERVER=false +export JACK_REPOSITORY="${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/jacks" +# e.g. /foo/bar/jack-3.10.ALPHA.jar -> 3.10.ALPHA +export JACK_VERSION="$(find "$JACK_REPOSITORY" -name '*ALPHA*' | sed 's/.*jack-//g' | sed 's/[.]jar//g')" + +./default-build "$@" --experimental default-methods diff --git a/test/048-reflect-v8/expected.txt b/test/048-reflect-v8/expected.txt new file mode 100644 index 0000000000..2d0b4ccb6b --- /dev/null +++ b/test/048-reflect-v8/expected.txt @@ -0,0 +1,4 @@ +Main$DefaultInterface is default = yes +Main$RegularInterface is default = no +Main$ImplementsWithDefault is default = yes +Main$ImplementsWithRegular is default = no diff --git a/test/048-reflect-v8/info.txt b/test/048-reflect-v8/info.txt new file mode 100644 index 0000000000..a336d301d6 --- /dev/null +++ b/test/048-reflect-v8/info.txt @@ -0,0 +1 @@ +Test reflection for 1.8 APIs diff --git a/test/048-reflect-v8/run b/test/048-reflect-v8/run new file mode 100644 index 0000000000..0ad55fd6e9 --- /dev/null +++ b/test/048-reflect-v8/run @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Ensure that the default methods are turned on for dalvikvm and dex2oat +${RUN} "$@" --experimental default-methods -Xcompiler-option diff --git a/test/048-reflect-v8/src/Main.java b/test/048-reflect-v8/src/Main.java new file mode 100644 index 0000000000..7fa2a923d7 --- /dev/null +++ b/test/048-reflect-v8/src/Main.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Method; + +public class Main { + interface DefaultInterface { + default void sayHi() { + System.out.println("hi default"); + } + } + + interface RegularInterface { + void sayHi(); + } + + class ImplementsWithDefault implements DefaultInterface {} + class ImplementsWithRegular implements RegularInterface { + public void sayHi() { + System.out.println("hello specific"); + } + } + + private static void printIsDefault(Class<?> klass) { + Method m; + try { + m = klass.getMethod("sayHi"); + } catch (Throwable t) { + System.out.println(t); + return; + } + + boolean isDefault = m.isDefault(); + System.out.println(klass.getName() + " is default = " + (isDefault ? "yes" : "no")); + } + + public static void main(String[] args) { + printIsDefault(DefaultInterface.class); + printIsDefault(RegularInterface.class); + printIsDefault(ImplementsWithDefault.class); + printIsDefault(ImplementsWithRegular.class); + } +} diff --git a/test/127-secondarydex/build b/test/127-checker-secondarydex/build index 0d9f4d6291..0d9f4d6291 100755 --- a/test/127-secondarydex/build +++ b/test/127-checker-secondarydex/build diff --git a/test/127-secondarydex/expected.txt b/test/127-checker-secondarydex/expected.txt index 1c8defb6ec..1c8defb6ec 100644 --- a/test/127-secondarydex/expected.txt +++ b/test/127-checker-secondarydex/expected.txt diff --git a/test/127-secondarydex/info.txt b/test/127-checker-secondarydex/info.txt index 0479d1a784..0479d1a784 100644 --- a/test/127-secondarydex/info.txt +++ b/test/127-checker-secondarydex/info.txt diff --git a/test/127-secondarydex/run b/test/127-checker-secondarydex/run index d8c3c798bf..d8c3c798bf 100755 --- a/test/127-secondarydex/run +++ b/test/127-checker-secondarydex/run diff --git a/test/127-secondarydex/src/Main.java b/test/127-checker-secondarydex/src/Main.java index 0ede8ed2b2..0ede8ed2b2 100644 --- a/test/127-secondarydex/src/Main.java +++ b/test/127-checker-secondarydex/src/Main.java diff --git a/test/127-secondarydex/src/Super.java b/test/127-checker-secondarydex/src/Super.java index 7608d4a7c8..7608d4a7c8 100644 --- a/test/127-secondarydex/src/Super.java +++ b/test/127-checker-secondarydex/src/Super.java diff --git a/test/127-secondarydex/src/Test.java b/test/127-checker-secondarydex/src/Test.java index 8547e791c2..266ed191bc 100644 --- a/test/127-secondarydex/src/Test.java +++ b/test/127-checker-secondarydex/src/Test.java @@ -23,6 +23,13 @@ public class Test extends Super { System.out.println("Test"); } + /// CHECK-START: java.lang.Integer Test.toInteger() ssa_builder (after) + /// CHECK: LoadClass needs_access_check:false klass:java.lang.Integer + + public Integer toInteger() { + return new Integer(42); + } + public String toString() { return new String("Test"); } diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc index 9bfe42922b..77301d20e8 100644 --- a/test/137-cfi/cfi.cc +++ b/test/137-cfi/cfi.cc @@ -76,7 +76,7 @@ static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) { } } - printf("Can not find %s in backtrace:\n", seq[cur_search_index].c_str()); + printf("Cannot find %s in backtrace:\n", seq[cur_search_index].c_str()); for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) { if (BacktraceMap::IsValid(it->map)) { printf(" %s\n", it->func_name.c_str()); @@ -112,7 +112,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid())); if (!bt->Unwind(0, nullptr)) { - printf("Can not unwind in process.\n"); + printf("Cannot unwind in process.\n"); return JNI_FALSE; } else if (bt->NumFrames() == 0) { printf("No frames for unwind in process.\n"); @@ -205,7 +205,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobj std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD)); bool result = true; if (!bt->Unwind(0, nullptr)) { - printf("Can not unwind other process.\n"); + printf("Cannot unwind other process.\n"); result = false; } else if (bt->NumFrames() == 0) { printf("No frames for unwind of other process.\n"); diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java index 0640b364c9..bcb697a396 100644 --- a/test/141-class-unload/src/Main.java +++ b/test/141-class-unload/src/Main.java @@ -79,7 +79,7 @@ public class Main { private static void testUnloadClass(Constructor constructor) throws Exception { WeakReference<Class> klass = setUpUnloadClass(constructor); - // No strong refernces to class loader, should get unloaded. + // No strong references to class loader, should get unloaded. Runtime.getRuntime().gc(); WeakReference<Class> klass2 = setUpUnloadClass(constructor); Runtime.getRuntime().gc(); @@ -91,7 +91,7 @@ public class Main { private static void testUnloadLoader(Constructor constructor) throws Exception { WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true); - // No strong refernces to class loader, should get unloaded. + // No strong references to class loader, should get unloaded. Runtime.getRuntime().gc(); // If the weak reference is cleared, then it was unloaded. System.out.println(loader.get()); @@ -109,7 +109,7 @@ public class Main { private static void testLoadAndUnloadLibrary(Constructor constructor) throws Exception { WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor); - // No strong refernces to class loader, should get unloaded. + // No strong references to class loader, should get unloaded. Runtime.getRuntime().gc(); // If the weak reference is cleared, then it was unloaded. System.out.println(loader.get()); diff --git a/test/143-string-value/check b/test/143-string-value/check index cdf7b783a3..92f6e90023 100755 --- a/test/143-string-value/check +++ b/test/143-string-value/check @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Strip run-specific numbers (pid and line number) -sed -e 's/^art E[ ]\+[0-9]\+[ ]\+[0-9]\+ art\/runtime\/native\/java_lang_Class.cc:[0-9]\+\] //' "$2" > "$2.tmp" +# Strip error log messages. +sed -e '/^art E.*\] /d' "$2" > "$2.tmp" diff --strip-trailing-cr -q "$1" "$2.tmp" >/dev/null diff --git a/test/482-checker-loop-back-edge-use/src/Main.java b/test/482-checker-loop-back-edge-use/src/Main.java index d0b33b9282..f8f0aa3f0a 100644 --- a/test/482-checker-loop-back-edge-use/src/Main.java +++ b/test/482-checker-loop-back-edge-use/src/Main.java @@ -40,6 +40,9 @@ public class Main { /// CHECK-EVAL: <<GotoLiv2>> + 2 == <<ArgLoopUse2>> public static void loop2(boolean incoming) { + // Add some code at entry to avoid having the entry block be a pre header. + // This avoids having to create a synthesized block. + System.out.println("Enter"); while (true) { System.out.println("foo"); while (incoming) {} @@ -170,6 +173,9 @@ public class Main { /// CHECK-EVAL: <<GotoLiv1>> + 2 == <<ArgLoopUse>> public static void loop9() { + // Add some code at entry to avoid having the entry block be a pre header. + // This avoids having to create a synthesized block. + System.out.println("Enter"); while (Runtime.getRuntime() != null) { // 'incoming' must only have a use in the inner loop. boolean incoming = field; diff --git a/test/510-checker-try-catch/smali/SsaBuilder.smali b/test/510-checker-try-catch/smali/SsaBuilder.smali index 710e849c45..a6a5bfebee 100644 --- a/test/510-checker-try-catch/smali/SsaBuilder.smali +++ b/test/510-checker-try-catch/smali/SsaBuilder.smali @@ -21,7 +21,7 @@ ## CHECK-START: int SsaBuilder.testSimplifyCatchBlock(int, int, int) ssa_builder (after) -## CHECK: name "B0" +## CHECK: name "B1" ## CHECK-NEXT: from_bci ## CHECK-NEXT: to_bci ## CHECK-NEXT: predecessors @@ -39,12 +39,15 @@ ## CHECK: name "<<BExtracted>>" ## CHECK-NEXT: from_bci ## CHECK-NEXT: to_bci -## CHECK-NEXT: predecessors "B0" "<<BCatch>>" +## CHECK-NEXT: predecessors "B1" "<<BCatch>>" ## CHECK-NOT: flags "catch_block" ## CHECK: Add .method public static testSimplifyCatchBlock(III)I .registers 4 + # Avoid entry block be a pre header, which leads to + # the cfg simplifier to add a synthesized block. + goto :catch_all :catch_all add-int/2addr p0, p1 diff --git a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali index 30a648d764..971ad84241 100644 --- a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali +++ b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali @@ -91,9 +91,7 @@ goto :other_loop_entry .end method -# Check that if a irreducible loop entry is dead, the loop can become -# natural. -# We start with: +# Check that dce does not apply for irreducible loops. # # entry # / \ @@ -106,18 +104,8 @@ ## CHECK-START: int IrreducibleLoop.dce(int) dead_code_elimination (before) ## CHECK: irreducible:true -# And end with: -# -# entry -# / -# / -# loop_entry -# / \- -# exit \- -# other_loop_entry - ## CHECK-START: int IrreducibleLoop.dce(int) dead_code_elimination (after) -## CHECK-NOT: irreducible:true +## CHECK: irreducible:true .method public static dce(I)I .registers 3 const/16 v0, 42 diff --git a/test/563-checker-fakestring/smali/TestCase.smali b/test/563-checker-fakestring/smali/TestCase.smali index 814cda5345..54312a43d0 100644 --- a/test/563-checker-fakestring/smali/TestCase.smali +++ b/test/563-checker-fakestring/smali/TestCase.smali @@ -64,9 +64,15 @@ .end method -# Test deoptimization between String's allocation and initialization. +# Test deoptimization between String's allocation and initialization. When not +# compiling --debuggable, the NewInstance will be optimized out. ## CHECK-START: int TestCase.deoptimizeNewInstance(int[], byte[]) register (after) +## CHECK: <<Null:l\d+>> NullConstant +## CHECK: Deoptimize env:[[<<Null>>,{{.*]]}} +## CHECK: InvokeStaticOrDirect method_name:java.lang.String.<init> + +## CHECK-START-DEBUGGABLE: int TestCase.deoptimizeNewInstance(int[], byte[]) register (after) ## CHECK: <<String:l\d+>> NewInstance ## CHECK: Deoptimize env:[[<<String>>,{{.*]]}} ## CHECK: InvokeStaticOrDirect method_name:java.lang.String.<init> @@ -99,39 +105,78 @@ .end method -# Test throwing and catching an exception between String's allocation and initialization. +# Test that a redundant NewInstance is removed if not used and not compiling +# --debuggable. -## CHECK-START: void TestCase.catchNewInstance() register (after) -## CHECK-DAG: <<Null:l\d+>> NullConstant -## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 -## CHECK-DAG: <<String:l\d+>> NewInstance -## CHECK-DAG: <<UTF8:l\d+>> LoadString -## CHECK-DAG: InvokeStaticOrDirect [<<Null>>,<<UTF8>>] env:[[<<String>>,<<Zero>>,<<UTF8>>]] -## CHECK-DAG: Phi [<<Null>>,<<Null>>,<<String>>] reg:0 is_catch_phi:true +## CHECK-START: java.lang.String TestCase.removeNewInstance(byte[]) register (after) +## CHECK-NOT: NewInstance +## CHECK-NOT: LoadClass -.method public static catchNewInstance()V - .registers 3 +## CHECK-START-DEBUGGABLE: java.lang.String TestCase.removeNewInstance(byte[]) register (after) +## CHECK: NewInstance + +.method public static removeNewInstance([B)Ljava/lang/String; + .registers 5 - const v0, 0x0 - :try_start new-instance v0, Ljava/lang/String; + const-string v1, "UTF8" + invoke-direct {v0, p0, v1}, Ljava/lang/String;-><init>([BLjava/lang/String;)V + return-object v0 - # Calling String.<init> on null byte array will throw NullPointerException - # with v0 = new-instance. - const v1, 0x0 - const-string v2, "UTF8" - invoke-direct {v0, v1, v2}, Ljava/lang/String;-><init>([BLjava/lang/String;)V - :try_end - .catchall {:try_start .. :try_end} :catch - return-void +.end method - # Catch exception and test v0. Do not throw if it is not null. - :catch - move-exception v1 - if-nez v0, :return - throw v1 +# Test that the compiler does not assume that the first argument of String.<init> +# is a NewInstance by inserting an irreducible loop between them (b/26676472). - :return - return-void +# We verify the type of the input instruction (Phi) in debuggable mode, because +# it is eliminated by later stages of SsaBuilder otherwise. + +## CHECK-START-DEBUGGABLE: java.lang.String TestCase.thisNotNewInstance1(byte[], boolean) register (after) +## CHECK-DAG: InvokeStaticOrDirect env:[[<<Phi:l\d+>>,{{.*]]}} +## CHECK-DAG: <<Phi>> Phi + +.method public static thisNotNewInstance1([BZ)Ljava/lang/String; + .registers 5 + + new-instance v0, Ljava/lang/String; + + # Irreducible loop + if-eqz p1, :loop_entry + :loop_header + const v1, 0x1 + xor-int p1, p1, v1 + :loop_entry + if-eqz p1, :string_init + goto :loop_header + + :string_init + const-string v1, "UTF8" + invoke-direct {v0, p0, v1}, Ljava/lang/String;-><init>([BLjava/lang/String;)V + return-object v0 + +.end method + +## CHECK-START-DEBUGGABLE: java.lang.String TestCase.thisNotNewInstance2(byte[], boolean) register (after) +## CHECK-DAG: InvokeStaticOrDirect env:[[<<Phi:l\d+>>,{{.*]]}} +## CHECK-DAG: <<Phi>> Phi + +.method public static thisNotNewInstance2([BZ)Ljava/lang/String; + .registers 5 + + new-instance v0, Ljava/lang/String; + + # Irreducible loop + if-eqz p1, :loop_entry + :loop_header + if-eqz p1, :string_init + :loop_entry + const v1, 0x1 + xor-int p1, p1, v1 + goto :loop_header + + :string_init + const-string v1, "UTF8" + invoke-direct {v0, p0, v1}, Ljava/lang/String;-><init>([BLjava/lang/String;)V + return-object v0 .end method diff --git a/test/563-checker-fakestring/src/Main.java b/test/563-checker-fakestring/src/Main.java index e0bfa7d5b1..1ac8a5bfcf 100644 --- a/test/563-checker-fakestring/src/Main.java +++ b/test/563-checker-fakestring/src/Main.java @@ -59,7 +59,24 @@ public class Main { } { - c.getMethod("catchNewInstance").invoke(null, (Object[]) null); + Method m = c.getMethod("removeNewInstance", byte[].class); + String result = (String) m.invoke(null, new Object[] { testData }); + assertEqual(testString, result); + } + + { + Method m = c.getMethod("thisNotNewInstance1", byte[].class, boolean.class); + String result = (String) m.invoke(null, new Object[] { testData, true }); + assertEqual(testString, result); + result = (String) m.invoke(null, new Object[] { testData, false }); + assertEqual(testString, result); + } + { + Method m = c.getMethod("thisNotNewInstance2", byte[].class, boolean.class); + String result = (String) m.invoke(null, new Object[] { testData, true }); + assertEqual(testString, result); + result = (String) m.invoke(null, new Object[] { testData, false }); + assertEqual(testString, result); } } } diff --git a/test/564-checker-inline-loop/expected.txt b/test/564-checker-inline-loop/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/564-checker-inline-loop/expected.txt diff --git a/test/564-checker-inline-loop/info.txt b/test/564-checker-inline-loop/info.txt new file mode 100644 index 0000000000..a590bc6f23 --- /dev/null +++ b/test/564-checker-inline-loop/info.txt @@ -0,0 +1 @@ +Tests inlining of loops in the optimizing compiler. diff --git a/test/564-checker-inline-loop/src/Main.java b/test/564-checker-inline-loop/src/Main.java new file mode 100644 index 0000000000..6929913864 --- /dev/null +++ b/test/564-checker-inline-loop/src/Main.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + + /// CHECK-START: int Main.inlineLoop() inliner (before) + /// CHECK-DAG: <<Invoke:i\d+>> InvokeStaticOrDirect + /// CHECK-DAG: Return [<<Invoke>>] + + /// CHECK-START: int Main.inlineLoop() inliner (after) + /// CHECK-NOT: InvokeStaticOrDirect + + /// CHECK-START: int Main.inlineLoop() inliner (after) + /// CHECK-DAG: <<Constant:i\d+>> IntConstant 42 + /// CHECK-DAG: Return [<<Constant>>] + + /// CHECK-START: int Main.inlineLoop() licm (after) + /// CHECK: Goto loop:{{B\d+}} + + public static int inlineLoop() { + return loopMethod(); + } + + /// CHECK-START: void Main.inlineWithinLoop() inliner (before) + /// CHECK: InvokeStaticOrDirect + + /// CHECK-START: void Main.inlineWithinLoop() inliner (after) + /// CHECK-NOT: InvokeStaticOrDirect + + /// CHECK-START: void Main.inlineWithinLoop() licm (after) + /// CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none + /// CHECK-DAG: Goto outer_loop:<<OuterLoop>> + + public static void inlineWithinLoop() { + while (doLoop) { + loopMethod(); + } + } + + public static int loopMethod() { + while (doLoop) {} + return 42; + } + + public static boolean doLoop = false; + + public static void main(String[] args) { + inlineLoop(); + inlineWithinLoop(); + } +} diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt index 27f5b5d552..2e66af59e7 100644 --- a/test/800-smali/expected.txt +++ b/test/800-smali/expected.txt @@ -49,4 +49,5 @@ b/23502994 (check-cast) b/25494456 b/21869691 b/26143249 +b/26579108 Done! diff --git a/test/800-smali/smali/b_26579108.smali b/test/800-smali/smali/b_26579108.smali new file mode 100644 index 0000000000..dde38256a1 --- /dev/null +++ b/test/800-smali/smali/b_26579108.smali @@ -0,0 +1,34 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LB26579108; +.super Ljava/lang/Object; + +# Ensure that merging uninitialized type and null does not pass verification. + +.field public static field:I + +.method public static run()Ljava/lang/String; + .registers 2 + new-instance v0, Ljava/lang/String; + + sget v1, LB26579108;->field:I + if-eqz v1, :cond_5 + + const/4 v0, 0x0 + :cond_5 + + invoke-direct {v0}, Ljava/lang/String;-><init>()V + return-object v0 + .end method diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java index cc3b0b44f9..38aa58de77 100644 --- a/test/800-smali/src/Main.java +++ b/test/800-smali/src/Main.java @@ -143,6 +143,8 @@ public class Main { new IncompatibleClassChangeError(), null)); testCases.add(new TestCase("b/26143249", "B26143249", "run", null, new AbstractMethodError(), null)); + testCases.add(new TestCase("b/26579108", "B26579108", "run", null, new VerifyError(), + null)); } public void runTests() { @@ -188,8 +190,7 @@ public class Main { if (tc.expectedException != null) { errorReturn = new IllegalStateException("Expected an exception in test " + tc.testName); - } - if (tc.expectedReturn == null && retValue != null) { + } else if (tc.expectedReturn == null && retValue != null) { errorReturn = new IllegalStateException("Expected a null result in test " + tc.testName); } else if (tc.expectedReturn != null && diff --git a/test/971-iface-super-partial-compile-generated/build b/test/971-iface-super/build index 1e9f8aadd5..1e9f8aadd5 100755 --- a/test/971-iface-super-partial-compile-generated/build +++ b/test/971-iface-super/build diff --git a/test/971-iface-super-partial-compile-generated/expected.txt b/test/971-iface-super/expected.txt index 1ddd65d177..1ddd65d177 100644 --- a/test/971-iface-super-partial-compile-generated/expected.txt +++ b/test/971-iface-super/expected.txt diff --git a/test/971-iface-super-partial-compile-generated/info.txt b/test/971-iface-super/info.txt index bc1c42816e..bc1c42816e 100644 --- a/test/971-iface-super-partial-compile-generated/info.txt +++ b/test/971-iface-super/info.txt diff --git a/test/971-iface-super-partial-compile-generated/run b/test/971-iface-super/run index 6d2930d463..6d2930d463 100755 --- a/test/971-iface-super-partial-compile-generated/run +++ b/test/971-iface-super/run diff --git a/test/971-iface-super-partial-compile-generated/util-src/generate_java.py b/test/971-iface-super/util-src/generate_java.py index 99b0479c8f..99b0479c8f 100755 --- a/test/971-iface-super-partial-compile-generated/util-src/generate_java.py +++ b/test/971-iface-super/util-src/generate_java.py diff --git a/test/971-iface-super-partial-compile-generated/util-src/generate_smali.py b/test/971-iface-super/util-src/generate_smali.py index f01c9043b9..f01c9043b9 100755 --- a/test/971-iface-super-partial-compile-generated/util-src/generate_smali.py +++ b/test/971-iface-super/util-src/generate_smali.py diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 7ec30677e1..586c80528e 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -242,7 +242,7 @@ TEST_ART_PYTHON3_DEPENDENCY_RUN_TESTS := \ 968-default-partial-compile-generated \ 969-iface-super \ 970-iface-super-resolution-generated \ - 971-iface-super-partial-compile-generated + 971-iface-super # Check if we have python3 to run our tests. ifeq ($(wildcard /usr/bin/python3),) @@ -270,16 +270,6 @@ endif TEST_ART_BROKEN_PREBUILD_RUN_TESTS := -# 143-string-value tests for a LOG(E) tag, which is only supported on host. -TEST_ART_BROKEN_TARGET_RUN_TESTS := \ - 143-string-value \ - -ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ - $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ - $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_TARGET_RUN_TESTS), $(ALL_ADDRESS_SIZES)) - -TEST_ART_BROKEN_TARGET_RUN_TESTS := - # 554-jit-profile-file is disabled because it needs a primary oat file to know what it should save. TEST_ART_BROKEN_NO_PREBUILD_TESTS := \ 117-nopatchoat \ @@ -546,10 +536,13 @@ TEST_ART_BROKEN_DEFAULT_READ_BARRIER_RUN_TESTS := \ # Tests that should fail in the read barrier configuration with the Optimizing compiler. # 484: Baker's fast path based read barrier compiler instrumentation generates code containing # more parallel moves on x86, thus some Checker assertions may fail. +# 527: On ARM64, the read barrier instrumentation does not support the HArm64IntermediateAddress +# instruction yet (b/26601270). # 537: Expects an array copy to be intrinsified on x86-64, but calling-on-slowpath intrinsics are # not yet handled in the read barrier configuration. TEST_ART_BROKEN_OPTIMIZING_READ_BARRIER_RUN_TESTS := \ 484-checker-register-hints \ + 527-checker-array-access-split \ 537-checker-arraycopy # Tests that should fail in the read barrier configuration with JIT. diff --git a/test/ProfileTestMultiDex/Main.java b/test/ProfileTestMultiDex/Main.java new file mode 100644 index 0000000000..41532ea8f7 --- /dev/null +++ b/test/ProfileTestMultiDex/Main.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Main { + public String getA() { + return "A"; + } + public String getB() { + return "B"; + } + public String getC() { + return "C"; + } +} diff --git a/test/ProfileTestMultiDex/Second.java b/test/ProfileTestMultiDex/Second.java new file mode 100644 index 0000000000..4ac5abc300 --- /dev/null +++ b/test/ProfileTestMultiDex/Second.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Second { + public String getX() { + return "X"; + } + public String getY() { + return "Y"; + } + public String getZ() { + return "Z"; + } +} diff --git a/test/ProfileTestMultiDex/main.jpp b/test/ProfileTestMultiDex/main.jpp new file mode 100644 index 0000000000..f2e3b4e14c --- /dev/null +++ b/test/ProfileTestMultiDex/main.jpp @@ -0,0 +1,3 @@ +main: + @@com.android.jack.annotations.ForceInMainDex + class Second diff --git a/test/ProfileTestMultiDex/main.list b/test/ProfileTestMultiDex/main.list new file mode 100644 index 0000000000..44ba78ead5 --- /dev/null +++ b/test/ProfileTestMultiDex/main.list @@ -0,0 +1 @@ +Main.class @@ -75,6 +75,7 @@ PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" ANDROID_ROOT=$PROG_DIR/.. LIBDIR=$(find_libdir) LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBDIR +DEBUG_OPTION="" DELETE_ANDROID_DATA=false # If ANDROID_DATA is the system ANDROID_DATA or is not set, use our own, @@ -87,6 +88,7 @@ fi if [ z"$PERF" != z ]; then invoke_with="perf record -o $ANDROID_DATA/perf.data -e cycles:u $invoke_with" + DEBUG_OPTION="-Xcompiler-option --generate-debug-info" fi # We use the PIC core image to work with perf. @@ -99,7 +101,7 @@ ANDROID_DATA=$ANDROID_DATA \ -XXlib:$LIBART \ -Xnorelocate \ -Ximage:$ANDROID_ROOT/framework/core-optimizing-pic.art \ - -Xcompiler-option --generate-debug-info \ + $DEBUG_OPTION \ "$@" EXIT_STATUS=$? diff --git a/tools/libcore_failures_concurrent_collector.txt b/tools/libcore_failures_concurrent_collector.txt index 2cb2c50257..6ea83d2777 100644 --- a/tools/libcore_failures_concurrent_collector.txt +++ b/tools/libcore_failures_concurrent_collector.txt @@ -27,7 +27,10 @@ description: "TimeoutException on host-{x86,x86-64}-concurrent-collector", result: EXEC_FAILED, modes: [host], - names: ["libcore.java.util.zip.GZIPOutputStreamTest#testSyncFlushEnabled", + names: ["libcore.java.util.zip.DeflaterOutputStreamTest#testSyncFlushDisabled", + "libcore.java.util.zip.GZIPOutputStreamTest#testSyncFlushEnabled", + "libcore.java.util.zip.OldAndroidGZIPStreamTest#testGZIPStream", + "libcore.java.util.zip.OldAndroidZipStreamTest#testZipStream", "libcore.java.util.zip.ZipFileTest#testZipFileWithLotsOfEntries", "libcore.java.util.zip.ZipInputStreamTest#testLongMessage"], bug: 26507762 |