diff options
59 files changed, 1275 insertions, 560 deletions
diff --git a/build/Android.bp b/build/Android.bp index 09d3a183f9..46fb0c5b97 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -7,6 +7,7 @@ bootstrap_go_package { "blueprint-proptools", "soong", "soong-android", + "soong-apex", "soong-cc", ], srcs: [ diff --git a/build/apex/Android.bp b/build/apex/Android.bp index f1a21e8428..88178a013e 100644 --- a/build/apex/Android.bp +++ b/build/apex/Android.bp @@ -49,10 +49,13 @@ art_runtime_debug_native_shared_libs = [ ] // Modules listed in LOCAL_REQUIRED_MODULES for module art-tools in art/Android.mk. -art_tools_binaries = [ +art_tools_common_binaries = [ "dexdiag", "dexdump", "dexlist", +] + +art_tools_device_binaries = [ "oatdump", ] @@ -66,6 +69,8 @@ art_tools_host_binaries = [ // ... ] +art_tools_binaries = art_tools_common_binaries + art_tools_device_binaries + apex_key { name: "com.android.runtime.key", public_key: "com.android.runtime.avbpubkey", @@ -135,3 +140,39 @@ apex { prebuilts: ["com.android.runtime.ld.config.txt"], key: "com.android.runtime.key", } + +// TODO: Do this better. art_apex will disable host builds when +// HOST_PREFER_32_BIT is set. We cannot simply use com.android.runtime.debug +// because binaries have different multilib classes and 'multilib: {}' isn't +// supported by target: { ... }. +// See b/120617876 for more information. +art_apex { + name: "com.android.runtime.host", + compile_multilib: "both", + payload_type: "zip", + host_supported: true, + device_supported: false, + manifest: "manifest.json", + native_shared_libs: art_runtime_base_native_shared_libs + + art_runtime_fake_native_shared_libs + + art_runtime_debug_native_shared_libs, + multilib: { + both: { + // TODO: Add logic to create a `dalvikvm` symlink to `dalvikvm32` or `dalvikvm64` + // (see `symlink_preferred_arch` in art/dalvikvm/Android.bp). + binaries: art_runtime_base_binaries_both, + }, + first: { + // TODO: oatdump cannot link with host linux_bionic due to not using clang ld + binaries: art_tools_common_binaries + + art_runtime_base_binaries_prefer32 + + art_runtime_debug_binaries_prefer32, + } + }, + key: "com.android.runtime.key", + target: { + darwin: { + enabled: false, + }, + }, +} diff --git a/build/apex/runtests.sh b/build/apex/runtests.sh index 86cd8cb758..c19c7bd94c 100755 --- a/build/apex/runtests.sh +++ b/build/apex/runtests.sh @@ -69,29 +69,129 @@ done work_dir=$(mktemp -d) mount_point="$work_dir/image" +function check_binary { + [[ -x "$mount_point/bin/$1" ]] || die "Cannot find binary '$1' in mounted image" +} + +function check_multilib_binary { + # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve + # the precision of this test? + [[ -x "$mount_point/bin/${1}32" ]] || [[ -x "$mount_point/bin/${1}64" ]] \ + || die "Cannot find binary '$1' in mounted image" +} + +function check_binary_symlink { + [[ -h "$mount_point/bin/$1" ]] || die "Cannot find symbolic link '$1' in mounted image" +} + +function check_library { + # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve + # the precision of this test? + [[ -f "$mount_point/lib/$1" ]] || [[ -f "$mount_point/lib64/$1" ]] \ + || die "Cannot find library '$1' in mounted image" +} + +function build_apex { + if $build_apex_p; then + say "Building package $1" && make "$1" || die "Cannot build $1" + fi +} + +function check_contents { + + # Check that the mounted image contains a manifest. + [[ -f "$mount_point/apex_manifest.json" ]] || die "no manifest" + + # Check that the mounted image contains ART base binaries. + check_multilib_binary dalvikvm + # TODO: Does not work yet. + : check_binary_symlink dalvikvm + check_binary dex2oat + check_binary dexoptanalyzer + check_binary profman + + # Check that the mounted image contains ART tools binaries. + check_binary dexdiag + check_binary dexdump + check_binary dexlist + # oatdump is only in device apex's due to build rules + # check_binary oatdump + + # Check that the mounted image contains ART debug binaries. + check_binary dex2oatd + check_binary dexoptanalyzerd + check_binary profmand + + # Check that the mounted image contains ART libraries. + check_library libart-compiler.so + check_library libart.so + check_library libopenjdkjvm.so + check_library libopenjdkjvmti.so + check_library libadbconnection.so + # TODO: Should we check for these libraries too, even if they are not explicitly + # listed as dependencies in the Android Runtime APEX module rule? + check_library libartbase.so + check_library libart-dexlayout.so + check_library libdexfile.so + check_library libprofile.so + + # Check that the mounted image contains ART debug libraries. + check_library libartd-compiler.so + check_library libartd.so + check_library libopenjdkd.so + check_library libopenjdkjvmd.so + check_library libopenjdkjvmtid.so + check_library libadbconnectiond.so + # TODO: Should we check for these libraries too, even if they are not explicitly + # listed as dependencies in the Android Runtime APEX module rule? + check_library libdexfiled.so + check_library libartbased.so + check_library libartd-dexlayout.so + check_library libprofiled.so + + # TODO: Should we check for other libraries, such as: + # + # libbacktrace.so + # libbase.so + # liblog.so + # libsigchain.so + # libtombstoned_client.so + # libunwindstack.so + # libvixl.so + # libvixld.so + # ... + # + # ? +} + + +# ***************************************** +# * Testing for com.android.runtime.debug * +# ***************************************** + # Garbage collection. -function finish { +function finish_device_debug { # Don't fail early during cleanup. set +e guestunmount "$mount_point" rm -rf "$work_dir" } -trap finish EXIT +trap finish_device_debug EXIT # TODO: Also exercise the Release Runtime APEX (`com.android.runtime.release`). apex_module="com.android.runtime.debug" # Build the Android Runtime APEX package (optional). -$build_apex_p && say "Building package" && make "$apex_module" +build_apex $apex_module system_apexdir="$ANDROID_PRODUCT_OUT/system/apex" apex_package="$system_apexdir/$apex_module.apex" say "Extracting and mounting image" -# Extract the image from the Android Runtime APEX. -image_filename="image.img" +# Extract the payload from the Android Runtime APEX. +image_filename="apex_payload.img" unzip -q "$apex_package" "$image_filename" -d "$work_dir" mkdir "$mount_point" image_file="$work_dir/$image_filename" @@ -111,90 +211,58 @@ $list_image_files_p && say "Listing image files" && ls -ld "$mount_point" && tre say "Running tests" -# Check that the mounted image contains a manifest. -[[ -f "$mount_point/manifest.json" ]] +check_contents -function check_binary { - [[ -x "$mount_point/bin/$1" ]] || die "Cannot find binary '$1' in mounted image" -} +# Check for files pulled in from device-only oatdump. +check_binary oatdump +check_library libart-disassembler.so -function check_multilib_binary { - # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve - # the precision of this test? - [[ -x "$mount_point/bin/${1}32" ]] || [[ -x "$mount_point/bin/${1}64" ]] \ - || die "Cannot find binary '$1' in mounted image" -} +# Cleanup +trap - EXIT +guestunmount "$mount_point" +rm -rf "$work_dir" -function check_binary_symlink { - [[ -h "$mount_point/bin/$1" ]] || die "Cannot find symbolic link '$1' in mounted image" -} +say "$apex_module Tests passed" -function check_library { - # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve - # the precision of this test? - [[ -f "$mount_point/lib/$1" ]] || [[ -f "$mount_point/lib64/$1" ]] \ - || die "Cannot find library '$1' in mounted image" +# **************************************** +# * Testing for com.android.runtime.host * +# **************************************** + +# Garbage collection. +function finish_host { + # Don't fail early during cleanup. + set +e + rm -rf "$work_dir" } -# Check that the mounted image contains ART base binaries. -check_multilib_binary dalvikvm -# TODO: Does not work yet. -: check_binary_symlink dalvikvm -check_binary dex2oat -check_binary dexoptanalyzer -check_binary profman - -# Check that the mounted image contains ART tools binaries. -check_binary dexdiag -check_binary dexdump -check_binary dexlist -check_binary oatdump +work_dir=$(mktemp -d) +mount_point="$work_dir/zip" -# Check that the mounted image contains ART debug binaries. -check_binary dex2oatd -check_binary dexoptanalyzerd -check_binary profmand - -# Check that the mounted image contains ART libraries. -check_library libart-compiler.so -check_library libart.so -check_library libopenjdkjvm.so -check_library libopenjdkjvmti.so -check_library libadbconnection.so -# TODO: Should we check for these libraries too, even if they are not explicitly -# listed as dependencies in the Android Runtime APEX module rule? -check_library libartbase.so -check_library libart-dexlayout.so -check_library libart-disassembler.so -check_library libdexfile.so -check_library libprofile.so - -# Check that the mounted image contains ART debug libraries. -check_library libartd-compiler.so -check_library libartd.so -check_library libdexfiled.so -check_library libopenjdkd.so -check_library libopenjdkjvmd.so -check_library libopenjdkjvmtid.so -check_library libadbconnectiond.so -# TODO: Should we check for these libraries too, even if they are not explicitly -# listed as dependencies in the Android Runtime APEX module rule? -check_library libartbased.so -check_library libartd-dexlayout.so -check_library libprofiled.so - -# TODO: Should we check for other libraries, such as: -# -# libbacktrace.so -# libbase.so -# liblog.so -# libsigchain.so -# libtombstoned_client.so -# libunwindstack.so -# libvixl.so -# libvixld.so -# ... -# -# ? +trap finish_host EXIT + +apex_module="com.android.runtime.host" + +# Build the Android Runtime APEX package (optional). +build_apex $apex_module + +system_apexdir="$ANDROID_HOST_OUT/apex" +apex_package="$system_apexdir/$apex_module.zipapex" + +say "Extracting payload" + +# Extract the payload from the Android Runtime APEX. +image_filename="apex_payload.zip" +unzip -q "$apex_package" "$image_filename" -d "$work_dir" +mkdir "$mount_point" +image_file="$work_dir/$image_filename" + +# Unzipping the payload +unzip -q "$image_file" -d "$mount_point" + +say "Running tests" + +check_contents + +say "$apex_module Tests passed" say "Tests passed" diff --git a/build/art.go b/build/art.go index 1c8be0f056..01848c88a7 100644 --- a/build/art.go +++ b/build/art.go @@ -16,8 +16,10 @@ package art import ( "android/soong/android" + "android/soong/apex" "android/soong/cc" "fmt" + "log" "sync" "github.com/google/blueprint/proptools" @@ -289,6 +291,36 @@ func init() { android.RegisterModuleType("libart_static_cc_defaults", libartStaticDefaultsFactory) android.RegisterModuleType("art_global_defaults", artGlobalDefaultsFactory) android.RegisterModuleType("art_debug_defaults", artDebugDefaultsFactory) + + // TODO: This makes the module disable itself for host if HOST_PREFER_32_BIT is + // set. We need this because the multilib types of binaries listed in the apex + // rule must match the declared type. This is normally not difficult but HOST_PREFER_32_BIT + // changes this to 'prefer32' on all host binaries. Since HOST_PREFER_32_BIT is + // only used for testing we can just disable the module. + // See b/120617876 for more information. + android.RegisterModuleType("art_apex", artApexBundleFactory) +} + +func artApexBundleFactory() android.Module { + module := apex.ApexBundleFactory() + android.AddLoadHook(module, func(ctx android.LoadHookContext) { + if envTrue(ctx, "HOST_PREFER_32_BIT") { + type props struct { + Target struct { + Host struct { + Enabled *bool + } + } + } + + p := &props{} + p.Target.Host.Enabled = proptools.BoolPtr(false) + ctx.AppendProperties(p) + log.Print("Disabling host build of " + ctx.ModuleName() + " for HOST_PREFER_32_BIT=true") + } + }) + + return module } func artGlobalDefaultsFactory() android.Module { diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index 66421e2562..be6da714eb 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -22,6 +22,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "base/callee_save_type.h" +#include "base/casts.h" #include "base/enums.h" #include "base/utils.h" #include "class_linker.h" @@ -152,6 +153,10 @@ void CommonCompilerTest::SetUp() { CreateCompilerDriver(); } + // Note: We cannot use MemMap because some tests tear down the Runtime and destroy + // the gMaps, so when destroying the MemMap, the test would crash. + inaccessible_page_ = mmap(nullptr, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + CHECK(inaccessible_page_ != MAP_FAILED) << strerror(errno); } void CommonCompilerTest::ApplyInstructionSet() { @@ -190,9 +195,7 @@ void CommonCompilerTest::CreateCompilerDriver() { compiler_options_->image_classes_.swap(*GetImageClasses()); compiler_options_->profile_compilation_info_ = GetProfileCompilationInfo(); compiler_driver_.reset(new CompilerDriver(compiler_options_.get(), - verification_results_.get(), compiler_kind_, - &compiler_options_->image_classes_, number_of_threads_, /* swap_fd */ -1)); } @@ -222,6 +225,10 @@ void CommonCompilerTest::TearDown() { verification_results_.reset(); compiler_options_.reset(); image_reservation_.Reset(); + if (inaccessible_page_ != nullptr) { + munmap(inaccessible_page_, kPageSize); + inaccessible_page_ = nullptr; + } CommonRuntimeTest::TearDown(); } @@ -267,8 +274,16 @@ void CommonCompilerTest::CompileMethod(ArtMethod* method) { compiler_driver_->InitializeThreadPools(); - compiler_driver_->PreCompile(class_loader, dex_files, &timings); + compiler_driver_->PreCompile(class_loader, + dex_files, + &timings, + &compiler_options_->image_classes_, + verification_results_.get()); + // Verification results in the `callback_` should not be used during compilation. + down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults( + reinterpret_cast<VerificationResults*>(inaccessible_page_)); + compiler_options_->verification_results_ = verification_results_.get(); compiler_driver_->CompileOne(self, class_loader, *dex_file, @@ -279,6 +294,9 @@ void CommonCompilerTest::CompileMethod(ArtMethod* method) { code_item, dex_cache, h_class_loader); + compiler_options_->verification_results_ = nullptr; + down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults( + verification_results_.get()); compiler_driver_->FreeThreadPools(); @@ -334,6 +352,32 @@ void CommonCompilerTest::ReserveImageSpace() { CHECK(image_reservation_.IsValid()) << error_msg; } +void CommonCompilerTest::CompileAll(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) { + TimingLogger::ScopedTiming t(__FUNCTION__, timings); + SetDexFilesForOatFile(dex_files); + + compiler_driver_->InitializeThreadPools(); + + compiler_driver_->PreCompile(class_loader, + dex_files, + timings, + &compiler_options_->image_classes_, + verification_results_.get()); + + // Verification results in the `callback_` should not be used during compilation. + down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults( + reinterpret_cast<VerificationResults*>(inaccessible_page_)); + compiler_options_->verification_results_ = verification_results_.get(); + compiler_driver_->CompileAll(class_loader, dex_files, timings); + compiler_options_->verification_results_ = nullptr; + down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults( + verification_results_.get()); + + compiler_driver_->FreeThreadPools(); +} + void CommonCompilerTest::UnreserveImageSpace() { image_reservation_.Reset(); } diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index e6d1564621..a71908e6c8 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -20,6 +20,8 @@ #include <list> #include <vector> +#include <jni.h> + #include "arch/instruction_set.h" #include "arch/instruction_set_features.h" #include "base/hash_set.h" @@ -37,6 +39,7 @@ class CompilerOptions; class CumulativeLogger; class DexFile; class ProfileCompilationInfo; +class TimingLogger; class VerificationResults; template<class T> class Handle; @@ -88,6 +91,10 @@ class CommonCompilerTest : public CommonRuntimeTest { const char* method_name, const char* signature) REQUIRES_SHARED(Locks::mutator_lock_); + void CompileAll(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); + void ApplyInstructionSet(); void OverrideInstructionSetFeatures(InstructionSet instruction_set, const std::string& variant); @@ -116,6 +123,7 @@ class CommonCompilerTest : public CommonRuntimeTest { private: MemMap image_reservation_; + void* inaccessible_page_; // Chunks must not move their storage after being created - use the node-based std::list. std::list<std::vector<uint8_t>> header_code_and_maps_chunks_; diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc index baf8643e9b..1ecb1d8ed9 100644 --- a/compiler/debug/elf_debug_writer.cc +++ b/compiler/debug/elf_debug_writer.cc @@ -138,7 +138,7 @@ static std::vector<uint8_t> MakeMiniDebugInfoInternal( CHECK(builder->Good()); std::vector<uint8_t> compressed_buffer; compressed_buffer.reserve(buffer.size() / 4); - XzCompress(ArrayRef<uint8_t>(buffer), &compressed_buffer); + XzCompress(ArrayRef<const uint8_t>(buffer), &compressed_buffer); return compressed_buffer; } diff --git a/compiler/debug/xz_utils.cc b/compiler/debug/xz_utils.cc index a9e30a6970..a8f60ac280 100644 --- a/compiler/debug/xz_utils.cc +++ b/compiler/debug/xz_utils.cc @@ -17,13 +17,16 @@ #include "xz_utils.h" #include <vector> +#include <mutex> #include "base/array_ref.h" -#include "dwarf/writer.h" +#include "base/bit_utils.h" #include "base/leb128.h" +#include "dwarf/writer.h" // liblzma. #include "7zCrc.h" +#include "Xz.h" #include "XzCrc64.h" #include "XzEnc.h" @@ -32,10 +35,17 @@ namespace debug { constexpr size_t kChunkSize = kPageSize; -static void XzCompressChunk(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst) { +static void XzInitCrc() { + static std::once_flag crc_initialized; + std::call_once(crc_initialized, []() { + CrcGenerateTable(); + Crc64GenerateTable(); + }); +} + +static void XzCompressChunk(ArrayRef<const uint8_t> src, std::vector<uint8_t>* dst) { // Configure the compression library. - CrcGenerateTable(); - Crc64GenerateTable(); + XzInitCrc(); CLzma2EncProps lzma2Props; Lzma2EncProps_Init(&lzma2Props); lzma2Props.lzmaProps.level = 1; // Fast compression. @@ -62,7 +72,7 @@ static void XzCompressChunk(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst) { return SZ_OK; } size_t src_pos_; - ArrayRef<uint8_t> src_; + ArrayRef<const uint8_t> src_; std::vector<uint8_t>* dst_; }; XzCallbacks callbacks; @@ -85,7 +95,7 @@ static void XzCompressChunk(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst) { // In short, the file format is: [header] [compressed_block]* [index] [footer] // Where [index] is: [num_records] ([compressed_size] [uncompressed_size])* [crc32] // -void XzCompress(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst) { +void XzCompress(ArrayRef<const uint8_t> src, std::vector<uint8_t>* dst) { uint8_t header[] = { 0xFD, '7', 'z', 'X', 'Z', 0, 0, 1, 0x69, 0x22, 0xDE, 0x36 }; uint8_t footer[] = { 0, 1, 'Y', 'Z' }; dst->insert(dst->end(), header, header + sizeof(header)); @@ -138,6 +148,47 @@ void XzCompress(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst) { writer.UpdateUint32(0, CrcCalc(tmp.data() + 4, 6)); dst->insert(dst->end(), tmp.begin(), tmp.end()); } + + // Decompress the data back and check that we get the original. + if (kIsDebugBuild) { + std::vector<uint8_t> decompressed; + XzDecompress(ArrayRef<const uint8_t>(*dst), &decompressed); + DCHECK_EQ(decompressed.size(), src.size()); + DCHECK_EQ(memcmp(decompressed.data(), src.data(), src.size()), 0); + } +} + +void XzDecompress(ArrayRef<const uint8_t> src, std::vector<uint8_t>* dst) { + XzInitCrc(); + std::unique_ptr<CXzUnpacker> state(new CXzUnpacker()); + ISzAlloc alloc; + alloc.Alloc = [](ISzAllocPtr, size_t size) { return malloc(size); }; + alloc.Free = [](ISzAllocPtr, void* ptr) { return free(ptr); }; + XzUnpacker_Construct(state.get(), &alloc); + + size_t src_offset = 0; + size_t dst_offset = 0; + ECoderStatus status; + do { + dst->resize(RoundUp(dst_offset + kPageSize / 4, kPageSize)); + size_t src_remaining = src.size() - src_offset; + size_t dst_remaining = dst->size() - dst_offset; + int return_val = XzUnpacker_Code(state.get(), + dst->data() + dst_offset, + &dst_remaining, + src.data() + src_offset, + &src_remaining, + true, + CODER_FINISH_ANY, + &status); + CHECK_EQ(return_val, SZ_OK); + src_offset += src_remaining; + dst_offset += dst_remaining; + } while (status == CODER_STATUS_NOT_FINISHED); + CHECK_EQ(src_offset, src.size()); + CHECK(XzUnpacker_IsStreamWasFinished(state.get())); + XzUnpacker_Free(state.get()); + dst->resize(dst_offset); } } // namespace debug diff --git a/compiler/debug/xz_utils.h b/compiler/debug/xz_utils.h index c4076c6581..731b03c7e1 100644 --- a/compiler/debug/xz_utils.h +++ b/compiler/debug/xz_utils.h @@ -24,7 +24,8 @@ namespace art { namespace debug { -void XzCompress(ArrayRef<uint8_t> src, std::vector<uint8_t>* dst); +void XzCompress(ArrayRef<const uint8_t> src, std::vector<uint8_t>* dst); +void XzDecompress(ArrayRef<const uint8_t> src, std::vector<uint8_t>* dst); } // namespace debug } // namespace art diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc index 04ad10c41e..c124ef5dde 100644 --- a/compiler/dex/dex_to_dex_compiler.cc +++ b/compiler/dex/dex_to_dex_compiler.cc @@ -528,7 +528,7 @@ CompiledMethod* DexToDexCompiler::CompileMethod( class_def_idx, method_idx, access_flags, - driver_->GetVerifiedMethod(&dex_file, method_idx), + driver_->GetCompilerOptions().GetVerifiedMethod(&dex_file, method_idx), hs.NewHandle(class_linker->FindDexCache(soa.Self(), dex_file))); std::vector<uint8_t> quicken_data; diff --git a/compiler/dex/dex_to_dex_decompiler_test.cc b/compiler/dex/dex_to_dex_decompiler_test.cc index f61e6c4848..b055416c1e 100644 --- a/compiler/dex/dex_to_dex_decompiler_test.cc +++ b/compiler/dex/dex_to_dex_decompiler_test.cc @@ -39,16 +39,15 @@ namespace art { class DexToDexDecompilerTest : public CommonCompilerTest { public: void CompileAll(jobject class_loader) REQUIRES(!Locks::mutator_lock_) { - TimingLogger timings("CompilerDriverTest::CompileAll", false, false); - TimingLogger::ScopedTiming t(__FUNCTION__, &timings); + TimingLogger timings("DexToDexDecompilerTest::CompileAll", false, false); compiler_options_->image_type_ = CompilerOptions::ImageType::kNone; compiler_options_->SetCompilerFilter(CompilerFilter::kQuicken); // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate // the results for all the dex files, not just the results for the current dex file. down_cast<QuickCompilerCallbacks*>(Runtime::Current()->GetCompilerCallbacks())->SetVerifierDeps( new verifier::VerifierDeps(GetDexFiles(class_loader))); - SetDexFilesForOatFile(GetDexFiles(class_loader)); - compiler_driver_->CompileAll(class_loader, GetDexFiles(class_loader), &timings); + std::vector<const DexFile*> dex_files = GetDexFiles(class_loader); + CommonCompilerTest::CompileAll(class_loader, dex_files, &timings); } void RunTest(const char* dex_name) { diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc index dd947d90b7..5a34efb73c 100644 --- a/compiler/dex/verification_results.cc +++ b/compiler/dex/verification_results.cc @@ -97,7 +97,7 @@ void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method } } -const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref) { +const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref) const { const VerifiedMethod* ret = nullptr; if (atomic_verified_methods_.Get(ref, &ret)) { return ret; @@ -129,13 +129,13 @@ void VerificationResults::AddRejectedClass(ClassReference ref) { DCHECK(IsClassRejected(ref)); } -bool VerificationResults::IsClassRejected(ClassReference ref) { +bool VerificationResults::IsClassRejected(ClassReference ref) const { ReaderMutexLock mu(Thread::Current(), rejected_classes_lock_); return (rejected_classes_.find(ref) != rejected_classes_.end()); } bool VerificationResults::IsCandidateForCompilation(MethodReference&, - const uint32_t access_flags) { + const uint32_t access_flags) const { if (!compiler_options_->IsAotCompilationEnabled()) { return false; } diff --git a/compiler/dex/verification_results.h b/compiler/dex/verification_results.h index 56f00309c0..04c4fa65e6 100644 --- a/compiler/dex/verification_results.h +++ b/compiler/dex/verification_results.h @@ -51,13 +51,13 @@ class VerificationResults { void CreateVerifiedMethodFor(MethodReference ref) REQUIRES(!verified_methods_lock_); - const VerifiedMethod* GetVerifiedMethod(MethodReference ref) + const VerifiedMethod* GetVerifiedMethod(MethodReference ref) const REQUIRES(!verified_methods_lock_); void AddRejectedClass(ClassReference ref) REQUIRES(!rejected_classes_lock_); - bool IsClassRejected(ClassReference ref) REQUIRES(!rejected_classes_lock_); + bool IsClassRejected(ClassReference ref) const REQUIRES(!rejected_classes_lock_); - bool IsCandidateForCompilation(MethodReference& method_ref, const uint32_t access_flags); + bool IsCandidateForCompilation(MethodReference& method_ref, const uint32_t access_flags) const; // Add a dex file to enable using the atomic map. void AddDexFile(const DexFile* dex_file) REQUIRES(!verified_methods_lock_); @@ -74,10 +74,12 @@ class VerificationResults { // GetVerifiedMethod. AtomicMap atomic_verified_methods_; - ReaderWriterMutex verified_methods_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation. + mutable ReaderWriterMutex verified_methods_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; // Rejected classes. - ReaderWriterMutex rejected_classes_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation. + mutable ReaderWriterMutex rejected_classes_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; std::set<ClassReference> rejected_classes_ GUARDED_BY(rejected_classes_lock_); friend class verifier::VerifierDepsTest; diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 792f508199..63dcb4664c 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -99,11 +99,6 @@ inline std::pair<bool, bool> CompilerDriver::IsFastInstanceField( return std::make_pair(fast_get, fast_put); } -inline VerificationResults* CompilerDriver::GetVerificationResults() const { - DCHECK(Runtime::Current()->IsAotCompiler()); - return verification_results_; -} - } // namespace art #endif // ART_COMPILER_DRIVER_COMPILER_DRIVER_INL_H_ diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 67cabef641..18f7105769 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -245,16 +245,12 @@ class CompilerDriver::AOTCompilationStats { CompilerDriver::CompilerDriver( const CompilerOptions* compiler_options, - VerificationResults* verification_results, Compiler::Kind compiler_kind, - HashSet<std::string>* image_classes, size_t thread_count, int swap_fd) : compiler_options_(compiler_options), - verification_results_(verification_results), compiler_(Compiler::Create(this, compiler_kind)), compiler_kind_(compiler_kind), - image_classes_(std::move(image_classes)), number_of_soft_verifier_failures_(0), had_hard_verifier_failure_(false), parallel_thread_count_(thread_count), @@ -266,10 +262,6 @@ CompilerDriver::CompilerDriver( compiler_->Init(); - if (GetCompilerOptions().IsBootImage()) { - CHECK(image_classes_ != nullptr) << "Expected image classes for boot image"; - } - compiled_method_storage_.SetDedupeEnabled(compiler_options_->DeduplicateCode()); } @@ -325,9 +317,8 @@ void CompilerDriver::CompileAll(jobject class_loader, TimingLogger* timings) { DCHECK(!Runtime::Current()->IsStarted()); - InitializeThreadPools(); + CheckThreadPools(); - PreCompile(class_loader, dex_files, timings); if (GetCompilerOptions().IsBootImage()) { // We don't need to setup the intrinsics for non boot image compilation, as // those compilations will pick up a boot image that have the ArtMethod already @@ -343,8 +334,6 @@ void CompilerDriver::CompileAll(jobject class_loader, if (GetCompilerOptions().GetDumpStats()) { stats_->Dump(); } - - FreeThreadPools(); } static optimizer::DexToDexCompiler::CompilationLevel GetDexToDexCompilationLevel( @@ -496,7 +485,7 @@ static void CompileMethodDex2Dex( optimizer::DexToDexCompiler* const compiler = &driver->GetDexToDexCompiler(); if (compiler->ShouldCompileMethod(method_ref)) { - VerificationResults* results = driver->GetVerificationResults(); + const VerificationResults* results = driver->GetCompilerOptions().GetVerificationResults(); DCHECK(results != nullptr); const VerifiedMethod* verified_method = results->GetVerifiedMethod(method_ref); // Do not optimize if a VerifiedMethod is missing. SafeCast elision, @@ -574,7 +563,7 @@ static void CompileMethodQuick( } else if ((access_flags & kAccAbstract) != 0) { // Abstract methods don't have code. } else { - VerificationResults* results = driver->GetVerificationResults(); + const VerificationResults* results = driver->GetCompilerOptions().GetVerificationResults(); DCHECK(results != nullptr); const VerifiedMethod* verified_method = results->GetVerifiedMethod(method_ref); bool compile = @@ -881,7 +870,9 @@ static void EnsureVerifiedOrVerifyAtRuntime(jobject jclass_loader, void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings) { + TimingLogger* timings, + /*inout*/ HashSet<std::string>* image_classes, + /*out*/ VerificationResults* verification_results) { CheckThreadPools(); VLOG(compiler) << "Before precompile " << GetMemoryUsageString(false); @@ -898,7 +889,7 @@ void CompilerDriver::PreCompile(jobject class_loader, // 6) Update the set of image classes. // 7) For deterministic boot image, initialize bitstrings for type checking. - LoadImageClasses(timings); + LoadImageClasses(timings, image_classes); VLOG(compiler) << "LoadImageClasses: " << GetMemoryUsageString(false); if (compiler_options_->IsAnyCompilationEnabled()) { @@ -932,7 +923,7 @@ void CompilerDriver::PreCompile(jobject class_loader, ResolveConstStrings(dex_files, /*only_startup_strings=*/ true, timings); } - Verify(class_loader, dex_files, timings); + Verify(class_loader, dex_files, timings, verification_results); VLOG(compiler) << "Verify: " << GetMemoryUsageString(false); if (had_hard_verifier_failure_ && GetCompilerOptions().AbortOnHardVerifierFailure()) { @@ -957,7 +948,7 @@ void CompilerDriver::PreCompile(jobject class_loader, VLOG(compiler) << "InitializeClasses: " << GetMemoryUsageString(false); } - UpdateImageClasses(timings); + UpdateImageClasses(timings, image_classes); VLOG(compiler) << "UpdateImageClasses: " << GetMemoryUsageString(false); if (kBitstringSubtypeCheckEnabled && @@ -1071,7 +1062,8 @@ class RecordImageClassesVisitor : public ClassVisitor { }; // Make a list of descriptors for classes to include in the image -void CompilerDriver::LoadImageClasses(TimingLogger* timings) { +void CompilerDriver::LoadImageClasses(TimingLogger* timings, + /*inout*/ HashSet<std::string>* image_classes) { CHECK(timings != nullptr); if (!GetCompilerOptions().IsBootImage()) { return; @@ -1082,15 +1074,15 @@ void CompilerDriver::LoadImageClasses(TimingLogger* timings) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - CHECK(image_classes_ != nullptr); - for (auto it = image_classes_->begin(), end = image_classes_->end(); it != end;) { + CHECK(image_classes != nullptr); + for (auto it = image_classes->begin(), end = image_classes->end(); it != end;) { const std::string& descriptor(*it); StackHandleScope<1> hs(self); Handle<mirror::Class> klass( hs.NewHandle(class_linker->FindSystemClass(self, descriptor.c_str()))); if (klass == nullptr) { VLOG(compiler) << "Failed to find class " << descriptor; - it = image_classes_->erase(it); + it = image_classes->erase(it); self->ClearException(); } else { ++it; @@ -1140,10 +1132,10 @@ void CompilerDriver::LoadImageClasses(TimingLogger* timings) { // We walk the roots looking for classes so that we'll pick up the // above classes plus any classes them depend on such super // classes, interfaces, and the required ClassLinker roots. - RecordImageClassesVisitor visitor(image_classes_); + RecordImageClassesVisitor visitor(image_classes); class_linker->VisitClasses(&visitor); - CHECK(!image_classes_->empty()); + CHECK(!image_classes->empty()); } static void MaybeAddToImageClasses(Thread* self, @@ -1312,7 +1304,8 @@ class ClinitImageUpdate { DISALLOW_COPY_AND_ASSIGN(ClinitImageUpdate); }; -void CompilerDriver::UpdateImageClasses(TimingLogger* timings) { +void CompilerDriver::UpdateImageClasses(TimingLogger* timings, + /*inout*/ HashSet<std::string>* image_classes) { if (GetCompilerOptions().IsBootImage()) { TimingLogger::ScopedTiming t("UpdateImageClasses", timings); @@ -1324,7 +1317,7 @@ void CompilerDriver::UpdateImageClasses(TimingLogger* timings) { VariableSizedHandleScope hs(Thread::Current()); std::string error_msg; std::unique_ptr<ClinitImageUpdate> update(ClinitImageUpdate::Create(hs, - image_classes_, + image_classes, Thread::Current(), runtime->GetClassLinker())); @@ -1393,12 +1386,6 @@ bool CompilerDriver::ComputeInstanceFieldInfo(uint32_t field_idx, const DexCompi } } -const VerifiedMethod* CompilerDriver::GetVerifiedMethod(const DexFile* dex_file, - uint32_t method_idx) const { - MethodReference ref(dex_file, method_idx); - return verification_results_->GetVerifiedMethod(ref); -} - bool CompilerDriver::IsSafeCast(const DexCompilationUnit* mUnit, uint32_t dex_pc) { if (!compiler_options_->IsVerificationEnabled()) { // If we didn't verify, every cast has to be treated as non-safe. @@ -1764,7 +1751,8 @@ static void LoadAndUpdateStatus(const ClassAccessor& accessor, bool CompilerDriver::FastVerify(jobject jclass_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings) { + TimingLogger* timings, + /*out*/ VerificationResults* verification_results) { verifier::VerifierDeps* verifier_deps = Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps(); // If there exist VerifierDeps that aren't the ones we just created to output, use them to verify. @@ -1812,7 +1800,7 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, // - Quickening will not do checkcast ellision. // TODO(ngeoffray): Reconsider this once we refactor compiler filters. for (const ClassAccessor::Method& method : accessor.GetMethods()) { - verification_results_->CreateVerifiedMethodFor(method.GetReference()); + verification_results->CreateVerifiedMethodFor(method.GetReference()); } } } else if (!compiler_only_verifies) { @@ -1830,8 +1818,9 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, void CompilerDriver::Verify(jobject jclass_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings) { - if (FastVerify(jclass_loader, dex_files, timings)) { + TimingLogger* timings, + /*out*/ VerificationResults* verification_results) { + if (FastVerify(jclass_loader, dex_files, timings, verification_results)) { return; } @@ -2530,7 +2519,7 @@ void CompilerDriver::InitializeClasses(jobject class_loader, // SetVerificationAttempted so that the access flags are set. If we do not do this they get // changed at runtime resulting in more dirty image pages. // Also create conflict tables. - // Only useful if we are compiling an image (image_classes_ is not null). + // Only useful if we are compiling an image. ScopedObjectAccess soa(Thread::Current()); VariableSizedHandleScope hs(soa.Self()); InitializeArrayClassesAndCreateConflictTablesVisitor visitor(hs); @@ -2569,8 +2558,9 @@ static void CompileDexFile(CompilerDriver* driver, ClassReference ref(&dex_file, class_def_index); const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index); ClassAccessor accessor(dex_file, class_def_index); + CompilerDriver* const driver = context.GetCompiler(); // Skip compiling classes with generic verifier failures since they will still fail at runtime - if (context.GetCompiler()->GetVerificationResults()->IsClassRejected(ref)) { + if (driver->GetCompilerOptions().GetVerificationResults()->IsClassRejected(ref)) { return; } // Use a scoped object access to perform to the quick SkipClass check. @@ -2602,8 +2592,6 @@ static void CompileDexFile(CompilerDriver* driver, // Go to native so that we don't block GC during compilation. ScopedThreadSuspension sts(soa.Self(), kNative); - CompilerDriver* const driver = context.GetCompiler(); - // Can we run DEX-to-DEX compiler on this class ? optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level = GetDexToDexCompilationLevel(soa.Self(), *driver, jclass_loader, dex_file, class_def); @@ -2775,31 +2763,6 @@ CompiledMethod* CompilerDriver::GetCompiledMethod(MethodReference ref) const { return compiled_method; } -bool CompilerDriver::IsMethodVerifiedWithoutFailures(uint32_t method_idx, - uint16_t class_def_idx, - const DexFile& dex_file) const { - const VerifiedMethod* verified_method = GetVerifiedMethod(&dex_file, method_idx); - if (verified_method != nullptr) { - return !verified_method->HasVerificationFailures(); - } - - // If we can't find verification metadata, check if this is a system class (we trust that system - // classes have their methods verified). If it's not, be conservative and assume the method - // has not been verified successfully. - - // TODO: When compiling the boot image it should be safe to assume that everything is verified, - // even if methods are not found in the verification cache. - const char* descriptor = dex_file.GetClassDescriptor(dex_file.GetClassDef(class_def_idx)); - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - Thread* self = Thread::Current(); - ScopedObjectAccess soa(self); - bool is_system_class = class_linker->FindSystemClass(self, descriptor) != nullptr; - if (!is_system_class) { - self->ClearException(); - } - return is_system_class; -} - std::string CompilerDriver::GetMemoryUsageString(bool extended) const { std::ostringstream oss; const gc::Heap* const heap = Runtime::Current()->GetHeap(); diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 714b2d1fc8..7c0fc6450f 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -76,7 +76,6 @@ template <class Allocator> class SrcMap; class TimingLogger; class VdexFile; class VerificationResults; -class VerifiedMethod; enum EntryPointCallingConvention { // ABI of invocations to a method's interpreter entry point. @@ -95,9 +94,7 @@ class CompilerDriver { // can assume will be in the image, with null implying all available // classes. CompilerDriver(const CompilerOptions* compiler_options, - VerificationResults* verification_results, Compiler::Kind compiler_kind, - HashSet<std::string>* image_classes, size_t thread_count, int swap_fd); @@ -106,6 +103,17 @@ class CompilerDriver { // Set dex files classpath. void SetClasspathDexFiles(const std::vector<const DexFile*>& dex_files); + // Initialize and destroy thread pools. This is exposed because we do not want + // to do this twice, for PreCompile() and CompileAll(). + void InitializeThreadPools(); + void FreeThreadPools(); + + void PreCompile(jobject class_loader, + const std::vector<const DexFile*>& dex_files, + TimingLogger* timings, + /*inout*/ HashSet<std::string>* image_classes, + /*out*/ VerificationResults* verification_results) + REQUIRES(!Locks::mutator_lock_); void CompileAll(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) @@ -124,8 +132,6 @@ class CompilerDriver { Handle<mirror::ClassLoader> h_class_loader) REQUIRES(!Locks::mutator_lock_); - VerificationResults* GetVerificationResults() const; - const CompilerOptions& GetCompilerOptions() const { return *compiler_options_; } @@ -194,7 +200,6 @@ class CompilerDriver { REQUIRES_SHARED(Locks::mutator_lock_); - const VerifiedMethod* GetVerifiedMethod(const DexFile* dex_file, uint32_t method_idx) const; bool IsSafeCast(const DexCompilationUnit* mUnit, uint32_t dex_pc); size_t GetThreadCount() const { @@ -219,12 +224,6 @@ class CompilerDriver { void RecordClassStatus(const ClassReference& ref, ClassStatus status); - // Checks if the specified method has been verified without failures. Returns - // false if the method is not in the verification results (GetVerificationResults). - bool IsMethodVerifiedWithoutFailures(uint32_t method_idx, - uint16_t class_def_idx, - const DexFile& dex_file) const; - // Get memory usage during compilation. std::string GetMemoryUsageString(bool extended) const; @@ -265,13 +264,9 @@ class CompilerDriver { } private: - void PreCompile(jobject class_loader, - const std::vector<const DexFile*>& dex_files, - TimingLogger* timings) + void LoadImageClasses(TimingLogger* timings, /*inout*/ HashSet<std::string>* image_classes) REQUIRES(!Locks::mutator_lock_); - void LoadImageClasses(TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - // Attempt to resolve all type, methods, fields, and strings // referenced from code in the dex file following PathClassLoader // ordering semantics. @@ -291,11 +286,13 @@ class CompilerDriver { // verification was successful. bool FastVerify(jobject class_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings); + TimingLogger* timings, + /*out*/ VerificationResults* verification_results); void Verify(jobject class_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings); + TimingLogger* timings, + /*out*/ VerificationResults* verification_results); void VerifyDexFile(jobject class_loader, const DexFile& dex_file, @@ -326,14 +323,13 @@ class CompilerDriver { TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - void UpdateImageClasses(TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); + void UpdateImageClasses(TimingLogger* timings, /*inout*/ HashSet<std::string>* image_classes) + REQUIRES(!Locks::mutator_lock_); void Compile(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings); - void InitializeThreadPools(); - void FreeThreadPools(); void CheckThreadPools(); // Resolve const string literals that are loaded from dex code. If only_startup_strings is @@ -343,7 +339,6 @@ class CompilerDriver { /*inout*/ TimingLogger* timings); const CompilerOptions* const compiler_options_; - VerificationResults* const verification_results_; std::unique_ptr<Compiler> compiler_; Compiler::Kind compiler_kind_; @@ -359,12 +354,6 @@ class CompilerDriver { // All method references that this compiler has compiled. MethodTable compiled_methods_; - // Image classes to be updated by PreCompile(). - // TODO: Remove this member which is a non-const pointer to the CompilerOptions' data. - // Pass this explicitly to the PreCompile() which should be called directly from - // Dex2Oat rather than implicitly by CompileAll(). - HashSet<std::string>* image_classes_; - std::atomic<uint32_t> number_of_soft_verifier_failures_; bool had_hard_verifier_failure_; diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index fe1568da83..b9241292ee 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -42,20 +42,18 @@ namespace art { class CompilerDriverTest : public CommonCompilerTest { protected: - void CompileAll(jobject class_loader) REQUIRES(!Locks::mutator_lock_) { - TimingLogger timings("CompilerDriverTest::CompileAll", false, false); - TimingLogger::ScopedTiming t(__FUNCTION__, &timings); + void CompileAllAndMakeExecutable(jobject class_loader) REQUIRES(!Locks::mutator_lock_) { + TimingLogger timings("CompilerDriverTest::CompileAllAndMakeExecutable", false, false); dex_files_ = GetDexFiles(class_loader); - SetDexFilesForOatFile(dex_files_); - compiler_driver_->CompileAll(class_loader, dex_files_, &timings); - t.NewTiming("MakeAllExecutable"); + CompileAll(class_loader, dex_files_, &timings); + TimingLogger::ScopedTiming t("MakeAllExecutable", &timings); MakeAllExecutable(class_loader); } void EnsureCompiled(jobject class_loader, const char* class_name, const char* method, const char* signature, bool is_virtual) REQUIRES(!Locks::mutator_lock_) { - CompileAll(class_loader); + CompileAllAndMakeExecutable(class_loader); Thread::Current()->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); CHECK(started); @@ -106,7 +104,7 @@ class CompilerDriverTest : public CommonCompilerTest { // Disabled due to 10 second runtime on host // TODO: Update the test for hash-based dex cache arrays. Bug: 30627598 TEST_F(CompilerDriverTest, DISABLED_LARGE_CompileDexLibCore) { - CompileAll(nullptr); + CompileAllAndMakeExecutable(nullptr); // All libcore references should resolve ScopedObjectAccess soa(Thread::Current()); @@ -266,7 +264,7 @@ TEST_F(CompilerDriverProfileTest, ProfileGuidedCompilation) { ASSERT_TRUE(dex_file->EnableWrite()); } - CompileAll(class_loader); + CompileAllAndMakeExecutable(class_loader); std::unordered_set<std::string> m = GetExpectedMethodsForClass("Main"); std::unordered_set<std::string> s = GetExpectedMethodsForClass("Second"); @@ -310,7 +308,7 @@ TEST_F(CompilerDriverVerifyTest, VerifyCompilation) { } ASSERT_NE(class_loader, nullptr); - CompileAll(class_loader); + CompileAllAndMakeExecutable(class_loader); CheckVerifiedClass(class_loader, "LMain;"); CheckVerifiedClass(class_loader, "LSecond;"); diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index b28c7e0edb..8d1ae3d524 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -24,9 +24,14 @@ #include "arch/instruction_set_features.h" #include "base/runtime_debug.h" #include "base/variant_map.h" +#include "class_linker.h" #include "cmdline_parser.h" #include "compiler_options_map-inl.h" +#include "dex/dex_file-inl.h" +#include "dex/verification_results.h" +#include "dex/verified_method.h" #include "runtime.h" +#include "scoped_thread_state_change-inl.h" #include "simple_compiler_options_map.h" namespace art { @@ -44,6 +49,7 @@ CompilerOptions::CompilerOptions() no_inline_from_(), dex_files_for_oat_file_(), image_classes_(), + verification_results_(nullptr), image_type_(ImageType::kNone), compiling_with_core_image_(false), baseline_(false), @@ -141,4 +147,40 @@ bool CompilerOptions::IsImageClass(const char* descriptor) const { return image_classes_.find(StringPiece(descriptor)) != image_classes_.end(); } +const VerificationResults* CompilerOptions::GetVerificationResults() const { + DCHECK(Runtime::Current()->IsAotCompiler()); + return verification_results_; +} + +const VerifiedMethod* CompilerOptions::GetVerifiedMethod(const DexFile* dex_file, + uint32_t method_idx) const { + MethodReference ref(dex_file, method_idx); + return verification_results_->GetVerifiedMethod(ref); +} + +bool CompilerOptions::IsMethodVerifiedWithoutFailures(uint32_t method_idx, + uint16_t class_def_idx, + const DexFile& dex_file) const { + const VerifiedMethod* verified_method = GetVerifiedMethod(&dex_file, method_idx); + if (verified_method != nullptr) { + return !verified_method->HasVerificationFailures(); + } + + // If we can't find verification metadata, check if this is a system class (we trust that system + // classes have their methods verified). If it's not, be conservative and assume the method + // has not been verified successfully. + + // TODO: When compiling the boot image it should be safe to assume that everything is verified, + // even if methods are not found in the verification cache. + const char* descriptor = dex_file.GetClassDescriptor(dex_file.GetClassDef(class_def_idx)); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + bool is_system_class = class_linker->FindSystemClass(self, descriptor) != nullptr; + if (!is_system_class) { + self->ClearException(); + } + return is_system_class; +} + } // namespace art diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index a8f246dcab..bd12bf7dda 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -47,6 +47,8 @@ class DexFile; enum class InstructionSet; class InstructionSetFeatures; class ProfileCompilationInfo; +class VerificationResults; +class VerifiedMethod; // Enum for CheckProfileMethodsCompiled. Outside CompilerOptions so it can be forward-declared. enum class ProfileMethodsCheck : uint8_t { @@ -283,6 +285,16 @@ class CompilerOptions final { bool IsImageClass(const char* descriptor) const; + const VerificationResults* GetVerificationResults() const; + + const VerifiedMethod* GetVerifiedMethod(const DexFile* dex_file, uint32_t method_idx) const; + + // Checks if the specified method has been verified without failures. Returns + // false if the method is not in the verification results (GetVerificationResults). + bool IsMethodVerifiedWithoutFailures(uint32_t method_idx, + uint16_t class_def_idx, + const DexFile& dex_file) const; + bool ParseCompilerOptions(const std::vector<std::string>& options, bool ignore_unrecognized, std::string* error_msg); @@ -381,6 +393,9 @@ class CompilerOptions final { // Must not be empty for real boot image, only for tests pretending to compile boot image. HashSet<std::string> image_classes_; + // Results of AOT verification. + const VerificationResults* verification_results_; + ImageType image_type_; bool compiling_with_core_image_; bool baseline_; diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 0eab8356e7..b9fd868f1c 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -167,9 +167,7 @@ JitCompiler::JitCompiler() { compiler_driver_.reset(new CompilerDriver( compiler_options_.get(), - /* verification_results */ nullptr, Compiler::kOptimizing, - /* image_classes */ nullptr, /* thread_count */ 1, /* swap_fd */ -1)); // Disable dedupe so we can remove compiled methods. diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index ec9322270a..417d794264 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -28,7 +28,6 @@ #include "dex/inline_method_analyser.h" #include "dex/verification_results.h" #include "dex/verified_method.h" -#include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" #include "driver/dex_compilation_unit.h" #include "instruction_simplifier.h" @@ -408,7 +407,7 @@ ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { return single_impl; } -static bool IsMethodUnverified(CompilerDriver* const compiler_driver, ArtMethod* method) +static bool IsMethodUnverified(const CompilerOptions& compiler_options, ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { if (!method->GetDeclaringClass()->IsVerified()) { if (Runtime::Current()->UseJitCompilation()) { @@ -417,8 +416,9 @@ static bool IsMethodUnverified(CompilerDriver* const compiler_driver, ArtMethod* return true; } uint16_t class_def_idx = method->GetDeclaringClass()->GetDexClassDefIndex(); - if (!compiler_driver->IsMethodVerifiedWithoutFailures( - method->GetDexMethodIndex(), class_def_idx, *method->GetDexFile())) { + if (!compiler_options.IsMethodVerifiedWithoutFailures(method->GetDexMethodIndex(), + class_def_idx, + *method->GetDexFile())) { // Method has soft or hard failures, don't analyze. return true; } @@ -426,11 +426,11 @@ static bool IsMethodUnverified(CompilerDriver* const compiler_driver, ArtMethod* return false; } -static bool AlwaysThrows(CompilerDriver* const compiler_driver, ArtMethod* method) +static bool AlwaysThrows(const CompilerOptions& compiler_options, ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(method != nullptr); // Skip non-compilable and unverified methods. - if (!method->IsCompilable() || IsMethodUnverified(compiler_driver, method)) { + if (!method->IsCompilable() || IsMethodUnverified(compiler_options, method)) { return false; } // Skip native methods, methods with try blocks, and methods that are too large. @@ -518,7 +518,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface); } } - } else if (!cha_devirtualize && AlwaysThrows(compiler_driver_, actual_method)) { + } else if (!cha_devirtualize && AlwaysThrows(codegen_->GetCompilerOptions(), actual_method)) { // Set always throws property for non-inlined method call with single target // (unless it was obtained through CHA, because that would imply we have // to add the CHA dependency, which seems not worth it). @@ -1500,7 +1500,7 @@ bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction, return false; } - if (IsMethodUnverified(compiler_driver_, method)) { + if (IsMethodUnverified(codegen_->GetCompilerOptions(), method)) { LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedNotVerified) << "Method " << method->PrettyMethod() << " couldn't be verified, so it cannot be inlined"; @@ -2069,7 +2069,6 @@ void HInliner::RunOptimizations(HGraph* callee_graph, codegen_, outer_compilation_unit_, dex_compilation_unit, - compiler_driver_, handles_, inline_stats_, total_number_of_dex_registers_ + accessor.RegistersSize(), diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 6fd0c204b2..8ac2163a94 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -38,7 +38,6 @@ class HInliner : public HOptimization { CodeGenerator* codegen, const DexCompilationUnit& outer_compilation_unit, const DexCompilationUnit& caller_compilation_unit, - CompilerDriver* compiler_driver, VariableSizedHandleScope* handles, OptimizingCompilerStats* stats, size_t total_number_of_dex_registers, @@ -51,7 +50,6 @@ class HInliner : public HOptimization { outer_compilation_unit_(outer_compilation_unit), caller_compilation_unit_(caller_compilation_unit), codegen_(codegen), - compiler_driver_(compiler_driver), total_number_of_dex_registers_(total_number_of_dex_registers), total_number_of_instructions_(total_number_of_instructions), parent_(parent), @@ -280,7 +278,6 @@ class HInliner : public HOptimization { const DexCompilationUnit& outer_compilation_unit_; const DexCompilationUnit& caller_compilation_unit_; CodeGenerator* const codegen_; - CompilerDriver* const compiler_driver_; const size_t total_number_of_dex_registers_; size_t total_number_of_instructions_; diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index 6d04b0e9d9..1688ea7811 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -2950,6 +2950,151 @@ void IntrinsicCodeGeneratorARM64::VisitCRC32Update(HInvoke* invoke) { __ Mvn(out, out); } +// The threshold for sizes of arrays to use the library provided implementation +// of CRC32.updateBytes instead of the intrinsic. +static constexpr int32_t kCRC32UpdateBytesThreshold = 64 * 1024; + +void IntrinsicLocationsBuilderARM64::VisitCRC32UpdateBytes(HInvoke* invoke) { + if (!codegen_->GetInstructionSetFeatures().HasCRC()) { + return; + } + + LocationSummary* locations + = new (allocator_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetInAt(2, Location::RegisterOrConstant(invoke->InputAt(2))); + locations->SetInAt(3, Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister()); +} + +// Lower the invoke of CRC32.updateBytes(int crc, byte[] b, int off, int len) +// +// Note: The intrinsic is not used if len exceeds a threshold. +void IntrinsicCodeGeneratorARM64::VisitCRC32UpdateBytes(HInvoke* invoke) { + DCHECK(codegen_->GetInstructionSetFeatures().HasCRC()); + + auto masm = GetVIXLAssembler(); + auto locations = invoke->GetLocations(); + + auto slow_path = + new (codegen_->GetScopedAllocator()) IntrinsicSlowPathARM64(invoke); + codegen_->AddSlowPath(slow_path); + + Register length = WRegisterFrom(locations->InAt(3)); + __ Cmp(length, kCRC32UpdateBytesThreshold); + __ B(slow_path->GetEntryLabel(), hi); + + const uint32_t array_data_offset = + mirror::Array::DataOffset(Primitive::kPrimByte).Uint32Value(); + Register ptr = XRegisterFrom(locations->GetTemp(0)); + Register array = XRegisterFrom(locations->InAt(1)); + auto offset = locations->InAt(2); + if (offset.IsConstant()) { + int32_t offset_value = offset.GetConstant()->AsIntConstant()->GetValue(); + __ Add(ptr, array, array_data_offset + offset_value); + } else { + __ Add(ptr, array, array_data_offset); + __ Add(ptr, ptr, XRegisterFrom(offset)); + } + + // The algorithm of CRC32 of bytes is: + // crc = ~crc + // process a few first bytes to make the array 8-byte aligned + // while array has 8 bytes do: + // crc = crc32_of_8bytes(crc, 8_bytes(array)) + // if array has 4 bytes: + // crc = crc32_of_4bytes(crc, 4_bytes(array)) + // if array has 2 bytes: + // crc = crc32_of_2bytes(crc, 2_bytes(array)) + // if array has a byte: + // crc = crc32_of_byte(crc, 1_byte(array)) + // crc = ~crc + + vixl::aarch64::Label loop, done; + vixl::aarch64::Label process_4bytes, process_2bytes, process_1byte; + vixl::aarch64::Label aligned2, aligned4, aligned8; + + // Use VIXL scratch registers as the VIXL macro assembler won't use them in + // instructions below. + UseScratchRegisterScope temps(masm); + Register len = temps.AcquireW(); + Register array_elem = temps.AcquireW(); + + Register out = WRegisterFrom(locations->Out()); + __ Mvn(out, WRegisterFrom(locations->InAt(0))); + __ Mov(len, length); + + __ Tbz(ptr, 0, &aligned2); + __ Subs(len, len, 1); + __ B(&done, lo); + __ Ldrb(array_elem, MemOperand(ptr, 1, PostIndex)); + __ Crc32b(out, out, array_elem); + + __ Bind(&aligned2); + __ Tbz(ptr, 1, &aligned4); + __ Subs(len, len, 2); + __ B(&process_1byte, lo); + __ Ldrh(array_elem, MemOperand(ptr, 2, PostIndex)); + __ Crc32h(out, out, array_elem); + + __ Bind(&aligned4); + __ Tbz(ptr, 2, &aligned8); + __ Subs(len, len, 4); + __ B(&process_2bytes, lo); + __ Ldr(array_elem, MemOperand(ptr, 4, PostIndex)); + __ Crc32w(out, out, array_elem); + + __ Bind(&aligned8); + __ Subs(len, len, 8); + // If len < 8 go to process data by 4 bytes, 2 bytes and a byte. + __ B(&process_4bytes, lo); + + // The main loop processing data by 8 bytes. + __ Bind(&loop); + __ Ldr(array_elem.X(), MemOperand(ptr, 8, PostIndex)); + __ Subs(len, len, 8); + __ Crc32x(out, out, array_elem.X()); + // if len >= 8, process the next 8 bytes. + __ B(&loop, hs); + + // Process the data which is less than 8 bytes. + // The code generated below works with values of len + // which come in the range [-8, 0]. + // The first three bits are used to detect whether 4 bytes or 2 bytes or + // a byte can be processed. + // The checking order is from bit 2 to bit 0: + // bit 2 is set: at least 4 bytes available + // bit 1 is set: at least 2 bytes available + // bit 0 is set: at least a byte available + __ Bind(&process_4bytes); + // Goto process_2bytes if less than four bytes available + __ Tbz(len, 2, &process_2bytes); + __ Ldr(array_elem, MemOperand(ptr, 4, PostIndex)); + __ Crc32w(out, out, array_elem); + + __ Bind(&process_2bytes); + // Goto process_1bytes if less than two bytes available + __ Tbz(len, 1, &process_1byte); + __ Ldrh(array_elem, MemOperand(ptr, 2, PostIndex)); + __ Crc32h(out, out, array_elem); + + __ Bind(&process_1byte); + // Goto done if no bytes available + __ Tbz(len, 0, &done); + __ Ldrb(array_elem, MemOperand(ptr)); + __ Crc32b(out, out, array_elem); + + __ Bind(&done); + __ Mvn(out, out); + + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(ARM64, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(ARM64, StringStringIndexOf); diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc index 4d45a9991c..88f1457c20 100644 --- a/compiler/optimizing/intrinsics_arm_vixl.cc +++ b/compiler/optimizing/intrinsics_arm_vixl.cc @@ -3060,6 +3060,7 @@ UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeCASLong) // High register pressure. UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyChar) UNIMPLEMENTED_INTRINSIC(ARMVIXL, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32Update) +UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index 21fb7d7f1c..08ba0a0adf 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -2697,6 +2697,7 @@ UNIMPLEMENTED_INTRINSIC(MIPS, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(MIPS, SystemArrayCopy) UNIMPLEMENTED_INTRINSIC(MIPS, CRC32Update) +UNIMPLEMENTED_INTRINSIC(MIPS, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(MIPS, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(MIPS, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 4b86f5d423..59d3ba2488 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -2347,6 +2347,7 @@ void IntrinsicCodeGeneratorMIPS64::VisitReachabilityFence(HInvoke* invoke ATTRIB UNIMPLEMENTED_INTRINSIC(MIPS64, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(MIPS64, SystemArrayCopy) UNIMPLEMENTED_INTRINSIC(MIPS64, CRC32Update) +UNIMPLEMENTED_INTRINSIC(MIPS64, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(MIPS64, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(MIPS64, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index a73f4e8b94..1d94950e4d 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -3071,6 +3071,7 @@ UNIMPLEMENTED_INTRINSIC(X86, DoubleIsInfinite) UNIMPLEMENTED_INTRINSIC(X86, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(X86, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(X86, CRC32Update) +UNIMPLEMENTED_INTRINSIC(X86, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index 88c766fabc..4f0b61d88e 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -2738,6 +2738,7 @@ UNIMPLEMENTED_INTRINSIC(X86_64, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(X86_64, FloatIsInfinite) UNIMPLEMENTED_INTRINSIC(X86_64, DoubleIsInfinite) UNIMPLEMENTED_INTRINSIC(X86_64, CRC32Update) +UNIMPLEMENTED_INTRINSIC(X86_64, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOfAfter); diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc index 0f971e100e..b75afad4c8 100644 --- a/compiler/optimizing/optimization.cc +++ b/compiler/optimizing/optimization.cc @@ -181,7 +181,6 @@ ArenaVector<HOptimization*> ConstructOptimizations( HGraph* graph, OptimizingCompilerStats* stats, CodeGenerator* codegen, - CompilerDriver* driver, const DexCompilationUnit& dex_compilation_unit, VariableSizedHandleScope* handles) { ArenaVector<HOptimization*> optimizations(allocator->Adapter()); @@ -258,7 +257,6 @@ ArenaVector<HOptimization*> ConstructOptimizations( codegen, dex_compilation_unit, // outer_compilation_unit dex_compilation_unit, // outermost_compilation_unit - driver, handles, stats, accessor.RegistersSize(), diff --git a/compiler/optimizing/optimization.h b/compiler/optimizing/optimization.h index 490007d9d9..ce44b5f81a 100644 --- a/compiler/optimizing/optimization.h +++ b/compiler/optimizing/optimization.h @@ -147,7 +147,6 @@ ArenaVector<HOptimization*> ConstructOptimizations( HGraph* graph, OptimizingCompilerStats* stats, CodeGenerator* codegen, - CompilerDriver* driver, const DexCompilationUnit& dex_compilation_unit, VariableSizedHandleScope* handles); diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 641368b87a..92aaa19121 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -321,7 +321,6 @@ class OptimizingCompiler final : public Compiler { graph, compilation_stats_.get(), codegen, - GetCompilerDriver(), dex_compilation_unit, handles); DCHECK_EQ(length, optimizations.size()); @@ -962,9 +961,9 @@ CodeGenerator* OptimizingCompiler::TryCompileIntrinsic( arena_stack, dex_file, method_idx, - compiler_driver->GetCompilerOptions().GetInstructionSet(), + compiler_options.GetInstructionSet(), kInvalidInvokeType, - compiler_driver->GetCompilerOptions().GetDebuggable(), + compiler_options.GetDebuggable(), /* osr */ false); DCHECK(Runtime::Current()->IsAotCompiler()); @@ -1043,12 +1042,13 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, const DexFile& dex_file, Handle<mirror::DexCache> dex_cache) const { CompilerDriver* compiler_driver = GetCompilerDriver(); + const CompilerOptions& compiler_options = compiler_driver->GetCompilerOptions(); CompiledMethod* compiled_method = nullptr; Runtime* runtime = Runtime::Current(); DCHECK(runtime->IsAotCompiler()); - const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx); + const VerifiedMethod* verified_method = compiler_options.GetVerifiedMethod(&dex_file, method_idx); DCHECK(!verified_method->HasRuntimeThrow()); - if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) || + if (compiler_options.IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) || verifier::CanCompilerHandleVerificationFailure( verified_method->GetEncounteredVerificationFailures())) { ArenaAllocator allocator(runtime->GetArenaPool()); @@ -1080,7 +1080,7 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, // Go to native so that we don't block GC during compilation. ScopedThreadSuspension sts(soa.Self(), kNative); if (method != nullptr && UNLIKELY(method->IsIntrinsic())) { - DCHECK(compiler_driver->GetCompilerOptions().IsBootImage()); + DCHECK(compiler_options.IsBootImage()); codegen.reset( TryCompileIntrinsic(&allocator, &arena_stack, @@ -1099,7 +1099,7 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, &code_allocator, dex_compilation_unit, method, - compiler_driver->GetCompilerOptions().IsBaseline(), + compiler_options.IsBaseline(), /* osr= */ false, &handles)); } @@ -1128,7 +1128,7 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, } } else { MethodCompilationStat method_stat; - if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) { + if (compiler_options.VerifyAtRuntime()) { method_stat = MethodCompilationStat::kNotCompiledVerifyAtRuntime; } else { method_stat = MethodCompilationStat::kNotCompiledVerificationError; @@ -1137,8 +1137,8 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, } if (kIsDebugBuild && - compiler_driver->GetCompilerOptions().CompilingWithCoreImage() && - IsInstructionSetSupported(compiler_driver->GetCompilerOptions().GetInstructionSet())) { + compiler_options.CompilingWithCoreImage() && + IsInstructionSetSupported(compiler_options.GetInstructionSet())) { // For testing purposes, we put a special marker on method names // that should be compiled with this compiler (when the // instruction set is supported). This makes sure we're not diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index eb44dd7f1d..8c90aa7e37 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -93,12 +93,12 @@ class VerifierDepsTest : public CommonCompilerTest { verifier_deps_.reset(deps); } callbacks_->SetVerifierDeps(deps); - compiler_driver_->Verify(class_loader_, dex_files_, &timings); + compiler_driver_->Verify(class_loader_, dex_files_, &timings, verification_results_.get()); callbacks_->SetVerifierDeps(nullptr); // Clear entries in the verification results to avoid hitting a DCHECK that // we always succeed inserting a new entry after verifying. AtomicDexRefMap<MethodReference, const VerifiedMethod*>* map = - &compiler_driver_->GetVerificationResults()->atomic_verified_methods_; + &verification_results_->atomic_verified_methods_; map->Visit([](const DexFileReference& ref ATTRIBUTE_UNUSED, const VerifiedMethod* method) { delete method; }); @@ -126,7 +126,7 @@ class VerifierDepsTest : public CommonCompilerTest { class_linker_->RegisterDexFile(*dex_file, loader.Get()); } for (const DexFile* dex_file : dex_files_) { - compiler_driver_->GetVerificationResults()->AddDexFile(dex_file); + verification_results_->AddDexFile(dex_file); } SetDexFilesForOatFile(dex_files_); } diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index edd61897a9..f7299344f2 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -630,6 +630,7 @@ class Dex2Oat final { thread_count_(sysconf(_SC_NPROCESSORS_CONF)), start_ns_(NanoTime()), start_cputime_ns_(ProcessCpuNanoTime()), + strip_(false), oat_fd_(-1), input_vdex_fd_(-1), output_vdex_fd_(-1), @@ -1788,9 +1789,7 @@ class Dex2Oat final { compiler_options_->profile_compilation_info_ = profile_compilation_info_.get(); driver_.reset(new CompilerDriver(compiler_options_.get(), - verification_results_.get(), compiler_kind_, - &compiler_options_->image_classes_, thread_count_, swap_fd_)); if (!IsBootImage()) { @@ -1862,7 +1861,16 @@ class Dex2Oat final { << soa.Self()->GetException()->Dump(); } } + driver_->InitializeThreadPools(); + driver_->PreCompile(class_loader, + dex_files, + timings_, + &compiler_options_->image_classes_, + verification_results_.get()); + callbacks_->SetVerificationResults(nullptr); // Should not be needed anymore. + compiler_options_->verification_results_ = verification_results_.get(); driver_->CompileAll(class_loader, dex_files, timings_); + driver_->FreeThreadPools(); return class_loader; } diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 6ffcef12a7..13fa0f033b 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -218,11 +218,9 @@ inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode, { jobject class_loader = nullptr; TimingLogger timings("ImageTest::WriteRead", false, false); - TimingLogger::ScopedTiming t("CompileAll", &timings); - SetDexFilesForOatFile(class_path); - driver->CompileAll(class_loader, class_path, &timings); + CompileAll(class_loader, class_path, &timings); - t.NewTiming("WriteElf"); + TimingLogger::ScopedTiming t("WriteElf", &timings); SafeMap<std::string, std::string> key_value_store; key_value_store.Put(OatHeader::kBootClassPathKey, gc::space::ImageSpace::GetMultiImageBootClassPath( diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index 6b17ef6e87..d045698d07 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -968,7 +968,7 @@ class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { ClassStatus status; bool found = writer_->compiler_driver_->GetCompiledClass(class_ref, &status); if (!found) { - VerificationResults* results = writer_->compiler_driver_->GetVerificationResults(); + const VerificationResults* results = writer_->compiler_options_.GetVerificationResults(); if (results != nullptr && results->IsClassRejected(class_ref)) { // The oat class status is used only for verification of resolved classes, // so use ClassStatus::kErrorResolved whether the class was resolved or unresolved diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index 598a0f846b..5de1540839 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -391,8 +391,7 @@ TEST_F(OatTest, WriteRead) { jobject class_loader = nullptr; if (kCompile) { TimingLogger timings2("OatTest::WriteRead", false, false); - SetDexFilesForOatFile(class_linker->GetBootClassPath()); - compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings2); + CompileAll(class_loader, class_linker->GetBootClassPath(), &timings2); } ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); @@ -405,7 +404,7 @@ TEST_F(OatTest, WriteRead) { ASSERT_TRUE(success); if (kCompile) { // OatWriter strips the code, regenerate to compare - compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); + CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); } std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, tmp_oat.GetFilename(), @@ -513,8 +512,7 @@ TEST_F(OatTest, EmptyTextSection) { ScopedObjectAccess soa(Thread::Current()); class_linker->RegisterDexFile(*dex_file, soa.Decode<mirror::ClassLoader>(class_loader)); } - SetDexFilesForOatFile(dex_files); - compiler_driver_->CompileAll(class_loader, dex_files, &timings); + CompileAll(class_loader, dex_files, &timings); ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); SafeMap<std::string, std::string> key_value_store; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 9b2e1a10e4..c964dbcf59 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2026,7 +2026,7 @@ bool ClassLinker::AddImageSpace( // Image class loader [A][B][C][image dex files] // Class loader = [???][dex_elements][image dex files] // Need to ensure that [???][dex_elements] == [A][B][C]. - // For each class loader, PathClassLoader, the laoder checks the parent first. Also the logic + // For each class loader, PathClassLoader, the loader checks the parent first. Also the logic // for PathClassLoader does this by looping through the array of dex files. To ensure they // resolve the same way, simply flatten the hierarchy in the way the resolution order would be, // and check that the dex file names are the same. @@ -2662,7 +2662,7 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl return true; } - if (IsPathOrDexClassLoader(soa, class_loader)) { + if (IsPathOrDexClassLoader(soa, class_loader) || IsInMemoryDexClassLoader(soa, class_loader)) { // For regular path or dex class loader the search order is: // - parent // - shared libraries @@ -2756,7 +2756,9 @@ ObjPtr<mirror::Class> ClassLinker::FindClassInBaseDexClassLoaderClassPath( const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader) { - DCHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader)) + DCHECK(IsPathOrDexClassLoader(soa, class_loader) || + IsInMemoryDexClassLoader(soa, class_loader) || + IsDelegateLastClassLoader(soa, class_loader)) << "Unexpected class loader for descriptor " << descriptor; ObjPtr<mirror::Class> ret; diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h index 69476dfb98..562dc47d21 100644 --- a/runtime/class_loader_utils.h +++ b/runtime/class_loader_utils.h @@ -29,7 +29,7 @@ namespace art { // Returns true if the given class loader is either a PathClassLoader or a DexClassLoader. -// (they both have the same behaviour with respect to class lockup order) +// (they both have the same behaviour with respect to class lookup order) inline bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Handle<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -41,6 +41,15 @@ inline bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader)); } +// Returns true if the given class loader is an InMemoryDexClassLoader. +inline bool IsInMemoryDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) { + mirror::Class* class_loader_class = class_loader->GetClass(); + return (class_loader_class == + soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_InMemoryDexClassLoader)); +} + inline bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Handle<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index d9d81f02ae..13bead28be 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -235,6 +235,7 @@ ALWAYS_INLINE inline uint32_t GetRuntimeFlags(ArtMethod* method) case Intrinsics::kUnsafeStoreFence: case Intrinsics::kUnsafeFullFence: case Intrinsics::kCRC32Update: + case Intrinsics::kCRC32UpdateBytes: case Intrinsics::kStringNewStringFromBytes: case Intrinsics::kStringNewStringFromChars: case Intrinsics::kStringNewStringFromString: diff --git a/runtime/image.cc b/runtime/image.cc index ae3d8e34e6..fb581f94d2 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -29,7 +29,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '1', '\0' }; // Add image blocks. +const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '2', '\0' }; // CRC32UpdateBytes intrinsic ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc index 24a026a92e..16e118c9cf 100644 --- a/runtime/interpreter/interpreter_intrinsics.cc +++ b/runtime/interpreter/interpreter_intrinsics.cc @@ -559,6 +559,7 @@ bool MterpHandleIntrinsic(ShadowFrame* shadow_frame, UNIMPLEMENTED_CASE(IntegerValueOf /* (I)Ljava/lang/Integer; */) UNIMPLEMENTED_CASE(ThreadInterrupted /* ()Z */) UNIMPLEMENTED_CASE(CRC32Update /* (II)I */) + UNIMPLEMENTED_CASE(CRC32UpdateBytes /* (I[BII)I */) INTRINSIC_CASE(VarHandleFullFence) INTRINSIC_CASE(VarHandleAcquireFence) INTRINSIC_CASE(VarHandleReleaseFence) diff --git a/runtime/intrinsics_list.h b/runtime/intrinsics_list.h index 093dd7f400..82ea47609b 100644 --- a/runtime/intrinsics_list.h +++ b/runtime/intrinsics_list.h @@ -220,6 +220,7 @@ V(VarHandleStoreStoreFence, kStatic, kNeedsEnvironmentOrCache, kReadSideEffects, kNoThrow, "Ljava/lang/invoke/VarHandle;", "storeStoreFence", "()V") \ V(ReachabilityFence, kStatic, kNeedsEnvironmentOrCache, kWriteSideEffects, kNoThrow, "Ljava/lang/ref/Reference;", "reachabilityFence", "(Ljava/lang/Object;)V") \ V(CRC32Update, kStatic, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow, "Ljava/util/zip/CRC32;", "update", "(II)I") \ + V(CRC32UpdateBytes, kStatic, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow, "Ljava/util/zip/CRC32;", "updateBytes", "(I[BII)I") \ SIGNATURE_POLYMORPHIC_INTRINSICS_LIST(V) #endif // ART_RUNTIME_INTRINSICS_LIST_H_ diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 4a3ef07819..e43d771270 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -291,22 +291,6 @@ bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) { return success; } -void Jit::CreateThreadPool() { - if (Runtime::Current()->IsSafeMode()) { - // Never create the pool in safe mode. - return; - } - // There is a DCHECK in the 'AddSamples' method to ensure the thread pool - // is not null when we instrument. - - // We need peers as we may report the JIT thread, e.g., in the debugger. - constexpr bool kJitPoolNeedsPeers = true; - thread_pool_.reset(new ThreadPool("Jit thread pool", 1, kJitPoolNeedsPeers)); - - thread_pool_->SetPthreadPriority(options_->GetThreadPoolPthreadPriority()); - Start(); -} - void Jit::DeleteThreadPool() { Thread* self = Thread::Current(); DCHECK(Runtime::Current()->IsShuttingDown(self)); @@ -562,10 +546,10 @@ void Jit::AddMemoryUsage(ArtMethod* method, size_t bytes) { class JitCompileTask final : public Task { public: - enum TaskKind { + enum class TaskKind { kAllocateProfile, kCompile, - kCompileOsr + kCompileOsr, }; JitCompileTask(ArtMethod* method, TaskKind kind) : method_(method), kind_(kind) { @@ -582,14 +566,20 @@ class JitCompileTask final : public Task { void Run(Thread* self) override { ScopedObjectAccess soa(self); - if (kind_ == kCompile) { - Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr= */ false); - } else if (kind_ == kCompileOsr) { - Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr= */ true); - } else { - DCHECK(kind_ == kAllocateProfile); - if (ProfilingInfo::Create(self, method_, /* retry_allocation= */ true)) { - VLOG(jit) << "Start profiling " << ArtMethod::PrettyMethod(method_); + switch (kind_) { + case TaskKind::kCompile: + case TaskKind::kCompileOsr: { + Runtime::Current()->GetJit()->CompileMethod( + method_, + self, + /* osr= */ (kind_ == TaskKind::kCompileOsr)); + break; + } + case TaskKind::kAllocateProfile: { + if (ProfilingInfo::Create(self, method_, /* retry_allocation= */ true)) { + VLOG(jit) << "Start profiling " << ArtMethod::PrettyMethod(method_); + } + break; } } ProfileSaver::NotifyJitActivity(); @@ -607,6 +597,18 @@ class JitCompileTask final : public Task { DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask); }; +void Jit::CreateThreadPool() { + // There is a DCHECK in the 'AddSamples' method to ensure the tread pool + // is not null when we instrument. + + // We need peers as we may report the JIT thread, e.g., in the debugger. + constexpr bool kJitPoolNeedsPeers = true; + thread_pool_.reset(new ThreadPool("Jit thread pool", 1, kJitPoolNeedsPeers)); + + thread_pool_->SetPthreadPriority(options_->GetThreadPoolPthreadPriority()); + Start(); +} + static bool IgnoreSamplesForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { if (method->IsClassInitializer() || !method->IsCompilable()) { // We do not want to compile such methods. @@ -630,11 +632,10 @@ static bool IgnoreSamplesForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mut void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_backedges) { if (thread_pool_ == nullptr) { - // Should only see this when shutting down, starting up, or in zygote, which doesn't - // have a thread pool. + // Should only see this when shutting down, starting up, or in safe mode. DCHECK(Runtime::Current()->IsShuttingDown(self) || !Runtime::Current()->IsFinishedStarting() || - Runtime::Current()->IsZygote()); + Runtime::Current()->IsSafeMode()); return; } if (IgnoreSamplesForMethod(method)) { @@ -675,7 +676,8 @@ void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_ if (!success) { // We failed allocating. Instead of doing the collection on the Java thread, we push // an allocation to a compiler thread, that will do the collection. - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile)); + thread_pool_->AddTask( + self, new JitCompileTask(method, JitCompileTask::TaskKind::kAllocateProfile)); } } // Avoid jumping more than one state at a time. @@ -685,7 +687,7 @@ void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_ if ((new_count >= HotMethodThreshold()) && !code_cache_->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) { DCHECK(thread_pool_ != nullptr); - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile)); + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::TaskKind::kCompile)); } // Avoid jumping more than one state at a time. new_count = std::min(new_count, static_cast<uint32_t>(OSRMethodThreshold() - 1)); @@ -697,7 +699,8 @@ void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_ DCHECK(!method->IsNative()); // No back edges reported for native methods. if ((new_count >= OSRMethodThreshold()) && !code_cache_->IsOsrCompiled(method)) { DCHECK(thread_pool_ != nullptr); - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompileOsr)); + thread_pool_->AddTask( + self, new JitCompileTask(method, JitCompileTask::TaskKind::kCompileOsr)); } } } @@ -730,7 +733,7 @@ void Jit::MethodEntered(Thread* thread, ArtMethod* method) { // The compiler requires a ProfilingInfo object for non-native methods. ProfilingInfo::Create(thread, np_method, /* retry_allocation= */ true); } - JitCompileTask compile_task(method, JitCompileTask::kCompile); + JitCompileTask compile_task(method, JitCompileTask::TaskKind::kCompile); // Fake being in a runtime thread so that class-load behavior will be the same as normal jit. ScopedSetRuntimeThread ssrt(thread); compile_task.Run(thread); @@ -798,7 +801,16 @@ ScopedJitSuspend::~ScopedJitSuspend() { } } -void Jit::PostForkChildAction() { +void Jit::PostForkChildAction(bool is_zygote) { + if (is_zygote) { + // Don't transition if this is for a child zygote. + return; + } + if (Runtime::Current()->IsSafeMode()) { + // Delete the thread pool, we are not going to JIT. + thread_pool_.reset(nullptr); + return; + } // At this point, the compiler options have been adjusted to the particular configuration // of the forked child. Parse them again. jit_update_options_(jit_compiler_handle_); @@ -806,6 +818,28 @@ void Jit::PostForkChildAction() { // Adjust the status of code cache collection: the status from zygote was to not collect. code_cache_->SetGarbageCollectCode(!jit_generate_debug_info_(jit_compiler_handle_) && !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()); + + if (thread_pool_ != nullptr) { + // Remove potential tasks that have been inherited from the zygote. + thread_pool_->RemoveAllTasks(Thread::Current()); + + // Resume JIT compilation. + thread_pool_->CreateThreads(); + } +} + +void Jit::PreZygoteFork() { + if (thread_pool_ == nullptr) { + return; + } + thread_pool_->DeleteThreads(); +} + +void Jit::PostZygoteFork() { + if (thread_pool_ == nullptr) { + return; + } + thread_pool_->CreateThreads(); } } // namespace jit diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index e12b032feb..7ce5f07672 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -285,8 +285,14 @@ class Jit { // Start JIT threads. void Start(); - // Transition to a zygote child state. - void PostForkChildAction(); + // Transition to a child state. + void PostForkChildAction(bool is_zygote); + + // Prepare for forking. + void PreZygoteFork(); + + // Adjust state after forking. + void PostZygoteFork(); private: Jit(JitCodeCache* code_cache, JitOptions* options); diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 97887ccbc9..fdb6b86028 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -436,6 +436,12 @@ void JitCodeCache::InitializeState(size_t initial_capacity, size_t max_capacity) initial_capacity = RoundDown(initial_capacity, 2 * kPageSize); max_capacity = RoundDown(max_capacity, 2 * kPageSize); + used_memory_for_data_ = 0; + used_memory_for_code_ = 0; + number_of_compilations_ = 0; + number_of_osr_compilations_ = 0; + number_of_collections_ = 0; + data_pages_ = MemMap(); exec_pages_ = MemMap(); non_exec_pages_ = MemMap(); @@ -477,7 +483,7 @@ void JitCodeCache::InitializeSpaces() { JitCodeCache::~JitCodeCache() {} bool JitCodeCache::ContainsPc(const void* ptr) const { - return exec_pages_.Begin() <= ptr && ptr < exec_pages_.End(); + return exec_pages_.HasAddress(ptr) || zygote_exec_pages_.HasAddress(ptr); } bool JitCodeCache::WillExecuteJitCode(ArtMethod* method) { @@ -1321,7 +1327,7 @@ class MarkCodeClosure final : public Closure { return true; } const void* code = method_header->GetCode(); - if (code_cache_->ContainsPc(code)) { + if (code_cache_->ContainsPc(code) && !code_cache_->IsInZygoteExecSpace(code)) { // Use the atomic set version, as multiple threads are executing this code. bitmap_->AtomicTestAndSet(FromCodeToAllocation(code)); } @@ -1493,7 +1499,7 @@ void JitCodeCache::GarbageCollectCache(Thread* self) { // interpreter will update its entry point to the compiled code and call it. for (ProfilingInfo* info : profiling_infos_) { const void* entry_point = info->GetMethod()->GetEntryPointFromQuickCompiledCode(); - if (ContainsPc(entry_point)) { + if (!IsInZygoteDataSpace(info) && ContainsPc(entry_point)) { info->SetSavedEntryPoint(entry_point); // Don't call Instrumentation::UpdateMethodsCode(), as it can check the declaring // class of the method. We may be concurrently running a GC which makes accessing @@ -1508,7 +1514,7 @@ void JitCodeCache::GarbageCollectCache(Thread* self) { // Change entry points of native methods back to the GenericJNI entrypoint. for (const auto& entry : jni_stubs_map_) { const JniStubData& data = entry.second; - if (!data.IsCompiled()) { + if (!data.IsCompiled() || IsInZygoteExecSpace(data.GetCode())) { continue; } // Make sure a single invocation of the GenericJNI trampoline tries to recompile. @@ -1540,7 +1546,9 @@ void JitCodeCache::RemoveUnmarkedCode(Thread* self) { // Iterate over all compiled code and remove entries that are not marked. for (auto it = jni_stubs_map_.begin(); it != jni_stubs_map_.end();) { JniStubData* data = &it->second; - if (!data->IsCompiled() || GetLiveBitmap()->Test(FromCodeToAllocation(data->GetCode()))) { + if (IsInZygoteExecSpace(data->GetCode()) || + !data->IsCompiled() || + GetLiveBitmap()->Test(FromCodeToAllocation(data->GetCode()))) { ++it; } else { method_headers.insert(OatQuickMethodHeader::FromCodePointer(data->GetCode())); @@ -1550,7 +1558,7 @@ void JitCodeCache::RemoveUnmarkedCode(Thread* self) { for (auto it = method_code_map_.begin(); it != method_code_map_.end();) { const void* code_ptr = it->first; uintptr_t allocation = FromCodeToAllocation(code_ptr); - if (GetLiveBitmap()->Test(allocation)) { + if (IsInZygoteExecSpace(code_ptr) || GetLiveBitmap()->Test(allocation)) { ++it; } else { OatQuickMethodHeader* header = OatQuickMethodHeader::FromCodePointer(code_ptr); @@ -1571,7 +1579,7 @@ void JitCodeCache::DoCollection(Thread* self, bool collect_profiling_info) { // Also remove the saved entry point from the ProfilingInfo objects. for (ProfilingInfo* info : profiling_infos_) { const void* ptr = info->GetMethod()->GetEntryPointFromQuickCompiledCode(); - if (!ContainsPc(ptr) && !info->IsInUseByCompiler()) { + if (!ContainsPc(ptr) && !info->IsInUseByCompiler() && !IsInZygoteDataSpace(info)) { info->GetMethod()->SetProfilingInfo(nullptr); } @@ -1596,6 +1604,9 @@ void JitCodeCache::DoCollection(Thread* self, bool collect_profiling_info) { for (const auto& entry : jni_stubs_map_) { const JniStubData& data = entry.second; const void* code_ptr = data.GetCode(); + if (IsInZygoteExecSpace(code_ptr)) { + continue; + } const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); for (ArtMethod* method : data.GetMethods()) { if (method_header->GetEntryPoint() == method->GetEntryPointFromQuickCompiledCode()) { @@ -1607,6 +1618,9 @@ void JitCodeCache::DoCollection(Thread* self, bool collect_profiling_info) { for (const auto& it : method_code_map_) { ArtMethod* method = it.second; const void* code_ptr = it.first; + if (IsInZygoteExecSpace(code_ptr)) { + continue; + } const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); if (method_header->GetEntryPoint() == method->GetEntryPointFromQuickCompiledCode()) { GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr)); @@ -1953,6 +1967,7 @@ bool JitCodeCache::NotifyCompilationOf(ArtMethod* method, Thread* self, bool osr instrumentation->UpdateNativeMethodsCodeToJitCode(m, entrypoint); } if (collection_in_progress_) { + CHECK(!IsInZygoteExecSpace(data->GetCode())); GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(data->GetCode())); } } @@ -2057,6 +2072,10 @@ uint8_t* JitCodeCache::AllocateCode(size_t code_size) { } void JitCodeCache::FreeCode(uint8_t* code) { + if (IsInZygoteExecSpace(code)) { + // No need to free, this is shared memory. + return; + } used_memory_for_code_ -= mspace_usable_size(code); mspace_free(exec_mspace_, code); } @@ -2068,6 +2087,10 @@ uint8_t* JitCodeCache::AllocateData(size_t data_size) { } void JitCodeCache::FreeData(uint8_t* data) { + if (IsInZygoteDataSpace(data)) { + // No need to free, this is shared memory. + return; + } used_memory_for_data_ -= mspace_usable_size(data); mspace_free(data_mspace_, data); } @@ -2091,13 +2114,11 @@ void JitCodeCache::Dump(std::ostream& os) { } void JitCodeCache::PostForkChildAction(bool is_system_server, bool is_zygote) { + if (is_zygote) { + // Don't transition if this is for a child zygote. + return; + } MutexLock mu(Thread::Current(), lock_); - // Currently, we don't expect any compilations from zygote. - CHECK_EQ(number_of_compilations_, 0u); - CHECK_EQ(number_of_osr_compilations_, 0u); - CHECK(jni_stubs_map_.empty()); - CHECK(method_code_map_.empty()); - CHECK(osr_code_map_.empty()); zygote_data_pages_ = std::move(data_pages_); zygote_exec_pages_ = std::move(exec_pages_); diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index 7a838fddd6..e2f3357ba2 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -71,6 +71,7 @@ template<class T> class ObjectArray; namespace jit { +class MarkCodeClosure; class ScopedCodeCacheWrite; // Alignment in bits that will suit all architectures. @@ -387,6 +388,14 @@ class JitCodeCache { const MemMap* GetUpdatableCodeMapping() const; + bool IsInZygoteDataSpace(const void* ptr) const { + return zygote_data_pages_.HasAddress(ptr); + } + + bool IsInZygoteExecSpace(const void* ptr) const { + return zygote_exec_pages_.HasAddress(ptr); + } + bool IsWeakAccessEnabled(Thread* self) const; void WaitUntilInlineCacheAccessible(Thread* self) REQUIRES(!lock_) @@ -487,6 +496,7 @@ class JitCodeCache { friend class art::JitJniStubTestHelper; friend class ScopedCodeCacheWrite; + friend class MarkCodeClosure; DISALLOW_COPY_AND_ASSIGN(JitCodeCache); }; diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 0f655b94de..b7ac1e8fe3 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -249,6 +249,13 @@ static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { return reinterpret_cast<jlong>(ThreadForEnv(env)); } +static void ZygoteHooks_nativePostZygoteFork(JNIEnv*, jclass) { + Runtime* runtime = Runtime::Current(); + if (runtime->IsZygote()) { + runtime->PostZygoteFork(); + } +} + static void ZygoteHooks_nativePostForkSystemServer(JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { // This JIT code cache for system server is created whilst the runtime is still single threaded. @@ -305,7 +312,7 @@ static void ZygoteHooks_nativePostForkChild(JNIEnv* env, /* is_system_server= */ false, is_zygote); } // This must be called after EnableDebugFeatures. - Runtime::Current()->GetJit()->PostForkChildAction(); + Runtime::Current()->GetJit()->PostForkChildAction(is_zygote); } // Update tracing. @@ -403,6 +410,7 @@ static void ZygoteHooks_stopZygoteNoThreadCreation(JNIEnv* env ATTRIBUTE_UNUSED, static JNINativeMethod gMethods[] = { NATIVE_METHOD(ZygoteHooks, nativePreFork, "()J"), + NATIVE_METHOD(ZygoteHooks, nativePostZygoteFork, "()V"), NATIVE_METHOD(ZygoteHooks, nativePostForkSystemServer, "()V"), NATIVE_METHOD(ZygoteHooks, nativePostForkChild, "(JIZZLjava/lang/String;)V"), NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V"), diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 8be3730629..182319a27c 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -600,9 +600,18 @@ void Runtime::Abort(const char* msg) { } void Runtime::PreZygoteFork() { + if (GetJit() != nullptr) { + GetJit()->PreZygoteFork(); + } heap_->PreZygoteFork(); } +void Runtime::PostZygoteFork() { + if (GetJit() != nullptr) { + GetJit()->PostZygoteFork(); + } +} + void Runtime::CallExitHook(jint status) { if (exit_ != nullptr) { ScopedThreadStateChange tsc(Thread::Current(), kNative); @@ -916,10 +925,6 @@ void Runtime::InitNonZygoteOrPostFork( } } - if (jit_ != nullptr) { - jit_->CreateThreadPool(); - } - if (thread_pool_ == nullptr) { constexpr size_t kStackSize = 64 * KB; constexpr size_t kMaxRuntimeWorkers = 4u; @@ -2448,6 +2453,8 @@ void Runtime::CreateJit() { LOG(WARNING) << "Failed to allocate JIT"; // Release JIT code cache resources (several MB of memory). jit_code_cache_.reset(); + } else { + jit->CreateThreadPool(); } } diff --git a/runtime/runtime.h b/runtime/runtime.h index 5450c0f057..45333761af 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -445,6 +445,7 @@ class Runtime { bool UseJitCompilation() const; void PreZygoteFork(); + void PostZygoteFork(); void InitNonZygoteOrPostFork( JNIEnv* env, bool is_system_server, diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc index 0f96510e86..de698c269f 100644 --- a/runtime/thread_pool.cc +++ b/runtime/thread_pool.cc @@ -136,26 +136,33 @@ ThreadPool::ThreadPool(const char* name, waiting_count_(0), start_time_(0), total_wait_time_(0), - // Add one since the caller of constructor waits on the barrier too. - creation_barier_(num_threads + 1), + creation_barier_(0), max_active_workers_(num_threads), - create_peers_(create_peers) { - while (GetThreadCount() < num_threads) { - const std::string worker_name = StringPrintf("%s worker thread %zu", name_.c_str(), - GetThreadCount()); - threads_.push_back(new ThreadPoolWorker(this, worker_name, worker_stack_size)); + create_peers_(create_peers), + worker_stack_size_(worker_stack_size) { + CreateThreads(); +} + +void ThreadPool::CreateThreads() { + CHECK(threads_.empty()); + Thread* self = Thread::Current(); + { + MutexLock mu(self, task_queue_lock_); + shutting_down_ = false; + // Add one since the caller of constructor waits on the barrier too. + creation_barier_.Init(self, max_active_workers_ + 1); + while (GetThreadCount() < max_active_workers_) { + const std::string worker_name = StringPrintf("%s worker thread %zu", name_.c_str(), + GetThreadCount()); + threads_.push_back( + new ThreadPoolWorker(this, worker_name, worker_stack_size_)); + } } // Wait for all of the threads to attach. creation_barier_.Wait(Thread::Current()); } -void ThreadPool::SetMaxActiveWorkers(size_t threads) { - MutexLock mu(Thread::Current(), task_queue_lock_); - CHECK_LE(threads, GetThreadCount()); - max_active_workers_ = threads; -} - -ThreadPool::~ThreadPool() { +void ThreadPool::DeleteThreads() { { Thread* self = Thread::Current(); MutexLock mu(self, task_queue_lock_); @@ -165,10 +172,22 @@ ThreadPool::~ThreadPool() { task_queue_condition_.Broadcast(self); completion_condition_.Broadcast(self); } - // Wait for the threads to finish. + // Wait for the threads to finish. We expect the user of the pool + // not to run multi-threaded calls to `CreateThreads` and `DeleteThreads`, + // so we don't guard the field here. STLDeleteElements(&threads_); } +void ThreadPool::SetMaxActiveWorkers(size_t max_workers) { + MutexLock mu(Thread::Current(), task_queue_lock_); + CHECK_LE(max_workers, GetThreadCount()); + max_active_workers_ = max_workers; +} + +ThreadPool::~ThreadPool() { + DeleteThreads(); +} + void ThreadPool::StartWorkers(Thread* self) { MutexLock mu(self, task_queue_lock_); started_ = true; diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h index fee009b7c0..f55d72ec30 100644 --- a/runtime/thread_pool.h +++ b/runtime/thread_pool.h @@ -129,6 +129,12 @@ class ThreadPool { size_t worker_stack_size = ThreadPoolWorker::kDefaultStackSize); virtual ~ThreadPool(); + // Create the threads of this pool. + void CreateThreads(); + + // Stops and deletes all threads in this pool. + void DeleteThreads(); + // Wait for all tasks currently on queue to get completed. If the pool has been stopped, only // wait till all already running tasks are done. // When the pool was created with peers for workers, do_work must not be true (see ThreadPool()). @@ -174,7 +180,6 @@ class ThreadPool { // How many worker threads are waiting on the condition. volatile size_t waiting_count_ GUARDED_BY(task_queue_lock_); std::deque<Task*> tasks_ GUARDED_BY(task_queue_lock_); - // TODO: make this immutable/const? std::vector<ThreadPoolWorker*> threads_; // Work balance detection. uint64_t start_time_ GUARDED_BY(task_queue_lock_); @@ -182,6 +187,7 @@ class ThreadPool { Barrier creation_barier_; size_t max_active_workers_ GUARDED_BY(task_queue_lock_); const bool create_peers_; + const size_t worker_stack_size_; private: friend class ThreadPoolWorker; diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index cde885c288..a19cc92fe8 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -48,6 +48,7 @@ jclass WellKnownClasses::dalvik_system_DexFile; jclass WellKnownClasses::dalvik_system_DexPathList; jclass WellKnownClasses::dalvik_system_DexPathList__Element; jclass WellKnownClasses::dalvik_system_EmulatedStackFrame; +jclass WellKnownClasses::dalvik_system_InMemoryDexClassLoader; jclass WellKnownClasses::dalvik_system_PathClassLoader; jclass WellKnownClasses::dalvik_system_VMRuntime; jclass WellKnownClasses::java_lang_annotation_Annotation__array; @@ -306,6 +307,7 @@ void WellKnownClasses::Init(JNIEnv* env) { dalvik_system_DexPathList = CacheClass(env, "dalvik/system/DexPathList"); dalvik_system_DexPathList__Element = CacheClass(env, "dalvik/system/DexPathList$Element"); dalvik_system_EmulatedStackFrame = CacheClass(env, "dalvik/system/EmulatedStackFrame"); + dalvik_system_InMemoryDexClassLoader = CacheClass(env, "dalvik/system/InMemoryDexClassLoader"); dalvik_system_PathClassLoader = CacheClass(env, "dalvik/system/PathClassLoader"); dalvik_system_VMRuntime = CacheClass(env, "dalvik/system/VMRuntime"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index 66cbbec916..f0e98a83d7 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -57,6 +57,7 @@ struct WellKnownClasses { static jclass dalvik_system_DexPathList; static jclass dalvik_system_DexPathList__Element; static jclass dalvik_system_EmulatedStackFrame; + static jclass dalvik_system_InMemoryDexClassLoader; static jclass dalvik_system_PathClassLoader; static jclass dalvik_system_VMRuntime; static jclass java_lang_annotation_Annotation__array; diff --git a/test/580-crc32/src/Main.java b/test/580-crc32/src/Main.java index 7fc1273600..6199e9b2a9 100644 --- a/test/580-crc32/src/Main.java +++ b/test/580-crc32/src/Main.java @@ -15,29 +15,29 @@ */ import java.util.zip.CRC32; +import java.util.Random; /** - * The ART compiler can use intrinsics for the java.util.zip.CRC32 method: - * private native static int update(int crc, int b) + * The ART compiler can use intrinsics for the java.util.zip.CRC32 methods: + * private native static int update(int crc, int b) + * private native static int updateBytes(int crc, byte[] b, int off, int len) * - * As the method is private it is not possible to check the use of intrinsics - * for it directly. + * As the methods are private it is not possible to check the use of intrinsics + * for them directly. * The tests check that correct checksums are produced. */ public class Main { - private static CRC32 crc32 = new CRC32(); - public Main() { } - public static long TestInt(int value) { - crc32.reset(); + public static long CRC32Byte(int value) { + CRC32 crc32 = new CRC32(); crc32.update(value); return crc32.getValue(); } - public static long TestInt(int... values) { - crc32.reset(); + public static long CRC32BytesUsingUpdateInt(int... values) { + CRC32 crc32 = new CRC32(); for (int value : values) { crc32.update(value); } @@ -50,82 +50,301 @@ public class Main { } } - public static void main(String args[]) { + private static void assertEqual(boolean expected, boolean actual) { + if (expected != actual) { + throw new Error("Expected: " + expected + ", found: " + actual); + } + } + + private static void TestCRC32Update() { // public void update(int b) // // Tests for checksums of the byte 0x0 - assertEqual(0xD202EF8DL, TestInt(0x0)); - assertEqual(0xD202EF8DL, TestInt(0x0100)); - assertEqual(0xD202EF8DL, TestInt(0x010000)); - assertEqual(0xD202EF8DL, TestInt(0x01000000)); - assertEqual(0xD202EF8DL, TestInt(0xff00)); - assertEqual(0xD202EF8DL, TestInt(0xffff00)); - assertEqual(0xD202EF8DL, TestInt(0xffffff00)); - assertEqual(0xD202EF8DL, TestInt(0x1200)); - assertEqual(0xD202EF8DL, TestInt(0x123400)); - assertEqual(0xD202EF8DL, TestInt(0x12345600)); - assertEqual(0xD202EF8DL, TestInt(Integer.MIN_VALUE)); + // Check that only the low eight bits of the argument are used. + assertEqual(0xD202EF8DL, CRC32Byte(0x0)); + assertEqual(0xD202EF8DL, CRC32Byte(0x0100)); + assertEqual(0xD202EF8DL, CRC32Byte(0x010000)); + assertEqual(0xD202EF8DL, CRC32Byte(0x01000000)); + assertEqual(0xD202EF8DL, CRC32Byte(0xff00)); + assertEqual(0xD202EF8DL, CRC32Byte(0xffff00)); + assertEqual(0xD202EF8DL, CRC32Byte(0xffffff00)); + assertEqual(0xD202EF8DL, CRC32Byte(0x1200)); + assertEqual(0xD202EF8DL, CRC32Byte(0x123400)); + assertEqual(0xD202EF8DL, CRC32Byte(0x12345600)); + assertEqual(0xD202EF8DL, CRC32Byte(Integer.MIN_VALUE)); // Tests for checksums of the byte 0x1 - assertEqual(0xA505DF1BL, TestInt(0x1)); - assertEqual(0xA505DF1BL, TestInt(0x0101)); - assertEqual(0xA505DF1BL, TestInt(0x010001)); - assertEqual(0xA505DF1BL, TestInt(0x01000001)); - assertEqual(0xA505DF1BL, TestInt(0xff01)); - assertEqual(0xA505DF1BL, TestInt(0xffff01)); - assertEqual(0xA505DF1BL, TestInt(0xffffff01)); - assertEqual(0xA505DF1BL, TestInt(0x1201)); - assertEqual(0xA505DF1BL, TestInt(0x123401)); - assertEqual(0xA505DF1BL, TestInt(0x12345601)); + // Check that only the low eight bits of the argument are used. + assertEqual(0xA505DF1BL, CRC32Byte(0x1)); + assertEqual(0xA505DF1BL, CRC32Byte(0x0101)); + assertEqual(0xA505DF1BL, CRC32Byte(0x010001)); + assertEqual(0xA505DF1BL, CRC32Byte(0x01000001)); + assertEqual(0xA505DF1BL, CRC32Byte(0xff01)); + assertEqual(0xA505DF1BL, CRC32Byte(0xffff01)); + assertEqual(0xA505DF1BL, CRC32Byte(0xffffff01)); + assertEqual(0xA505DF1BL, CRC32Byte(0x1201)); + assertEqual(0xA505DF1BL, CRC32Byte(0x123401)); + assertEqual(0xA505DF1BL, CRC32Byte(0x12345601)); // Tests for checksums of the byte 0x0f - assertEqual(0x42BDF21CL, TestInt(0x0f)); - assertEqual(0x42BDF21CL, TestInt(0x010f)); - assertEqual(0x42BDF21CL, TestInt(0x01000f)); - assertEqual(0x42BDF21CL, TestInt(0x0100000f)); - assertEqual(0x42BDF21CL, TestInt(0xff0f)); - assertEqual(0x42BDF21CL, TestInt(0xffff0f)); - assertEqual(0x42BDF21CL, TestInt(0xffffff0f)); - assertEqual(0x42BDF21CL, TestInt(0x120f)); - assertEqual(0x42BDF21CL, TestInt(0x12340f)); - assertEqual(0x42BDF21CL, TestInt(0x1234560f)); + // Check that only the low eight bits of the argument are used. + assertEqual(0x42BDF21CL, CRC32Byte(0x0f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x010f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x01000f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x0100000f)); + assertEqual(0x42BDF21CL, CRC32Byte(0xff0f)); + assertEqual(0x42BDF21CL, CRC32Byte(0xffff0f)); + assertEqual(0x42BDF21CL, CRC32Byte(0xffffff0f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x120f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x12340f)); + assertEqual(0x42BDF21CL, CRC32Byte(0x1234560f)); // Tests for checksums of the byte 0xff - assertEqual(0xFF000000L, TestInt(0x00ff)); - assertEqual(0xFF000000L, TestInt(0x01ff)); - assertEqual(0xFF000000L, TestInt(0x0100ff)); - assertEqual(0xFF000000L, TestInt(0x010000ff)); - assertEqual(0xFF000000L, TestInt(0x0000ffff)); - assertEqual(0xFF000000L, TestInt(0x00ffffff)); - assertEqual(0xFF000000L, TestInt(0xffffffff)); - assertEqual(0xFF000000L, TestInt(0x12ff)); - assertEqual(0xFF000000L, TestInt(0x1234ff)); - assertEqual(0xFF000000L, TestInt(0x123456ff)); - assertEqual(0xFF000000L, TestInt(Integer.MAX_VALUE)); + // Check that only the low eight bits of the argument are used. + assertEqual(0xFF000000L, CRC32Byte(0x00ff)); + assertEqual(0xFF000000L, CRC32Byte(0x01ff)); + assertEqual(0xFF000000L, CRC32Byte(0x0100ff)); + assertEqual(0xFF000000L, CRC32Byte(0x010000ff)); + assertEqual(0xFF000000L, CRC32Byte(0x0000ffff)); + assertEqual(0xFF000000L, CRC32Byte(0x00ffffff)); + assertEqual(0xFF000000L, CRC32Byte(0xffffffff)); + assertEqual(0xFF000000L, CRC32Byte(0x12ff)); + assertEqual(0xFF000000L, CRC32Byte(0x1234ff)); + assertEqual(0xFF000000L, CRC32Byte(0x123456ff)); + assertEqual(0xFF000000L, CRC32Byte(Integer.MAX_VALUE)); // Tests for sequences - assertEqual(0xFF41D912L, TestInt(0, 0, 0)); - assertEqual(0xFF41D912L, TestInt(0x0100, 0x010000, 0x01000000)); - assertEqual(0xFF41D912L, TestInt(0xff00, 0xffff00, 0xffffff00)); - assertEqual(0xFF41D912L, TestInt(0x1200, 0x123400, 0x12345600)); + // Check that only the low eight bits of the values are used. + assertEqual(0xFF41D912L, CRC32BytesUsingUpdateInt(0, 0, 0)); + assertEqual(0xFF41D912L, + CRC32BytesUsingUpdateInt(0x0100, 0x010000, 0x01000000)); + assertEqual(0xFF41D912L, + CRC32BytesUsingUpdateInt(0xff00, 0xffff00, 0xffffff00)); + assertEqual(0xFF41D912L, + CRC32BytesUsingUpdateInt(0x1200, 0x123400, 0x12345600)); + + assertEqual(0x909FB2F2L, CRC32BytesUsingUpdateInt(1, 1, 1)); + assertEqual(0x909FB2F2L, + CRC32BytesUsingUpdateInt(0x0101, 0x010001, 0x01000001)); + assertEqual(0x909FB2F2L, + CRC32BytesUsingUpdateInt(0xff01, 0xffff01, 0xffffff01)); + assertEqual(0x909FB2F2L, + CRC32BytesUsingUpdateInt(0x1201, 0x123401, 0x12345601)); + + assertEqual(0xE33A9F71L, CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f)); + assertEqual(0xE33A9F71L, + CRC32BytesUsingUpdateInt(0x010f, 0x01000f, 0x0100000f)); + assertEqual(0xE33A9F71L, + CRC32BytesUsingUpdateInt(0xff0f, 0xffff0f, 0xffffff0f)); + assertEqual(0xE33A9F71L, + CRC32BytesUsingUpdateInt(0x120f, 0x12340f, 0x1234560f)); + + assertEqual(0xFFFFFF00L, CRC32BytesUsingUpdateInt(0x0ff, 0x0ff, 0x0ff)); + assertEqual(0xFFFFFF00L, + CRC32BytesUsingUpdateInt(0x01ff, 0x0100ff, 0x010000ff)); + assertEqual(0xFFFFFF00L, + CRC32BytesUsingUpdateInt(0x00ffff, 0x00ffffff, 0xffffffff)); + assertEqual(0xFFFFFF00L, + CRC32BytesUsingUpdateInt(0x12ff, 0x1234ff, 0x123456ff)); + + assertEqual(0xB6CC4292L, CRC32BytesUsingUpdateInt(0x01, 0x02)); + + assertEqual(0xB2DE047CL, + CRC32BytesUsingUpdateInt(0x0, -1, Integer.MIN_VALUE, Integer.MAX_VALUE)); + } + + private static long CRC32ByteArray(byte[] bytes, int off, int len) { + CRC32 crc32 = new CRC32(); + crc32.update(bytes, off, len); + return crc32.getValue(); + } + + // This is used to test we generate correct code for constant offsets. + // In this case the offset is 0. + private static long CRC32ByteArray(byte[] bytes) { + CRC32 crc32 = new CRC32(); + crc32.update(bytes); + return crc32.getValue(); + } + + private static long CRC32ByteAndByteArray(int value, byte[] bytes) { + CRC32 crc32 = new CRC32(); + crc32.update(value); + crc32.update(bytes); + return crc32.getValue(); + } + + private static long CRC32ByteArrayAndByte(byte[] bytes, int value) { + CRC32 crc32 = new CRC32(); + crc32.update(bytes); + crc32.update(value); + return crc32.getValue(); + } + + private static boolean CRC32ByteArrayThrowsAIOOBE(byte[] bytes, int off, int len) { + try { + CRC32 crc32 = new CRC32(); + crc32.update(bytes, off, len); + } catch (ArrayIndexOutOfBoundsException ex) { + return true; + } + return false; + } + + private static boolean CRC32ByteArrayThrowsNPE() { + try { + CRC32 crc32 = new CRC32(); + crc32.update(null, 0, 0); + return false; + } catch (NullPointerException e) {} + + try { + CRC32 crc32 = new CRC32(); + crc32.update(null, 1, 2); + return false; + } catch (NullPointerException e) {} - assertEqual(0x909FB2F2L, TestInt(1, 1, 1)); - assertEqual(0x909FB2F2L, TestInt(0x0101, 0x010001, 0x01000001)); - assertEqual(0x909FB2F2L, TestInt(0xff01, 0xffff01, 0xffffff01)); - assertEqual(0x909FB2F2L, TestInt(0x1201, 0x123401, 0x12345601)); + try { + CRC32 crc32 = new CRC32(); + crc32.update((byte[])null); + return false; + } catch (NullPointerException e) {} + + return true; + } + + private static long CRC32BytesUsingUpdateInt(byte[] bytes, int off, int len) { + CRC32 crc32 = new CRC32(); + while (len-- > 0) { + crc32.update(bytes[off++]); + } + return crc32.getValue(); + } - assertEqual(0xE33A9F71L, TestInt(0x0f, 0x0f, 0x0f)); - assertEqual(0xE33A9F71L, TestInt(0x010f, 0x01000f, 0x0100000f)); - assertEqual(0xE33A9F71L, TestInt(0xff0f, 0xffff0f, 0xffffff0f)); - assertEqual(0xE33A9F71L, TestInt(0x120f, 0x12340f, 0x1234560f)); + private static void TestCRC32UpdateBytes() { + assertEqual(0L, CRC32ByteArray(new byte[] {})); + assertEqual(0L, CRC32ByteArray(new byte[] {}, 0, 0)); + assertEqual(0L, CRC32ByteArray(new byte[] {0}, 0, 0)); + assertEqual(0L, CRC32ByteArray(new byte[] {0}, 1, 0)); + assertEqual(0L, CRC32ByteArray(new byte[] {0, 0}, 1, 0)); - assertEqual(0xFFFFFF00L, TestInt(0x0ff, 0x0ff, 0x0ff)); - assertEqual(0xFFFFFF00L, TestInt(0x01ff, 0x0100ff, 0x010000ff)); - assertEqual(0xFFFFFF00L, TestInt(0x00ffff, 0x00ffffff, 0xffffffff)); - assertEqual(0xFFFFFF00L, TestInt(0x12ff, 0x1234ff, 0x123456ff)); + assertEqual(true, CRC32ByteArrayThrowsNPE()); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, -1, 0)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, -1, 1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, 0, -1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, -1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 1, 0)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, -1, 1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 1, -1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, 1)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, 10)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, 0, 10)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 10, 10)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0, 0, 0, 0}, 2, 3)); + assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0, 0, 0, 0}, 3, 2)); - assertEqual(0xB6CC4292L, TestInt(0x01, 0x02)); + assertEqual(CRC32Byte(0), CRC32ByteArray(new byte[] {0})); + assertEqual(CRC32Byte(0), CRC32ByteArray(new byte[] {0}, 0, 1)); + assertEqual(CRC32Byte(1), CRC32ByteArray(new byte[] {1})); + assertEqual(CRC32Byte(1), CRC32ByteArray(new byte[] {1}, 0, 1)); + assertEqual(CRC32Byte(0x0f), CRC32ByteArray(new byte[] {0x0f})); + assertEqual(CRC32Byte(0x0f), CRC32ByteArray(new byte[] {0x0f}, 0, 1)); + assertEqual(CRC32Byte(0xff), CRC32ByteArray(new byte[] {-1})); + assertEqual(CRC32Byte(0xff), CRC32ByteArray(new byte[] {-1}, 0, 1)); + assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0), + CRC32ByteArray(new byte[] {0, 0, 0})); + assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0), + CRC32ByteArray(new byte[] {0, 0, 0}, 0, 3)); + assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1), + CRC32ByteArray(new byte[] {1, 1, 1})); + assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1), + CRC32ByteArray(new byte[] {1, 1, 1}, 0, 3)); + assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f), + CRC32ByteArray(new byte[] {0x0f, 0x0f, 0x0f})); + assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f), + CRC32ByteArray(new byte[] {0x0f, 0x0f, 0x0f}, 0, 3)); + assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff), + CRC32ByteArray(new byte[] {-1, -1, -1})); + assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff), + CRC32ByteArray(new byte[] {-1, -1, -1}, 0, 3)); + assertEqual(CRC32BytesUsingUpdateInt(1, 2), + CRC32ByteArray(new byte[] {1, 2})); + assertEqual(CRC32BytesUsingUpdateInt(1, 2), + CRC32ByteArray(new byte[] {1, 2}, 0, 2)); + assertEqual( + CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE), + CRC32ByteArray(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE})); + assertEqual( + CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE), + CRC32ByteArray(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE}, 0, 4)); - assertEqual(0xB2DE047CL, TestInt(0x0, -1, Integer.MIN_VALUE, Integer.MAX_VALUE)); + assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0), + CRC32ByteAndByteArray(0, new byte[] {0, 0})); + assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1), + CRC32ByteAndByteArray(1, new byte[] {1, 1})); + assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f), + CRC32ByteAndByteArray(0x0f, new byte[] {0x0f, 0x0f})); + assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff), + CRC32ByteAndByteArray(-1, new byte[] {-1, -1})); + assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3), + CRC32ByteAndByteArray(1, new byte[] {2, 3})); + assertEqual( + CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE), + CRC32ByteAndByteArray(0, new byte[] {-1, Byte.MIN_VALUE, Byte.MAX_VALUE})); + + assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0), + CRC32ByteArrayAndByte(new byte[] {0, 0}, 0)); + assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1), + CRC32ByteArrayAndByte(new byte[] {1, 1}, 1)); + assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f), + CRC32ByteArrayAndByte(new byte[] {0x0f, 0x0f}, 0x0f)); + assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff), + CRC32ByteArrayAndByte(new byte[] {-1, -1}, -1)); + assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3), + CRC32ByteArrayAndByte(new byte[] {1, 2}, 3)); + assertEqual( + CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE), + CRC32ByteArrayAndByte(new byte[] {0, -1, Byte.MIN_VALUE}, Byte.MAX_VALUE)); + + byte[] bytes = new byte[128 * 1024]; + Random rnd = new Random(0); + rnd.nextBytes(bytes); + + assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, bytes.length), + CRC32ByteArray(bytes)); + assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, 8 * 1024), + CRC32ByteArray(bytes, 0, 8 * 1024)); + + int off = rnd.nextInt(bytes.length / 2); + for (int len = 0; len <= 16; ++len) { + assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len), + CRC32ByteArray(bytes, off, len)); + } + + // Check there are no issues with unaligned accesses. + for (int o = 1; o < 8; ++o) { + for (int l = 0; l <= 16; ++l) { + assertEqual(CRC32BytesUsingUpdateInt(bytes, o, l), + CRC32ByteArray(bytes, o, l)); + } + } + + int len = bytes.length / 2; + assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len - 1), + CRC32ByteArray(bytes, 0, len - 1)); + assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len), + CRC32ByteArray(bytes, 0, len)); + assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len + 1), + CRC32ByteArray(bytes, 0, len + 1)); + + len = rnd.nextInt(bytes.length + 1); + off = rnd.nextInt(bytes.length - len); + assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len), + CRC32ByteArray(bytes, off, len)); + } + + public static void main(String args[]) { + TestCRC32Update(); + TestCRC32UpdateBytes(); } } diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java index 6305185471..433c2c7012 100644 --- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java +++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java @@ -17,7 +17,6 @@ package com.android.class2greylist; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet; @@ -35,16 +34,10 @@ import org.apache.commons.cli.ParseException; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.HashMap; import java.util.Set; -import java.util.function.Predicate; /** * Build time tool for extracting a list of members from jar files that have the @UsedByApps @@ -193,12 +186,31 @@ public class Class2Greylist { new UnsupportedAppUsageAnnotationHandler( mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP); GREYLIST_ANNOTATIONS.forEach(a -> builder.put(a, greylistAnnotationHandler)); + + CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler( + mOutput, mPublicApis, FLAG_WHITELIST); + + return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME, + CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler) + .build(); + } + + /** + * Add a handler for an annotation as well as an handler for the container annotation that is + * used when the annotation is repeated. + * + * @param builder the builder for the map to which the handlers will be added. + * @param annotationName the name of the annotation. + * @param containerAnnotationName the name of the annotation container. + * @param handler the handler for the annotation. + */ + private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers( + Builder<String, AnnotationHandler> builder, + String annotationName, String containerAnnotationName, + AnnotationHandler handler) { return builder - .put(CovariantReturnTypeHandler.ANNOTATION_NAME, - new CovariantReturnTypeHandler(mOutput, mPublicApis, FLAG_WHITELIST)) - .put(CovariantReturnTypeMultiHandler.ANNOTATION_NAME, - new CovariantReturnTypeMultiHandler(mOutput, mPublicApis, FLAG_WHITELIST)) - .build(); + .put(annotationName, handler) + .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler)); } private void main() throws IOException { diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java index b8de7e92fa..64d8997850 100644 --- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java +++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java @@ -30,6 +30,8 @@ public class CovariantReturnTypeHandler extends AnnotationHandler { private static final String SHORT_NAME = "CovariantReturnType"; public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;"; + public static final String REPEATED_ANNOTATION_NAME = + "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;"; private static final String RETURN_TYPE = "returnType"; diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java b/tools/class2greylist/src/com/android/class2greylist/RepeatedAnnotationHandler.java index f2bc5254fa..61949e3972 100644 --- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java +++ b/tools/class2greylist/src/com/android/class2greylist/RepeatedAnnotationHandler.java @@ -1,42 +1,26 @@ package com.android.class2greylist; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; - import org.apache.bcel.classfile.AnnotationElementValue; import org.apache.bcel.classfile.AnnotationEntry; import org.apache.bcel.classfile.ArrayElementValue; import org.apache.bcel.classfile.ElementValue; import org.apache.bcel.classfile.ElementValuePair; -import java.util.Set; - /** - * Handles {@code CovariantReturnType$CovariantReturnTypes} annotations, which - * are generated by the compiler when multiple {@code CovariantReturnType} - * annotations appear on a single method. + * Handles a repeated annotation container. * - * <p>The enclosed annotations are passed to {@link CovariantReturnTypeHandler}. + * <p>The enclosed annotations are passed to the {@link #mWrappedHandler}. */ -public class CovariantReturnTypeMultiHandler extends AnnotationHandler { - - public static final String ANNOTATION_NAME = - "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;"; +public class RepeatedAnnotationHandler extends AnnotationHandler { private static final String VALUE = "value"; - private final CovariantReturnTypeHandler mWrappedHandler; + private final AnnotationHandler mWrappedHandler; private final String mInnerAnnotationName; - public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis, - String hiddenapiFlag) { - this(consumer, publicApis, hiddenapiFlag, CovariantReturnTypeHandler.ANNOTATION_NAME); - } - - @VisibleForTesting - public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis, - String hiddenapiFlag, String innerAnnotationName) { - mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis, hiddenapiFlag); + RepeatedAnnotationHandler(String innerAnnotationName, AnnotationHandler wrappedHandler) { + mWrappedHandler = wrappedHandler; mInnerAnnotationName = innerAnnotationName; } @@ -45,7 +29,7 @@ public class CovariantReturnTypeMultiHandler extends AnnotationHandler { // Verify that the annotation has the form we expect ElementValuePair value = findValue(annotation); if (value == null) { - context.reportError("No value found on CovariantReturnType$CovariantReturnTypes"); + context.reportError("No value found on %s", annotation.getAnnotationType()); return; } Preconditions.checkArgument(value.getValue() instanceof ArrayElementValue); diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java deleted file mode 100644 index 25f284455b..0000000000 --- a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -package com.android.class2greylist; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import static java.util.Collections.emptySet; - -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.util.Map; - -public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase { - - private static final String FLAG = "test-flag"; - - @Before - public void setup() throws IOException { - // To keep the test simpler and more concise, we don't use the real - // @CovariantReturnType annotation here, but use our own @Annotation - // and @Annotation.Multi that have the same semantics. It doesn't have - // to match the real annotation, just have the same properties - // (returnType and value). - mJavac.addSource("annotation.Annotation", Joiner.on('\n').join( - "package annotation;", - "import static java.lang.annotation.RetentionPolicy.CLASS;", - "import java.lang.annotation.Repeatable;", - "import java.lang.annotation.Retention;", - "@Repeatable(Annotation.Multi.class)", - "@Retention(CLASS)", - "public @interface Annotation {", - " Class<?> returnType();", - " @Retention(CLASS)", - " @interface Multi {", - " Annotation[] value();", - " }", - "}")); - } - - @Test - public void testReturnTypeMulti() throws IOException { - mJavac.addSource("a.b.Class", Joiner.on('\n').join( - "package a.b;", - "import annotation.Annotation;", - "public class Class {", - " @Annotation(returnType=Integer.class)", - " @Annotation(returnType=Long.class)", - " public String method() {return null;}", - "}")); - mJavac.compile(); - - Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of("Lannotation/Annotation$Multi;", - new CovariantReturnTypeMultiHandler( - mConsumer, - ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"), - FLAG, - "Lannotation/Annotation;")); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); - - assertNoErrors(); - ArgumentCaptor<String> whitelist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(2)).consume(whitelist.capture(), any(), - eq(ImmutableSet.of(FLAG))); - assertThat(whitelist.getAllValues()).containsExactly( - "La/b/Class;->method()Ljava/lang/Integer;", - "La/b/Class;->method()Ljava/lang/Long;"); - } - - @Test - public void testReturnTypeMultiNotPublicApi() throws IOException { - mJavac.addSource("a.b.Class", Joiner.on('\n').join( - "package a.b;", - "import annotation.Annotation;", - "public class Class {", - " @Annotation(returnType=Integer.class)", - " @Annotation(returnType=Long.class)", - " public String method() {return null;}", - "}")); - mJavac.compile(); - - Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of("Lannotation/Annotation$Multi;", - new CovariantReturnTypeMultiHandler( - mConsumer, - emptySet(), - FLAG, - "Lannotation/Annotation;")); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); - - verify(mStatus, atLeastOnce()).error(any(), any()); - } -} diff --git a/tools/class2greylist/test/src/com/android/class2greylist/RepeatedAnnotationHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/RepeatedAnnotationHandlerTest.java new file mode 100644 index 0000000000..f2f70ee1ba --- /dev/null +++ b/tools/class2greylist/test/src/com/android/class2greylist/RepeatedAnnotationHandlerTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 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. + */ + +package com.android.class2greylist; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.bcel.classfile.AnnotationEntry; +import org.junit.Before; +import org.junit.Test; + +public class RepeatedAnnotationHandlerTest extends AnnotationHandlerTestBase { + + @Before + public void setup() { + // To keep the test simpler and more concise, we don't use a real annotation here, but use + // our own @Annotation and @Annotation.Multi that have the same relationship. + mJavac.addSource("annotation.Annotation", Joiner.on('\n').join( + "package annotation;", + "import static java.lang.annotation.RetentionPolicy.CLASS;", + "import java.lang.annotation.Repeatable;", + "import java.lang.annotation.Retention;", + "@Repeatable(Annotation.Multi.class)", + "@Retention(CLASS)", + "public @interface Annotation {", + " Class<?> clazz();", + " @Retention(CLASS)", + " @interface Multi {", + " Annotation[] value();", + " }", + "}")); + } + + @Test + public void testRepeated() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Annotation;", + "public class Class {", + " @Annotation(clazz=Integer.class)", + " @Annotation(clazz=Long.class)", + " public String method() {return null;}", + "}")); + mJavac.compile(); + + TestAnnotationHandler handler = new TestAnnotationHandler(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of("Lannotation/Annotation$Multi;", + new RepeatedAnnotationHandler("Lannotation/Annotation;", handler)); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); + + assertNoErrors(); + assertThat(handler.getClasses()).containsExactly( + "Ljava/lang/Integer;", + "Ljava/lang/Long;"); + } + + private static class TestAnnotationHandler extends AnnotationHandler { + + private final List<String> classes; + + private TestAnnotationHandler() { + this.classes = new ArrayList<>(); + } + + @Override + void handleAnnotation(AnnotationEntry annotation, + AnnotationContext context) { + classes.add(annotation.getElementValuePairs()[0].getValue().stringifyValue()); + } + + private List<String> getClasses() { + return classes; + } + } +} |