diff options
280 files changed, 16756 insertions, 2882 deletions
diff --git a/Android.mk b/Android.mk index 7de07a89a0..7eb0bf926e 100644 --- a/Android.mk +++ b/Android.mk @@ -81,7 +81,6 @@ include $(art_path)/tools/Android.mk include $(art_path)/tools/ahat/Android.mk include $(art_path)/tools/dexfuzz/Android.mk include $(art_path)/libart_fake/Android.mk -include $(art_path)/test/Android.run-test-jvmti-java-library.mk ART_HOST_DEPENDENCIES := \ $(ART_HOST_EXECUTABLES) \ @@ -375,6 +374,26 @@ LOCAL_REQUIRED_MODULES := \ # For nosy apps, we provide a fake library that avoids namespace issues and gives some warnings. LOCAL_REQUIRED_MODULES += libart_fake +# Potentially add in debug variants: +# +# * We will never add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = false. +# * We will always add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = true. +# * Otherwise, we will add them by default to userdebug and eng builds. +art_target_include_debug_build := $(PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD) +ifneq (false,$(art_target_include_debug_build)) +ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) + art_target_include_debug_build := true +endif +ifeq (true,$(art_target_include_debug_build)) +LOCAL_REQUIRED_MODULES += \ + libartd \ + libartd-compiler \ + libopenjdkjvmd \ + libopenjdkjvmtid \ + +endif +endif + include $(BUILD_PHONY_PACKAGE) # The art-tools package depends on helpers and tools that are useful for developers and on-device diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..7297a14da2 --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +ngeoffray@google.com +sehr@google.com +* diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index c87abe5664..b6ffcc54f7 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -30,6 +30,10 @@ GTEST_DEX_DIRECTORIES := \ ErroneousA \ ErroneousB \ ErroneousInit \ + ForClassLoaderA \ + ForClassLoaderB \ + ForClassLoaderC \ + ForClassLoaderD \ ExceptionHandle \ GetMethodSignature \ ImageLayoutA \ @@ -99,7 +103,7 @@ $(ART_TEST_TARGET_GTEST_VerifierDepsMulti_DEX): $(ART_TEST_GTEST_VerifierDepsMul ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces -ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode +ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode ART_GTEST_class_table_test_DEX_DEPS := XandY ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes diff --git a/build/art.go b/build/art.go index f52c63525a..1b9c646433 100644 --- a/build/art.go +++ b/build/art.go @@ -19,8 +19,6 @@ import ( "android/soong/cc" "fmt" "sync" - - "github.com/google/blueprint" ) var supportedArches = []string{"arm", "arm64", "mips", "mips64", "x86", "x86_64"} @@ -83,20 +81,20 @@ func globalFlags(ctx android.BaseContext) ([]string, []string) { // the debug version. So make the gap consistent (and adjust for the worst). if len(ctx.AConfig().SanitizeDevice()) > 0 || len(ctx.AConfig().SanitizeHost()) > 0 { cflags = append(cflags, - "-DART_STACK_OVERFLOW_GAP_arm=8192", - "-DART_STACK_OVERFLOW_GAP_arm64=8192", - "-DART_STACK_OVERFLOW_GAP_mips=16384", - "-DART_STACK_OVERFLOW_GAP_mips64=16384", - "-DART_STACK_OVERFLOW_GAP_x86=16384", - "-DART_STACK_OVERFLOW_GAP_x86_64=20480") + "-DART_STACK_OVERFLOW_GAP_arm=8192", + "-DART_STACK_OVERFLOW_GAP_arm64=8192", + "-DART_STACK_OVERFLOW_GAP_mips=16384", + "-DART_STACK_OVERFLOW_GAP_mips64=16384", + "-DART_STACK_OVERFLOW_GAP_x86=16384", + "-DART_STACK_OVERFLOW_GAP_x86_64=20480") } else { cflags = append(cflags, - "-DART_STACK_OVERFLOW_GAP_arm=8192", - "-DART_STACK_OVERFLOW_GAP_arm64=8192", - "-DART_STACK_OVERFLOW_GAP_mips=16384", - "-DART_STACK_OVERFLOW_GAP_mips64=16384", - "-DART_STACK_OVERFLOW_GAP_x86=8192", - "-DART_STACK_OVERFLOW_GAP_x86_64=8192") + "-DART_STACK_OVERFLOW_GAP_arm=8192", + "-DART_STACK_OVERFLOW_GAP_arm64=8192", + "-DART_STACK_OVERFLOW_GAP_mips=16384", + "-DART_STACK_OVERFLOW_GAP_mips64=16384", + "-DART_STACK_OVERFLOW_GAP_x86=8192", + "-DART_STACK_OVERFLOW_GAP_x86_64=8192") } return cflags, asflags @@ -168,10 +166,10 @@ func globalDefaults(ctx android.LoadHookContext) { Cflags []string } } - Cflags []string - Asflags []string + Cflags []string + Asflags []string Sanitize struct { - Recover []string + Recover []string } } @@ -182,7 +180,7 @@ func globalDefaults(ctx android.LoadHookContext) { if envTrue(ctx, "ART_DEX_FILE_ACCESS_TRACKING") { p.Cflags = append(p.Cflags, "-DART_DEX_FILE_ACCESS_TRACKING") - p.Sanitize.Recover = []string { + p.Sanitize.Recover = []string{ "address", } } @@ -266,67 +264,67 @@ func init() { android.RegisterModuleType("art_debug_defaults", artDebugDefaultsFactory) } -func artGlobalDefaultsFactory() (blueprint.Module, []interface{}) { - module, props := artDefaultsFactory() +func artGlobalDefaultsFactory() android.Module { + module := artDefaultsFactory() android.AddLoadHook(module, globalDefaults) - return module, props + return module } -func artDebugDefaultsFactory() (blueprint.Module, []interface{}) { - module, props := artDefaultsFactory() +func artDebugDefaultsFactory() android.Module { + module := artDefaultsFactory() android.AddLoadHook(module, debugDefaults) - return module, props + return module } -func artDefaultsFactory() (blueprint.Module, []interface{}) { +func artDefaultsFactory() android.Module { c := &codegenProperties{} - module, props := cc.DefaultsFactory(c) + module := cc.DefaultsFactory(c) android.AddLoadHook(module, func(ctx android.LoadHookContext) { codegen(ctx, c, true) }) - return module, props + return module } -func artLibrary() (blueprint.Module, []interface{}) { +func artLibrary() android.Module { library, _ := cc.NewLibrary(android.HostAndDeviceSupported) - module, props := library.Init() + module := library.Init() - props = installCodegenCustomizer(module, props, true) + installCodegenCustomizer(module, true) - return module, props + return module } -func artBinary() (blueprint.Module, []interface{}) { +func artBinary() android.Module { binary, _ := cc.NewBinary(android.HostAndDeviceSupported) - module, props := binary.Init() + module := binary.Init() android.AddLoadHook(module, customLinker) android.AddLoadHook(module, prefer32Bit) - return module, props + return module } -func artTest() (blueprint.Module, []interface{}) { +func artTest() android.Module { test := cc.NewTest(android.HostAndDeviceSupported) - module, props := test.Init() + module := test.Init() - props = installCodegenCustomizer(module, props, false) + installCodegenCustomizer(module, false) android.AddLoadHook(module, customLinker) android.AddLoadHook(module, prefer32Bit) android.AddInstallHook(module, testInstall) - return module, props + return module } -func artTestLibrary() (blueprint.Module, []interface{}) { +func artTestLibrary() android.Module { test := cc.NewTestLibrary(android.HostAndDeviceSupported) - module, props := test.Init() + module := test.Init() - props = installCodegenCustomizer(module, props, false) + installCodegenCustomizer(module, false) android.AddLoadHook(module, prefer32Bit) android.AddInstallHook(module, testInstall) - return module, props + return module } func envDefault(ctx android.BaseContext, key string, defaultValue string) string { diff --git a/build/codegen.go b/build/codegen.go index ba6f2142c9..8526bf192b 100644 --- a/build/codegen.go +++ b/build/codegen.go @@ -22,8 +22,6 @@ import ( "android/soong/android" "sort" "strings" - - "github.com/google/blueprint" ) func codegen(ctx android.LoadHookContext, c *codegenProperties, library bool) { @@ -159,10 +157,8 @@ func defaultDeviceCodegenArches(ctx android.LoadHookContext) []string { return ret } -func installCodegenCustomizer(module blueprint.Module, props []interface{}, library bool) []interface{} { +func installCodegenCustomizer(module android.Module, library bool) { c := &codegenProperties{} android.AddLoadHook(module, func(ctx android.LoadHookContext) { codegen(ctx, c, library) }) - props = append(props, c) - - return props + module.AddProperties(c) } diff --git a/compiler/Android.bp b/compiler/Android.bp index a1269dcaf9..761f53fa5a 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -356,6 +356,7 @@ art_cc_test { "optimizing/live_interval_test.cc", "optimizing/loop_optimization_test.cc", "optimizing/nodes_test.cc", + "optimizing/nodes_vector_test.cc", "optimizing/parallel_move_test.cc", "optimizing/pretty_printer_test.cc", "optimizing/reference_type_propagation_test.cc", @@ -429,13 +430,20 @@ art_cc_test { shared_libs: [ "libartd-compiler", - "libartd-simulator", "libvixld-arm", "libvixld-arm64", "libbacktrace", "libnativeloader", ], + + target: { + host: { + shared_libs: [ + "libartd-simulator", + ], + }, + }, } art_cc_test { @@ -455,6 +463,7 @@ art_cc_test { srcs: [ "optimizing/emit_swap_mips_test.cc", "utils/mips/assembler_mips_test.cc", + "utils/mips/assembler_mips32r5_test.cc", "utils/mips/assembler_mips32r6_test.cc", ], }, diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 8cc1cc38e2..db95bd6e03 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -102,46 +102,17 @@ inline std::pair<bool, bool> CompilerDriver::IsFastInstanceField( return std::make_pair(fast_get, fast_put); } -template <typename ArtMember> -inline bool CompilerDriver::CanAccessResolvedMember(mirror::Class* referrer_class ATTRIBUTE_UNUSED, - mirror::Class* access_to ATTRIBUTE_UNUSED, - ArtMember* member ATTRIBUTE_UNUSED, - mirror::DexCache* dex_cache ATTRIBUTE_UNUSED, - uint32_t field_idx ATTRIBUTE_UNUSED) { - // Not defined for ArtMember values other than ArtField or ArtMethod. - UNREACHABLE(); -} - -template <> -inline bool CompilerDriver::CanAccessResolvedMember<ArtField>(mirror::Class* referrer_class, - mirror::Class* access_to, - ArtField* field, - mirror::DexCache* dex_cache, - uint32_t field_idx) { - return referrer_class->CanAccessResolvedField(access_to, field, dex_cache, field_idx); -} - -template <> -inline bool CompilerDriver::CanAccessResolvedMember<ArtMethod>( - mirror::Class* referrer_class, - mirror::Class* access_to, - ArtMethod* method, - mirror::DexCache* dex_cache, - uint32_t field_idx) { - return referrer_class->CanAccessResolvedMethod(access_to, method, dex_cache, field_idx); -} - inline ArtMethod* CompilerDriver::ResolveMethod( - ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache, - Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit, - uint32_t method_idx, InvokeType invoke_type, bool check_incompatible_class_change) { + ScopedObjectAccess& soa, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + const DexCompilationUnit* mUnit, + uint32_t method_idx, + InvokeType invoke_type) { DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get()); ArtMethod* resolved_method = - check_incompatible_class_change - ? mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>( - *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type) - : mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kNoICCECheckForCache>( - *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type); + mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>( + *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type); if (UNLIKELY(resolved_method == nullptr)) { DCHECK(soa.Self()->IsExceptionPending()); // Clean up any exception left by type resolution. diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 622448fc59..bb64755c9e 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -87,6 +87,10 @@ static constexpr bool kTimeCompileMethod = !kIsDebugBuild; // Print additional info during profile guided compilation. static constexpr bool kDebugProfileGuidedCompilation = false; +// Max encoded fields allowed for initializing app image. Hardcode the number for now +// because 5000 should be large enough. +static constexpr uint32_t kMaxEncodedFields = 5000; + static double Percentage(size_t x, size_t y) { return 100.0 * (static_cast<double>(x)) / (static_cast<double>(x + y)); } @@ -2244,6 +2248,10 @@ class InitializeClassVisitor : public CompilationVisitor { mirror::Class::Status old_status = klass->GetStatus();; // Only try to initialize classes that were successfully verified. if (klass->IsVerified()) { + // Don't initialize classes in boot space when compiling app image + if (is_app_image && klass->IsBootStrapClassLoaded()) { + return; + } // Attempt to initialize the class but bail if we either need to initialize the super-class // or static fields. manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, false); @@ -2261,12 +2269,25 @@ class InitializeClassVisitor : public CompilationVisitor { ObjectLock<mirror::Class> lock(soa.Self(), h_klass); // Attempt to initialize allowing initialization of parent classes but still not static // fields. - manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, true); + // Initialize dependencies first only for app image, to make TryInitialize recursive. + bool is_superclass_initialized = !is_app_image ? true : + InitializeDependencies(klass, class_loader, soa.Self()); + if (!is_app_image || (is_app_image && is_superclass_initialized)) { + manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, true); + } + // Otherwise it's in app image but superclasses can't be initialized, no need to proceed. old_status = klass->GetStatus(); + + bool too_many_encoded_fields = false; + if (!is_boot_image && klass->NumStaticFields() > kMaxEncodedFields) { + too_many_encoded_fields = true; + } // If the class was not initialized, we can proceed to see if we can initialize static - // fields. + // fields. Limit the max number of encoded fields. if (!klass->IsInitialized() && (is_app_image || is_boot_image) && + is_superclass_initialized && + !too_many_encoded_fields && manager_->GetCompiler()->IsImageClass(descriptor)) { bool can_init_static_fields = false; if (is_boot_image) { @@ -2278,8 +2299,6 @@ class InitializeClassVisitor : public CompilationVisitor { CHECK(is_app_image); // The boot image case doesn't need to recursively initialize the dependencies with // special logic since the class linker already does this. - bool is_superclass_initialized = - InitializeDependencies(klass, class_loader, soa.Self()); can_init_static_fields = !soa.Self()->IsExceptionPending() && is_superclass_initialized && @@ -2406,30 +2425,6 @@ class InitializeClassVisitor : public CompilationVisitor { } } - bool NoPotentialInternStrings(Handle<mirror::Class> klass, - Handle<mirror::ClassLoader>* class_loader) - REQUIRES_SHARED(Locks::mutator_lock_) { - StackHandleScope<1> hs(Thread::Current()); - Handle<mirror::DexCache> h_dex_cache = hs.NewHandle(klass->GetDexCache()); - const DexFile* dex_file = h_dex_cache->GetDexFile(); - const DexFile::ClassDef* class_def = klass->GetClassDef(); - annotations::RuntimeEncodedStaticFieldValueIterator value_it(*dex_file, - &h_dex_cache, - class_loader, - manager_->GetClassLinker(), - *class_def); - - const auto jString = annotations::RuntimeEncodedStaticFieldValueIterator::kString; - for ( ; value_it.HasNext(); value_it.Next()) { - if (value_it.GetValueType() == jString) { - // We don't want cache the static encoded strings which is a potential intern. - return false; - } - } - - return true; - } - bool ResolveTypesOfMethods(Thread* self, ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_) { auto rtn_type = m->GetReturnType(true); // return value is discarded because resolve will be done internally. @@ -2559,7 +2554,7 @@ class InitializeClassVisitor : public CompilationVisitor { } } - return NoPotentialInternStrings(klass, class_loader); + return true; } const ParallelCompilationManager* const manager_; diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 69f7b1bd3c..e9e73787e4 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -254,9 +254,12 @@ class CompilerDriver { // Resolve a method. Returns null on failure, including incompatible class change. ArtMethod* ResolveMethod( - ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache, - Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit, - uint32_t method_idx, InvokeType invoke_type, bool check_incompatible_class_change = true) + ScopedObjectAccess& soa, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + const DexCompilationUnit* mUnit, + uint32_t method_idx, + InvokeType invoke_type) REQUIRES_SHARED(Locks::mutator_lock_); void ProcessedInstanceField(bool resolved); @@ -381,19 +384,6 @@ class CompilerDriver { } private: - // Can `referrer_class` access the resolved `member`? - // Dispatch call to mirror::Class::CanAccessResolvedField or - // mirror::Class::CanAccessResolvedMember depending on the value of - // ArtMember. - template <typename ArtMember> - static bool CanAccessResolvedMember(mirror::Class* referrer_class, - mirror::Class* access_to, - ArtMember* member, - mirror::DexCache* dex_cache, - uint32_t field_idx) - REQUIRES_SHARED(Locks::mutator_lock_); - - private: void PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index a4e2083fe4..76f0ae9202 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -40,7 +40,7 @@ CompilerOptions::CompilerOptions() implicit_so_checks_(true), implicit_suspend_checks_(false), compile_pic_(false), - verbose_methods_(nullptr), + verbose_methods_(), abort_on_hard_verifier_failure_(false), init_failure_output_(nullptr), dump_cfg_file_name_(""), @@ -55,58 +55,6 @@ CompilerOptions::~CompilerOptions() { // because we don't want to include the PassManagerOptions definition from the header file. } -CompilerOptions::CompilerOptions(CompilerFilter::Filter compiler_filter, - size_t huge_method_threshold, - size_t large_method_threshold, - size_t small_method_threshold, - size_t tiny_method_threshold, - size_t num_dex_methods_threshold, - size_t inline_max_code_units, - const std::vector<const DexFile*>* no_inline_from, - double top_k_profile_threshold, - bool debuggable, - bool generate_debug_info, - bool implicit_null_checks, - bool implicit_so_checks, - bool implicit_suspend_checks, - bool compile_pic, - const std::vector<std::string>* verbose_methods, - std::ostream* init_failure_output, - bool abort_on_hard_verifier_failure, - const std::string& dump_cfg_file_name, - bool dump_cfg_append, - bool force_determinism, - RegisterAllocator::Strategy regalloc_strategy, - const std::vector<std::string>* passes_to_run) - : compiler_filter_(compiler_filter), - huge_method_threshold_(huge_method_threshold), - large_method_threshold_(large_method_threshold), - small_method_threshold_(small_method_threshold), - tiny_method_threshold_(tiny_method_threshold), - num_dex_methods_threshold_(num_dex_methods_threshold), - inline_max_code_units_(inline_max_code_units), - no_inline_from_(no_inline_from), - boot_image_(false), - app_image_(false), - top_k_profile_threshold_(top_k_profile_threshold), - debuggable_(debuggable), - generate_debug_info_(generate_debug_info), - generate_mini_debug_info_(kDefaultGenerateMiniDebugInfo), - generate_build_id_(false), - implicit_null_checks_(implicit_null_checks), - implicit_so_checks_(implicit_so_checks), - implicit_suspend_checks_(implicit_suspend_checks), - compile_pic_(compile_pic), - verbose_methods_(verbose_methods), - abort_on_hard_verifier_failure_(abort_on_hard_verifier_failure), - init_failure_output_(init_failure_output), - dump_cfg_file_name_(dump_cfg_file_name), - dump_cfg_append_(dump_cfg_append), - force_determinism_(force_determinism), - register_allocation_strategy_(regalloc_strategy), - passes_to_run_(passes_to_run) { -} - void CompilerOptions::ParseHugeMethodMax(const StringPiece& option, UsageFn Usage) { ParseUintOption(option, "--huge-method-max", &huge_method_threshold_, Usage); } @@ -204,6 +152,11 @@ bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usa dump_cfg_append_ = true; } else if (option.starts_with("--register-allocation-strategy=")) { ParseRegisterAllocationStrategy(option, Usage); + } else if (option.starts_with("--verbose-methods=")) { + // TODO: rather than switch off compiler logging, make all VLOG(compiler) messages + // conditional on having verbose methods. + gLogVerbosity.compiler = false; + Split(option.substr(strlen("--verbose-methods=")).ToString(), ',', &verbose_methods_); } else { // Option not recognized. return false; diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index 89c2537476..b99263db0e 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -52,30 +52,6 @@ class CompilerOptions FINAL { CompilerOptions(); ~CompilerOptions(); - CompilerOptions(CompilerFilter::Filter compiler_filter, - size_t huge_method_threshold, - size_t large_method_threshold, - size_t small_method_threshold, - size_t tiny_method_threshold, - size_t num_dex_methods_threshold, - size_t inline_max_code_units, - const std::vector<const DexFile*>* no_inline_from, - double top_k_profile_threshold, - bool debuggable, - bool generate_debug_info, - bool implicit_null_checks, - bool implicit_so_checks, - bool implicit_suspend_checks, - bool compile_pic, - const std::vector<std::string>* verbose_methods, - std::ostream* init_failure_output, - bool abort_on_hard_verifier_failure, - const std::string& dump_cfg_file_name, - bool dump_cfg_append, - bool force_determinism, - RegisterAllocator::Strategy regalloc_strategy, - const std::vector<std::string>* passes_to_run); - CompilerFilter::Filter GetCompilerFilter() const { return compiler_filter_; } @@ -163,6 +139,10 @@ class CompilerOptions FINAL { return debuggable_; } + void SetDebuggable(bool value) { + debuggable_ = value; + } + bool GetNativeDebuggable() const { return GetDebuggable() && GetGenerateDebugInfo(); } @@ -211,11 +191,11 @@ class CompilerOptions FINAL { } bool HasVerboseMethods() const { - return verbose_methods_ != nullptr && !verbose_methods_->empty(); + return !verbose_methods_.empty(); } bool IsVerboseMethod(const std::string& pretty_method) const { - for (const std::string& cur_method : *verbose_methods_) { + for (const std::string& cur_method : verbose_methods_) { if (pretty_method.find(cur_method) != std::string::npos) { return true; } @@ -299,7 +279,7 @@ class CompilerOptions FINAL { bool compile_pic_; // Vector of methods to have verbose output enabled for. - const std::vector<std::string>* verbose_methods_; + std::vector<std::string> verbose_methods_; // Abort compilation with an error if we find a class that fails verification with a hard // failure. diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index a8fdecaa4a..1c73dfab37 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -398,12 +398,18 @@ void ImageWriter::SetImageBinSlot(mirror::Object* object, BinSlot bin_slot) { // Before we stomp over the lock word, save the hash code for later. LockWord lw(object->GetLockWord(false)); switch (lw.GetState()) { - case LockWord::kFatLocked: { - LOG(FATAL) << "Fat locked object " << object << " found during object copy"; - break; - } + case LockWord::kFatLocked: + FALLTHROUGH_INTENDED; case LockWord::kThinLocked: { - LOG(FATAL) << "Thin locked object " << object << " found during object copy"; + std::ostringstream oss; + bool thin = (lw.GetState() == LockWord::kThinLocked); + oss << (thin ? "Thin" : "Fat") + << " locked object " << object << "(" << object->PrettyTypeOf() + << ") found during object copy"; + if (thin) { + oss << ". Lock owner:" << lw.ThinLockOwner(); + } + LOG(FATAL) << oss.str(); break; } case LockWord::kUnlocked: @@ -473,6 +479,11 @@ void ImageWriter::PrepareDexCacheArraySlots() { start + layout.MethodTypesOffset(), dex_cache); } + if (dex_cache->GetResolvedCallSites() != nullptr) { + AddDexCacheArrayRelocation(dex_cache->GetResolvedCallSites(), + start + layout.CallSitesOffset(), + dex_cache); + } } } @@ -946,11 +957,18 @@ void ImageWriter::PruneAndPreloadDexCache(ObjPtr<mirror::DexCache> dex_cache, ArtMethod* method = mirror::DexCache::GetElementPtrSize(resolved_methods, i, target_ptr_size_); DCHECK(method != nullptr) << "Expected resolution method instead of null method"; - mirror::Class* declaring_class = method->GetDeclaringClass(); + // Check if the referenced class is in the image. Note that we want to check the referenced + // class rather than the declaring class to preserve the semantics, i.e. using a MethodId + // results in resolving the referenced class and that can for example throw OOME. + ObjPtr<mirror::Class> referencing_class = class_linker->LookupResolvedType( + dex_file, + dex_file.GetMethodId(i).class_idx_, + dex_cache, + class_loader); // Copied methods may be held live by a class which was not an image class but have a // declaring class which is an image class. Set it to the resolution method to be safe and // prevent dangling pointers. - if (method->IsCopied() || !KeepClass(declaring_class)) { + if (method->IsCopied() || !KeepClass(referencing_class)) { mirror::DexCache::SetElementPtrSize(resolved_methods, i, resolution_method, @@ -958,8 +976,8 @@ void ImageWriter::PruneAndPreloadDexCache(ObjPtr<mirror::DexCache> dex_cache, } else if (kIsDebugBuild) { // Check that the class is still in the classes table. ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); - CHECK(class_linker->ClassInClassTable(declaring_class)) << "Class " - << Class::PrettyClass(declaring_class) << " not in class linker table"; + CHECK(class_linker->ClassInClassTable(referencing_class)) << "Class " + << Class::PrettyClass(referencing_class) << " not in class linker table"; } } // Prune fields and make the contents of the field array deterministic. diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 66135414f7..28a3f1edae 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -90,36 +90,20 @@ NO_RETURN static void Usage(const char* fmt, ...) { } JitCompiler::JitCompiler() { - compiler_options_.reset(new CompilerOptions( - CompilerFilter::kDefaultCompilerFilter, - CompilerOptions::kDefaultHugeMethodThreshold, - CompilerOptions::kDefaultLargeMethodThreshold, - CompilerOptions::kDefaultSmallMethodThreshold, - CompilerOptions::kDefaultTinyMethodThreshold, - CompilerOptions::kDefaultNumDexMethodsThreshold, - CompilerOptions::kDefaultInlineMaxCodeUnits, - /* no_inline_from */ nullptr, - CompilerOptions::kDefaultTopKProfileThreshold, - Runtime::Current()->IsJavaDebuggable(), - CompilerOptions::kDefaultGenerateDebugInfo, - /* implicit_null_checks */ true, - /* implicit_so_checks */ true, - /* implicit_suspend_checks */ false, - /* pic */ false, - /* verbose_methods */ nullptr, - /* init_failure_output */ nullptr, - /* abort_on_hard_verifier_failure */ false, - /* dump_cfg_file_name */ "", - /* dump_cfg_append */ false, - /* force_determinism */ false, - RegisterAllocator::kRegisterAllocatorDefault, - /* passes_to_run */ nullptr)); + compiler_options_.reset(new CompilerOptions()); for (const std::string& argument : Runtime::Current()->GetCompilerOptions()) { compiler_options_->ParseCompilerOption(argument, Usage); } // JIT is never PIC, no matter what the runtime compiler options specify. compiler_options_->SetNonPic(); + // Set debuggability based on the runtime value. + compiler_options_->SetDebuggable(Runtime::Current()->IsJavaDebuggable()); + + // Special case max code units for inlining, whose default is "unset" (implictly + // meaning no limit). + compiler_options_->SetInlineMaxCodeUnits(CompilerOptions::kDefaultInlineMaxCodeUnits); + const InstructionSet instruction_set = kRuntimeISA; for (const StringPiece option : Runtime::Current()->GetCompilerOptions()) { VLOG(compiler) << "JIT compiler option " << option; diff --git a/compiler/linker/mips/relative_patcher_mips.cc b/compiler/linker/mips/relative_patcher_mips.cc index d99d237a23..3bec30f1e8 100644 --- a/compiler/linker/mips/relative_patcher_mips.cc +++ b/compiler/linker/mips/relative_patcher_mips.cc @@ -49,43 +49,27 @@ void MipsRelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, uint32_t target_offset) { uint32_t anchor_literal_offset = patch.PcInsnOffset(); uint32_t literal_offset = patch.LiteralOffset(); - uint32_t literal_low_offset; + bool high_patch = ((*code)[literal_offset + 0] == 0x34) && ((*code)[literal_offset + 1] == 0x12); - // Perform basic sanity checks and initialize `literal_low_offset` to point - // to the instruction containing the 16 least significant bits of the - // relative address. - if (is_r6) { - DCHECK_GE(code->size(), 8u); - DCHECK_LE(literal_offset, code->size() - 8u); - DCHECK_EQ(literal_offset, anchor_literal_offset); - // AUIPC reg, offset_high - DCHECK_EQ((*code)[literal_offset + 0], 0x34); - DCHECK_EQ((*code)[literal_offset + 1], 0x12); - DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); - DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); - // instr reg(s), offset_low - DCHECK_EQ((*code)[literal_offset + 4], 0x78); - DCHECK_EQ((*code)[literal_offset + 5], 0x56); - literal_low_offset = literal_offset + 4; + // Perform basic sanity checks. + if (high_patch) { + if (is_r6) { + // auipc reg, offset_high + DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); + DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); + } else { + // lui reg, offset_high + DCHECK_EQ(((*code)[literal_offset + 2] & 0xE0), 0x00); + DCHECK_EQ((*code)[literal_offset + 3], 0x3C); + // addu reg, reg, reg2 + DCHECK_EQ((*code)[literal_offset + 4], 0x21); + DCHECK_EQ(((*code)[literal_offset + 5] & 0x07), 0x00); + DCHECK_EQ(((*code)[literal_offset + 7] & 0xFC), 0x00); + } } else { - DCHECK_GE(code->size(), 16u); - DCHECK_LE(literal_offset, code->size() - 12u); - DCHECK_GE(literal_offset, 4u); - // The NAL instruction does not precede immediately as the PC+0 - // comes from HMipsComputeBaseMethodAddress. - // LUI reg, offset_high - DCHECK_EQ((*code)[literal_offset + 0], 0x34); - DCHECK_EQ((*code)[literal_offset + 1], 0x12); - DCHECK_EQ(((*code)[literal_offset + 2] & 0xE0), 0x00); - DCHECK_EQ((*code)[literal_offset + 3], 0x3C); - // ADDU reg, reg, reg2 - DCHECK_EQ((*code)[literal_offset + 4], 0x21); - DCHECK_EQ(((*code)[literal_offset + 5] & 0x07), 0x00); - DCHECK_EQ(((*code)[literal_offset + 7] & 0xFC), 0x00); // instr reg(s), offset_low - DCHECK_EQ((*code)[literal_offset + 8], 0x78); - DCHECK_EQ((*code)[literal_offset + 9], 0x56); - literal_low_offset = literal_offset + 8; + CHECK_EQ((*code)[literal_offset + 0], 0x78); + CHECK_EQ((*code)[literal_offset + 1], 0x56); } // Apply patch. @@ -93,12 +77,15 @@ void MipsRelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, uint32_t diff = target_offset - anchor_offset; diff += (diff & 0x8000) << 1; // Account for sign extension in "instr reg(s), offset_low". - // LUI reg, offset_high / AUIPC reg, offset_high - (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); - (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); - // instr reg(s), offset_low - (*code)[literal_low_offset + 0] = static_cast<uint8_t>(diff >> 0); - (*code)[literal_low_offset + 1] = static_cast<uint8_t>(diff >> 8); + if (high_patch) { + // lui reg, offset_high / auipc reg, offset_high + (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); + (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); + } else { + // instr reg(s), offset_low + (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 0); + (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 8); + } } void MipsRelativePatcher::PatchBakerReadBarrierBranch(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, diff --git a/compiler/linker/mips/relative_patcher_mips32r6_test.cc b/compiler/linker/mips/relative_patcher_mips32r6_test.cc index 63ad8a58d5..d1a75e28a2 100644 --- a/compiler/linker/mips/relative_patcher_mips32r6_test.cc +++ b/compiler/linker/mips/relative_patcher_mips32r6_test.cc @@ -26,7 +26,9 @@ class Mips32r6RelativePatcherTest : public RelativePatcherTest { protected: static const uint8_t kUnpatchedPcRelativeRawCode[]; - static const uint32_t kLiteralOffset; + static const uint32_t kLiteralOffsetHigh; + static const uint32_t kLiteralOffsetLow1; + static const uint32_t kLiteralOffsetLow2; static const uint32_t kAnchorOffset; static const ArrayRef<const uint8_t> kUnpatchedPcRelativeCode; @@ -44,9 +46,11 @@ class Mips32r6RelativePatcherTest : public RelativePatcherTest { const uint8_t Mips32r6RelativePatcherTest::kUnpatchedPcRelativeRawCode[] = { 0x34, 0x12, 0x5E, 0xEE, // auipc s2, high(diff); placeholder = 0x1234 0x78, 0x56, 0x52, 0x26, // addiu s2, s2, low(diff); placeholder = 0x5678 + 0x78, 0x56, 0x52, 0x8E, // lw s2, (low(diff))(s2) ; placeholder = 0x5678 }; -const uint32_t Mips32r6RelativePatcherTest::kLiteralOffset = 0; // At auipc (where - // patching starts). +const uint32_t Mips32r6RelativePatcherTest::kLiteralOffsetHigh = 0; // At auipc. +const uint32_t Mips32r6RelativePatcherTest::kLiteralOffsetLow1 = 4; // At addiu. +const uint32_t Mips32r6RelativePatcherTest::kLiteralOffsetLow2 = 8; // At lw. const uint32_t Mips32r6RelativePatcherTest::kAnchorOffset = 0; // At auipc (where PC+0 points). const ArrayRef<const uint8_t> Mips32r6RelativePatcherTest::kUnpatchedPcRelativeCode( kUnpatchedPcRelativeRawCode); @@ -60,11 +64,12 @@ void Mips32r6RelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const Link ASSERT_TRUE(result.first); uint32_t diff = target_offset - (result.second + kAnchorOffset); - diff += (diff & 0x8000) << 1; // Account for sign extension in addiu. + diff += (diff & 0x8000) << 1; // Account for sign extension in addiu/lw. const uint8_t expected_code[] = { static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24), 0x5E, 0xEE, static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x26, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x8E, }; EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } @@ -75,7 +80,9 @@ void Mips32r6RelativePatcherTest::TestStringBssEntry(uint32_t bss_begin, string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); bss_begin_ = bss_begin; LinkerPatch patches[] = { - LinkerPatch::StringBssEntryPatch(kLiteralOffset, nullptr, kAnchorOffset, kStringIndex) + LinkerPatch::StringBssEntryPatch(kLiteralOffsetHigh, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow1, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow2, nullptr, kAnchorOffset, kStringIndex) }; CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), bss_begin_ + string_entry_offset); } @@ -84,7 +91,9 @@ void Mips32r6RelativePatcherTest::TestStringReference(uint32_t string_offset) { constexpr uint32_t kStringIndex = 1u; string_index_to_offset_map_.Put(kStringIndex, string_offset); LinkerPatch patches[] = { - LinkerPatch::RelativeStringPatch(kLiteralOffset, nullptr, kAnchorOffset, kStringIndex) + LinkerPatch::RelativeStringPatch(kLiteralOffsetHigh, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::RelativeStringPatch(kLiteralOffsetLow1, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::RelativeStringPatch(kLiteralOffsetLow2, nullptr, kAnchorOffset, kStringIndex) }; CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), string_offset); } diff --git a/compiler/linker/mips/relative_patcher_mips_test.cc b/compiler/linker/mips/relative_patcher_mips_test.cc index 49af7c614b..2f7a0752a6 100644 --- a/compiler/linker/mips/relative_patcher_mips_test.cc +++ b/compiler/linker/mips/relative_patcher_mips_test.cc @@ -26,7 +26,9 @@ class MipsRelativePatcherTest : public RelativePatcherTest { protected: static const uint8_t kUnpatchedPcRelativeRawCode[]; - static const uint32_t kLiteralOffset; + static const uint32_t kLiteralOffsetHigh; + static const uint32_t kLiteralOffsetLow1; + static const uint32_t kLiteralOffsetLow2; static const uint32_t kAnchorOffset; static const ArrayRef<const uint8_t> kUnpatchedPcRelativeCode; @@ -46,8 +48,11 @@ const uint8_t MipsRelativePatcherTest::kUnpatchedPcRelativeRawCode[] = { 0x34, 0x12, 0x12, 0x3C, // lui s2, high(diff); placeholder = 0x1234 0x21, 0x90, 0x5F, 0x02, // addu s2, s2, ra 0x78, 0x56, 0x52, 0x26, // addiu s2, s2, low(diff); placeholder = 0x5678 + 0x78, 0x56, 0x52, 0x8E, // lw s2, (low(diff))(s2) ; placeholder = 0x5678 }; -const uint32_t MipsRelativePatcherTest::kLiteralOffset = 4; // At lui (where patching starts). +const uint32_t MipsRelativePatcherTest::kLiteralOffsetHigh = 4; // At lui. +const uint32_t MipsRelativePatcherTest::kLiteralOffsetLow1 = 12; // At addiu. +const uint32_t MipsRelativePatcherTest::kLiteralOffsetLow2 = 16; // At lw. const uint32_t MipsRelativePatcherTest::kAnchorOffset = 8; // At addu (where PC+0 points). const ArrayRef<const uint8_t> MipsRelativePatcherTest::kUnpatchedPcRelativeCode( kUnpatchedPcRelativeRawCode); @@ -61,13 +66,14 @@ void MipsRelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const LinkerPa ASSERT_TRUE(result.first); uint32_t diff = target_offset - (result.second + kAnchorOffset); - diff += (diff & 0x8000) << 1; // Account for sign extension in addiu. + diff += (diff & 0x8000) << 1; // Account for sign extension in addiu/lw. const uint8_t expected_code[] = { 0x00, 0x00, 0x10, 0x04, static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24), 0x12, 0x3C, 0x21, 0x90, 0x5F, 0x02, static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x26, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x8E, }; EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } @@ -78,7 +84,9 @@ void MipsRelativePatcherTest::TestStringBssEntry(uint32_t bss_begin, string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); bss_begin_ = bss_begin; LinkerPatch patches[] = { - LinkerPatch::StringBssEntryPatch(kLiteralOffset, nullptr, kAnchorOffset, kStringIndex) + LinkerPatch::StringBssEntryPatch(kLiteralOffsetHigh, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow1, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow2, nullptr, kAnchorOffset, kStringIndex) }; CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), bss_begin_ + string_entry_offset); } @@ -87,7 +95,9 @@ void MipsRelativePatcherTest::TestStringReference(uint32_t string_offset) { constexpr uint32_t kStringIndex = 1u; string_index_to_offset_map_.Put(kStringIndex, string_offset); LinkerPatch patches[] = { - LinkerPatch::RelativeStringPatch(kLiteralOffset, nullptr, kAnchorOffset, kStringIndex) + LinkerPatch::RelativeStringPatch(kLiteralOffsetHigh, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::RelativeStringPatch(kLiteralOffsetLow1, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::RelativeStringPatch(kLiteralOffsetLow2, nullptr, kAnchorOffset, kStringIndex) }; CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), string_offset); } diff --git a/compiler/linker/mips64/relative_patcher_mips64.cc b/compiler/linker/mips64/relative_patcher_mips64.cc index 3488d6d21c..d9f4758eb1 100644 --- a/compiler/linker/mips64/relative_patcher_mips64.cc +++ b/compiler/linker/mips64/relative_patcher_mips64.cc @@ -36,38 +36,11 @@ uint32_t Mips64RelativePatcher::WriteThunks(OutputStream* out ATTRIBUTE_UNUSED, return offset; // No thunks added; no limit on relative call distance. } -void Mips64RelativePatcher::PatchCall(std::vector<uint8_t>* code, - uint32_t literal_offset, - uint32_t patch_offset, - uint32_t target_offset) { - // Basic sanity checks. - DCHECK_GE(code->size(), 8u); - DCHECK_LE(literal_offset, code->size() - 8u); - // auipc reg, offset_high - DCHECK_EQ((*code)[literal_offset + 0], 0x34); - DCHECK_EQ((*code)[literal_offset + 1], 0x12); - DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); - DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); - // jialc reg, offset_low - DCHECK_EQ((*code)[literal_offset + 4], 0x78); - DCHECK_EQ((*code)[literal_offset + 5], 0x56); - DCHECK_EQ(((*code)[literal_offset + 6] & 0xE0), 0x00); - DCHECK_EQ((*code)[literal_offset + 7], 0xF8); - - // Apply patch. - uint32_t diff = target_offset - patch_offset; - // Note that a combination of auipc with an instruction that adds a sign-extended - // 16-bit immediate operand (e.g. jialc) provides a PC-relative range of - // PC-0x80000000 to PC+0x7FFF7FFF on MIPS64, that is, short of 2GB on one end - // by 32KB. - diff += (diff & 0x8000) << 1; // Account for sign extension in jialc. - - // auipc reg, offset_high - (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); - (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); - // jialc reg, offset_low - (*code)[literal_offset + 4] = static_cast<uint8_t>(diff >> 0); - (*code)[literal_offset + 5] = static_cast<uint8_t>(diff >> 8); +void Mips64RelativePatcher::PatchCall(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, + uint32_t literal_offset ATTRIBUTE_UNUSED, + uint32_t patch_offset ATTRIBUTE_UNUSED, + uint32_t target_offset ATTRIBUTE_UNUSED) { + UNIMPLEMENTED(FATAL) << "PatchCall unimplemented on MIPS64"; } void Mips64RelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, @@ -76,19 +49,18 @@ void Mips64RelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, uint32_t target_offset) { uint32_t anchor_literal_offset = patch.PcInsnOffset(); uint32_t literal_offset = patch.LiteralOffset(); + bool high_patch = ((*code)[literal_offset + 0] == 0x34) && ((*code)[literal_offset + 1] == 0x12); - // Basic sanity checks. - DCHECK_GE(code->size(), 8u); - DCHECK_LE(literal_offset, code->size() - 8u); - DCHECK_EQ(literal_offset, anchor_literal_offset); - // auipc reg, offset_high - DCHECK_EQ((*code)[literal_offset + 0], 0x34); - DCHECK_EQ((*code)[literal_offset + 1], 0x12); - DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); - DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); - // instr reg(s), offset_low - DCHECK_EQ((*code)[literal_offset + 4], 0x78); - DCHECK_EQ((*code)[literal_offset + 5], 0x56); + // Perform basic sanity checks. + if (high_patch) { + // auipc reg, offset_high + DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); + DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); + } else { + // instr reg(s), offset_low + CHECK_EQ((*code)[literal_offset + 0], 0x78); + CHECK_EQ((*code)[literal_offset + 1], 0x56); + } // Apply patch. uint32_t anchor_offset = patch_offset - literal_offset + anchor_literal_offset; @@ -97,14 +69,17 @@ void Mips64RelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, // 16-bit immediate operand (e.g. ld) provides a PC-relative range of // PC-0x80000000 to PC+0x7FFF7FFF on MIPS64, that is, short of 2GB on one end // by 32KB. - diff += (diff & 0x8000) << 1; // Account for sign extension in instruction following auipc. + diff += (diff & 0x8000) << 1; // Account for sign extension in "instr reg(s), offset_low". - // auipc reg, offset_high - (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); - (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); - // instr reg(s), offset_low - (*code)[literal_offset + 4] = static_cast<uint8_t>(diff >> 0); - (*code)[literal_offset + 5] = static_cast<uint8_t>(diff >> 8); + if (high_patch) { + // auipc reg, offset_high + (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); + (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); + } else { + // instr reg(s), offset_low + (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 0); + (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 8); + } } void Mips64RelativePatcher::PatchBakerReadBarrierBranch(std::vector<uint8_t>* code ATTRIBUTE_UNUSED, diff --git a/compiler/linker/mips64/relative_patcher_mips64_test.cc b/compiler/linker/mips64/relative_patcher_mips64_test.cc index 9c9e24a96e..a5f494d645 100644 --- a/compiler/linker/mips64/relative_patcher_mips64_test.cc +++ b/compiler/linker/mips64/relative_patcher_mips64_test.cc @@ -27,10 +27,11 @@ class Mips64RelativePatcherTest : public RelativePatcherTest { protected: static const uint8_t kUnpatchedPcRelativeRawCode[]; static const uint8_t kUnpatchedPcRelativeCallRawCode[]; - static const uint32_t kLiteralOffset; + static const uint32_t kLiteralOffsetHigh; + static const uint32_t kLiteralOffsetLow1; + static const uint32_t kLiteralOffsetLow2; static const uint32_t kAnchorOffset; static const ArrayRef<const uint8_t> kUnpatchedPcRelativeCode; - static const ArrayRef<const uint8_t> kUnpatchedPcRelativeCallCode; uint32_t GetMethodOffset(uint32_t method_idx) { auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); @@ -44,19 +45,16 @@ class Mips64RelativePatcherTest : public RelativePatcherTest { }; const uint8_t Mips64RelativePatcherTest::kUnpatchedPcRelativeRawCode[] = { - 0x34, 0x12, 0x5E, 0xEE, // auipc s2, high(diff); placeholder = 0x1234 + 0x34, 0x12, 0x5E, 0xEE, // auipc s2, high(diff); placeholder = 0x1234 0x78, 0x56, 0x52, 0x66, // daddiu s2, s2, low(diff); placeholder = 0x5678 + 0x78, 0x56, 0x52, 0x9E, // lwu s2, (low(diff))(s2) ; placeholder = 0x5678 }; -const uint8_t Mips64RelativePatcherTest::kUnpatchedPcRelativeCallRawCode[] = { - 0x34, 0x12, 0x3E, 0xEC, // auipc at, high(diff); placeholder = 0x1234 - 0x78, 0x56, 0x01, 0xF8, // jialc at, low(diff); placeholder = 0x5678 -}; -const uint32_t Mips64RelativePatcherTest::kLiteralOffset = 0; // At auipc (where patching starts). +const uint32_t Mips64RelativePatcherTest::kLiteralOffsetHigh = 0; // At auipc. +const uint32_t Mips64RelativePatcherTest::kLiteralOffsetLow1 = 4; // At daddiu. +const uint32_t Mips64RelativePatcherTest::kLiteralOffsetLow2 = 8; // At lwu. const uint32_t Mips64RelativePatcherTest::kAnchorOffset = 0; // At auipc (where PC+0 points). const ArrayRef<const uint8_t> Mips64RelativePatcherTest::kUnpatchedPcRelativeCode( kUnpatchedPcRelativeRawCode); -const ArrayRef<const uint8_t> Mips64RelativePatcherTest::kUnpatchedPcRelativeCallCode( - kUnpatchedPcRelativeCallRawCode); void Mips64RelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const LinkerPatch>& patches, uint32_t target_offset) { @@ -67,11 +65,12 @@ void Mips64RelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const Linker ASSERT_TRUE(result.first); uint32_t diff = target_offset - (result.second + kAnchorOffset); - diff += (diff & 0x8000) << 1; // Account for sign extension in instruction following auipc. + diff += (diff & 0x8000) << 1; // Account for sign extension in daddiu/lwu. const uint8_t expected_code[] = { static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24), 0x5E, 0xEE, static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x66, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x9E, }; EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } @@ -82,7 +81,9 @@ void Mips64RelativePatcherTest::TestStringBssEntry(uint32_t bss_begin, string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); bss_begin_ = bss_begin; LinkerPatch patches[] = { - LinkerPatch::StringBssEntryPatch(kLiteralOffset, nullptr, kAnchorOffset, kStringIndex) + LinkerPatch::StringBssEntryPatch(kLiteralOffsetHigh, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow1, nullptr, kAnchorOffset, kStringIndex), + LinkerPatch::StringBssEntryPatch(kLiteralOffsetLow2, nullptr, kAnchorOffset, kStringIndex) }; CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), bss_begin_ + string_entry_offset); } @@ -91,38 +92,5 @@ TEST_F(Mips64RelativePatcherTest, StringBssEntry) { TestStringBssEntry(/* bss_begin */ 0x12345678, /* string_entry_offset */ 0x1234); } -TEST_F(Mips64RelativePatcherTest, CallOther) { - LinkerPatch method1_patches[] = { - LinkerPatch::RelativeCodePatch(kLiteralOffset, nullptr, 2u), - }; - AddCompiledMethod(MethodRef(1u), - kUnpatchedPcRelativeCallCode, - ArrayRef<const LinkerPatch>(method1_patches)); - LinkerPatch method2_patches[] = { - LinkerPatch::RelativeCodePatch(kLiteralOffset, nullptr, 1u), - }; - AddCompiledMethod(MethodRef(2u), - kUnpatchedPcRelativeCallCode, - ArrayRef<const LinkerPatch>(method2_patches)); - Link(); - - uint32_t method1_offset = GetMethodOffset(1u); - uint32_t method2_offset = GetMethodOffset(2u); - uint32_t diff_after = method2_offset - (method1_offset + kAnchorOffset /* PC adjustment */); - diff_after += (diff_after & 0x8000) << 1; // Account for sign extension in jialc. - static const uint8_t method1_expected_code[] = { - static_cast<uint8_t>(diff_after >> 16), static_cast<uint8_t>(diff_after >> 24), 0x3E, 0xEC, - static_cast<uint8_t>(diff_after), static_cast<uint8_t>(diff_after >> 8), 0x01, 0xF8, - }; - EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code))); - uint32_t diff_before = method1_offset - (method2_offset + kAnchorOffset /* PC adjustment */); - diff_before += (diff_before & 0x8000) << 1; // Account for sign extension in jialc. - static const uint8_t method2_expected_code[] = { - static_cast<uint8_t>(diff_before >> 16), static_cast<uint8_t>(diff_before >> 24), 0x3E, 0xEC, - static_cast<uint8_t>(diff_before), static_cast<uint8_t>(diff_before >> 8), 0x01, 0xF8, - }; - EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code))); -} - } // namespace linker } // namespace art diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h index 805a3f4366..ad3283ad4f 100644 --- a/compiler/optimizing/code_generator_arm_vixl.h +++ b/compiler/optimizing/code_generator_arm_vixl.h @@ -408,6 +408,17 @@ class InstructionCodeGeneratorARMVIXL : public InstructionCodeGenerator { void GenerateDivRemConstantIntegral(HBinaryOperation* instruction); void HandleGoto(HInstruction* got, HBasicBlock* successor); + vixl::aarch32::MemOperand VecAddress( + HVecMemoryOperation* instruction, + // This function may acquire a scratch register. + vixl::aarch32::UseScratchRegisterScope* temps_scope, + /*out*/ vixl32::Register* scratch); + vixl::aarch32::AlignedMemOperand VecAddressUnaligned( + HVecMemoryOperation* instruction, + // This function may acquire a scratch register. + vixl::aarch32::UseScratchRegisterScope* temps_scope, + /*out*/ vixl32::Register* scratch); + ArmVIXLAssembler* const assembler_; CodeGeneratorARMVIXL* const codegen_; diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index b39d412ac2..4c4d97bc8d 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -208,8 +208,13 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { LoadClassSlowPathMIPS(HLoadClass* cls, HInstruction* at, uint32_t dex_pc, - bool do_clinit) - : SlowPathCodeMIPS(at), cls_(cls), dex_pc_(dex_pc), do_clinit_(do_clinit) { + bool do_clinit, + const CodeGeneratorMIPS::PcRelativePatchInfo* bss_info_high = nullptr) + : SlowPathCodeMIPS(at), + cls_(cls), + dex_pc_(dex_pc), + do_clinit_(do_clinit), + bss_info_high_(bss_info_high) { DCHECK(at->IsLoadClass() || at->IsClinitCheck()); } @@ -217,8 +222,7 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { LocationSummary* locations = instruction_->GetLocations(); Location out = locations->Out(); CodeGeneratorMIPS* mips_codegen = down_cast<CodeGeneratorMIPS*>(codegen); - const bool isR6 = mips_codegen->GetInstructionSetFeatures().IsR6(); - const bool r2_baker_or_no_read_barriers = !isR6 && (!kUseReadBarrier || kUseBakerReadBarrier); + const bool baker_or_no_read_barriers = (!kUseReadBarrier || kUseBakerReadBarrier); InvokeRuntimeCallingConvention calling_convention; DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); const bool is_load_class_bss_entry = @@ -228,7 +232,7 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { // For HLoadClass/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. Register entry_address = kNoRegister; - if (is_load_class_bss_entry && r2_baker_or_no_read_barriers) { + if (is_load_class_bss_entry && baker_or_no_read_barriers) { Register temp = locations->GetTemp(0).AsRegister<Register>(); bool temp_is_a0 = (temp == calling_convention.GetRegisterAt(0)); // In the unlucky case that `temp` is A0, we preserve the address in `out` across the @@ -252,9 +256,18 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { } // For HLoadClass/kBssEntry, store the resolved class to the BSS entry. - if (is_load_class_bss_entry && r2_baker_or_no_read_barriers) { + if (is_load_class_bss_entry && baker_or_no_read_barriers) { // The class entry address was preserved in `entry_address` thanks to kSaveEverything. - __ StoreToOffset(kStoreWord, calling_convention.GetRegisterAt(0), entry_address, 0); + DCHECK(bss_info_high_); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + mips_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index, bss_info_high_); + bool reordering = __ SetReorder(false); + __ Bind(&info_low->label); + __ StoreToOffset(kStoreWord, + calling_convention.GetRegisterAt(0), + entry_address, + /* placeholder */ 0x5678); + __ SetReorder(reordering); } // Move the class to the desired location. @@ -268,14 +281,17 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { RestoreLiveRegisters(codegen, locations); // For HLoadClass/kBssEntry, store the resolved class to the BSS entry. - if (is_load_class_bss_entry && !r2_baker_or_no_read_barriers) { - // For non-Baker read barriers (or on R6), we need to re-calculate the address of + if (is_load_class_bss_entry && !baker_or_no_read_barriers) { + // For non-Baker read barriers we need to re-calculate the address of // the class entry. + const bool isR6 = mips_codegen->GetInstructionSetFeatures().IsR6(); Register base = isR6 ? ZERO : locations->InAt(0).AsRegister<Register>(); - CodeGeneratorMIPS::PcRelativePatchInfo* info = + CodeGeneratorMIPS::PcRelativePatchInfo* info_high = mips_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + mips_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index, info_high); bool reordering = __ SetReorder(false); - mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info, TMP, base); + mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, base, info_low); __ StoreToOffset(kStoreWord, out.AsRegister<Register>(), TMP, /* placeholder */ 0x5678); __ SetReorder(reordering); } @@ -294,12 +310,17 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { // Whether to initialize the class. const bool do_clinit_; + // Pointer to the high half PC-relative patch info for HLoadClass/kBssEntry. + const CodeGeneratorMIPS::PcRelativePatchInfo* bss_info_high_; + DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathMIPS); }; class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { public: - explicit LoadStringSlowPathMIPS(HLoadString* instruction) : SlowPathCodeMIPS(instruction) {} + explicit LoadStringSlowPathMIPS(HLoadString* instruction, + const CodeGeneratorMIPS::PcRelativePatchInfo* bss_info_high) + : SlowPathCodeMIPS(instruction), bss_info_high_(bss_info_high) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { DCHECK(instruction_->IsLoadString()); @@ -310,15 +331,14 @@ class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { const dex::StringIndex string_index = load->GetStringIndex(); Register out = locations->Out().AsRegister<Register>(); CodeGeneratorMIPS* mips_codegen = down_cast<CodeGeneratorMIPS*>(codegen); - const bool isR6 = mips_codegen->GetInstructionSetFeatures().IsR6(); - const bool r2_baker_or_no_read_barriers = !isR6 && (!kUseReadBarrier || kUseBakerReadBarrier); + const bool baker_or_no_read_barriers = (!kUseReadBarrier || kUseBakerReadBarrier); InvokeRuntimeCallingConvention calling_convention; __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); // For HLoadString/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. Register entry_address = kNoRegister; - if (r2_baker_or_no_read_barriers) { + if (baker_or_no_read_barriers) { Register temp = locations->GetTemp(0).AsRegister<Register>(); bool temp_is_a0 = (temp == calling_convention.GetRegisterAt(0)); // In the unlucky case that `temp` is A0, we preserve the address in `out` across the @@ -335,9 +355,18 @@ class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); // Store the resolved string to the BSS entry. - if (r2_baker_or_no_read_barriers) { + if (baker_or_no_read_barriers) { // The string entry address was preserved in `entry_address` thanks to kSaveEverything. - __ StoreToOffset(kStoreWord, calling_convention.GetRegisterAt(0), entry_address, 0); + DCHECK(bss_info_high_); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, bss_info_high_); + bool reordering = __ SetReorder(false); + __ Bind(&info_low->label); + __ StoreToOffset(kStoreWord, + calling_convention.GetRegisterAt(0), + entry_address, + /* placeholder */ 0x5678); + __ SetReorder(reordering); } Primitive::Type type = instruction_->GetType(); @@ -347,14 +376,17 @@ class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { RestoreLiveRegisters(codegen, locations); // Store the resolved string to the BSS entry. - if (!r2_baker_or_no_read_barriers) { - // For non-Baker read barriers (or on R6), we need to re-calculate the address of + if (!baker_or_no_read_barriers) { + // For non-Baker read barriers we need to re-calculate the address of // the string entry. + const bool isR6 = mips_codegen->GetInstructionSetFeatures().IsR6(); Register base = isR6 ? ZERO : locations->InAt(0).AsRegister<Register>(); - CodeGeneratorMIPS::PcRelativePatchInfo* info = + CodeGeneratorMIPS::PcRelativePatchInfo* info_high = mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, info_high); bool reordering = __ SetReorder(false); - mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info, TMP, base); + mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, base, info_low); __ StoreToOffset(kStoreWord, out, TMP, /* placeholder */ 0x5678); __ SetReorder(reordering); } @@ -364,6 +396,9 @@ class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathMIPS"; } private: + // Pointer to the high half PC-relative patch info. + const CodeGeneratorMIPS::PcRelativePatchInfo* bss_info_high_; + DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathMIPS); }; @@ -950,7 +985,9 @@ class ReadBarrierForHeapReferenceSlowPathMIPS : public SlowPathCodeMIPS { this); CheckEntrypointTypes< kQuickReadBarrierSlow, mirror::Object*, mirror::Object*, mirror::Object*, uint32_t>(); - mips_codegen->Move32(out_, calling_convention.GetReturnLocation(Primitive::kPrimNot)); + mips_codegen->MoveLocation(out_, + calling_convention.GetReturnLocation(Primitive::kPrimNot), + Primitive::kPrimNot); RestoreLiveRegisters(codegen, locations); __ B(GetExitLabel()); @@ -1013,13 +1050,17 @@ class ReadBarrierForRootSlowPathMIPS : public SlowPathCodeMIPS { InvokeRuntimeCallingConvention calling_convention; CodeGeneratorMIPS* mips_codegen = down_cast<CodeGeneratorMIPS*>(codegen); - mips_codegen->Move32(Location::RegisterLocation(calling_convention.GetRegisterAt(0)), root_); + mips_codegen->MoveLocation(Location::RegisterLocation(calling_convention.GetRegisterAt(0)), + root_, + Primitive::kPrimNot); mips_codegen->InvokeRuntime(kQuickReadBarrierForRootSlow, instruction_, instruction_->GetDexPc(), this); CheckEntrypointTypes<kQuickReadBarrierForRootSlow, mirror::Object*, GcRoot<mirror::Object>*>(); - mips_codegen->Move32(out_, calling_convention.GetReturnLocation(Primitive::kPrimNot)); + mips_codegen->MoveLocation(out_, + calling_convention.GetReturnLocation(Primitive::kPrimNot), + Primitive::kPrimNot); RestoreLiveRegisters(codegen, locations); __ B(GetExitLabel()); @@ -1407,106 +1448,92 @@ void CodeGeneratorMIPS::Bind(HBasicBlock* block) { __ Bind(GetLabelOf(block)); } -void CodeGeneratorMIPS::MoveLocation(Location dst, Location src, Primitive::Type dst_type) { - if (src.Equals(dst)) { - return; - } - - if (src.IsConstant()) { - MoveConstant(dst, src.GetConstant()); - } else { - if (Primitive::Is64BitType(dst_type)) { - Move64(dst, src); - } else { - Move32(dst, src); - } - } -} - -void CodeGeneratorMIPS::Move32(Location destination, Location source) { +void CodeGeneratorMIPS::MoveLocation(Location destination, + Location source, + Primitive::Type dst_type) { if (source.Equals(destination)) { return; } - if (destination.IsRegister()) { - if (source.IsRegister()) { - __ Move(destination.AsRegister<Register>(), source.AsRegister<Register>()); - } else if (source.IsFpuRegister()) { - __ Mfc1(destination.AsRegister<Register>(), source.AsFpuRegister<FRegister>()); - } else { - DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; - __ LoadFromOffset(kLoadWord, destination.AsRegister<Register>(), SP, source.GetStackIndex()); - } - } else if (destination.IsFpuRegister()) { - if (source.IsRegister()) { - __ Mtc1(source.AsRegister<Register>(), destination.AsFpuRegister<FRegister>()); - } else if (source.IsFpuRegister()) { - __ MovS(destination.AsFpuRegister<FRegister>(), source.AsFpuRegister<FRegister>()); - } else { - DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; - __ LoadSFromOffset(destination.AsFpuRegister<FRegister>(), SP, source.GetStackIndex()); - } - } else { - DCHECK(destination.IsStackSlot()) << destination; - if (source.IsRegister()) { - __ StoreToOffset(kStoreWord, source.AsRegister<Register>(), SP, destination.GetStackIndex()); - } else if (source.IsFpuRegister()) { - __ StoreSToOffset(source.AsFpuRegister<FRegister>(), SP, destination.GetStackIndex()); - } else { - DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; - __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex()); - __ StoreToOffset(kStoreWord, TMP, SP, destination.GetStackIndex()); - } - } -} - -void CodeGeneratorMIPS::Move64(Location destination, Location source) { - if (source.Equals(destination)) { - return; - } - - if (destination.IsRegisterPair()) { - if (source.IsRegisterPair()) { - __ Move(destination.AsRegisterPairHigh<Register>(), source.AsRegisterPairHigh<Register>()); - __ Move(destination.AsRegisterPairLow<Register>(), source.AsRegisterPairLow<Register>()); - } else if (source.IsFpuRegister()) { - Register dst_high = destination.AsRegisterPairHigh<Register>(); - Register dst_low = destination.AsRegisterPairLow<Register>(); - FRegister src = source.AsFpuRegister<FRegister>(); - __ Mfc1(dst_low, src); - __ MoveFromFpuHigh(dst_high, src); - } else { - DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination; - int32_t off = source.GetStackIndex(); - Register r = destination.AsRegisterPairLow<Register>(); - __ LoadFromOffset(kLoadDoubleword, r, SP, off); - } - } else if (destination.IsFpuRegister()) { - if (source.IsRegisterPair()) { - FRegister dst = destination.AsFpuRegister<FRegister>(); - Register src_high = source.AsRegisterPairHigh<Register>(); - Register src_low = source.AsRegisterPairLow<Register>(); - __ Mtc1(src_low, dst); - __ MoveToFpuHigh(src_high, dst); - } else if (source.IsFpuRegister()) { - __ MovD(destination.AsFpuRegister<FRegister>(), source.AsFpuRegister<FRegister>()); - } else { - DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination; - __ LoadDFromOffset(destination.AsFpuRegister<FRegister>(), SP, source.GetStackIndex()); - } + if (source.IsConstant()) { + MoveConstant(destination, source.GetConstant()); } else { - DCHECK(destination.IsDoubleStackSlot()) << destination; - int32_t off = destination.GetStackIndex(); - if (source.IsRegisterPair()) { - __ StoreToOffset(kStoreDoubleword, source.AsRegisterPairLow<Register>(), SP, off); - } else if (source.IsFpuRegister()) { - __ StoreDToOffset(source.AsFpuRegister<FRegister>(), SP, off); + if (destination.IsRegister()) { + if (source.IsRegister()) { + __ Move(destination.AsRegister<Register>(), source.AsRegister<Register>()); + } else if (source.IsFpuRegister()) { + __ Mfc1(destination.AsRegister<Register>(), source.AsFpuRegister<FRegister>()); + } else { + DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; + __ LoadFromOffset(kLoadWord, destination.AsRegister<Register>(), SP, source.GetStackIndex()); + } + } else if (destination.IsRegisterPair()) { + if (source.IsRegisterPair()) { + __ Move(destination.AsRegisterPairHigh<Register>(), source.AsRegisterPairHigh<Register>()); + __ Move(destination.AsRegisterPairLow<Register>(), source.AsRegisterPairLow<Register>()); + } else if (source.IsFpuRegister()) { + Register dst_high = destination.AsRegisterPairHigh<Register>(); + Register dst_low = destination.AsRegisterPairLow<Register>(); + FRegister src = source.AsFpuRegister<FRegister>(); + __ Mfc1(dst_low, src); + __ MoveFromFpuHigh(dst_high, src); + } else { + DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination; + int32_t off = source.GetStackIndex(); + Register r = destination.AsRegisterPairLow<Register>(); + __ LoadFromOffset(kLoadDoubleword, r, SP, off); + } + } else if (destination.IsFpuRegister()) { + if (source.IsRegister()) { + DCHECK(!Primitive::Is64BitType(dst_type)); + __ Mtc1(source.AsRegister<Register>(), destination.AsFpuRegister<FRegister>()); + } else if (source.IsRegisterPair()) { + DCHECK(Primitive::Is64BitType(dst_type)); + FRegister dst = destination.AsFpuRegister<FRegister>(); + Register src_high = source.AsRegisterPairHigh<Register>(); + Register src_low = source.AsRegisterPairLow<Register>(); + __ Mtc1(src_low, dst); + __ MoveToFpuHigh(src_high, dst); + } else if (source.IsFpuRegister()) { + if (Primitive::Is64BitType(dst_type)) { + __ MovD(destination.AsFpuRegister<FRegister>(), source.AsFpuRegister<FRegister>()); + } else { + DCHECK_EQ(dst_type, Primitive::kPrimFloat); + __ MovS(destination.AsFpuRegister<FRegister>(), source.AsFpuRegister<FRegister>()); + } + } else if (source.IsDoubleStackSlot()) { + DCHECK(Primitive::Is64BitType(dst_type)); + __ LoadDFromOffset(destination.AsFpuRegister<FRegister>(), SP, source.GetStackIndex()); + } else { + DCHECK(!Primitive::Is64BitType(dst_type)); + DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; + __ LoadSFromOffset(destination.AsFpuRegister<FRegister>(), SP, source.GetStackIndex()); + } + } else if (destination.IsDoubleStackSlot()) { + int32_t dst_offset = destination.GetStackIndex(); + if (source.IsRegisterPair()) { + __ StoreToOffset(kStoreDoubleword, source.AsRegisterPairLow<Register>(), SP, dst_offset); + } else if (source.IsFpuRegister()) { + __ StoreDToOffset(source.AsFpuRegister<FRegister>(), SP, dst_offset); + } else { + DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination; + __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex()); + __ StoreToOffset(kStoreWord, TMP, SP, dst_offset); + __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex() + 4); + __ StoreToOffset(kStoreWord, TMP, SP, dst_offset + 4); + } } else { - DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination; - __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex()); - __ StoreToOffset(kStoreWord, TMP, SP, off); - __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex() + 4); - __ StoreToOffset(kStoreWord, TMP, SP, off + 4); + DCHECK(destination.IsStackSlot()) << destination; + int32_t dst_offset = destination.GetStackIndex(); + if (source.IsRegister()) { + __ StoreToOffset(kStoreWord, source.AsRegister<Register>(), SP, dst_offset); + } else if (source.IsFpuRegister()) { + __ StoreSToOffset(source.AsFpuRegister<FRegister>(), SP, dst_offset); + } else { + DCHECK(source.IsStackSlot()) << "Cannot move from " << source << " to " << destination; + __ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex()); + __ StoreToOffset(kStoreWord, TMP, SP, dst_offset); + } } } } @@ -1584,14 +1611,15 @@ inline void CodeGeneratorMIPS::EmitPcRelativeLinkerPatches( for (const PcRelativePatchInfo& info : infos) { const DexFile& dex_file = info.target_dex_file; size_t offset_or_index = info.offset_or_index; - DCHECK(info.high_label.IsBound()); - uint32_t high_offset = __ GetLabelLocation(&info.high_label); + DCHECK(info.label.IsBound()); + uint32_t literal_offset = __ GetLabelLocation(&info.label); // On R2 we use HMipsComputeBaseMethodAddress and patch relative to // the assembler's base label used for PC-relative addressing. - uint32_t pc_rel_offset = info.pc_rel_label.IsBound() - ? __ GetLabelLocation(&info.pc_rel_label) + const PcRelativePatchInfo& info_high = info.patch_info_high ? *info.patch_info_high : info; + uint32_t pc_rel_offset = info_high.pc_rel_label.IsBound() + ? __ GetLabelLocation(&info_high.pc_rel_label) : __ GetPcRelBaseLabelLocation(); - linker_patches->push_back(Factory(high_offset, &dex_file, pc_rel_offset, offset_or_index)); + linker_patches->push_back(Factory(literal_offset, &dex_file, pc_rel_offset, offset_or_index)); } } @@ -1625,37 +1653,50 @@ void CodeGeneratorMIPS::EmitLinkerPatches(ArenaVector<LinkerPatch>* linker_patch } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativeMethodPatch( - MethodReference target_method) { + MethodReference target_method, + const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, target_method.dex_method_index, + info_high, &pc_relative_method_patches_); } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewMethodBssEntryPatch( - MethodReference target_method) { + MethodReference target_method, + const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, target_method.dex_method_index, + info_high, &method_bss_entry_patches_); } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativeTypePatch( - const DexFile& dex_file, dex::TypeIndex type_index) { - return NewPcRelativePatch(dex_file, type_index.index_, &pc_relative_type_patches_); + const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, type_index.index_, info_high, &pc_relative_type_patches_); } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewTypeBssEntryPatch( - const DexFile& dex_file, dex::TypeIndex type_index) { - return NewPcRelativePatch(dex_file, type_index.index_, &type_bss_entry_patches_); + const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, type_index.index_, info_high, &type_bss_entry_patches_); } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativeStringPatch( - const DexFile& dex_file, dex::StringIndex string_index) { - return NewPcRelativePatch(dex_file, string_index.index_, &pc_relative_string_patches_); + const DexFile& dex_file, + dex::StringIndex string_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, string_index.index_, info_high, &pc_relative_string_patches_); } CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativePatch( - const DexFile& dex_file, uint32_t offset_or_index, ArenaDeque<PcRelativePatchInfo>* patches) { - patches->emplace_back(dex_file, offset_or_index); + const DexFile& dex_file, + uint32_t offset_or_index, + const PcRelativePatchInfo* info_high, + ArenaDeque<PcRelativePatchInfo>* patches) { + patches->emplace_back(dex_file, offset_or_index, info_high); return &patches->back(); } @@ -1669,14 +1710,16 @@ Literal* CodeGeneratorMIPS::DeduplicateBootImageAddressLiteral(uint32_t address) return DeduplicateUint32Literal(dchecked_integral_cast<uint32_t>(address), &uint32_literals_); } -void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, +void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high, Register out, - Register base) { + Register base, + PcRelativePatchInfo* info_low) { + DCHECK(!info_high->patch_info_high); DCHECK_NE(out, base); if (GetInstructionSetFeatures().IsR6()) { DCHECK_EQ(base, ZERO); - __ Bind(&info->high_label); - __ Bind(&info->pc_rel_label); + __ Bind(&info_high->label); + __ Bind(&info_high->pc_rel_label); // Add the high half of a 32-bit offset to PC. __ Auipc(out, /* placeholder */ 0x1234); } else { @@ -1685,18 +1728,20 @@ void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo // Generate a dummy PC-relative call to obtain PC. __ Nal(); } - __ Bind(&info->high_label); + __ Bind(&info_high->label); __ Lui(out, /* placeholder */ 0x1234); // If we emitted the NAL, bind the pc_rel_label, otherwise base is a register holding // the HMipsComputeBaseMethodAddress which has its own label stored in MipsAssembler. if (base == ZERO) { - __ Bind(&info->pc_rel_label); + __ Bind(&info_high->pc_rel_label); } // Add the high half of a 32-bit offset to PC. __ Addu(out, out, (base == ZERO) ? RA : base); } - // The immediately following instruction will add the sign-extended low half of the 32-bit + // A following instruction will add the sign-extended low half of the 32-bit // offset to `out` (e.g. lw, jialc, addiu). + DCHECK_EQ(info_low->patch_info_high, info_high); + __ Bind(&info_low->label); } CodeGeneratorMIPS::JitPatchInfo* CodeGeneratorMIPS::NewJitRootStringPatch( @@ -2285,7 +2330,7 @@ void InstructionCodeGeneratorMIPS::HandleShift(HBinaryOperation* instr) { Register lhs_low = locations->InAt(0).AsRegisterPairLow<Register>(); if (use_imm) { if (shift_value == 0) { - codegen_->Move64(locations->Out(), locations->InAt(0)); + codegen_->MoveLocation(locations->Out(), locations->InAt(0), type); } else if (shift_value < kMipsBitsPerWord) { if (has_ins_rotr) { if (instr->IsShl()) { @@ -7140,10 +7185,12 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall( break; case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: { DCHECK(GetCompilerOptions().IsBootImage()); - PcRelativePatchInfo* info = NewPcRelativeMethodPatch(invoke->GetTargetMethod()); + PcRelativePatchInfo* info_high = NewPcRelativeMethodPatch(invoke->GetTargetMethod()); + PcRelativePatchInfo* info_low = + NewPcRelativeMethodPatch(invoke->GetTargetMethod(), info_high); bool reordering = __ SetReorder(false); Register temp_reg = temp.AsRegister<Register>(); - EmitPcRelativeAddressPlaceholderHigh(info, TMP, base_reg); + EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, base_reg, info_low); __ Addiu(temp_reg, TMP, /* placeholder */ 0x5678); __ SetReorder(reordering); break; @@ -7152,11 +7199,13 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall( __ LoadConst32(temp.AsRegister<Register>(), invoke->GetMethodAddress()); break; case HInvokeStaticOrDirect::MethodLoadKind::kBssEntry: { - PcRelativePatchInfo* info = NewMethodBssEntryPatch( + PcRelativePatchInfo* info_high = NewMethodBssEntryPatch( MethodReference(&GetGraph()->GetDexFile(), invoke->GetDexMethodIndex())); + PcRelativePatchInfo* info_low = NewMethodBssEntryPatch( + MethodReference(&GetGraph()->GetDexFile(), invoke->GetDexMethodIndex()), info_high); Register temp_reg = temp.AsRegister<Register>(); bool reordering = __ SetReorder(false); - EmitPcRelativeAddressPlaceholderHigh(info, TMP, base_reg); + EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, base_reg, info_low); __ Lw(temp_reg, TMP, /* placeholder */ 0x5678); __ SetReorder(reordering); break; @@ -7286,11 +7335,8 @@ void LocationsBuilderMIPS::VisitLoadClass(HLoadClass* cls) { if (load_kind == HLoadClass::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { // Rely on the type resolution or initialization and marking to save everything we need. - // Request a temp to hold the BSS entry location for the slow path on R2 - // (no benefit for R6). - if (!isR6) { - locations->AddTemp(Location::RequiresRegister()); - } + // Request a temp to hold the BSS entry location for the slow path. + locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); @@ -7336,6 +7382,7 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF ? kWithoutReadBarrier : kCompilerReadBarrierOption; bool generate_null_check = false; + CodeGeneratorMIPS::PcRelativePatchInfo* bss_info_high = nullptr; switch (load_kind) { case HLoadClass::LoadKind::kReferrersClass: { DCHECK(!cls->CanCallRuntime()); @@ -7351,10 +7398,15 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: { DCHECK(codegen_->GetCompilerOptions().IsBootImage()); DCHECK_EQ(read_barrier_option, kWithoutReadBarrier); - CodeGeneratorMIPS::PcRelativePatchInfo* info = + CodeGeneratorMIPS::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex()); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex(), info_high); bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, + out, + base_or_current_method_reg, + info_low); __ Addiu(out, out, /* placeholder */ 0x5678); __ SetReorder(reordering); break; @@ -7370,24 +7422,18 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF break; } case HLoadClass::LoadKind::kBssEntry: { - CodeGeneratorMIPS::PcRelativePatchInfo* info = - codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); + bss_info_high = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex(), bss_info_high); constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier; - if (isR6 || non_baker_read_barrier) { - bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); - GenerateGcRootFieldLoad(cls, out_loc, out, /* placeholder */ 0x5678, read_barrier_option); - __ SetReorder(reordering); - } else { - // On R2 save the BSS entry address in a temporary register instead of - // recalculating it in the slow path. - Register temp = locations->GetTemp(0).AsRegister<Register>(); - bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, temp, base_or_current_method_reg); - __ Addiu(temp, temp, /* placeholder */ 0x5678); - __ SetReorder(reordering); - GenerateGcRootFieldLoad(cls, out_loc, temp, /* offset */ 0, read_barrier_option); - } + Register temp = non_baker_read_barrier ? out : locations->GetTemp(0).AsRegister<Register>(); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(bss_info_high, + temp, + base_or_current_method_reg, + info_low); + GenerateGcRootFieldLoad(cls, out_loc, temp, /* placeholder */ 0x5678, read_barrier_option); + __ SetReorder(reordering); generate_null_check = true; break; } @@ -7411,7 +7457,7 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF if (generate_null_check || cls->MustGenerateClinitCheck()) { DCHECK(cls->CanCallRuntime()); SlowPathCodeMIPS* slow_path = new (GetGraph()->GetArena()) LoadClassSlowPathMIPS( - cls, cls, cls->GetDexPc(), cls->MustGenerateClinitCheck()); + cls, cls, cls->GetDexPc(), cls->MustGenerateClinitCheck(), bss_info_high); codegen_->AddSlowPath(slow_path); if (generate_null_check) { __ Beqz(out, slow_path->GetEntryLabel()); @@ -7476,11 +7522,8 @@ void LocationsBuilderMIPS::VisitLoadString(HLoadString* load) { if (load_kind == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { // Rely on the pResolveString and marking to save everything we need. - // Request a temp to hold the BSS entry location for the slow path on R2 - // (no benefit for R6). - if (!isR6) { - locations->AddTemp(Location::RequiresRegister()); - } + // Request a temp to hold the BSS entry location for the slow path. + locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); @@ -7516,10 +7559,15 @@ void InstructionCodeGeneratorMIPS::VisitLoadString(HLoadString* load) NO_THREAD_ switch (load_kind) { case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { DCHECK(codegen_->GetCompilerOptions().IsBootImage()); - CodeGeneratorMIPS::PcRelativePatchInfo* info = + CodeGeneratorMIPS::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high); bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, + out, + base_or_current_method_reg, + info_low); __ Addiu(out, out, /* placeholder */ 0x5678); __ SetReorder(reordering); return; // No dex cache slow path. @@ -7535,29 +7583,25 @@ void InstructionCodeGeneratorMIPS::VisitLoadString(HLoadString* load) NO_THREAD_ } case HLoadString::LoadKind::kBssEntry: { DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); - CodeGeneratorMIPS::PcRelativePatchInfo* info = + CodeGeneratorMIPS::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); + CodeGeneratorMIPS::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high); constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier; - if (isR6 || non_baker_read_barrier) { - bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); - GenerateGcRootFieldLoad(load, - out_loc, - out, - /* placeholder */ 0x5678, - kCompilerReadBarrierOption); - __ SetReorder(reordering); - } else { - // On R2 save the BSS entry address in a temporary register instead of - // recalculating it in the slow path. - Register temp = locations->GetTemp(0).AsRegister<Register>(); - bool reordering = __ SetReorder(false); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, temp, base_or_current_method_reg); - __ Addiu(temp, temp, /* placeholder */ 0x5678); - __ SetReorder(reordering); - GenerateGcRootFieldLoad(load, out_loc, temp, /* offset */ 0, kCompilerReadBarrierOption); - } - SlowPathCodeMIPS* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathMIPS(load); + Register temp = non_baker_read_barrier ? out : locations->GetTemp(0).AsRegister<Register>(); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, + temp, + base_or_current_method_reg, + info_low); + GenerateGcRootFieldLoad(load, + out_loc, + temp, + /* placeholder */ 0x5678, + kCompilerReadBarrierOption); + __ SetReorder(reordering); + SlowPathCodeMIPS* slow_path = + new (GetGraph()->GetArena()) LoadStringSlowPathMIPS(load, info_high); codegen_->AddSlowPath(slow_path); __ Beqz(out, slow_path->GetEntryLabel()); __ Bind(slow_path->GetExitLabel()); diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index e72e838dd9..c259ea3e48 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -368,8 +368,6 @@ class CodeGeneratorMIPS : public CodeGenerator { void Bind(HBasicBlock* block) OVERRIDE; - void Move32(Location destination, Location source); - void Move64(Location destination, Location source); void MoveConstant(Location location, HConstant* c); size_t GetWordSize() const OVERRIDE { return kMipsWordSize; } @@ -568,31 +566,68 @@ class CodeGeneratorMIPS : public CodeGenerator { // The PcRelativePatchInfo is used for PC-relative addressing of dex cache arrays // and boot image strings. The only difference is the interpretation of the offset_or_index. + // The 16-bit halves of the 32-bit PC-relative offset are patched separately, necessitating + // two patches/infos. There can be more than two patches/infos if the instruction supplying + // the high half is shared with e.g. a slow path, while the low half is supplied by separate + // instructions, e.g.: + // lui r1, high // patch + // addu r1, r1, rbase + // lw r2, low(r1) // patch + // beqz r2, slow_path + // back: + // ... + // slow_path: + // ... + // sw r2, low(r1) // patch + // b back struct PcRelativePatchInfo { - PcRelativePatchInfo(const DexFile& dex_file, uint32_t off_or_idx) - : target_dex_file(dex_file), offset_or_index(off_or_idx) { } - PcRelativePatchInfo(PcRelativePatchInfo&& other) = default; + PcRelativePatchInfo(const DexFile& dex_file, + uint32_t off_or_idx, + const PcRelativePatchInfo* info_high) + : target_dex_file(dex_file), + offset_or_index(off_or_idx), + label(), + pc_rel_label(), + patch_info_high(info_high) { } const DexFile& target_dex_file; // Either the dex cache array element offset or the string/type index. uint32_t offset_or_index; - // Label for the instruction loading the most significant half of the offset that's added to PC - // to form the base address (the least significant half is loaded with the instruction that - // follows). - MipsLabel high_label; - // Label for the instruction corresponding to PC+0. + // Label for the instruction to patch. + MipsLabel label; + // Label for the instruction corresponding to PC+0. Not bound or used in low half patches. + // Not bound in high half patches on R2 when using HMipsComputeBaseMethodAddress. + // Bound in high half patches on R2 when using the NAL instruction instead of + // HMipsComputeBaseMethodAddress. + // Bound in high half patches on R6. MipsLabel pc_rel_label; + // Pointer to the info for the high half patch or nullptr if this is the high half patch info. + const PcRelativePatchInfo* patch_info_high; + + private: + PcRelativePatchInfo(PcRelativePatchInfo&& other) = delete; + DISALLOW_COPY_AND_ASSIGN(PcRelativePatchInfo); }; - PcRelativePatchInfo* NewPcRelativeMethodPatch(MethodReference target_method); - PcRelativePatchInfo* NewMethodBssEntryPatch(MethodReference target_method); - PcRelativePatchInfo* NewPcRelativeTypePatch(const DexFile& dex_file, dex::TypeIndex type_index); - PcRelativePatchInfo* NewTypeBssEntryPatch(const DexFile& dex_file, dex::TypeIndex type_index); + PcRelativePatchInfo* NewPcRelativeMethodPatch(MethodReference target_method, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewMethodBssEntryPatch(MethodReference target_method, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewPcRelativeTypePatch(const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewTypeBssEntryPatch(const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high = nullptr); PcRelativePatchInfo* NewPcRelativeStringPatch(const DexFile& dex_file, - dex::StringIndex string_index); + dex::StringIndex string_index, + const PcRelativePatchInfo* info_high = nullptr); Literal* DeduplicateBootImageAddressLiteral(uint32_t address); - void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, Register out, Register base); + void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high, + Register out, + Register base, + PcRelativePatchInfo* info_low); // The JitPatchInfo is used for JIT string and class loads. struct JitPatchInfo { @@ -627,6 +662,7 @@ class CodeGeneratorMIPS : public CodeGenerator { Literal* DeduplicateUint32Literal(uint32_t value, Uint32ToLiteralMap* map); PcRelativePatchInfo* NewPcRelativePatch(const DexFile& dex_file, uint32_t offset_or_index, + const PcRelativePatchInfo* info_high, ArenaDeque<PcRelativePatchInfo>* patches); template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)> diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index e4f1cbd600..5fb8755086 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -164,19 +164,42 @@ class LoadClassSlowPathMIPS64 : public SlowPathCodeMIPS64 { LoadClassSlowPathMIPS64(HLoadClass* cls, HInstruction* at, uint32_t dex_pc, - bool do_clinit) - : SlowPathCodeMIPS64(at), cls_(cls), dex_pc_(dex_pc), do_clinit_(do_clinit) { + bool do_clinit, + const CodeGeneratorMIPS64::PcRelativePatchInfo* bss_info_high = nullptr) + : SlowPathCodeMIPS64(at), + cls_(cls), + dex_pc_(dex_pc), + do_clinit_(do_clinit), + bss_info_high_(bss_info_high) { DCHECK(at->IsLoadClass() || at->IsClinitCheck()); } void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { LocationSummary* locations = instruction_->GetLocations(); + Location out = locations->Out(); CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen); - + const bool baker_or_no_read_barriers = (!kUseReadBarrier || kUseBakerReadBarrier); + InvokeRuntimeCallingConvention calling_convention; + DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); + const bool is_load_class_bss_entry = + (cls_ == instruction_) && (cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry); __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); - InvokeRuntimeCallingConvention calling_convention; + // For HLoadClass/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. + GpuRegister entry_address = kNoGpuRegister; + if (is_load_class_bss_entry && baker_or_no_read_barriers) { + GpuRegister temp = locations->GetTemp(0).AsRegister<GpuRegister>(); + bool temp_is_a0 = (temp == calling_convention.GetRegisterAt(0)); + // In the unlucky case that `temp` is A0, we preserve the address in `out` across the + // kSaveEverything call. + entry_address = temp_is_a0 ? out.AsRegister<GpuRegister>() : temp; + DCHECK_NE(entry_address, calling_convention.GetRegisterAt(0)); + if (temp_is_a0) { + __ Move(entry_address, temp); + } + } + dex::TypeIndex type_index = cls_->GetTypeIndex(); __ LoadConst32(calling_convention.GetRegisterAt(0), type_index.index_); QuickEntrypointEnum entrypoint = do_clinit_ ? kQuickInitializeStaticStorage @@ -188,8 +211,20 @@ class LoadClassSlowPathMIPS64 : public SlowPathCodeMIPS64 { CheckEntrypointTypes<kQuickInitializeType, void*, uint32_t>(); } + // For HLoadClass/kBssEntry, store the resolved class to the BSS entry. + if (is_load_class_bss_entry && baker_or_no_read_barriers) { + // The class entry address was preserved in `entry_address` thanks to kSaveEverything. + DCHECK(bss_info_high_); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + mips64_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index, bss_info_high_); + __ Bind(&info_low->label); + __ StoreToOffset(kStoreWord, + calling_convention.GetRegisterAt(0), + entry_address, + /* placeholder */ 0x5678); + } + // Move the class to the desired location. - Location out = locations->Out(); if (out.IsValid()) { DCHECK(out.IsRegister() && !locations->GetLiveRegisters()->ContainsCoreRegister(out.reg())); Primitive::Type type = instruction_->GetType(); @@ -197,16 +232,18 @@ class LoadClassSlowPathMIPS64 : public SlowPathCodeMIPS64 { Location::RegisterLocation(calling_convention.GetRegisterAt(0)), type); } - RestoreLiveRegisters(codegen, locations); - // For HLoadClass/kBssEntry, store the resolved Class to the BSS entry. - DCHECK_EQ(instruction_->IsLoadClass(), cls_ == instruction_); - if (cls_ == instruction_ && cls_->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) { - DCHECK(out.IsValid()); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = + + // For HLoadClass/kBssEntry, store the resolved class to the BSS entry. + if (is_load_class_bss_entry && !baker_or_no_read_barriers) { + // For non-Baker read barriers we need to re-calculate the address of + // the class entry. + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = mips64_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); - mips64_codegen->EmitPcRelativeAddressPlaceholderHigh(info, AT); - __ Sw(out.AsRegister<GpuRegister>(), AT, /* placeholder */ 0x5678); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + mips64_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index, info_high); + mips64_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, info_low); + __ StoreToOffset(kStoreWord, out.AsRegister<GpuRegister>(), TMP, /* placeholder */ 0x5678); } __ Bc(GetExitLabel()); } @@ -223,50 +260,94 @@ class LoadClassSlowPathMIPS64 : public SlowPathCodeMIPS64 { // Whether to initialize the class. const bool do_clinit_; + // Pointer to the high half PC-relative patch info for HLoadClass/kBssEntry. + const CodeGeneratorMIPS64::PcRelativePatchInfo* bss_info_high_; + DISALLOW_COPY_AND_ASSIGN(LoadClassSlowPathMIPS64); }; class LoadStringSlowPathMIPS64 : public SlowPathCodeMIPS64 { public: - explicit LoadStringSlowPathMIPS64(HLoadString* instruction) : SlowPathCodeMIPS64(instruction) {} + explicit LoadStringSlowPathMIPS64(HLoadString* instruction, + const CodeGeneratorMIPS64::PcRelativePatchInfo* bss_info_high) + : SlowPathCodeMIPS64(instruction), bss_info_high_(bss_info_high) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { + DCHECK(instruction_->IsLoadString()); + DCHECK_EQ(instruction_->AsLoadString()->GetLoadKind(), HLoadString::LoadKind::kBssEntry); LocationSummary* locations = instruction_->GetLocations(); DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(locations->Out().reg())); + HLoadString* load = instruction_->AsLoadString(); + const dex::StringIndex string_index = load->GetStringIndex(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); CodeGeneratorMIPS64* mips64_codegen = down_cast<CodeGeneratorMIPS64*>(codegen); - + const bool baker_or_no_read_barriers = (!kUseReadBarrier || kUseBakerReadBarrier); + InvokeRuntimeCallingConvention calling_convention; __ Bind(GetEntryLabel()); SaveLiveRegisters(codegen, locations); - InvokeRuntimeCallingConvention calling_convention; - HLoadString* load = instruction_->AsLoadString(); - const dex::StringIndex string_index = instruction_->AsLoadString()->GetStringIndex(); + // For HLoadString/kBssEntry/kSaveEverything, make sure we preserve the address of the entry. + GpuRegister entry_address = kNoGpuRegister; + if (baker_or_no_read_barriers) { + GpuRegister temp = locations->GetTemp(0).AsRegister<GpuRegister>(); + bool temp_is_a0 = (temp == calling_convention.GetRegisterAt(0)); + // In the unlucky case that `temp` is A0, we preserve the address in `out` across the + // kSaveEverything call. + entry_address = temp_is_a0 ? out : temp; + DCHECK_NE(entry_address, calling_convention.GetRegisterAt(0)); + if (temp_is_a0) { + __ Move(entry_address, temp); + } + } + __ LoadConst32(calling_convention.GetRegisterAt(0), string_index.index_); mips64_codegen->InvokeRuntime(kQuickResolveString, instruction_, instruction_->GetDexPc(), this); CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); + + // Store the resolved string to the BSS entry. + if (baker_or_no_read_barriers) { + // The string entry address was preserved in `entry_address` thanks to kSaveEverything. + DCHECK(bss_info_high_); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), + string_index, + bss_info_high_); + __ Bind(&info_low->label); + __ StoreToOffset(kStoreWord, + calling_convention.GetRegisterAt(0), + entry_address, + /* placeholder */ 0x5678); + } + Primitive::Type type = instruction_->GetType(); mips64_codegen->MoveLocation(locations->Out(), Location::RegisterLocation(calling_convention.GetRegisterAt(0)), type); - RestoreLiveRegisters(codegen, locations); - // Store the resolved String to the BSS entry. - GpuRegister out = locations->Out().AsRegister<GpuRegister>(); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = - mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); - mips64_codegen->EmitPcRelativeAddressPlaceholderHigh(info, AT); - __ Sw(out, AT, /* placeholder */ 0x5678); - + // Store the resolved string to the BSS entry. + if (!baker_or_no_read_barriers) { + // For non-Baker read barriers we need to re-calculate the address of + // the string entry. + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = + mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, info_high); + mips64_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, info_low); + __ StoreToOffset(kStoreWord, out, TMP, /* placeholder */ 0x5678); + } __ Bc(GetExitLabel()); } const char* GetDescription() const OVERRIDE { return "LoadStringSlowPathMIPS64"; } private: + // Pointer to the high half PC-relative patch info. + const CodeGeneratorMIPS64::PcRelativePatchInfo* bss_info_high_; + DISALLOW_COPY_AND_ASSIGN(LoadStringSlowPathMIPS64); }; @@ -1431,9 +1512,11 @@ inline void CodeGeneratorMIPS64::EmitPcRelativeLinkerPatches( for (const PcRelativePatchInfo& info : infos) { const DexFile& dex_file = info.target_dex_file; size_t offset_or_index = info.offset_or_index; - DCHECK(info.pc_rel_label.IsBound()); - uint32_t pc_rel_offset = __ GetLabelLocation(&info.pc_rel_label); - linker_patches->push_back(Factory(pc_rel_offset, &dex_file, pc_rel_offset, offset_or_index)); + DCHECK(info.label.IsBound()); + uint32_t literal_offset = __ GetLabelLocation(&info.label); + const PcRelativePatchInfo& info_high = info.patch_info_high ? *info.patch_info_high : info; + uint32_t pc_rel_offset = __ GetLabelLocation(&info_high.label); + linker_patches->push_back(Factory(literal_offset, &dex_file, pc_rel_offset, offset_or_index)); } } @@ -1467,37 +1550,50 @@ void CodeGeneratorMIPS64::EmitLinkerPatches(ArenaVector<LinkerPatch>* linker_pat } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativeMethodPatch( - MethodReference target_method) { + MethodReference target_method, + const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, target_method.dex_method_index, + info_high, &pc_relative_method_patches_); } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewMethodBssEntryPatch( - MethodReference target_method) { + MethodReference target_method, + const PcRelativePatchInfo* info_high) { return NewPcRelativePatch(*target_method.dex_file, target_method.dex_method_index, + info_high, &method_bss_entry_patches_); } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativeTypePatch( - const DexFile& dex_file, dex::TypeIndex type_index) { - return NewPcRelativePatch(dex_file, type_index.index_, &pc_relative_type_patches_); + const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, type_index.index_, info_high, &pc_relative_type_patches_); } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewTypeBssEntryPatch( - const DexFile& dex_file, dex::TypeIndex type_index) { - return NewPcRelativePatch(dex_file, type_index.index_, &type_bss_entry_patches_); + const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, type_index.index_, info_high, &type_bss_entry_patches_); } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativeStringPatch( - const DexFile& dex_file, dex::StringIndex string_index) { - return NewPcRelativePatch(dex_file, string_index.index_, &pc_relative_string_patches_); + const DexFile& dex_file, + dex::StringIndex string_index, + const PcRelativePatchInfo* info_high) { + return NewPcRelativePatch(dex_file, string_index.index_, info_high, &pc_relative_string_patches_); } CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativePatch( - const DexFile& dex_file, uint32_t offset_or_index, ArenaDeque<PcRelativePatchInfo>* patches) { - patches->emplace_back(dex_file, offset_or_index); + const DexFile& dex_file, + uint32_t offset_or_index, + const PcRelativePatchInfo* info_high, + ArenaDeque<PcRelativePatchInfo>* patches) { + patches->emplace_back(dex_file, offset_or_index, info_high); return &patches->back(); } @@ -1517,13 +1613,17 @@ Literal* CodeGeneratorMIPS64::DeduplicateBootImageAddressLiteral(uint64_t addres return DeduplicateUint32Literal(dchecked_integral_cast<uint32_t>(address), &uint32_literals_); } -void CodeGeneratorMIPS64::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, - GpuRegister out) { - __ Bind(&info->pc_rel_label); +void CodeGeneratorMIPS64::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high, + GpuRegister out, + PcRelativePatchInfo* info_low) { + DCHECK(!info_high->patch_info_high); + __ Bind(&info_high->label); // Add the high half of a 32-bit offset to PC. __ Auipc(out, /* placeholder */ 0x1234); - // The immediately following instruction will add the sign-extended low half of the 32-bit + // A following instruction will add the sign-extended low half of the 32-bit // offset to `out` (e.g. ld, jialc, daddiu). + DCHECK_EQ(info_low->patch_info_high, info_high); + __ Bind(&info_low->label); } Literal* CodeGeneratorMIPS64::DeduplicateJitStringLiteral(const DexFile& dex_file, @@ -4940,9 +5040,11 @@ void CodeGeneratorMIPS64::GenerateStaticOrDirectCall( break; case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: { DCHECK(GetCompilerOptions().IsBootImage()); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = NewPcRelativeMethodPatch(invoke->GetTargetMethod()); - EmitPcRelativeAddressPlaceholderHigh(info, AT); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + NewPcRelativeMethodPatch(invoke->GetTargetMethod(), info_high); + EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low); __ Daddiu(temp.AsRegister<GpuRegister>(), AT, /* placeholder */ 0x5678); break; } @@ -4952,9 +5054,11 @@ void CodeGeneratorMIPS64::GenerateStaticOrDirectCall( DeduplicateUint64Literal(invoke->GetMethodAddress())); break; case HInvokeStaticOrDirect::MethodLoadKind::kBssEntry: { - PcRelativePatchInfo* info = NewMethodBssEntryPatch( + PcRelativePatchInfo* info_high = NewMethodBssEntryPatch( MethodReference(&GetGraph()->GetDexFile(), invoke->GetDexMethodIndex())); - EmitPcRelativeAddressPlaceholderHigh(info, AT); + PcRelativePatchInfo* info_low = NewMethodBssEntryPatch( + MethodReference(&GetGraph()->GetDexFile(), invoke->GetDexMethodIndex()), info_high); + EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low); __ Ld(temp.AsRegister<GpuRegister>(), AT, /* placeholder */ 0x5678); break; } @@ -5071,12 +5175,14 @@ void LocationsBuilderMIPS64::VisitLoadClass(HLoadClass* cls) { if (load_kind == HLoadClass::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { // Rely on the type resolution or initialization and marking to save everything we need. + // Request a temp to hold the BSS entry location for the slow path. + locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); locations->SetCustomSlowPathCallerSaves(caller_saves); } else { - // For non-Baker read barrier we have a temp-clobbering call. + // For non-Baker read barriers we have a temp-clobbering call. } } } @@ -5104,6 +5210,7 @@ void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S ? kWithoutReadBarrier : kCompilerReadBarrierOption; bool generate_null_check = false; + CodeGeneratorMIPS64::PcRelativePatchInfo* bss_info_high = nullptr; switch (load_kind) { case HLoadClass::LoadKind::kReferrersClass: DCHECK(!cls->CanCallRuntime()); @@ -5118,9 +5225,11 @@ void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: { DCHECK(codegen_->GetCompilerOptions().IsBootImage()); DCHECK_EQ(read_barrier_option, kWithoutReadBarrier); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex()); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, AT); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex(), info_high); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low); __ Daddiu(out, AT, /* placeholder */ 0x5678); break; } @@ -5135,10 +5244,15 @@ void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S break; } case HLoadClass::LoadKind::kBssEntry: { - CodeGeneratorMIPS64::PcRelativePatchInfo* info = - codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out); - GenerateGcRootFieldLoad(cls, out_loc, out, /* placeholder */ 0x5678, read_barrier_option); + bss_info_high = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex(), bss_info_high); + constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier; + GpuRegister temp = non_baker_read_barrier + ? out + : locations->GetTemp(0).AsRegister<GpuRegister>(); + codegen_->EmitPcRelativeAddressPlaceholderHigh(bss_info_high, temp, info_low); + GenerateGcRootFieldLoad(cls, out_loc, temp, /* placeholder */ 0x5678, read_barrier_option); generate_null_check = true; break; } @@ -5159,7 +5273,7 @@ void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S if (generate_null_check || cls->MustGenerateClinitCheck()) { DCHECK(cls->CanCallRuntime()); SlowPathCodeMIPS64* slow_path = new (GetGraph()->GetArena()) LoadClassSlowPathMIPS64( - cls, cls, cls->GetDexPc(), cls->MustGenerateClinitCheck()); + cls, cls, cls->GetDexPc(), cls->MustGenerateClinitCheck(), bss_info_high); codegen_->AddSlowPath(slow_path); if (generate_null_check) { __ Beqzc(out, slow_path->GetEntryLabel()); @@ -5207,12 +5321,14 @@ void LocationsBuilderMIPS64::VisitLoadString(HLoadString* load) { if (load_kind == HLoadString::LoadKind::kBssEntry) { if (!kUseReadBarrier || kUseBakerReadBarrier) { // Rely on the pResolveString and marking to save everything we need. + // Request a temp to hold the BSS entry location for the slow path. + locations->AddTemp(Location::RequiresRegister()); RegisterSet caller_saves = RegisterSet::Empty(); InvokeRuntimeCallingConvention calling_convention; caller_saves.Add(Location::RegisterLocation(calling_convention.GetRegisterAt(0))); locations->SetCustomSlowPathCallerSaves(caller_saves); } else { - // For non-Baker read barrier we have a temp-clobbering call. + // For non-Baker read barriers we have a temp-clobbering call. } } } @@ -5229,9 +5345,11 @@ void InstructionCodeGeneratorMIPS64::VisitLoadString(HLoadString* load) NO_THREA switch (load_kind) { case HLoadString::LoadKind::kBootImageLinkTimePcRelative: { DCHECK(codegen_->GetCompilerOptions().IsBootImage()); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, AT); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low); __ Daddiu(out, AT, /* placeholder */ 0x5678); return; // No dex cache slow path. } @@ -5246,15 +5364,22 @@ void InstructionCodeGeneratorMIPS64::VisitLoadString(HLoadString* load) NO_THREA } case HLoadString::LoadKind::kBssEntry: { DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); - CodeGeneratorMIPS64::PcRelativePatchInfo* info = + CodeGeneratorMIPS64::PcRelativePatchInfo* info_high = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); - codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out); + CodeGeneratorMIPS64::PcRelativePatchInfo* info_low = + codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high); + constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier; + GpuRegister temp = non_baker_read_barrier + ? out + : locations->GetTemp(0).AsRegister<GpuRegister>(); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, temp, info_low); GenerateGcRootFieldLoad(load, out_loc, - out, + temp, /* placeholder */ 0x5678, kCompilerReadBarrierOption); - SlowPathCodeMIPS64* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathMIPS64(load); + SlowPathCodeMIPS64* slow_path = + new (GetGraph()->GetArena()) LoadStringSlowPathMIPS64(load, info_high); codegen_->AddSlowPath(slow_path); __ Beqzc(out, slow_path->GetEntryLabel()); __ Bind(slow_path->GetExitLabel()); diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 6260c73614..b6209735b5 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -538,29 +538,59 @@ class CodeGeneratorMIPS64 : public CodeGenerator { // The PcRelativePatchInfo is used for PC-relative addressing of dex cache arrays, // boot image strings and method calls. The only difference is the interpretation of // the offset_or_index. + // The 16-bit halves of the 32-bit PC-relative offset are patched separately, necessitating + // two patches/infos. There can be more than two patches/infos if the instruction supplying + // the high half is shared with e.g. a slow path, while the low half is supplied by separate + // instructions, e.g.: + // auipc r1, high // patch + // lwu r2, low(r1) // patch + // beqzc r2, slow_path + // back: + // ... + // slow_path: + // ... + // sw r2, low(r1) // patch + // bc back struct PcRelativePatchInfo { - PcRelativePatchInfo(const DexFile& dex_file, uint32_t off_or_idx) - : target_dex_file(dex_file), offset_or_index(off_or_idx) { } - PcRelativePatchInfo(PcRelativePatchInfo&& other) = default; + PcRelativePatchInfo(const DexFile& dex_file, + uint32_t off_or_idx, + const PcRelativePatchInfo* info_high) + : target_dex_file(dex_file), + offset_or_index(off_or_idx), + label(), + patch_info_high(info_high) { } const DexFile& target_dex_file; // Either the dex cache array element offset or the string/type/method index. uint32_t offset_or_index; - // Label for the auipc instruction. - Mips64Label pc_rel_label; + // Label for the instruction to patch. + Mips64Label label; + // Pointer to the info for the high half patch or nullptr if this is the high half patch info. + const PcRelativePatchInfo* patch_info_high; + + private: + PcRelativePatchInfo(PcRelativePatchInfo&& other) = delete; + DISALLOW_COPY_AND_ASSIGN(PcRelativePatchInfo); }; - PcRelativePatchInfo* NewPcRelativeMethodPatch(MethodReference target_method); - PcRelativePatchInfo* NewMethodBssEntryPatch(MethodReference target_method); - PcRelativePatchInfo* NewPcRelativeTypePatch(const DexFile& dex_file, dex::TypeIndex type_index); - PcRelativePatchInfo* NewTypeBssEntryPatch(const DexFile& dex_file, dex::TypeIndex type_index); + PcRelativePatchInfo* NewPcRelativeMethodPatch(MethodReference target_method, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewMethodBssEntryPatch(MethodReference target_method, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewPcRelativeTypePatch(const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high = nullptr); + PcRelativePatchInfo* NewTypeBssEntryPatch(const DexFile& dex_file, + dex::TypeIndex type_index, + const PcRelativePatchInfo* info_high = nullptr); PcRelativePatchInfo* NewPcRelativeStringPatch(const DexFile& dex_file, - dex::StringIndex string_index); - PcRelativePatchInfo* NewPcRelativeCallPatch(const DexFile& dex_file, - uint32_t method_index); + dex::StringIndex string_index, + const PcRelativePatchInfo* info_high = nullptr); Literal* DeduplicateBootImageAddressLiteral(uint64_t address); - void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, GpuRegister out); + void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high, + GpuRegister out, + PcRelativePatchInfo* info_low); void PatchJitRootUse(uint8_t* code, const uint8_t* roots_data, @@ -588,6 +618,7 @@ class CodeGeneratorMIPS64 : public CodeGenerator { PcRelativePatchInfo* NewPcRelativePatch(const DexFile& dex_file, uint32_t offset_or_index, + const PcRelativePatchInfo* info_high, ArenaDeque<PcRelativePatchInfo>* patches); template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)> diff --git a/compiler/optimizing/code_generator_vector_arm_vixl.cc b/compiler/optimizing/code_generator_vector_arm_vixl.cc index 53f314ec40..527691d9d9 100644 --- a/compiler/optimizing/code_generator_vector_arm_vixl.cc +++ b/compiler/optimizing/code_generator_vector_arm_vixl.cc @@ -15,19 +15,62 @@ */ #include "code_generator_arm_vixl.h" +#include "mirror/array-inl.h" + +namespace vixl32 = vixl::aarch32; +using namespace vixl32; // NOLINT(build/namespaces) namespace art { namespace arm { -// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy. -#define __ reinterpret_cast<ArmVIXLAssembler*>(GetAssembler())->GetVIXLAssembler()-> // NOLINT +using helpers::DRegisterFrom; +using helpers::Int64ConstantFrom; +using helpers::InputDRegisterAt; +using helpers::InputRegisterAt; +using helpers::OutputDRegister; +using helpers::RegisterFrom; + +#define __ GetVIXLAssembler()-> void LocationsBuilderARMVIXL::VisitVecReplicateScalar(HVecReplicateScalar* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::RequiresFpuRegister()); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void InstructionCodeGeneratorARMVIXL::VisitVecReplicateScalar(HVecReplicateScalar* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vdup(Untyped8, dst, InputRegisterAt(instruction, 0)); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vdup(Untyped16, dst, InputRegisterAt(instruction, 0)); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vdup(Untyped32, dst, InputRegisterAt(instruction, 0)); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecSetScalars(HVecSetScalars* instruction) { @@ -51,13 +94,17 @@ static void CreateVecUnOpLocations(ArenaAllocator* arena, HVecUnaryOperation* in LocationSummary* locations = new (arena) LocationSummary(instruction); switch (instruction->GetPackedType()) { case Primitive::kPrimBoolean: + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), + instruction->IsVecNot() ? Location::kOutputOverlap + : Location::kNoOutputOverlap); + break; case Primitive::kPrimByte: case Primitive::kPrimChar: case Primitive::kPrimShort: case Primitive::kPrimInt: - case Primitive::kPrimFloat: - case Primitive::kPrimDouble: - DCHECK(locations); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); break; default: LOG(FATAL) << "Unsupported SIMD type"; @@ -78,7 +125,27 @@ void LocationsBuilderARMVIXL::VisitVecNeg(HVecNeg* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecNeg(HVecNeg* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister src = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vneg(DataTypeValue::S8, dst, src); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vneg(DataTypeValue::S16, dst, src); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vneg(DataTypeValue::S32, dst, src); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecAbs(HVecAbs* instruction) { @@ -86,7 +153,27 @@ void LocationsBuilderARMVIXL::VisitVecAbs(HVecAbs* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecAbs(HVecAbs* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister src = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vabs(DataTypeValue::S8, dst, src); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vabs(DataTypeValue::S16, dst, src); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vabs(DataTypeValue::S32, dst, src); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecNot(HVecNot* instruction) { @@ -94,7 +181,25 @@ void LocationsBuilderARMVIXL::VisitVecNot(HVecNot* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecNot(HVecNot* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister src = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: // special case boolean-not + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vmov(I8, dst, 1); + __ Veor(dst, dst, src); + break; + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + __ Vmvn(I8, dst, src); // lanes do not matter + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } // Helper to set up locations for vector binary operations. @@ -106,9 +211,9 @@ static void CreateVecBinOpLocations(ArenaAllocator* arena, HVecBinaryOperation* case Primitive::kPrimChar: case Primitive::kPrimShort: case Primitive::kPrimInt: - case Primitive::kPrimFloat: - case Primitive::kPrimDouble: - DCHECK(locations); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); break; default: LOG(FATAL) << "Unsupported SIMD type"; @@ -121,7 +226,28 @@ void LocationsBuilderARMVIXL::VisitVecAdd(HVecAdd* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecAdd(HVecAdd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vadd(I8, dst, lhs, rhs); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vadd(I16, dst, lhs, rhs); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vadd(I32, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { @@ -129,7 +255,40 @@ void LocationsBuilderARMVIXL::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + instruction->IsRounded() + ? __ Vrhadd(DataTypeValue::U8, dst, lhs, rhs) + : __ Vhadd(DataTypeValue::U8, dst, lhs, rhs); + } else { + instruction->IsRounded() + ? __ Vrhadd(DataTypeValue::S8, dst, lhs, rhs) + : __ Vhadd(DataTypeValue::S8, dst, lhs, rhs); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + instruction->IsRounded() + ? __ Vrhadd(DataTypeValue::U16, dst, lhs, rhs) + : __ Vhadd(DataTypeValue::U16, dst, lhs, rhs); + } else { + instruction->IsRounded() + ? __ Vrhadd(DataTypeValue::S16, dst, lhs, rhs) + : __ Vhadd(DataTypeValue::S16, dst, lhs, rhs); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecSub(HVecSub* instruction) { @@ -137,7 +296,28 @@ void LocationsBuilderARMVIXL::VisitVecSub(HVecSub* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecSub(HVecSub* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vsub(I8, dst, lhs, rhs); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vsub(I16, dst, lhs, rhs); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vsub(I32, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecMul(HVecMul* instruction) { @@ -145,7 +325,28 @@ void LocationsBuilderARMVIXL::VisitVecMul(HVecMul* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecMul(HVecMul* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vmul(I8, dst, lhs, rhs); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vmul(I16, dst, lhs, rhs); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vmul(I32, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecDiv(HVecDiv* instruction) { @@ -161,7 +362,40 @@ void LocationsBuilderARMVIXL::VisitVecMin(HVecMin* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecMin(HVecMin* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmin(DataTypeValue::U8, dst, lhs, rhs); + } else { + __ Vmin(DataTypeValue::S8, dst, lhs, rhs); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmin(DataTypeValue::U16, dst, lhs, rhs); + } else { + __ Vmin(DataTypeValue::S16, dst, lhs, rhs); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmin(DataTypeValue::U32, dst, lhs, rhs); + } else { + __ Vmin(DataTypeValue::S32, dst, lhs, rhs); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecMax(HVecMax* instruction) { @@ -169,7 +403,40 @@ void LocationsBuilderARMVIXL::VisitVecMax(HVecMax* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecMax(HVecMax* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmax(DataTypeValue::U8, dst, lhs, rhs); + } else { + __ Vmax(DataTypeValue::S8, dst, lhs, rhs); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmax(DataTypeValue::U16, dst, lhs, rhs); + } else { + __ Vmax(DataTypeValue::S16, dst, lhs, rhs); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Vmax(DataTypeValue::U32, dst, lhs, rhs); + } else { + __ Vmax(DataTypeValue::S32, dst, lhs, rhs); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecAnd(HVecAnd* instruction) { @@ -177,7 +444,22 @@ void LocationsBuilderARMVIXL::VisitVecAnd(HVecAnd* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecAnd(HVecAnd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + __ Vand(I8, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecAndNot(HVecAndNot* instruction) { @@ -193,7 +475,22 @@ void LocationsBuilderARMVIXL::VisitVecOr(HVecOr* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecOr(HVecOr* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + __ Vorr(I8, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecXor(HVecXor* instruction) { @@ -201,7 +498,22 @@ void LocationsBuilderARMVIXL::VisitVecXor(HVecXor* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecXor(HVecXor* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister rhs = DRegisterFrom(locations->InAt(1)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + __ Veor(I8, dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } // Helper to set up locations for vector shift operations. @@ -212,8 +524,9 @@ static void CreateVecShiftLocations(ArenaAllocator* arena, HVecBinaryOperation* case Primitive::kPrimChar: case Primitive::kPrimShort: case Primitive::kPrimInt: - case Primitive::kPrimLong: - DCHECK(locations); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant())); + locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); break; default: LOG(FATAL) << "Unsupported SIMD type"; @@ -226,7 +539,28 @@ void LocationsBuilderARMVIXL::VisitVecShl(HVecShl* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecShl(HVecShl* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + int32_t value = locations->InAt(1).GetConstant()->AsIntConstant()->GetValue(); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vshl(I8, dst, lhs, value); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vshl(I16, dst, lhs, value); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vshl(I32, dst, lhs, value); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecShr(HVecShr* instruction) { @@ -234,7 +568,28 @@ void LocationsBuilderARMVIXL::VisitVecShr(HVecShr* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecShr(HVecShr* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + int32_t value = locations->InAt(1).GetConstant()->AsIntConstant()->GetValue(); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::S8, dst, lhs, value); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::S16, dst, lhs, value); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::S32, dst, lhs, value); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecUShr(HVecUShr* instruction) { @@ -242,7 +597,28 @@ void LocationsBuilderARMVIXL::VisitVecUShr(HVecUShr* instruction) { } void InstructionCodeGeneratorARMVIXL::VisitVecUShr(HVecUShr* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + vixl32::DRegister lhs = DRegisterFrom(locations->InAt(0)); + vixl32::DRegister dst = DRegisterFrom(locations->Out()); + int32_t value = locations->InAt(1).GetConstant()->AsIntConstant()->GetValue(); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::U8, dst, lhs, value); + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::U16, dst, lhs, value); + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + __ Vshr(DataTypeValue::U32, dst, lhs, value); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecMultiplyAccumulate(HVecMultiplyAccumulate* instr) { @@ -253,20 +629,187 @@ void InstructionCodeGeneratorARMVIXL::VisitVecMultiplyAccumulate(HVecMultiplyAcc LOG(FATAL) << "No SIMD for " << instr->GetId(); } +// Return whether the vector memory access operation is guaranteed to be word-aligned (ARM word +// size equals to 4). +static bool IsWordAligned(HVecMemoryOperation* instruction) { + return instruction->GetAlignment().IsAlignedAt(4u); +} + +// Helper to set up locations for vector memory operations. +static void CreateVecMemLocations(ArenaAllocator* arena, + HVecMemoryOperation* instruction, + bool is_load) { + LocationSummary* locations = new (arena) LocationSummary(instruction); + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + case Primitive::kPrimChar: + case Primitive::kPrimShort: + case Primitive::kPrimInt: + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1))); + if (is_load) { + locations->SetOut(Location::RequiresFpuRegister()); + } else { + locations->SetInAt(2, Location::RequiresFpuRegister()); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } +} + +// Helper to set up locations for vector memory operations. Returns the memory operand and, +// if used, sets the output parameter scratch to a temporary register used in this operand, +// so that the client can release it right after the memory operand use. +MemOperand InstructionCodeGeneratorARMVIXL::VecAddress( + HVecMemoryOperation* instruction, + UseScratchRegisterScope* temps_scope, + /*out*/ vixl32::Register* scratch) { + LocationSummary* locations = instruction->GetLocations(); + vixl32::Register base = InputRegisterAt(instruction, 0); + + Location index = locations->InAt(1); + size_t size = Primitive::ComponentSize(instruction->GetPackedType()); + uint32_t offset = mirror::Array::DataOffset(size).Uint32Value(); + size_t shift = ComponentSizeShiftWidth(size); + + // HIntermediateAddress optimization is only applied for scalar ArrayGet and ArraySet. + DCHECK(!instruction->InputAt(0)->IsIntermediateAddress()); + + if (index.IsConstant()) { + offset += Int64ConstantFrom(index) << shift; + return MemOperand(base, offset); + } else { + *scratch = temps_scope->Acquire(); + __ Add(*scratch, base, Operand(RegisterFrom(index), ShiftType::LSL, shift)); + + return MemOperand(*scratch, offset); + } +} + +AlignedMemOperand InstructionCodeGeneratorARMVIXL::VecAddressUnaligned( + HVecMemoryOperation* instruction, + UseScratchRegisterScope* temps_scope, + /*out*/ vixl32::Register* scratch) { + LocationSummary* locations = instruction->GetLocations(); + vixl32::Register base = InputRegisterAt(instruction, 0); + + Location index = locations->InAt(1); + size_t size = Primitive::ComponentSize(instruction->GetPackedType()); + uint32_t offset = mirror::Array::DataOffset(size).Uint32Value(); + size_t shift = ComponentSizeShiftWidth(size); + + // HIntermediateAddress optimization is only applied for scalar ArrayGet and ArraySet. + DCHECK(!instruction->InputAt(0)->IsIntermediateAddress()); + + if (index.IsConstant()) { + offset += Int64ConstantFrom(index) << shift; + __ Add(*scratch, base, offset); + } else { + *scratch = temps_scope->Acquire(); + __ Add(*scratch, base, offset); + __ Add(*scratch, *scratch, Operand(RegisterFrom(index), ShiftType::LSL, shift)); + } + return AlignedMemOperand(*scratch, kNoAlignment); +} + void LocationsBuilderARMVIXL::VisitVecLoad(HVecLoad* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + CreateVecMemLocations(GetGraph()->GetArena(), instruction, /*is_load*/ true); } void InstructionCodeGeneratorARMVIXL::VisitVecLoad(HVecLoad* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + vixl32::DRegister reg = OutputDRegister(instruction); + UseScratchRegisterScope temps(GetVIXLAssembler()); + vixl32::Register scratch; + + DCHECK(instruction->GetPackedType() != Primitive::kPrimChar || !instruction->IsStringCharAt()); + + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vldr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vld1(Untyped8, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vldr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vld1(Untyped16, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vldr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vld1(Untyped32, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderARMVIXL::VisitVecStore(HVecStore* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + CreateVecMemLocations(GetGraph()->GetArena(), instruction, /*is_load*/ false); } void InstructionCodeGeneratorARMVIXL::VisitVecStore(HVecStore* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + vixl32::DRegister reg = InputDRegisterAt(instruction, 2); + UseScratchRegisterScope temps(GetVIXLAssembler()); + vixl32::Register scratch; + switch (instruction->GetPackedType()) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vstr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vst1(Untyped8, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vstr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vst1(Untyped16, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (IsWordAligned(instruction)) { + __ Vstr(reg, VecAddress(instruction, &temps, &scratch)); + } else { + __ Vst1(Untyped32, + NeonRegisterList(reg, kMultipleLanes), + VecAddressUnaligned(instruction, &temps, &scratch)); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } #undef __ diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index c0ec58f824..f35aace3a9 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -373,21 +373,23 @@ bool InductionVarRange::IsFinite(HLoopInformation* loop, /*out*/ int64_t* tc) co bool InductionVarRange::IsUnitStride(HInstruction* context, HInstruction* instruction, + HGraph* graph, /*out*/ HInstruction** offset) const { HLoopInformation* loop = nullptr; HInductionVarAnalysis::InductionInfo* info = nullptr; HInductionVarAnalysis::InductionInfo* trip = nullptr; if (HasInductionInfo(context, instruction, &loop, &info, &trip)) { if (info->induction_class == HInductionVarAnalysis::kLinear && - info->op_b->operation == HInductionVarAnalysis::kFetch && !HInductionVarAnalysis::IsNarrowingLinear(info)) { int64_t stride_value = 0; if (IsConstant(info->op_a, kExact, &stride_value) && stride_value == 1) { int64_t off_value = 0; - if (IsConstant(info->op_b, kExact, &off_value) && off_value == 0) { - *offset = nullptr; - } else { + if (IsConstant(info->op_b, kExact, &off_value)) { + *offset = graph->GetConstant(info->op_b->type, off_value); + } else if (info->op_b->operation == HInductionVarAnalysis::kFetch) { *offset = info->op_b->fetch; + } else { + return false; } return true; } diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h index a8ee829d08..ab1772bf15 100644 --- a/compiler/optimizing/induction_var_range.h +++ b/compiler/optimizing/induction_var_range.h @@ -163,6 +163,7 @@ class InductionVarRange { */ bool IsUnitStride(HInstruction* context, HInstruction* instruction, + HGraph* graph, /*out*/ HInstruction** offset) const; /** diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc index d01d3146fc..67d2093829 100644 --- a/compiler/optimizing/induction_var_range_test.cc +++ b/compiler/optimizing/induction_var_range_test.cc @@ -770,8 +770,8 @@ TEST_F(InductionVarRangeTest, ConstantTripCountUp) { EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); EXPECT_EQ(1000, tc); HInstruction* offset = nullptr; - EXPECT_TRUE(range_.IsUnitStride(phi, phi, &offset)); - EXPECT_TRUE(offset == nullptr); + EXPECT_TRUE(range_.IsUnitStride(phi, phi, graph_, &offset)); + ExpectInt(0, offset); HInstruction* tce = range_.GenerateTripCount( loop_header_->GetLoopInformation(), graph_, loop_preheader_); ASSERT_TRUE(tce != nullptr); @@ -826,7 +826,7 @@ TEST_F(InductionVarRangeTest, ConstantTripCountDown) { EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); EXPECT_EQ(1000, tc); HInstruction* offset = nullptr; - EXPECT_FALSE(range_.IsUnitStride(phi, phi, &offset)); + EXPECT_FALSE(range_.IsUnitStride(phi, phi, graph_, &offset)); HInstruction* tce = range_.GenerateTripCount( loop_header_->GetLoopInformation(), graph_, loop_preheader_); ASSERT_TRUE(tce != nullptr); @@ -908,8 +908,8 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountUp) { EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); EXPECT_EQ(0, tc); // unknown HInstruction* offset = nullptr; - EXPECT_TRUE(range_.IsUnitStride(phi, phi, &offset)); - EXPECT_TRUE(offset == nullptr); + EXPECT_TRUE(range_.IsUnitStride(phi, phi, graph_, &offset)); + ExpectInt(0, offset); HInstruction* tce = range_.GenerateTripCount( loop_header_->GetLoopInformation(), graph_, loop_preheader_); ASSERT_TRUE(tce != nullptr); @@ -994,7 +994,7 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountDown) { EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); EXPECT_EQ(0, tc); // unknown HInstruction* offset = nullptr; - EXPECT_FALSE(range_.IsUnitStride(phi, phi, &offset)); + EXPECT_FALSE(range_.IsUnitStride(phi, phi, graph_, &offset)); HInstruction* tce = range_.GenerateTripCount( loop_header_->GetLoopInformation(), graph_, loop_preheader_); ASSERT_TRUE(tce != nullptr); diff --git a/compiler/optimizing/load_store_analysis.cc b/compiler/optimizing/load_store_analysis.cc index f2ee345c8c..5a8ac59195 100644 --- a/compiler/optimizing/load_store_analysis.cc +++ b/compiler/optimizing/load_store_analysis.cc @@ -22,6 +22,117 @@ namespace art { // The number of heap locations for most of the methods stays below this threshold. constexpr size_t kMaxNumberOfHeapLocations = 32; +// Check if array indices array[idx1 +/- CONST] and array[idx2] MAY alias. +static bool BinaryOpAndIndexMayAlias(const HBinaryOperation* idx1, const HInstruction* idx2) { + DCHECK(idx1 != nullptr); + DCHECK(idx2 != nullptr); + + if (!idx1->IsAdd() && !idx1->IsSub()) { + // We currently only support Add and Sub operations. + return true; + } + + HConstant* cst = idx1->GetConstantRight(); + if (cst == nullptr || cst->IsArithmeticZero()) { + return true; + } + + if (idx1->GetLeastConstantLeft() == idx2) { + // for example, array[idx1 + 1] and array[idx1] + return false; + } + + return true; +} + +// Check if Add and Sub MAY alias when used as indices in arrays. +static bool BinaryOpsMayAlias(const HBinaryOperation* idx1, const HBinaryOperation* idx2) { + DCHECK(idx1!= nullptr); + DCHECK(idx2 != nullptr); + + HConstant* idx1_cst = idx1->GetConstantRight(); + HInstruction* idx1_other = idx1->GetLeastConstantLeft(); + HConstant* idx2_cst = idx2->GetConstantRight(); + HInstruction* idx2_other = idx2->GetLeastConstantLeft(); + + if (idx1_cst == nullptr || idx1_other == nullptr || + idx2_cst == nullptr || idx2_other == nullptr) { + // We only analyze patterns like [i +/- CONST]. + return true; + } + + if (idx1_other != idx2_other) { + // For example, [j+1] and [k+1] MAY alias. + return true; + } + + if ((idx1->IsAdd() && idx2->IsAdd()) || + (idx1->IsSub() && idx2->IsSub())) { + return idx1_cst->AsIntConstant()->GetValue() == idx2_cst->AsIntConstant()->GetValue(); + } + + if ((idx1->IsAdd() && idx2->IsSub()) || + (idx1->IsSub() && idx2->IsAdd())) { + // [i + CONST1] and [i - CONST2] MAY alias iff CONST1 == -CONST2. + // By checking CONST1 == -CONST2, following cases are handled: + // - Zero constants case [i+0] and [i-0] is handled. + // - Overflow cases are handled, for example: + // [i+0x80000000] and [i-0x80000000]; + // [i+0x10] and [i-0xFFFFFFF0]. + // - Other cases [i+CONST1] and [i-CONST2] without any overflow is handled. + return idx1_cst->AsIntConstant()->GetValue() == -(idx2_cst->AsIntConstant()->GetValue()); + } + + // All other cases, MAY alias. + return true; +} + +// The following array index cases are handled: +// [i] and [i] +// [CONST1] and [CONST2] +// [i] and [i+CONST] +// [i] and [i-CONST] +// [i+CONST1] and [i+CONST2] +// [i-CONST1] and [i-CONST2] +// [i+CONST1] and [i-CONST2] +// [i-CONST1] and [i+CONST2] +// For other complicated cases, we rely on other passes like GVN and simpilfier +// to optimize these cases before this pass. +// For example: [i+j+k+10] and [i+k+10+j] shall be optimized to [i7+10] and [i7+10]. +bool HeapLocationCollector::CanArrayIndicesAlias(const HInstruction* idx1, + const HInstruction* idx2) const { + DCHECK(idx1 != nullptr); + DCHECK(idx2 != nullptr); + + if (idx1 == idx2) { + // [i] and [i] + return true; + } + if (idx1->IsIntConstant() && idx2->IsIntConstant()) { + // [CONST1] and [CONST2] + return idx1->AsIntConstant()->GetValue() == idx2->AsIntConstant()->GetValue(); + } + + if (idx1->IsBinaryOperation() && !BinaryOpAndIndexMayAlias(idx1->AsBinaryOperation(), idx2)) { + // [i] and [i+/-CONST] + return false; + } + if (idx2->IsBinaryOperation() && !BinaryOpAndIndexMayAlias(idx2->AsBinaryOperation(), idx1)) { + // [i+/-CONST] and [i] + return false; + } + + if (idx1->IsBinaryOperation() && idx2->IsBinaryOperation()) { + // [i+/-CONST1] and [i+/-CONST2] + if (!BinaryOpsMayAlias(idx1->AsBinaryOperation(), idx2->AsBinaryOperation())) { + return false; + } + } + + // By default, MAY alias. + return true; +} + void LoadStoreAnalysis::Run() { for (HBasicBlock* block : graph_->GetReversePostOrder()) { heap_location_collector_.VisitBasicBlock(block); diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h index 4e940f30bf..86fb8e0165 100644 --- a/compiler/optimizing/load_store_analysis.h +++ b/compiler/optimizing/load_store_analysis.h @@ -214,6 +214,17 @@ class HeapLocationCollector : public HGraphVisitor { return nullptr; } + size_t GetArrayAccessHeapLocation(HInstruction* array, HInstruction* index) const { + DCHECK(array != nullptr); + DCHECK(index != nullptr); + HInstruction* original_ref = HuntForOriginalReference(array); + ReferenceInfo* ref_info = FindReferenceInfoOf(original_ref); + return FindHeapLocationIndex(ref_info, + HeapLocation::kInvalidFieldOffset, + index, + HeapLocation::kDeclaringClassDefIndexForArrays); + } + bool HasHeapStores() const { return has_heap_stores_; } @@ -300,6 +311,8 @@ class HeapLocationCollector : public HGraphVisitor { return true; } + bool CanArrayIndicesAlias(const HInstruction* i1, const HInstruction* i2) const; + // `index1` and `index2` are indices in the array of collected heap locations. // Returns the position in the bit vector that tracks whether the two heap // locations may alias. @@ -336,12 +349,7 @@ class HeapLocationCollector : public HGraphVisitor { if (loc1->IsArrayElement() && loc2->IsArrayElement()) { HInstruction* array_index1 = loc1->GetIndex(); HInstruction* array_index2 = loc2->GetIndex(); - DCHECK(array_index1 != nullptr); - DCHECK(array_index2 != nullptr); - if (array_index1->IsIntConstant() && - array_index2->IsIntConstant() && - array_index1->AsIntConstant()->GetValue() != array_index2->AsIntConstant()->GetValue()) { - // Different constant indices do not alias. + if (!CanArrayIndicesAlias(array_index1, array_index2)) { return false; } ReferenceInfo* ref_info = loc1->GetReferenceInfo(); diff --git a/compiler/optimizing/load_store_analysis_test.cc b/compiler/optimizing/load_store_analysis_test.cc index 24187777f6..81344b52f6 100644 --- a/compiler/optimizing/load_store_analysis_test.cc +++ b/compiler/optimizing/load_store_analysis_test.cc @@ -184,4 +184,198 @@ TEST_F(LoadStoreAnalysisTest, FieldHeapLocations) { ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); } +TEST_F(LoadStoreAnalysisTest, ArrayIndexAliasingTest) { + HBasicBlock* entry = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry); + graph_->SetEntryBlock(entry); + graph_->BuildDominatorTree(); + + HInstruction* array = new (&allocator_) HParameterValue( + graph_->GetDexFile(), dex::TypeIndex(0), 0, Primitive::kPrimNot); + HInstruction* index = new (&allocator_) HParameterValue( + graph_->GetDexFile(), dex::TypeIndex(1), 1, Primitive::kPrimInt); + HInstruction* c0 = graph_->GetIntConstant(0); + HInstruction* c1 = graph_->GetIntConstant(1); + HInstruction* c_neg1 = graph_->GetIntConstant(-1); + HInstruction* add0 = new (&allocator_) HAdd(Primitive::kPrimInt, index, c0); + HInstruction* add1 = new (&allocator_) HAdd(Primitive::kPrimInt, index, c1); + HInstruction* sub0 = new (&allocator_) HSub(Primitive::kPrimInt, index, c0); + HInstruction* sub1 = new (&allocator_) HSub(Primitive::kPrimInt, index, c1); + HInstruction* sub_neg1 = new (&allocator_) HSub(Primitive::kPrimInt, index, c_neg1); + HInstruction* rev_sub1 = new (&allocator_) HSub(Primitive::kPrimInt, c1, index); + HInstruction* arr_set1 = new (&allocator_) HArraySet(array, c0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set2 = new (&allocator_) HArraySet(array, c1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set3 = new (&allocator_) HArraySet(array, add0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set4 = new (&allocator_) HArraySet(array, add1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set5 = new (&allocator_) HArraySet(array, sub0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set6 = new (&allocator_) HArraySet(array, sub1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set7 = new (&allocator_) HArraySet(array, rev_sub1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set8 = new (&allocator_) HArraySet(array, sub_neg1, c0, Primitive::kPrimInt, 0); + + entry->AddInstruction(array); + entry->AddInstruction(index); + entry->AddInstruction(add0); + entry->AddInstruction(add1); + entry->AddInstruction(sub0); + entry->AddInstruction(sub1); + entry->AddInstruction(sub_neg1); + entry->AddInstruction(rev_sub1); + + entry->AddInstruction(arr_set1); // array[0] = c0 + entry->AddInstruction(arr_set2); // array[1] = c0 + entry->AddInstruction(arr_set3); // array[i+0] = c0 + entry->AddInstruction(arr_set4); // array[i+1] = c0 + entry->AddInstruction(arr_set5); // array[i-0] = c0 + entry->AddInstruction(arr_set6); // array[i-1] = c0 + entry->AddInstruction(arr_set7); // array[1-i] = c0 + entry->AddInstruction(arr_set8); // array[i-(-1)] = c0 + + LoadStoreAnalysis lsa(graph_); + lsa.Run(); + const HeapLocationCollector& heap_location_collector = lsa.GetHeapLocationCollector(); + + // LSA/HeapLocationCollector should see those ArrayGet instructions. + ASSERT_EQ(heap_location_collector.GetNumberOfHeapLocations(), 8U); + ASSERT_TRUE(heap_location_collector.HasHeapStores()); + + // Test queries on HeapLocationCollector's aliasing matrix after load store analysis. + size_t loc1 = HeapLocationCollector::kHeapLocationNotFound; + size_t loc2 = HeapLocationCollector::kHeapLocationNotFound; + + // Test alias: array[0] and array[1] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, c0); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, c1); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+0] and array[i-0] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add0); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub0); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+1] and array[i-1] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add1); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub1); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+1] and array[1-i] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add1); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, rev_sub1); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+1] and array[i-(-1)] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add1); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_neg1); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); +} + +TEST_F(LoadStoreAnalysisTest, ArrayIndexCalculationOverflowTest) { + HBasicBlock* entry = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry); + graph_->SetEntryBlock(entry); + graph_->BuildDominatorTree(); + + HInstruction* array = new (&allocator_) HParameterValue( + graph_->GetDexFile(), dex::TypeIndex(0), 0, Primitive::kPrimNot); + HInstruction* index = new (&allocator_) HParameterValue( + graph_->GetDexFile(), dex::TypeIndex(1), 1, Primitive::kPrimInt); + + HInstruction* c0 = graph_->GetIntConstant(0); + HInstruction* c_0x80000000 = graph_->GetIntConstant(0x80000000); + HInstruction* c_0x10 = graph_->GetIntConstant(0x10); + HInstruction* c_0xFFFFFFF0 = graph_->GetIntConstant(0xFFFFFFF0); + HInstruction* c_0x7FFFFFFF = graph_->GetIntConstant(0x7FFFFFFF); + HInstruction* c_0x80000001 = graph_->GetIntConstant(0x80000001); + + // `index+0x80000000` and `index-0x80000000` array indices MAY alias. + HInstruction* add_0x80000000 = new (&allocator_) HAdd(Primitive::kPrimInt, index, c_0x80000000); + HInstruction* sub_0x80000000 = new (&allocator_) HSub(Primitive::kPrimInt, index, c_0x80000000); + HInstruction* arr_set_1 = new (&allocator_) HArraySet( + array, add_0x80000000, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_2 = new (&allocator_) HArraySet( + array, sub_0x80000000, c0, Primitive::kPrimInt, 0); + + // `index+0x10` and `index-0xFFFFFFF0` array indices MAY alias. + HInstruction* add_0x10 = new (&allocator_) HAdd(Primitive::kPrimInt, index, c_0x10); + HInstruction* sub_0xFFFFFFF0 = new (&allocator_) HSub(Primitive::kPrimInt, index, c_0xFFFFFFF0); + HInstruction* arr_set_3 = new (&allocator_) HArraySet( + array, add_0x10, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_4 = new (&allocator_) HArraySet( + array, sub_0xFFFFFFF0, c0, Primitive::kPrimInt, 0); + + // `index+0x7FFFFFFF` and `index-0x80000001` array indices MAY alias. + HInstruction* add_0x7FFFFFFF = new (&allocator_) HAdd(Primitive::kPrimInt, index, c_0x7FFFFFFF); + HInstruction* sub_0x80000001 = new (&allocator_) HSub(Primitive::kPrimInt, index, c_0x80000001); + HInstruction* arr_set_5 = new (&allocator_) HArraySet( + array, add_0x7FFFFFFF, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_6 = new (&allocator_) HArraySet( + array, sub_0x80000001, c0, Primitive::kPrimInt, 0); + + // `index+0` and `index-0` array indices MAY alias. + HInstruction* add_0 = new (&allocator_) HAdd(Primitive::kPrimInt, index, c0); + HInstruction* sub_0 = new (&allocator_) HSub(Primitive::kPrimInt, index, c0); + HInstruction* arr_set_7 = new (&allocator_) HArraySet(array, add_0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_8 = new (&allocator_) HArraySet(array, sub_0, c0, Primitive::kPrimInt, 0); + + entry->AddInstruction(array); + entry->AddInstruction(index); + entry->AddInstruction(add_0x80000000); + entry->AddInstruction(sub_0x80000000); + entry->AddInstruction(add_0x10); + entry->AddInstruction(sub_0xFFFFFFF0); + entry->AddInstruction(add_0x7FFFFFFF); + entry->AddInstruction(sub_0x80000001); + entry->AddInstruction(add_0); + entry->AddInstruction(sub_0); + entry->AddInstruction(arr_set_1); + entry->AddInstruction(arr_set_2); + entry->AddInstruction(arr_set_3); + entry->AddInstruction(arr_set_4); + entry->AddInstruction(arr_set_5); + entry->AddInstruction(arr_set_6); + entry->AddInstruction(arr_set_7); + entry->AddInstruction(arr_set_8); + + LoadStoreAnalysis lsa(graph_); + lsa.Run(); + const HeapLocationCollector& heap_location_collector = lsa.GetHeapLocationCollector(); + + // LSA/HeapLocationCollector should see those ArrayGet instructions. + ASSERT_EQ(heap_location_collector.GetNumberOfHeapLocations(), 8U); + ASSERT_TRUE(heap_location_collector.HasHeapStores()); + + // Test queries on HeapLocationCollector's aliasing matrix after load store analysis. + size_t loc1 = HeapLocationCollector::kHeapLocationNotFound; + size_t loc2 = HeapLocationCollector::kHeapLocationNotFound; + + // Test alias: array[i+0x80000000] and array[i-0x80000000] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add_0x80000000); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0x80000000); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+0x10] and array[i-0xFFFFFFF0] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add_0x10); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0xFFFFFFF0); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+0x7FFFFFFF] and array[i-0x80000001] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add_0x7FFFFFFF); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0x80000001); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Test alias: array[i+0] and array[i-0] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add_0); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + + // Should not alias: + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0x80000000); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0x80000001); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + + // Should not alias: + loc1 = heap_location_collector.GetArrayAccessHeapLocation(array, add_0); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(array, sub_0x80000000); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); +} + } // namespace art diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index d2493137fe..83f31c77d3 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -31,6 +31,9 @@ namespace art { // Enables vectorization (SIMDization) in the loop optimizer. static constexpr bool kEnableVectorization = true; +// All current SIMD targets want 16-byte alignment. +static constexpr size_t kAlignedBase = 16; + // Remove the instruction from the graph. A bit more elaborate than the usual // instruction removal, since there may be a cycle in the use structure. static void RemoveFromCycle(HInstruction* instruction) { @@ -283,6 +286,9 @@ HLoopOptimization::HLoopOptimization(HGraph* graph, simplified_(false), vector_length_(0), vector_refs_(nullptr), + vector_peeling_candidate_(nullptr), + vector_runtime_test_a_(nullptr), + vector_runtime_test_b_(nullptr), vector_map_(nullptr) { } @@ -422,23 +428,6 @@ void HLoopOptimization::TraverseLoopsInnerToOuter(LoopNode* node) { // Optimization. // -bool HLoopOptimization::CanRemoveCycle() { - for (HInstruction* i : *iset_) { - // We can never remove instructions that have environment - // uses when we compile 'debuggable'. - if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { - return false; - } - // A deoptimization should never have an environment input removed. - for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { - if (use.GetUser()->GetHolder()->IsDeoptimize()) { - return false; - } - } - } - return true; -} - void HLoopOptimization::SimplifyInduction(LoopNode* node) { HBasicBlock* header = node->loop_info->GetHeader(); HBasicBlock* preheader = node->loop_info->GetPreHeader(); @@ -565,7 +554,7 @@ void HLoopOptimization::OptimizeInnerLoop(LoopNode* node) { if (kEnableVectorization) { iset_->clear(); // prepare phi induction if (TrySetSimpleLoopHeader(header) && - CanVectorize(node, body, trip_count) && + ShouldVectorize(node, body, trip_count) && TryAssignLastValue(node->loop_info, phi, preheader, /*collect_loop_uses*/ true)) { Vectorize(node, body, exit, trip_count); graph_->SetHasSIMD(true); // flag SIMD usage @@ -580,10 +569,11 @@ void HLoopOptimization::OptimizeInnerLoop(LoopNode* node) { // Intel Press, June, 2004 (http://www.aartbik.com/). // -bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count) { +bool HLoopOptimization::ShouldVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count) { // Reset vector bookkeeping. vector_length_ = 0; vector_refs_->clear(); + vector_peeling_candidate_ = nullptr; vector_runtime_test_a_ = vector_runtime_test_b_= nullptr; @@ -600,12 +590,9 @@ bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t } } - // Heuristics. Does vectorization seem profitable? - // TODO: refine - if (vector_length_ == 0) { - return false; // nothing found - } else if (0 < trip_count && trip_count < vector_length_) { - return false; // insufficient iterations + // Does vectorization seem profitable? + if (!IsVectorizationProfitable(trip_count)) { + return false; } // Data dependence analysis. Find each pair of references with same type, where @@ -633,18 +620,24 @@ bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t // Conservatively assume a potential loop-carried data dependence otherwise, avoided by // generating an explicit a != b disambiguation runtime test on the two references. if (x != y) { - // For now, we reject after one test to avoid excessive overhead. - if (vector_runtime_test_a_ != nullptr) { - return false; + // To avoid excessive overhead, we only accept one a != b test. + if (vector_runtime_test_a_ == nullptr) { + // First test found. + vector_runtime_test_a_ = a; + vector_runtime_test_b_ = b; + } else if ((vector_runtime_test_a_ != a || vector_runtime_test_b_ != b) && + (vector_runtime_test_a_ != b || vector_runtime_test_b_ != a)) { + return false; // second test would be needed } - vector_runtime_test_a_ = a; - vector_runtime_test_b_ = b; } } } } } + // Consider dynamic loop peeling for alignment. + SetPeelingCandidate(trip_count); + // Success! return true; } @@ -657,28 +650,52 @@ void HLoopOptimization::Vectorize(LoopNode* node, HBasicBlock* header = node->loop_info->GetHeader(); HBasicBlock* preheader = node->loop_info->GetPreHeader(); - // A cleanup is needed for any unknown trip count or for a known trip count - // with remainder iterations after vectorization. - bool needs_cleanup = trip_count == 0 || (trip_count % vector_length_) != 0; + // Pick a loop unrolling factor for the vector loop. + uint32_t unroll = GetUnrollingFactor(block, trip_count); + uint32_t chunk = vector_length_ * unroll; + + // A cleanup loop is needed, at least, for any unknown trip count or + // for a known trip count with remainder iterations after vectorization. + bool needs_cleanup = trip_count == 0 || (trip_count % chunk) != 0; // Adjust vector bookkeeping. iset_->clear(); // prepare phi induction bool is_simple_loop_header = TrySetSimpleLoopHeader(header); // fills iset_ DCHECK(is_simple_loop_header); + vector_header_ = header; + vector_body_ = block; + + // Generate dynamic loop peeling trip count, if needed: + // ptc = <peeling-needed-for-candidate> + HInstruction* ptc = nullptr; + if (vector_peeling_candidate_ != nullptr) { + DCHECK_LT(vector_length_, trip_count) << "dynamic peeling currently requires known trip count"; + // + // TODO: Implement this. Compute address of first access memory location and + // compute peeling factor to obtain kAlignedBase alignment. + // + needs_cleanup = true; + } - // Generate preheader: + // Generate loop control: // stc = <trip-count>; - // vtc = stc - stc % VL; + // vtc = stc - (stc - ptc) % chunk; + // i = 0; HInstruction* stc = induction_range_.GenerateTripCount(node->loop_info, graph_, preheader); HInstruction* vtc = stc; if (needs_cleanup) { - DCHECK(IsPowerOfTwo(vector_length_)); + DCHECK(IsPowerOfTwo(chunk)); + HInstruction* diff = stc; + if (ptc != nullptr) { + diff = Insert(preheader, new (global_allocator_) HSub(induc_type, stc, ptc)); + } HInstruction* rem = Insert( preheader, new (global_allocator_) HAnd(induc_type, - stc, - graph_->GetIntConstant(vector_length_ - 1))); + diff, + graph_->GetIntConstant(chunk - 1))); vtc = Insert(preheader, new (global_allocator_) HSub(induc_type, stc, rem)); } + vector_index_ = graph_->GetIntConstant(0); // Generate runtime disambiguation test: // vtc = a != b ? vtc : 0; @@ -691,16 +708,31 @@ void HLoopOptimization::Vectorize(LoopNode* node, needs_cleanup = true; } - // Generate vector loop: - // for (i = 0; i < vtc; i += VL) + // Generate dynamic peeling loop for alignment, if needed: + // for ( ; i < ptc; i += 1) + // <loop-body> + if (ptc != nullptr) { + vector_mode_ = kSequential; + GenerateNewLoop(node, + block, + graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), + vector_index_, + ptc, + graph_->GetIntConstant(1), + /*unroll*/ 1); + } + + // Generate vector loop, possibly further unrolled: + // for ( ; i < vtc; i += chunk) // <vectorized-loop-body> vector_mode_ = kVector; GenerateNewLoop(node, block, - graph_->TransformLoopForVectorization(header, block, exit), - graph_->GetIntConstant(0), + graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), + vector_index_, vtc, - graph_->GetIntConstant(vector_length_)); + graph_->GetIntConstant(vector_length_), // increment per unroll + unroll); HLoopInformation* vloop = vector_header_->GetLoopInformation(); // Generate cleanup loop, if needed: @@ -711,9 +743,10 @@ void HLoopOptimization::Vectorize(LoopNode* node, GenerateNewLoop(node, block, graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), - vector_phi_, + vector_index_, stc, - graph_->GetIntConstant(1)); + graph_->GetIntConstant(1), + /*unroll*/ 1); } // Remove the original loop by disconnecting the body block @@ -722,8 +755,9 @@ void HLoopOptimization::Vectorize(LoopNode* node, while (!header->GetFirstInstruction()->IsGoto()) { header->RemoveInstruction(header->GetFirstInstruction()); } - // Update loop hierarchy: the old header now resides in the - // same outer loop as the old preheader. + // Update loop hierarchy: the old header now resides in the same outer loop + // as the old preheader. Note that we don't bother putting sequential + // loops back in the hierarchy at this point. header->SetLoopInformation(preheader->GetLoopInformation()); // outward node->loop_info = vloop; } @@ -733,44 +767,64 @@ void HLoopOptimization::GenerateNewLoop(LoopNode* node, HBasicBlock* new_preheader, HInstruction* lo, HInstruction* hi, - HInstruction* step) { + HInstruction* step, + uint32_t unroll) { + DCHECK(unroll == 1 || vector_mode_ == kVector); Primitive::Type induc_type = Primitive::kPrimInt; // Prepare new loop. - vector_map_->clear(); vector_preheader_ = new_preheader, vector_header_ = vector_preheader_->GetSingleSuccessor(); vector_body_ = vector_header_->GetSuccessors()[1]; - vector_phi_ = new (global_allocator_) HPhi(global_allocator_, - kNoRegNumber, - 0, - HPhi::ToPhiType(induc_type)); + HPhi* phi = new (global_allocator_) HPhi(global_allocator_, + kNoRegNumber, + 0, + HPhi::ToPhiType(induc_type)); // Generate header and prepare body. // for (i = lo; i < hi; i += step) // <loop-body> - HInstruction* cond = new (global_allocator_) HAboveOrEqual(vector_phi_, hi); - vector_header_->AddPhi(vector_phi_); + HInstruction* cond = new (global_allocator_) HAboveOrEqual(phi, hi); + vector_header_->AddPhi(phi); vector_header_->AddInstruction(cond); vector_header_->AddInstruction(new (global_allocator_) HIf(cond)); - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - bool vectorized_def = VectorizeDef(node, it.Current(), /*generate_code*/ true); - DCHECK(vectorized_def); - } - // Generate body from the instruction map, but in original program order. - HEnvironment* env = vector_header_->GetFirstInstruction()->GetEnvironment(); - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - auto i = vector_map_->find(it.Current()); - if (i != vector_map_->end() && !i->second->IsInBlock()) { - Insert(vector_body_, i->second); - // Deal with instructions that need an environment, such as the scalar intrinsics. - if (i->second->NeedsEnvironment()) { - i->second->CopyEnvironmentFromWithLoopPhiAdjustment(env, vector_header_); + vector_index_ = phi; + for (uint32_t u = 0; u < unroll; u++) { + // Clear map, leaving loop invariants setup during unrolling. + if (u == 0) { + vector_map_->clear(); + } else { + for (auto i = vector_map_->begin(); i != vector_map_->end(); ) { + if (i->second->IsVecReplicateScalar()) { + DCHECK(node->loop_info->IsDefinedOutOfTheLoop(i->first)); + ++i; + } else { + i = vector_map_->erase(i); + } } } + // Generate instruction map. + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + bool vectorized_def = VectorizeDef(node, it.Current(), /*generate_code*/ true); + DCHECK(vectorized_def); + } + // Generate body from the instruction map, but in original program order. + HEnvironment* env = vector_header_->GetFirstInstruction()->GetEnvironment(); + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + auto i = vector_map_->find(it.Current()); + if (i != vector_map_->end() && !i->second->IsInBlock()) { + Insert(vector_body_, i->second); + // Deal with instructions that need an environment, such as the scalar intrinsics. + if (i->second->NeedsEnvironment()) { + i->second->CopyEnvironmentFromWithLoopPhiAdjustment(env, vector_header_); + } + } + } + vector_index_ = new (global_allocator_) HAdd(induc_type, vector_index_, step); + Insert(vector_body_, vector_index_); } - // Finalize increment and phi. - HInstruction* inc = new (global_allocator_) HAdd(induc_type, vector_phi_, step); - vector_phi_->AddInput(lo); - vector_phi_->AddInput(Insert(vector_body_, inc)); + // Finalize phi for the loop index. + phi->AddInput(lo); + phi->AddInput(vector_index_); + vector_index_ = phi; } // TODO: accept reductions at left-hand-side, mixed-type store idioms, etc. @@ -791,11 +845,11 @@ bool HLoopOptimization::VectorizeDef(LoopNode* node, HInstruction* offset = nullptr; if (TrySetVectorType(type, &restrictions) && node->loop_info->IsDefinedOutOfTheLoop(base) && - induction_range_.IsUnitStride(instruction, index, &offset) && + induction_range_.IsUnitStride(instruction, index, graph_, &offset) && VectorizeUse(node, value, generate_code, type, restrictions)) { if (generate_code) { GenerateVecSub(index, offset); - GenerateVecMem(instruction, vector_map_->Get(index), vector_map_->Get(value), type); + GenerateVecMem(instruction, vector_map_->Get(index), vector_map_->Get(value), offset, type); } else { vector_refs_->insert(ArrayReference(base, offset, type, /*lhs*/ true)); } @@ -849,10 +903,10 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node, HInstruction* offset = nullptr; if (type == instruction->GetType() && node->loop_info->IsDefinedOutOfTheLoop(base) && - induction_range_.IsUnitStride(instruction, index, &offset)) { + induction_range_.IsUnitStride(instruction, index, graph_, &offset)) { if (generate_code) { GenerateVecSub(index, offset); - GenerateVecMem(instruction, vector_map_->Get(index), nullptr, type); + GenerateVecMem(instruction, vector_map_->Get(index), nullptr, offset, type); } else { vector_refs_->insert(ArrayReference(base, offset, type, /*lhs*/ false)); } @@ -1043,6 +1097,23 @@ bool HLoopOptimization::TrySetVectorType(Primitive::Type type, uint64_t* restric switch (compiler_driver_->GetInstructionSet()) { case kArm: case kThumb2: + // Allow vectorization for all ARM devices, because Android assumes that + // ARM 32-bit always supports advanced SIMD. + switch (type) { + case Primitive::kPrimBoolean: + case Primitive::kPrimByte: + *restrictions |= kNoDiv; + return TrySetVectorLength(8); + case Primitive::kPrimChar: + case Primitive::kPrimShort: + *restrictions |= kNoDiv | kNoStringCharAt; + return TrySetVectorLength(4); + case Primitive::kPrimInt: + *restrictions |= kNoDiv; + return TrySetVectorLength(2); + default: + break; + } return false; case kArm64: // Allow vectorization for all ARM devices, because Android assumes that @@ -1164,8 +1235,9 @@ void HLoopOptimization::GenerateVecInv(HInstruction* org, Primitive::Type type) void HLoopOptimization::GenerateVecSub(HInstruction* org, HInstruction* offset) { if (vector_map_->find(org) == vector_map_->end()) { - HInstruction* subscript = vector_phi_; - if (offset != nullptr) { + HInstruction* subscript = vector_index_; + int64_t value = 0; + if (!IsInt64AndGet(offset, &value) || value != 0) { subscript = new (global_allocator_) HAdd(Primitive::kPrimInt, subscript, offset); if (org->IsPhi()) { Insert(vector_body_, subscript); // lacks layout placeholder @@ -1178,17 +1250,27 @@ void HLoopOptimization::GenerateVecSub(HInstruction* org, HInstruction* offset) void HLoopOptimization::GenerateVecMem(HInstruction* org, HInstruction* opa, HInstruction* opb, + HInstruction* offset, Primitive::Type type) { HInstruction* vector = nullptr; if (vector_mode_ == kVector) { // Vector store or load. + HInstruction* base = org->InputAt(0); if (opb != nullptr) { vector = new (global_allocator_) HVecStore( - global_allocator_, org->InputAt(0), opa, opb, type, vector_length_); + global_allocator_, base, opa, opb, type, vector_length_); } else { bool is_string_char_at = org->AsArrayGet()->IsStringCharAt(); vector = new (global_allocator_) HVecLoad( - global_allocator_, org->InputAt(0), opa, type, vector_length_, is_string_char_at); + global_allocator_, base, opa, type, vector_length_, is_string_char_at); + } + // Known dynamically enforced alignment? + // TODO: detect offset + constant differences. + // TODO: long run, static alignment analysis? + if (vector_peeling_candidate_ != nullptr && + vector_peeling_candidate_->base == base && + vector_peeling_candidate_->offset == offset) { + vector->AsVecMemoryOperation()->SetAlignment(Alignment(kAlignedBase, 0)); } } else { // Scalar store or load. @@ -1444,10 +1526,57 @@ bool HLoopOptimization::VectorizeHalvingAddIdiom(LoopNode* node, } // +// Vectorization heuristics. +// + +bool HLoopOptimization::IsVectorizationProfitable(int64_t trip_count) { + // Current heuristic: non-empty body with sufficient number + // of iterations (if known). + // TODO: refine by looking at e.g. operation count, alignment, etc. + if (vector_length_ == 0) { + return false; // nothing found + } else if (0 < trip_count && trip_count < vector_length_) { + return false; // insufficient iterations + } + return true; +} + +void HLoopOptimization::SetPeelingCandidate(int64_t trip_count ATTRIBUTE_UNUSED) { + // Current heuristic: none. + // TODO: implement +} + +uint32_t HLoopOptimization::GetUnrollingFactor(HBasicBlock* block, int64_t trip_count) { + // Current heuristic: unroll by 2 on ARM64/X86 for large known trip + // counts and small loop bodies. + // TODO: refine with operation count, remaining iterations, etc. + // Artem had some really cool ideas for this already. + switch (compiler_driver_->GetInstructionSet()) { + case kArm64: + case kX86: + case kX86_64: { + size_t num_instructions = block->GetInstructions().CountSize(); + if (num_instructions <= 10 && trip_count >= 4 * vector_length_) { + return 2; + } + return 1; + } + default: + return 1; + } +} + +// // Helpers. // bool HLoopOptimization::TrySetPhiInduction(HPhi* phi, bool restrict_uses) { + // Special case Phis that have equivalent in a debuggable setup. Our graph checker isn't + // smart enough to follow strongly connected components (and it's probably not worth + // it to make it so). See b/33775412. + if (graph_->IsDebuggable() && phi->HasEquivalentPhi()) { + return false; + } DCHECK(iset_->empty()); ArenaSet<HInstruction*>* set = induction_range_.LookupCycle(phi); if (set != nullptr) { @@ -1576,8 +1705,8 @@ bool HLoopOptimization::TryReplaceWithLastValue(HLoopInformation* loop_info, size_t index = it->GetIndex(); ++it; // increment before replacing if (iset_->find(user->GetHolder()) == iset_->end()) { // not excluded? - HLoopInformation* other_loop_info = user->GetHolder()->GetBlock()->GetLoopInformation(); // Only update environment uses after the loop. + HLoopInformation* other_loop_info = user->GetHolder()->GetBlock()->GetLoopInformation(); if (other_loop_info == nullptr || !other_loop_info->IsIn(*loop_info)) { user->RemoveAsUserOfInput(index); user->SetRawEnvAt(index, replacement); @@ -1614,4 +1743,21 @@ void HLoopOptimization::RemoveDeadInstructions(const HInstructionList& list) { } } +bool HLoopOptimization::CanRemoveCycle() { + for (HInstruction* i : *iset_) { + // We can never remove instructions that have environment + // uses when we compile 'debuggable'. + if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { + return false; + } + // A deoptimization should never have an environment input removed. + for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { + if (use.GetUser()->GetHolder()->IsDeoptimize()) { + return false; + } + } + } + return true; +} + } // namespace art diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index cc6343aeb5..de4bd85fc8 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -116,14 +116,15 @@ class HLoopOptimization : public HOptimization { void OptimizeInnerLoop(LoopNode* node); // Vectorization analysis and synthesis. - bool CanVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); + bool ShouldVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); void Vectorize(LoopNode* node, HBasicBlock* block, HBasicBlock* exit, int64_t trip_count); void GenerateNewLoop(LoopNode* node, HBasicBlock* block, HBasicBlock* new_preheader, HInstruction* lo, HInstruction* hi, - HInstruction* step); + HInstruction* step, + uint32_t unroll); bool VectorizeDef(LoopNode* node, HInstruction* instruction, bool generate_code); bool VectorizeUse(LoopNode* node, HInstruction* instruction, @@ -133,10 +134,11 @@ class HLoopOptimization : public HOptimization { bool TrySetVectorType(Primitive::Type type, /*out*/ uint64_t* restrictions); bool TrySetVectorLength(uint32_t length); void GenerateVecInv(HInstruction* org, Primitive::Type type); - void GenerateVecSub(HInstruction* org, HInstruction* off); + void GenerateVecSub(HInstruction* org, HInstruction* offset); void GenerateVecMem(HInstruction* org, HInstruction* opa, HInstruction* opb, + HInstruction* offset, Primitive::Type type); void GenerateVecOp(HInstruction* org, HInstruction* opa, @@ -151,6 +153,11 @@ class HLoopOptimization : public HOptimization { Primitive::Type type, uint64_t restrictions); + // Vectorization heuristics. + bool IsVectorizationProfitable(int64_t trip_count); + void SetPeelingCandidate(int64_t trip_count); + uint32_t GetUnrollingFactor(HBasicBlock* block, int64_t trip_count); + // Helpers. bool TrySetPhiInduction(HPhi* phi, bool restrict_uses); bool TrySetSimpleLoopHeader(HBasicBlock* block); @@ -208,20 +215,25 @@ class HLoopOptimization : public HOptimization { // Contents reside in phase-local heap memory. ArenaSet<ArrayReference>* vector_refs_; + // Dynamic loop peeling candidate for alignment. + const ArrayReference* vector_peeling_candidate_; + + // Dynamic data dependence test of the form a != b. + HInstruction* vector_runtime_test_a_; + HInstruction* vector_runtime_test_b_; + // Mapping used during vectorization synthesis for both the scalar peeling/cleanup - // loop (simd_ is false) and the actual vector loop (simd_ is true). The data + // loop (mode is kSequential) and the actual vector loop (mode is kVector). The data // structure maps original instructions into the new instructions. // Contents reside in phase-local heap memory. ArenaSafeMap<HInstruction*, HInstruction*>* vector_map_; // Temporary vectorization bookkeeping. + VectorMode vector_mode_; // synthesis mode HBasicBlock* vector_preheader_; // preheader of the new loop HBasicBlock* vector_header_; // header of the new loop HBasicBlock* vector_body_; // body of the new loop - HInstruction* vector_runtime_test_a_; - HInstruction* vector_runtime_test_b_; // defines a != b runtime test - HPhi* vector_phi_; // the Phi representing the normalized loop index - VectorMode vector_mode_; // selects synthesis mode + HInstruction* vector_index_; // normalized index of the new loop friend class LoopOptimizationTest; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index ffa16dd787..5e072cdb67 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -421,7 +421,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { void SimplifyLoop(HBasicBlock* header); int32_t GetNextInstructionId() { - DCHECK_NE(current_instruction_id_, INT32_MAX); + CHECK_NE(current_instruction_id_, INT32_MAX); return current_instruction_id_++; } @@ -430,7 +430,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { } void SetCurrentInstructionId(int32_t id) { - DCHECK_GE(id, current_instruction_id_); + CHECK_GE(id, current_instruction_id_); current_instruction_id_ = id; } @@ -2612,6 +2612,16 @@ class HPhi FINAL : public HVariableInputSizeInstruction { && other->AsPhi()->GetRegNumber() == GetRegNumber(); } + bool HasEquivalentPhi() const { + if (GetPrevious() != nullptr && GetPrevious()->AsPhi()->GetRegNumber() == GetRegNumber()) { + return true; + } + if (GetNext() != nullptr && GetNext()->AsPhi()->GetRegNumber() == GetRegNumber()) { + return true; + } + return false; + } + // Returns the next equivalent phi (starting from the current one) or null if there is none. // An equivalent phi is a phi having the same dex register and type. // It assumes that phis with the same dex register are adjacent. diff --git a/compiler/optimizing/nodes_vector.h b/compiler/optimizing/nodes_vector.h index 5dbe29b4fa..dc522a463e 100644 --- a/compiler/optimizing/nodes_vector.h +++ b/compiler/optimizing/nodes_vector.h @@ -46,6 +46,10 @@ class Alignment { return "ALIGN(" + std::to_string(base_) + "," + std::to_string(offset_) + ")"; } + bool operator==(const Alignment& other) const { + return base_ == other.base_ && offset_ == other.offset_; + } + private: size_t base_; size_t offset_; @@ -96,6 +100,13 @@ class HVecOperation : public HVariableInputSizeInstruction { return GetPackedField<TypeField>(); } + bool CanBeMoved() const OVERRIDE { return true; } + + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecOperation* o = other->AsVecOperation(); + return GetVectorLength() == o->GetVectorLength() && GetPackedType() == o->GetPackedType(); + } + DECLARE_ABSTRACT_INSTRUCTION(VecOperation); protected: @@ -189,6 +200,11 @@ class HVecMemoryOperation : public HVecOperation { HInstruction* GetArray() const { return InputAt(0); } HInstruction* GetIndex() const { return InputAt(1); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMemoryOperation* o = other->AsVecMemoryOperation(); + return HVecOperation::InstructionDataEquals(o) && GetAlignment() == o->GetAlignment(); + } + DECLARE_ABSTRACT_INSTRUCTION(VecMemoryOperation); private: @@ -378,6 +394,13 @@ class HVecHalvingAdd FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldHAddIsUnsigned>(); } bool IsRounded() const { return GetPackedFlag<kFieldHAddIsRounded>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecHalvingAdd* o = other->AsVecHalvingAdd(); + return HVecOperation::InstructionDataEquals(o) && + IsUnsigned() == o->IsUnsigned() && + IsRounded() == o->IsRounded(); + } + DECLARE_INSTRUCTION(VecHalvingAdd); private: @@ -466,6 +489,11 @@ class HVecMin FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldMinOpIsUnsigned>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMin* o = other->AsVecMin(); + return HVecOperation::InstructionDataEquals(o) && IsUnsigned() == o->IsUnsigned(); + } + DECLARE_INSTRUCTION(VecMin); private: @@ -496,6 +524,11 @@ class HVecMax FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldMaxOpIsUnsigned>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMax* o = other->AsVecMax(); + return HVecOperation::InstructionDataEquals(o) && IsUnsigned() == o->IsUnsigned(); + } + DECLARE_INSTRUCTION(VecMax); private: @@ -694,10 +727,9 @@ class HVecMultiplyAccumulate FINAL : public HVecOperation { static constexpr int kInputMulLeftIndex = 1; static constexpr int kInputMulRightIndex = 2; - bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { - return op_kind_ == other->AsVecMultiplyAccumulate()->op_kind_; + const HVecMultiplyAccumulate* o = other->AsVecMultiplyAccumulate(); + return HVecOperation::InstructionDataEquals(o) && GetOpKind() == o->GetOpKind(); } InstructionKind GetOpKind() const { return op_kind_; } @@ -732,10 +764,16 @@ class HVecLoad FINAL : public HVecMemoryOperation { SetRawInputAt(1, index); SetPackedFlag<kFieldIsStringCharAt>(is_string_char_at); } - DECLARE_INSTRUCTION(VecLoad); bool IsStringCharAt() const { return GetPackedFlag<kFieldIsStringCharAt>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecLoad* o = other->AsVecLoad(); + return HVecMemoryOperation::InstructionDataEquals(o) && IsStringCharAt() == o->IsStringCharAt(); + } + + DECLARE_INSTRUCTION(VecLoad); + private: // Additional packed bits. static constexpr size_t kFieldIsStringCharAt = HVecOperation::kNumberOfVectorOpPackedBits; @@ -767,7 +805,11 @@ class HVecStore FINAL : public HVecMemoryOperation { SetRawInputAt(1, index); SetRawInputAt(2, value); } + + bool CanBeMoved() const OVERRIDE { return false; } + DECLARE_INSTRUCTION(VecStore); + private: DISALLOW_COPY_AND_ASSIGN(HVecStore); }; diff --git a/compiler/optimizing/nodes_vector_test.cc b/compiler/optimizing/nodes_vector_test.cc new file mode 100644 index 0000000000..d33f8e5d2e --- /dev/null +++ b/compiler/optimizing/nodes_vector_test.cc @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/arena_allocator.h" +#include "nodes.h" +#include "optimizing_unit_test.h" + +namespace art { + +/** + * Fixture class for testing vector nodes. + */ +class NodesVectorTest : public CommonCompilerTest { + public: + NodesVectorTest() + : pool_(), + allocator_(&pool_), + graph_(CreateGraph(&allocator_)) { + BuildGraph(); + } + + ~NodesVectorTest() { } + + void BuildGraph() { + graph_->SetNumberOfVRegs(1); + entry_block_ = new (&allocator_) HBasicBlock(graph_); + exit_block_ = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry_block_); + graph_->AddBlock(exit_block_); + graph_->SetEntryBlock(entry_block_); + graph_->SetExitBlock(exit_block_); + parameter_ = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(0), + 0, + Primitive::kPrimInt); + entry_block_->AddInstruction(parameter_); + } + + // General building fields. + ArenaPool pool_; + ArenaAllocator allocator_; + HGraph* graph_; + + HBasicBlock* entry_block_; + HBasicBlock* exit_block_; + + HInstruction* parameter_; +}; + +// +// The actual vector nodes tests. +// + +TEST(NodesVector, Alignment) { + EXPECT_TRUE(Alignment(1, 0).IsAlignedAt(1)); + EXPECT_FALSE(Alignment(1, 0).IsAlignedAt(2)); + + EXPECT_TRUE(Alignment(2, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(2, 1).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(2, 0).IsAlignedAt(2)); + EXPECT_FALSE(Alignment(2, 1).IsAlignedAt(2)); + EXPECT_FALSE(Alignment(2, 0).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(2, 1).IsAlignedAt(4)); + + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(4, 2).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(4, 2).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(4, 2).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(4, 0).IsAlignedAt(8)); + EXPECT_FALSE(Alignment(4, 2).IsAlignedAt(8)); + + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(4)); + EXPECT_TRUE(Alignment(16, 8).IsAlignedAt(8)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 1).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 7).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 0).IsAlignedAt(32)); +} + +TEST(NodesVector, AlignmentEQ) { + EXPECT_TRUE(Alignment(2, 0) == Alignment(2, 0)); + EXPECT_TRUE(Alignment(2, 1) == Alignment(2, 1)); + EXPECT_TRUE(Alignment(4, 0) == Alignment(4, 0)); + EXPECT_TRUE(Alignment(4, 2) == Alignment(4, 2)); + + EXPECT_FALSE(Alignment(4, 0) == Alignment(2, 0)); + EXPECT_FALSE(Alignment(4, 0) == Alignment(4, 1)); + EXPECT_FALSE(Alignment(4, 0) == Alignment(8, 0)); +} + +TEST(NodesVector, AlignmentString) { + EXPECT_STREQ("ALIGN(1,0)", Alignment(1, 0).ToString().c_str()); + + EXPECT_STREQ("ALIGN(2,0)", Alignment(2, 0).ToString().c_str()); + EXPECT_STREQ("ALIGN(2,1)", Alignment(2, 1).ToString().c_str()); + + EXPECT_STREQ("ALIGN(16,0)", Alignment(16, 0).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,1)", Alignment(16, 1).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,8)", Alignment(16, 8).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,9)", Alignment(16, 9).ToString().c_str()); +} + +TEST_F(NodesVectorTest, VectorOperationProperties) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + HVecOperation* v1 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + HVecOperation* v2 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 2); + HVecOperation* v3 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimShort, 4); + HVecOperation* v4 = new (&allocator_) + HVecStore(&allocator_, parameter_, parameter_, v0, Primitive::kPrimInt, 4); + + EXPECT_TRUE(v0->Equals(v0)); + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + EXPECT_TRUE(v4->Equals(v4)); + + EXPECT_TRUE(v0->Equals(v1)); + EXPECT_FALSE(v0->Equals(v2)); // different vector lengths + EXPECT_FALSE(v0->Equals(v3)); // different packed types + EXPECT_FALSE(v0->Equals(v4)); // different kinds + + EXPECT_TRUE(v1->Equals(v0)); // switch operands + EXPECT_FALSE(v4->Equals(v0)); + + EXPECT_EQ(4u, v0->GetVectorLength()); + EXPECT_EQ(4u, v1->GetVectorLength()); + EXPECT_EQ(2u, v2->GetVectorLength()); + EXPECT_EQ(4u, v3->GetVectorLength()); + EXPECT_EQ(4u, v4->GetVectorLength()); + + EXPECT_EQ(Primitive::kPrimDouble, v0->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v1->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v2->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v3->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v4->GetType()); + + EXPECT_EQ(Primitive::kPrimInt, v0->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v1->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v2->GetPackedType()); + EXPECT_EQ(Primitive::kPrimShort, v3->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v4->GetPackedType()); + + EXPECT_EQ(16u, v0->GetVectorNumberOfBytes()); + EXPECT_EQ(16u, v1->GetVectorNumberOfBytes()); + EXPECT_EQ(8u, v2->GetVectorNumberOfBytes()); + EXPECT_EQ(8u, v3->GetVectorNumberOfBytes()); + EXPECT_EQ(16u, v4->GetVectorNumberOfBytes()); + + EXPECT_TRUE(v0->CanBeMoved()); + EXPECT_TRUE(v1->CanBeMoved()); + EXPECT_TRUE(v2->CanBeMoved()); + EXPECT_TRUE(v3->CanBeMoved()); + EXPECT_FALSE(v4->CanBeMoved()); +} + +TEST_F(NodesVectorTest, VectorAlignmentAndStringCharAtMatterOnLoad) { + HVecLoad* v0 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ false); + HVecLoad* v1 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ false); + HVecLoad* v2 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ true); + + EXPECT_FALSE(v0->IsStringCharAt()); + EXPECT_FALSE(v1->IsStringCharAt()); + EXPECT_TRUE(v2->IsStringCharAt()); + + EXPECT_TRUE(v0->Equals(v0)); + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + + EXPECT_TRUE(v0->Equals(v1)); + EXPECT_FALSE(v0->Equals(v2)); + + EXPECT_TRUE(v0->GetAlignment() == Alignment(4, 0)); + EXPECT_TRUE(v1->GetAlignment() == Alignment(4, 0)); + EXPECT_TRUE(v2->GetAlignment() == Alignment(4, 0)); + + v1->SetAlignment(Alignment(8, 0)); + + EXPECT_TRUE(v1->GetAlignment() == Alignment(8, 0)); + + EXPECT_FALSE(v0->Equals(v1)); // no longer equal +} + +TEST_F(NodesVectorTest, VectorSignMattersOnMin) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMin* v1 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true); + HVecMin* v2 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false); + HVecMin* v3 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true); + + EXPECT_TRUE(v1->IsUnsigned()); + EXPECT_FALSE(v2->IsUnsigned()); + EXPECT_TRUE(v3->IsUnsigned()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different signs + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorSignMattersOnMax) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMax* v1 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true); + HVecMax* v2 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false); + HVecMax* v3 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true); + + EXPECT_TRUE(v1->IsUnsigned()); + EXPECT_FALSE(v2->IsUnsigned()); + EXPECT_TRUE(v3->IsUnsigned()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different signs + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorAttributesMatterOnHalvingAdd) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecHalvingAdd* v1 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true, /*is_rounded*/ true); + HVecHalvingAdd* v2 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true, /*is_rounded*/ false); + HVecHalvingAdd* v3 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false, /*is_rounded*/ true); + HVecHalvingAdd* v4 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false, /*is_rounded*/ false); + HVecHalvingAdd* v5 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true, /*is_rounded*/ true); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + EXPECT_TRUE(v4->Equals(v4)); + EXPECT_TRUE(v5->Equals(v5)); + + EXPECT_TRUE(v1->IsUnsigned() && v1->IsRounded()); + EXPECT_TRUE(v2->IsUnsigned() && !v2->IsRounded()); + EXPECT_TRUE(!v3->IsUnsigned() && v3->IsRounded()); + EXPECT_TRUE(!v4->IsUnsigned() && !v4->IsRounded()); + EXPECT_TRUE(v5->IsUnsigned() && v5->IsRounded()); + + EXPECT_FALSE(v1->Equals(v2)); // different attributes + EXPECT_FALSE(v1->Equals(v3)); // different attributes + EXPECT_FALSE(v1->Equals(v4)); // different attributes + EXPECT_FALSE(v1->Equals(v5)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorOperationMattersOnMultiplyAccumulate) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMultiplyAccumulate* v1 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kAdd, v0, v0, v0, Primitive::kPrimInt, 4); + HVecMultiplyAccumulate* v2 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kSub, v0, v0, v0, Primitive::kPrimInt, 4); + HVecMultiplyAccumulate* v3 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kAdd, v0, v0, v0, Primitive::kPrimInt, 2); + + EXPECT_EQ(HInstruction::kAdd, v1->GetOpKind()); + EXPECT_EQ(HInstruction::kSub, v2->GetOpKind()); + EXPECT_EQ(HInstruction::kAdd, v3->GetOpKind()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different operators + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +} // namespace art diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc index 320f01a727..5ad011d8f9 100644 --- a/compiler/optimizing/scheduler.cc +++ b/compiler/optimizing/scheduler.cc @@ -66,28 +66,215 @@ static bool MayHaveReorderingDependency(SideEffects node, SideEffects other) { return false; } +size_t SchedulingGraph::ArrayAccessHeapLocation(HInstruction* array, HInstruction* index) const { + DCHECK(heap_location_collector_ != nullptr); + size_t heap_loc = heap_location_collector_->GetArrayAccessHeapLocation(array, index); + // This array access should be analyzed and added to HeapLocationCollector before. + DCHECK(heap_loc != HeapLocationCollector::kHeapLocationNotFound); + return heap_loc; +} -// Check whether `node` depends on `other`, taking into account `SideEffect` -// information and `CanThrow` information. -static bool HasSideEffectDependency(const HInstruction* node, const HInstruction* other) { - if (MayHaveReorderingDependency(node->GetSideEffects(), other->GetSideEffects())) { +bool SchedulingGraph::ArrayAccessMayAlias(const HInstruction* node, + const HInstruction* other) const { + DCHECK(heap_location_collector_ != nullptr); + size_t node_heap_loc = ArrayAccessHeapLocation(node->InputAt(0), node->InputAt(1)); + size_t other_heap_loc = ArrayAccessHeapLocation(other->InputAt(0), other->InputAt(1)); + + // For example: arr[0] and arr[0] + if (node_heap_loc == other_heap_loc) { return true; } - if (other->CanThrow() && node->GetSideEffects().DoesAnyWrite()) { + // For example: arr[0] and arr[i] + if (heap_location_collector_->MayAlias(node_heap_loc, other_heap_loc)) { return true; } - if (other->GetSideEffects().DoesAnyWrite() && node->CanThrow()) { + return false; +} + +static bool IsArrayAccess(const HInstruction* instruction) { + return instruction->IsArrayGet() || instruction->IsArraySet(); +} + +static bool IsInstanceFieldAccess(const HInstruction* instruction) { + return instruction->IsInstanceFieldGet() || + instruction->IsInstanceFieldSet() || + instruction->IsUnresolvedInstanceFieldGet() || + instruction->IsUnresolvedInstanceFieldSet(); +} + +static bool IsStaticFieldAccess(const HInstruction* instruction) { + return instruction->IsStaticFieldGet() || + instruction->IsStaticFieldSet() || + instruction->IsUnresolvedStaticFieldGet() || + instruction->IsUnresolvedStaticFieldSet(); +} + +static bool IsResolvedFieldAccess(const HInstruction* instruction) { + return instruction->IsInstanceFieldGet() || + instruction->IsInstanceFieldSet() || + instruction->IsStaticFieldGet() || + instruction->IsStaticFieldSet(); +} + +static bool IsUnresolvedFieldAccess(const HInstruction* instruction) { + return instruction->IsUnresolvedInstanceFieldGet() || + instruction->IsUnresolvedInstanceFieldSet() || + instruction->IsUnresolvedStaticFieldGet() || + instruction->IsUnresolvedStaticFieldSet(); +} + +static bool IsFieldAccess(const HInstruction* instruction) { + return IsResolvedFieldAccess(instruction) || IsUnresolvedFieldAccess(instruction); +} + +static const FieldInfo* GetFieldInfo(const HInstruction* instruction) { + if (instruction->IsInstanceFieldGet()) { + return &instruction->AsInstanceFieldGet()->GetFieldInfo(); + } else if (instruction->IsInstanceFieldSet()) { + return &instruction->AsInstanceFieldSet()->GetFieldInfo(); + } else if (instruction->IsStaticFieldGet()) { + return &instruction->AsStaticFieldGet()->GetFieldInfo(); + } else if (instruction->IsStaticFieldSet()) { + return &instruction->AsStaticFieldSet()->GetFieldInfo(); + } else { + LOG(FATAL) << "Unexpected field access type"; + UNREACHABLE(); + } +} + +size_t SchedulingGraph::FieldAccessHeapLocation(HInstruction* obj, const FieldInfo* field) const { + DCHECK(obj != nullptr); + DCHECK(field != nullptr); + DCHECK(heap_location_collector_ != nullptr); + + size_t heap_loc = heap_location_collector_->FindHeapLocationIndex( + heap_location_collector_->FindReferenceInfoOf( + heap_location_collector_->HuntForOriginalReference(obj)), + field->GetFieldOffset().SizeValue(), + nullptr, + field->GetDeclaringClassDefIndex()); + // This field access should be analyzed and added to HeapLocationCollector before. + DCHECK(heap_loc != HeapLocationCollector::kHeapLocationNotFound); + + return heap_loc; +} + +bool SchedulingGraph::FieldAccessMayAlias(const HInstruction* node, + const HInstruction* other) const { + DCHECK(heap_location_collector_ != nullptr); + + // Static and instance field accesses should not alias. + if ((IsInstanceFieldAccess(node) && IsStaticFieldAccess(other)) || + (IsStaticFieldAccess(node) && IsInstanceFieldAccess(other))) { + return false; + } + + // If either of the field accesses is unresolved. + if (IsUnresolvedFieldAccess(node) || IsUnresolvedFieldAccess(other)) { + // Conservatively treat these two accesses may alias. + return true; + } + + // If both fields accesses are resolved. + const FieldInfo* node_field = GetFieldInfo(node); + const FieldInfo* other_field = GetFieldInfo(other); + + size_t node_loc = FieldAccessHeapLocation(node->InputAt(0), node_field); + size_t other_loc = FieldAccessHeapLocation(other->InputAt(0), other_field); + + if (node_loc == other_loc) { return true; } + if (!heap_location_collector_->MayAlias(node_loc, other_loc)) { + return false; + } + + return true; +} + +bool SchedulingGraph::HasMemoryDependency(const HInstruction* node, + const HInstruction* other) const { + if (!MayHaveReorderingDependency(node->GetSideEffects(), other->GetSideEffects())) { + return false; + } + + if (heap_location_collector_ == nullptr || + heap_location_collector_->GetNumberOfHeapLocations() == 0) { + // Without HeapLocation information from load store analysis, + // we cannot do further disambiguation analysis on these two instructions. + // Just simply say that those two instructions have memory dependency. + return true; + } + + if (IsArrayAccess(node) && IsArrayAccess(other)) { + return ArrayAccessMayAlias(node, other); + } + if (IsFieldAccess(node) && IsFieldAccess(other)) { + return FieldAccessMayAlias(node, other); + } + + // TODO(xueliang): LSA to support alias analysis among HVecLoad, HVecStore and ArrayAccess + if (node->IsVecMemoryOperation() && other->IsVecMemoryOperation()) { + return true; + } + if (node->IsVecMemoryOperation() && IsArrayAccess(other)) { + return true; + } + if (IsArrayAccess(node) && other->IsVecMemoryOperation()) { + return true; + } + + // Heap accesses of different kinds should not alias. + if (IsArrayAccess(node) && IsFieldAccess(other)) { + return false; + } + if (IsFieldAccess(node) && IsArrayAccess(other)) { + return false; + } + if (node->IsVecMemoryOperation() && IsFieldAccess(other)) { + return false; + } + if (IsFieldAccess(node) && other->IsVecMemoryOperation()) { + return false; + } + + // We conservatively treat all other cases having dependency, + // for example, Invoke and ArrayGet. + return true; +} + +bool SchedulingGraph::HasExceptionDependency(const HInstruction* node, + const HInstruction* other) const { + if (other->CanThrow() && node->GetSideEffects().DoesAnyWrite()) { + return true; + } + if (other->GetSideEffects().DoesAnyWrite() && node->CanThrow()) { + return true; + } if (other->CanThrow() && node->CanThrow()) { return true; } - // Check side-effect dependency between ArrayGet and BoundsCheck. - if (node->IsArrayGet() && other->IsBoundsCheck() && node->InputAt(1) == other) { + // Above checks should cover all cases where we cannot reorder two + // instructions which may throw exception. + return false; +} + +// Check whether `node` depends on `other`, taking into account `SideEffect` +// information and `CanThrow` information. +bool SchedulingGraph::HasSideEffectDependency(const HInstruction* node, + const HInstruction* other) const { + if (HasMemoryDependency(node, other)) { + return true; + } + + // Even if above memory dependency check has passed, it is still necessary to + // check dependencies between instructions that can throw and instructions + // that write to memory. + if (HasExceptionDependency(node, other)) { return true; } @@ -109,6 +296,10 @@ void SchedulingGraph::AddDependencies(HInstruction* instruction, bool is_schedul // barrier depend on it. for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) { SchedulingNode* other_node = GetNode(other); + CHECK(other_node != nullptr) + << other->DebugName() + << " is in block " << other->GetBlock()->GetBlockId() + << ", and expected in block " << instruction->GetBlock()->GetBlockId(); bool other_is_barrier = other_node->IsSchedulingBarrier(); if (is_scheduling_barrier || other_is_barrier) { AddOtherDependency(other_node, instruction_node); @@ -375,8 +566,20 @@ void HScheduler::Schedule(HBasicBlock* block) { // Build the scheduling graph. scheduling_graph_.Clear(); + + // Only perform LSA/HeapLocation analysis on the basic block that + // is going to get instruction scheduled. + HeapLocationCollector heap_location_collector(block->GetGraph()); + heap_location_collector.VisitBasicBlock(block); + heap_location_collector.BuildAliasingMatrix(); + scheduling_graph_.SetHeapLocationCollector(heap_location_collector); + for (HBackwardInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* instruction = it.Current(); + CHECK_EQ(instruction->GetBlock(), block) + << instruction->DebugName() + << " is in block " << instruction->GetBlock()->GetBlockId() + << ", and expected in block " << block->GetBlockId(); SchedulingNode* node = scheduling_graph_.AddNode(instruction, IsSchedulingBarrier(instruction)); CalculateLatency(node); scheduling_nodes.push_back(node); @@ -598,7 +801,9 @@ void HInstructionScheduling::Run(bool only_optimize_loop_blocks, // Avoid compilation error when compiling for unsupported instruction set. UNUSED(only_optimize_loop_blocks); UNUSED(schedule_randomly); + UNUSED(codegen_); #endif + switch (instruction_set_) { #ifdef ART_ENABLE_CODEGEN_arm64 case kArm64: { diff --git a/compiler/optimizing/scheduler.h b/compiler/optimizing/scheduler.h index 73e8087cd0..930a2c82cf 100644 --- a/compiler/optimizing/scheduler.h +++ b/compiler/optimizing/scheduler.h @@ -21,6 +21,7 @@ #include "base/time_utils.h" #include "driver/compiler_driver.h" +#include "load_store_analysis.h" #include "nodes.h" #include "optimization.h" #include "code_generator.h" @@ -246,7 +247,8 @@ class SchedulingGraph : public ValueObject { : scheduler_(scheduler), arena_(arena), contains_scheduling_barrier_(false), - nodes_map_(arena_->Adapter(kArenaAllocScheduler)) {} + nodes_map_(arena_->Adapter(kArenaAllocScheduler)), + heap_location_collector_(nullptr) {} SchedulingNode* AddNode(HInstruction* instr, bool is_scheduling_barrier = false) { SchedulingNode* node = new (arena_) SchedulingNode(instr, arena_, is_scheduling_barrier); @@ -261,6 +263,10 @@ class SchedulingGraph : public ValueObject { contains_scheduling_barrier_ = false; } + void SetHeapLocationCollector(const HeapLocationCollector& heap_location_collector) { + heap_location_collector_ = &heap_location_collector; + } + SchedulingNode* GetNode(const HInstruction* instr) const { auto it = nodes_map_.Find(instr); if (it == nodes_map_.end()) { @@ -294,6 +300,13 @@ class SchedulingGraph : public ValueObject { void AddOtherDependency(SchedulingNode* node, SchedulingNode* dependency) { AddDependency(node, dependency, /*is_data_dependency*/false); } + bool HasMemoryDependency(const HInstruction* node, const HInstruction* other) const; + bool HasExceptionDependency(const HInstruction* node, const HInstruction* other) const; + bool HasSideEffectDependency(const HInstruction* node, const HInstruction* other) const; + bool ArrayAccessMayAlias(const HInstruction* node, const HInstruction* other) const; + bool FieldAccessMayAlias(const HInstruction* node, const HInstruction* other) const; + size_t ArrayAccessHeapLocation(HInstruction* array, HInstruction* index) const; + size_t FieldAccessHeapLocation(HInstruction* obj, const FieldInfo* field) const; // Add dependencies nodes for the given `HInstruction`: inputs, environments, and side-effects. void AddDependencies(HInstruction* instruction, bool is_scheduling_barrier = false); @@ -305,6 +318,8 @@ class SchedulingGraph : public ValueObject { bool contains_scheduling_barrier_; ArenaHashMap<const HInstruction*, SchedulingNode*> nodes_map_; + + const HeapLocationCollector* heap_location_collector_; }; /* @@ -482,10 +497,9 @@ class HInstructionScheduling : public HOptimization { static constexpr const char* kInstructionScheduling = "scheduler"; + private: CodeGenerator* const codegen_; const InstructionSet instruction_set_; - - private: DISALLOW_COPY_AND_ASSIGN(HInstructionScheduling); }; diff --git a/compiler/optimizing/scheduler_test.cc b/compiler/optimizing/scheduler_test.cc index d87600aa5e..cc7222dd45 100644 --- a/compiler/optimizing/scheduler_test.cc +++ b/compiler/optimizing/scheduler_test.cc @@ -18,6 +18,7 @@ #include "builder.h" #include "codegen_test_utils.h" #include "common_compiler_test.h" +#include "load_store_analysis.h" #include "nodes.h" #include "optimizing_unit_test.h" #include "pc_relative_fixups_x86.h" @@ -193,6 +194,147 @@ class SchedulerTest : public CommonCompilerTest { } } + void TestDependencyGraphOnAliasingArrayAccesses(HScheduler* scheduler) { + HBasicBlock* entry = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry); + graph_->SetEntryBlock(entry); + graph_->BuildDominatorTree(); + + HInstruction* arr = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(0), + 0, + Primitive::kPrimNot); + HInstruction* i = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(1), + 1, + Primitive::kPrimInt); + HInstruction* j = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(1), + 1, + Primitive::kPrimInt); + HInstruction* object = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(0), + 0, + Primitive::kPrimNot); + HInstruction* c0 = graph_->GetIntConstant(0); + HInstruction* c1 = graph_->GetIntConstant(1); + HInstruction* add0 = new (&allocator_) HAdd(Primitive::kPrimInt, i, c0); + HInstruction* add1 = new (&allocator_) HAdd(Primitive::kPrimInt, i, c1); + HInstruction* sub0 = new (&allocator_) HSub(Primitive::kPrimInt, i, c0); + HInstruction* sub1 = new (&allocator_) HSub(Primitive::kPrimInt, i, c1); + HInstruction* arr_set_0 = new (&allocator_) HArraySet(arr, c0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_1 = new (&allocator_) HArraySet(arr, c1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_i = new (&allocator_) HArraySet(arr, i, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_add0 = new (&allocator_) HArraySet(arr, add0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_add1 = new (&allocator_) HArraySet(arr, add1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_sub0 = new (&allocator_) HArraySet(arr, sub0, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_sub1 = new (&allocator_) HArraySet(arr, sub1, c0, Primitive::kPrimInt, 0); + HInstruction* arr_set_j = new (&allocator_) HArraySet(arr, j, c0, Primitive::kPrimInt, 0); + HInstanceFieldSet* set_field10 = new (&allocator_) HInstanceFieldSet(object, + c1, + nullptr, + Primitive::kPrimInt, + MemberOffset(10), + false, + kUnknownFieldIndex, + kUnknownClassDefIndex, + graph_->GetDexFile(), + 0); + + HInstruction* block_instructions[] = {arr, + i, + j, + object, + add0, + add1, + sub0, + sub1, + arr_set_0, + arr_set_1, + arr_set_i, + arr_set_add0, + arr_set_add1, + arr_set_sub0, + arr_set_sub1, + arr_set_j, + set_field10}; + + for (HInstruction* instr : block_instructions) { + entry->AddInstruction(instr); + } + + SchedulingGraph scheduling_graph(scheduler, graph_->GetArena()); + HeapLocationCollector heap_location_collector(graph_); + heap_location_collector.VisitBasicBlock(entry); + heap_location_collector.BuildAliasingMatrix(); + scheduling_graph.SetHeapLocationCollector(heap_location_collector); + + for (HInstruction* instr : ReverseRange(block_instructions)) { + // Build scheduling graph with memory access aliasing information + // from LSA/heap_location_collector. + scheduling_graph.AddNode(instr); + } + + // LSA/HeapLocationCollector should see those ArraySet instructions. + ASSERT_EQ(heap_location_collector.GetNumberOfHeapLocations(), 9U); + ASSERT_TRUE(heap_location_collector.HasHeapStores()); + + // Test queries on HeapLocationCollector's aliasing matrix after load store analysis. + // HeapLocationCollector and SchedulingGraph should report consistent relationships. + size_t loc1 = HeapLocationCollector::kHeapLocationNotFound; + size_t loc2 = HeapLocationCollector::kHeapLocationNotFound; + + // Test side effect dependency: array[0] and array[1] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, c0); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, c1); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_1, arr_set_0)); + + // Test side effect dependency based on LSA analysis: array[i] and array[j] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, i); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, j); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i)); + + // Test side effect dependency based on LSA analysis: array[i] and array[i+0] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, i); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, add0); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_add0, arr_set_i)); + + // Test side effect dependency based on LSA analysis: array[i] and array[i-0] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, i); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, sub0); + ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub0, arr_set_i)); + + // Test side effect dependency based on LSA analysis: array[i] and array[i+1] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, i); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, add1); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_add1, arr_set_i)); + + // Test side effect dependency based on LSA analysis: array[i+1] and array[i-1] + loc1 = heap_location_collector.GetArrayAccessHeapLocation(arr, add1); + loc2 = heap_location_collector.GetArrayAccessHeapLocation(arr, sub1); + ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2)); + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub1, arr_set_add1)); + + // Test side effect dependency based on LSA analysis: array[j] and all others array accesses + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_add0)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_sub0)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_add1)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_sub1)); + + // Test that ArraySet and FieldSet should not have side effect dependency + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_i, set_field10)); + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, set_field10)); + + // Exercise target specific scheduler and SchedulingLatencyVisitor. + scheduler->Schedule(graph_); + } + ArenaPool pool_; ArenaAllocator allocator_; HGraph* graph_; @@ -204,15 +346,28 @@ TEST_F(SchedulerTest, DependencyGraphAndSchedulerARM64) { arm64::HSchedulerARM64 scheduler(&allocator_, &critical_path_selector); TestBuildDependencyGraphAndSchedule(&scheduler); } + +TEST_F(SchedulerTest, ArrayAccessAliasingARM64) { + CriticalPathSchedulingNodeSelector critical_path_selector; + arm64::HSchedulerARM64 scheduler(&allocator_, &critical_path_selector); + TestDependencyGraphOnAliasingArrayAccesses(&scheduler); +} #endif #if defined(ART_ENABLE_CODEGEN_arm) -TEST_F(SchedulerTest, DependencyGrapAndSchedulerARM) { +TEST_F(SchedulerTest, DependencyGraphAndSchedulerARM) { CriticalPathSchedulingNodeSelector critical_path_selector; arm::SchedulingLatencyVisitorARM arm_latency_visitor(/*CodeGenerator*/ nullptr); arm::HSchedulerARM scheduler(&allocator_, &critical_path_selector, &arm_latency_visitor); TestBuildDependencyGraphAndSchedule(&scheduler); } + +TEST_F(SchedulerTest, ArrayAccessAliasingARM) { + CriticalPathSchedulingNodeSelector critical_path_selector; + arm::SchedulingLatencyVisitorARM arm_latency_visitor(/*CodeGenerator*/ nullptr); + arm::HSchedulerARM scheduler(&allocator_, &critical_path_selector, &arm_latency_visitor); + TestDependencyGraphOnAliasingArrayAccesses(&scheduler); +} #endif TEST_F(SchedulerTest, RandomScheduling) { diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index 7b7495bf3b..185303bc8c 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -197,7 +197,7 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { HInstruction* instruction = environment->GetInstructionAt(i); bool should_be_live = ShouldBeLiveForEnvironment(current, instruction); if (should_be_live) { - DCHECK(instruction->HasSsaIndex()); + CHECK(instruction->HasSsaIndex()) << instruction->DebugName(); live_in->SetBit(instruction->GetSsaIndex()); } if (instruction != nullptr) { diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc index 0b05b752da..44b9bb4eb9 100644 --- a/compiler/utils/mips/assembler_mips.cc +++ b/compiler/utils/mips/assembler_mips.cc @@ -4440,6 +4440,106 @@ void MipsAssembler::AdjustBaseAndOffset(Register& base, CHECK_EQ(misalignment, offset & (kMipsDoublewordSize - 1)); } +void MipsAssembler::AdjustBaseOffsetAndElementSizeShift(Register& base, + int32_t& offset, + int& element_size_shift) { + // This method is used to adjust the base register, offset and element_size_shift + // for a vector load/store when the offset doesn't fit into allowed number of bits. + // MSA ld.df and st.df instructions take signed offsets as arguments, but maximum + // offset is dependant on the size of the data format df (10-bit offsets for ld.b, + // 11-bit for ld.h, 12-bit for ld.w and 13-bit for ld.d). + // If element_size_shift is non-negative at entry, it won't be changed, but offset + // will be checked for appropriate alignment. If negative at entry, it will be + // adjusted based on offset for maximum fit. + // It's assumed that `base` is a multiple of 8. + CHECK_NE(base, AT); // Must not overwrite the register `base` while loading `offset`. + + if (element_size_shift >= 0) { + CHECK_LE(element_size_shift, TIMES_8); + CHECK_GE(JAVASTYLE_CTZ(offset), element_size_shift); + } else if (IsAligned<kMipsDoublewordSize>(offset)) { + element_size_shift = TIMES_8; + } else if (IsAligned<kMipsWordSize>(offset)) { + element_size_shift = TIMES_4; + } else if (IsAligned<kMipsHalfwordSize>(offset)) { + element_size_shift = TIMES_2; + } else { + element_size_shift = TIMES_1; + } + + const int low_len = 10 + element_size_shift; // How many low bits of `offset` ld.df/st.df + // will take. + int16_t low = offset & ((1 << low_len) - 1); // Isolate these bits. + low -= (low & (1 << (low_len - 1))) << 1; // Sign-extend these bits. + if (low == offset) { + return; // `offset` fits into ld.df/st.df. + } + + // First, see if `offset` can be represented as a sum of two or three signed offsets. + // This can save an instruction or two. + + // Max int16_t that's a multiple of element size. + const int32_t kMaxDeltaForSimpleAdjustment = 0x8000 - (1 << element_size_shift); + // Max ld.df/st.df offset that's a multiple of element size. + const int32_t kMaxLoadStoreOffset = 0x1ff << element_size_shift; + const int32_t kMaxOffsetForSimpleAdjustment = kMaxDeltaForSimpleAdjustment + kMaxLoadStoreOffset; + const int32_t kMinOffsetForMediumAdjustment = 2 * kMaxDeltaForSimpleAdjustment; + const int32_t kMaxOffsetForMediumAdjustment = kMinOffsetForMediumAdjustment + kMaxLoadStoreOffset; + + if (IsInt<16>(offset)) { + Addiu(AT, base, offset); + offset = 0; + } else if (0 <= offset && offset <= kMaxOffsetForSimpleAdjustment) { + Addiu(AT, base, kMaxDeltaForSimpleAdjustment); + offset -= kMaxDeltaForSimpleAdjustment; + } else if (-kMaxOffsetForSimpleAdjustment <= offset && offset < 0) { + Addiu(AT, base, -kMaxDeltaForSimpleAdjustment); + offset += kMaxDeltaForSimpleAdjustment; + } else if (!IsR6() && 0 <= offset && offset <= kMaxOffsetForMediumAdjustment) { + Addiu(AT, base, kMaxDeltaForSimpleAdjustment); + if (offset <= kMinOffsetForMediumAdjustment) { + Addiu(AT, AT, offset - kMaxDeltaForSimpleAdjustment); + offset = 0; + } else { + Addiu(AT, AT, kMaxDeltaForSimpleAdjustment); + offset -= kMinOffsetForMediumAdjustment; + } + } else if (!IsR6() && -kMaxOffsetForMediumAdjustment <= offset && offset < 0) { + Addiu(AT, base, -kMaxDeltaForSimpleAdjustment); + if (-kMinOffsetForMediumAdjustment <= offset) { + Addiu(AT, AT, offset + kMaxDeltaForSimpleAdjustment); + offset = 0; + } else { + Addiu(AT, AT, -kMaxDeltaForSimpleAdjustment); + offset += kMinOffsetForMediumAdjustment; + } + } else { + // 16-bit or smaller parts of `offset`: + // |31 hi 16|15 mid 13-10|12-9 low 0| + // + // Instructions that supply each part as a signed integer addend: + // |aui |addiu |ld.df/st.df | + uint32_t tmp = static_cast<uint32_t>(offset) - low; // Exclude `low` from the rest of `offset` + // (accounts for sign of `low`). + tmp += (tmp & (UINT32_C(1) << 15)) << 1; // Account for sign extension in addiu. + int16_t mid = Low16Bits(tmp); + int16_t hi = High16Bits(tmp); + if (IsR6()) { + Aui(AT, base, hi); + } else { + Lui(AT, hi); + Addu(AT, AT, base); + } + if (mid != 0) { + Addiu(AT, AT, mid); + } + offset = low; + } + base = AT; + CHECK_GE(JAVASTYLE_CTZ(offset), element_size_shift); + CHECK(IsInt<10>(offset >> element_size_shift)); +} + void MipsAssembler::LoadFromOffset(LoadOperandType type, Register reg, Register base, @@ -4455,6 +4555,10 @@ void MipsAssembler::LoadDFromOffset(FRegister reg, Register base, int32_t offset LoadDFromOffset<>(reg, base, offset); } +void MipsAssembler::LoadQFromOffset(FRegister reg, Register base, int32_t offset) { + LoadQFromOffset<>(reg, base, offset); +} + void MipsAssembler::EmitLoad(ManagedRegister m_dst, Register src_register, int32_t src_offset, size_t size) { MipsManagedRegister dst = m_dst.AsMips(); @@ -4494,6 +4598,10 @@ void MipsAssembler::StoreDToOffset(FRegister reg, Register base, int32_t offset) StoreDToOffset<>(reg, base, offset); } +void MipsAssembler::StoreQToOffset(FRegister reg, Register base, int32_t offset) { + StoreQToOffset<>(reg, base, offset); +} + static dwarf::Reg DWARFReg(Register reg) { return dwarf::Reg::MipsCore(static_cast<int>(reg)); } diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h index dd4ce6dc80..a229882d18 100644 --- a/compiler/utils/mips/assembler_mips.h +++ b/compiler/utils/mips/assembler_mips.h @@ -47,14 +47,16 @@ enum LoadOperandType { kLoadSignedHalfword, kLoadUnsignedHalfword, kLoadWord, - kLoadDoubleword + kLoadDoubleword, + kLoadQuadword }; enum StoreOperandType { kStoreByte, kStoreHalfword, kStoreWord, - kStoreDoubleword + kStoreDoubleword, + kStoreQuadword }; // Used to test the values returned by ClassS/ClassD. @@ -646,6 +648,9 @@ class MipsAssembler FINAL : public Assembler, public JNIMacroAssembler<PointerSi int32_t& offset, bool is_doubleword, bool is_float = false); + void AdjustBaseOffsetAndElementSizeShift(Register& base, + int32_t& offset, + int& element_size_shift); private: // This will be used as an argument for loads/stores @@ -793,6 +798,24 @@ class MipsAssembler FINAL : public Assembler, public JNIMacroAssembler<PointerSi } template <typename ImplicitNullChecker = NoImplicitNullChecker> + void LoadQFromOffset(FRegister reg, + Register base, + int32_t offset, + ImplicitNullChecker null_checker = NoImplicitNullChecker()) { + int element_size_shift = -1; + AdjustBaseOffsetAndElementSizeShift(base, offset, element_size_shift); + switch (element_size_shift) { + case TIMES_1: LdB(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_2: LdH(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_4: LdW(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_8: LdD(static_cast<VectorRegister>(reg), base, offset); break; + default: + LOG(FATAL) << "UNREACHABLE"; + } + null_checker(); + } + + template <typename ImplicitNullChecker = NoImplicitNullChecker> void StoreToOffset(StoreOperandType type, Register reg, Register base, @@ -861,12 +884,32 @@ class MipsAssembler FINAL : public Assembler, public JNIMacroAssembler<PointerSi } } + template <typename ImplicitNullChecker = NoImplicitNullChecker> + void StoreQToOffset(FRegister reg, + Register base, + int32_t offset, + ImplicitNullChecker null_checker = NoImplicitNullChecker()) { + int element_size_shift = -1; + AdjustBaseOffsetAndElementSizeShift(base, offset, element_size_shift); + switch (element_size_shift) { + case TIMES_1: StB(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_2: StH(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_4: StW(static_cast<VectorRegister>(reg), base, offset); break; + case TIMES_8: StD(static_cast<VectorRegister>(reg), base, offset); break; + default: + LOG(FATAL) << "UNREACHABLE"; + } + null_checker(); + } + void LoadFromOffset(LoadOperandType type, Register reg, Register base, int32_t offset); void LoadSFromOffset(FRegister reg, Register base, int32_t offset); void LoadDFromOffset(FRegister reg, Register base, int32_t offset); + void LoadQFromOffset(FRegister reg, Register base, int32_t offset); void StoreToOffset(StoreOperandType type, Register reg, Register base, int32_t offset); void StoreSToOffset(FRegister reg, Register base, int32_t offset); void StoreDToOffset(FRegister reg, Register base, int32_t offset); + void StoreQToOffset(FRegister reg, Register base, int32_t offset); // Emit data (e.g. encoded instruction or immediate) to the instruction stream. void Emit(uint32_t value); diff --git a/compiler/utils/mips/assembler_mips32r5_test.cc b/compiler/utils/mips/assembler_mips32r5_test.cc new file mode 100644 index 0000000000..24b09b5524 --- /dev/null +++ b/compiler/utils/mips/assembler_mips32r5_test.cc @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "assembler_mips.h" + +#include <map> + +#include "base/stl_util.h" +#include "utils/assembler_test.h" + +#define __ GetAssembler()-> + +namespace art { + +struct MIPSCpuRegisterCompare { + bool operator()(const mips::Register& a, const mips::Register& b) const { + return a < b; + } +}; + +class AssemblerMIPS32r5Test : public AssemblerTest<mips::MipsAssembler, + mips::Register, + mips::FRegister, + uint32_t, + mips::VectorRegister> { + public: + typedef AssemblerTest<mips::MipsAssembler, + mips::Register, + mips::FRegister, + uint32_t, + mips::VectorRegister> Base; + + AssemblerMIPS32r5Test() : + instruction_set_features_(MipsInstructionSetFeatures::FromVariant("mips32r5", nullptr)) { + } + + protected: + // Get the typically used name for this architecture, e.g., aarch64, x86-64, ... + std::string GetArchitectureString() OVERRIDE { + return "mips"; + } + + std::string GetAssemblerParameters() OVERRIDE { + return " --no-warn -32 -march=mips32r5 -mmsa"; + } + + void Pad(std::vector<uint8_t>& data) OVERRIDE { + // The GNU linker unconditionally pads the code segment with NOPs to a size that is a multiple + // of 16 and there doesn't appear to be a way to suppress this padding. Our assembler doesn't + // pad, so, in order for two assembler outputs to match, we need to match the padding as well. + // NOP is encoded as four zero bytes on MIPS. + size_t pad_size = RoundUp(data.size(), 16u) - data.size(); + data.insert(data.end(), pad_size, 0); + } + + std::string GetDisassembleParameters() OVERRIDE { + return " -D -bbinary -mmips:isa32r5"; + } + + mips::MipsAssembler* CreateAssembler(ArenaAllocator* arena) OVERRIDE { + return new (arena) mips::MipsAssembler(arena, instruction_set_features_.get()); + } + + void SetUpHelpers() OVERRIDE { + if (registers_.size() == 0) { + registers_.push_back(new mips::Register(mips::ZERO)); + registers_.push_back(new mips::Register(mips::AT)); + registers_.push_back(new mips::Register(mips::V0)); + registers_.push_back(new mips::Register(mips::V1)); + registers_.push_back(new mips::Register(mips::A0)); + registers_.push_back(new mips::Register(mips::A1)); + registers_.push_back(new mips::Register(mips::A2)); + registers_.push_back(new mips::Register(mips::A3)); + registers_.push_back(new mips::Register(mips::T0)); + registers_.push_back(new mips::Register(mips::T1)); + registers_.push_back(new mips::Register(mips::T2)); + registers_.push_back(new mips::Register(mips::T3)); + registers_.push_back(new mips::Register(mips::T4)); + registers_.push_back(new mips::Register(mips::T5)); + registers_.push_back(new mips::Register(mips::T6)); + registers_.push_back(new mips::Register(mips::T7)); + registers_.push_back(new mips::Register(mips::S0)); + registers_.push_back(new mips::Register(mips::S1)); + registers_.push_back(new mips::Register(mips::S2)); + registers_.push_back(new mips::Register(mips::S3)); + registers_.push_back(new mips::Register(mips::S4)); + registers_.push_back(new mips::Register(mips::S5)); + registers_.push_back(new mips::Register(mips::S6)); + registers_.push_back(new mips::Register(mips::S7)); + registers_.push_back(new mips::Register(mips::T8)); + registers_.push_back(new mips::Register(mips::T9)); + registers_.push_back(new mips::Register(mips::K0)); + registers_.push_back(new mips::Register(mips::K1)); + registers_.push_back(new mips::Register(mips::GP)); + registers_.push_back(new mips::Register(mips::SP)); + registers_.push_back(new mips::Register(mips::FP)); + registers_.push_back(new mips::Register(mips::RA)); + + secondary_register_names_.emplace(mips::Register(mips::ZERO), "zero"); + secondary_register_names_.emplace(mips::Register(mips::AT), "at"); + secondary_register_names_.emplace(mips::Register(mips::V0), "v0"); + secondary_register_names_.emplace(mips::Register(mips::V1), "v1"); + secondary_register_names_.emplace(mips::Register(mips::A0), "a0"); + secondary_register_names_.emplace(mips::Register(mips::A1), "a1"); + secondary_register_names_.emplace(mips::Register(mips::A2), "a2"); + secondary_register_names_.emplace(mips::Register(mips::A3), "a3"); + secondary_register_names_.emplace(mips::Register(mips::T0), "t0"); + secondary_register_names_.emplace(mips::Register(mips::T1), "t1"); + secondary_register_names_.emplace(mips::Register(mips::T2), "t2"); + secondary_register_names_.emplace(mips::Register(mips::T3), "t3"); + secondary_register_names_.emplace(mips::Register(mips::T4), "t4"); + secondary_register_names_.emplace(mips::Register(mips::T5), "t5"); + secondary_register_names_.emplace(mips::Register(mips::T6), "t6"); + secondary_register_names_.emplace(mips::Register(mips::T7), "t7"); + secondary_register_names_.emplace(mips::Register(mips::S0), "s0"); + secondary_register_names_.emplace(mips::Register(mips::S1), "s1"); + secondary_register_names_.emplace(mips::Register(mips::S2), "s2"); + secondary_register_names_.emplace(mips::Register(mips::S3), "s3"); + secondary_register_names_.emplace(mips::Register(mips::S4), "s4"); + secondary_register_names_.emplace(mips::Register(mips::S5), "s5"); + secondary_register_names_.emplace(mips::Register(mips::S6), "s6"); + secondary_register_names_.emplace(mips::Register(mips::S7), "s7"); + secondary_register_names_.emplace(mips::Register(mips::T8), "t8"); + secondary_register_names_.emplace(mips::Register(mips::T9), "t9"); + secondary_register_names_.emplace(mips::Register(mips::K0), "k0"); + secondary_register_names_.emplace(mips::Register(mips::K1), "k1"); + secondary_register_names_.emplace(mips::Register(mips::GP), "gp"); + secondary_register_names_.emplace(mips::Register(mips::SP), "sp"); + secondary_register_names_.emplace(mips::Register(mips::FP), "fp"); + secondary_register_names_.emplace(mips::Register(mips::RA), "ra"); + + fp_registers_.push_back(new mips::FRegister(mips::F0)); + fp_registers_.push_back(new mips::FRegister(mips::F1)); + fp_registers_.push_back(new mips::FRegister(mips::F2)); + fp_registers_.push_back(new mips::FRegister(mips::F3)); + fp_registers_.push_back(new mips::FRegister(mips::F4)); + fp_registers_.push_back(new mips::FRegister(mips::F5)); + fp_registers_.push_back(new mips::FRegister(mips::F6)); + fp_registers_.push_back(new mips::FRegister(mips::F7)); + fp_registers_.push_back(new mips::FRegister(mips::F8)); + fp_registers_.push_back(new mips::FRegister(mips::F9)); + fp_registers_.push_back(new mips::FRegister(mips::F10)); + fp_registers_.push_back(new mips::FRegister(mips::F11)); + fp_registers_.push_back(new mips::FRegister(mips::F12)); + fp_registers_.push_back(new mips::FRegister(mips::F13)); + fp_registers_.push_back(new mips::FRegister(mips::F14)); + fp_registers_.push_back(new mips::FRegister(mips::F15)); + fp_registers_.push_back(new mips::FRegister(mips::F16)); + fp_registers_.push_back(new mips::FRegister(mips::F17)); + fp_registers_.push_back(new mips::FRegister(mips::F18)); + fp_registers_.push_back(new mips::FRegister(mips::F19)); + fp_registers_.push_back(new mips::FRegister(mips::F20)); + fp_registers_.push_back(new mips::FRegister(mips::F21)); + fp_registers_.push_back(new mips::FRegister(mips::F22)); + fp_registers_.push_back(new mips::FRegister(mips::F23)); + fp_registers_.push_back(new mips::FRegister(mips::F24)); + fp_registers_.push_back(new mips::FRegister(mips::F25)); + fp_registers_.push_back(new mips::FRegister(mips::F26)); + fp_registers_.push_back(new mips::FRegister(mips::F27)); + fp_registers_.push_back(new mips::FRegister(mips::F28)); + fp_registers_.push_back(new mips::FRegister(mips::F29)); + fp_registers_.push_back(new mips::FRegister(mips::F30)); + fp_registers_.push_back(new mips::FRegister(mips::F31)); + + vec_registers_.push_back(new mips::VectorRegister(mips::W0)); + vec_registers_.push_back(new mips::VectorRegister(mips::W1)); + vec_registers_.push_back(new mips::VectorRegister(mips::W2)); + vec_registers_.push_back(new mips::VectorRegister(mips::W3)); + vec_registers_.push_back(new mips::VectorRegister(mips::W4)); + vec_registers_.push_back(new mips::VectorRegister(mips::W5)); + vec_registers_.push_back(new mips::VectorRegister(mips::W6)); + vec_registers_.push_back(new mips::VectorRegister(mips::W7)); + vec_registers_.push_back(new mips::VectorRegister(mips::W8)); + vec_registers_.push_back(new mips::VectorRegister(mips::W9)); + vec_registers_.push_back(new mips::VectorRegister(mips::W10)); + vec_registers_.push_back(new mips::VectorRegister(mips::W11)); + vec_registers_.push_back(new mips::VectorRegister(mips::W12)); + vec_registers_.push_back(new mips::VectorRegister(mips::W13)); + vec_registers_.push_back(new mips::VectorRegister(mips::W14)); + vec_registers_.push_back(new mips::VectorRegister(mips::W15)); + vec_registers_.push_back(new mips::VectorRegister(mips::W16)); + vec_registers_.push_back(new mips::VectorRegister(mips::W17)); + vec_registers_.push_back(new mips::VectorRegister(mips::W18)); + vec_registers_.push_back(new mips::VectorRegister(mips::W19)); + vec_registers_.push_back(new mips::VectorRegister(mips::W20)); + vec_registers_.push_back(new mips::VectorRegister(mips::W21)); + vec_registers_.push_back(new mips::VectorRegister(mips::W22)); + vec_registers_.push_back(new mips::VectorRegister(mips::W23)); + vec_registers_.push_back(new mips::VectorRegister(mips::W24)); + vec_registers_.push_back(new mips::VectorRegister(mips::W25)); + vec_registers_.push_back(new mips::VectorRegister(mips::W26)); + vec_registers_.push_back(new mips::VectorRegister(mips::W27)); + vec_registers_.push_back(new mips::VectorRegister(mips::W28)); + vec_registers_.push_back(new mips::VectorRegister(mips::W29)); + vec_registers_.push_back(new mips::VectorRegister(mips::W30)); + vec_registers_.push_back(new mips::VectorRegister(mips::W31)); + } + } + + void TearDown() OVERRIDE { + AssemblerTest::TearDown(); + STLDeleteElements(®isters_); + STLDeleteElements(&fp_registers_); + STLDeleteElements(&vec_registers_); + } + + std::vector<mips::Register*> GetRegisters() OVERRIDE { + return registers_; + } + + std::vector<mips::FRegister*> GetFPRegisters() OVERRIDE { + return fp_registers_; + } + + std::vector<mips::VectorRegister*> GetVectorRegisters() OVERRIDE { + return vec_registers_; + } + + uint32_t CreateImmediate(int64_t imm_value) OVERRIDE { + return imm_value; + } + + std::string GetSecondaryRegisterName(const mips::Register& reg) OVERRIDE { + CHECK(secondary_register_names_.find(reg) != secondary_register_names_.end()); + return secondary_register_names_[reg]; + } + + std::string RepeatInsn(size_t count, const std::string& insn) { + std::string result; + for (; count != 0u; --count) { + result += insn; + } + return result; + } + + private: + std::vector<mips::Register*> registers_; + std::map<mips::Register, std::string, MIPSCpuRegisterCompare> secondary_register_names_; + + std::vector<mips::FRegister*> fp_registers_; + std::vector<mips::VectorRegister*> vec_registers_; + std::unique_ptr<const MipsInstructionSetFeatures> instruction_set_features_; +}; + +TEST_F(AssemblerMIPS32r5Test, Toolchain) { + EXPECT_TRUE(CheckTools()); +} + +TEST_F(AssemblerMIPS32r5Test, LoadQFromOffset) { + __ LoadQFromOffset(mips::F0, mips::A0, 0); + __ LoadQFromOffset(mips::F0, mips::A0, 1); + __ LoadQFromOffset(mips::F0, mips::A0, 2); + __ LoadQFromOffset(mips::F0, mips::A0, 4); + __ LoadQFromOffset(mips::F0, mips::A0, 8); + __ LoadQFromOffset(mips::F0, mips::A0, 511); + __ LoadQFromOffset(mips::F0, mips::A0, 512); + __ LoadQFromOffset(mips::F0, mips::A0, 513); + __ LoadQFromOffset(mips::F0, mips::A0, 514); + __ LoadQFromOffset(mips::F0, mips::A0, 516); + __ LoadQFromOffset(mips::F0, mips::A0, 1022); + __ LoadQFromOffset(mips::F0, mips::A0, 1024); + __ LoadQFromOffset(mips::F0, mips::A0, 1025); + __ LoadQFromOffset(mips::F0, mips::A0, 1026); + __ LoadQFromOffset(mips::F0, mips::A0, 1028); + __ LoadQFromOffset(mips::F0, mips::A0, 2044); + __ LoadQFromOffset(mips::F0, mips::A0, 2048); + __ LoadQFromOffset(mips::F0, mips::A0, 2049); + __ LoadQFromOffset(mips::F0, mips::A0, 2050); + __ LoadQFromOffset(mips::F0, mips::A0, 2052); + __ LoadQFromOffset(mips::F0, mips::A0, 4088); + __ LoadQFromOffset(mips::F0, mips::A0, 4096); + __ LoadQFromOffset(mips::F0, mips::A0, 4097); + __ LoadQFromOffset(mips::F0, mips::A0, 4098); + __ LoadQFromOffset(mips::F0, mips::A0, 4100); + __ LoadQFromOffset(mips::F0, mips::A0, 4104); + __ LoadQFromOffset(mips::F0, mips::A0, 0x7FFC); + __ LoadQFromOffset(mips::F0, mips::A0, 0x8000); + __ LoadQFromOffset(mips::F0, mips::A0, 0x10000); + __ LoadQFromOffset(mips::F0, mips::A0, 0x12345678); + __ LoadQFromOffset(mips::F0, mips::A0, 0x12350078); + __ LoadQFromOffset(mips::F0, mips::A0, -256); + __ LoadQFromOffset(mips::F0, mips::A0, -511); + __ LoadQFromOffset(mips::F0, mips::A0, -513); + __ LoadQFromOffset(mips::F0, mips::A0, -1022); + __ LoadQFromOffset(mips::F0, mips::A0, -1026); + __ LoadQFromOffset(mips::F0, mips::A0, -2044); + __ LoadQFromOffset(mips::F0, mips::A0, -2052); + __ LoadQFromOffset(mips::F0, mips::A0, -4096); + __ LoadQFromOffset(mips::F0, mips::A0, -4104); + __ LoadQFromOffset(mips::F0, mips::A0, -32768); + __ LoadQFromOffset(mips::F0, mips::A0, -36856); + __ LoadQFromOffset(mips::F0, mips::A0, 36856); + __ LoadQFromOffset(mips::F0, mips::A0, -69608); + __ LoadQFromOffset(mips::F0, mips::A0, 69608); + __ LoadQFromOffset(mips::F0, mips::A0, 0xABCDEF00); + __ LoadQFromOffset(mips::F0, mips::A0, 0x7FFFABCD); + + const char* expected = + "ld.d $w0, 0($a0)\n" + "ld.b $w0, 1($a0)\n" + "ld.h $w0, 2($a0)\n" + "ld.w $w0, 4($a0)\n" + "ld.d $w0, 8($a0)\n" + "ld.b $w0, 511($a0)\n" + "ld.d $w0, 512($a0)\n" + "addiu $at, $a0, 513\n" + "ld.b $w0, 0($at)\n" + "ld.h $w0, 514($a0)\n" + "ld.w $w0, 516($a0)\n" + "ld.h $w0, 1022($a0)\n" + "ld.d $w0, 1024($a0)\n" + "addiu $at, $a0, 1025\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 1026\n" + "ld.h $w0, 0($at)\n" + "ld.w $w0, 1028($a0)\n" + "ld.w $w0, 2044($a0)\n" + "ld.d $w0, 2048($a0)\n" + "addiu $at, $a0, 2049\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 2050\n" + "ld.h $w0, 0($at)\n" + "addiu $at, $a0, 2052\n" + "ld.w $w0, 0($at)\n" + "ld.d $w0, 4088($a0)\n" + "addiu $at, $a0, 4096\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, 4097\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 4098\n" + "ld.h $w0, 0($at)\n" + "addiu $at, $a0, 4100\n" + "ld.w $w0, 0($at)\n" + "addiu $at, $a0, 4104\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, 0x7FFC\n" + "ld.w $w0, 0($at)\n" + "addiu $at, $a0, 0x7FF8\n" + "ld.d $w0, 8($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 32760\n" + "ld.d $w0, 16($at)\n" + "lui $at, 4660\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, 24576\n" + "ld.d $w0, -2440($at) # 0xF678\n" + "lui $at, 4661\n" + "addu $at, $at, $a0\n" + "ld.d $w0, 120($at)\n" + "ld.d $w0, -256($a0)\n" + "ld.b $w0, -511($a0)\n" + "addiu $at, $a0, -513\n" + "ld.b $w0, 0($at)\n" + "ld.h $w0, -1022($a0)\n" + "addiu $at, $a0, -1026\n" + "ld.h $w0, 0($at)\n" + "ld.w $w0, -2044($a0)\n" + "addiu $at, $a0, -2052\n" + "ld.w $w0, 0($at)\n" + "ld.d $w0, -4096($a0)\n" + "addiu $at, $a0, -4104\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, -32768\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, -32760\n" + "addiu $at, $at, -4096\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 4096\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, -32760\n" + "addiu $at, $at, -32760\n" + "ld.d $w0, -4088($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 32760\n" + "ld.d $w0, 4088($at)\n" + "lui $at, 0xABCE\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, -8192 # 0xE000\n" + "ld.d $w0, 0xF00($at)\n" + "lui $at, 0x8000\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, -21504 # 0xAC00\n" + "ld.b $w0, -51($at) # 0xFFCD\n"; + DriverStr(expected, "LoadQFromOffset"); +} + +TEST_F(AssemblerMIPS32r5Test, StoreQToOffset) { + __ StoreQToOffset(mips::F0, mips::A0, 0); + __ StoreQToOffset(mips::F0, mips::A0, 1); + __ StoreQToOffset(mips::F0, mips::A0, 2); + __ StoreQToOffset(mips::F0, mips::A0, 4); + __ StoreQToOffset(mips::F0, mips::A0, 8); + __ StoreQToOffset(mips::F0, mips::A0, 511); + __ StoreQToOffset(mips::F0, mips::A0, 512); + __ StoreQToOffset(mips::F0, mips::A0, 513); + __ StoreQToOffset(mips::F0, mips::A0, 514); + __ StoreQToOffset(mips::F0, mips::A0, 516); + __ StoreQToOffset(mips::F0, mips::A0, 1022); + __ StoreQToOffset(mips::F0, mips::A0, 1024); + __ StoreQToOffset(mips::F0, mips::A0, 1025); + __ StoreQToOffset(mips::F0, mips::A0, 1026); + __ StoreQToOffset(mips::F0, mips::A0, 1028); + __ StoreQToOffset(mips::F0, mips::A0, 2044); + __ StoreQToOffset(mips::F0, mips::A0, 2048); + __ StoreQToOffset(mips::F0, mips::A0, 2049); + __ StoreQToOffset(mips::F0, mips::A0, 2050); + __ StoreQToOffset(mips::F0, mips::A0, 2052); + __ StoreQToOffset(mips::F0, mips::A0, 4088); + __ StoreQToOffset(mips::F0, mips::A0, 4096); + __ StoreQToOffset(mips::F0, mips::A0, 4097); + __ StoreQToOffset(mips::F0, mips::A0, 4098); + __ StoreQToOffset(mips::F0, mips::A0, 4100); + __ StoreQToOffset(mips::F0, mips::A0, 4104); + __ StoreQToOffset(mips::F0, mips::A0, 0x7FFC); + __ StoreQToOffset(mips::F0, mips::A0, 0x8000); + __ StoreQToOffset(mips::F0, mips::A0, 0x10000); + __ StoreQToOffset(mips::F0, mips::A0, 0x12345678); + __ StoreQToOffset(mips::F0, mips::A0, 0x12350078); + __ StoreQToOffset(mips::F0, mips::A0, -256); + __ StoreQToOffset(mips::F0, mips::A0, -511); + __ StoreQToOffset(mips::F0, mips::A0, -513); + __ StoreQToOffset(mips::F0, mips::A0, -1022); + __ StoreQToOffset(mips::F0, mips::A0, -1026); + __ StoreQToOffset(mips::F0, mips::A0, -2044); + __ StoreQToOffset(mips::F0, mips::A0, -2052); + __ StoreQToOffset(mips::F0, mips::A0, -4096); + __ StoreQToOffset(mips::F0, mips::A0, -4104); + __ StoreQToOffset(mips::F0, mips::A0, -32768); + __ StoreQToOffset(mips::F0, mips::A0, -36856); + __ StoreQToOffset(mips::F0, mips::A0, 36856); + __ StoreQToOffset(mips::F0, mips::A0, -69608); + __ StoreQToOffset(mips::F0, mips::A0, 69608); + __ StoreQToOffset(mips::F0, mips::A0, 0xABCDEF00); + __ StoreQToOffset(mips::F0, mips::A0, 0x7FFFABCD); + + const char* expected = + "st.d $w0, 0($a0)\n" + "st.b $w0, 1($a0)\n" + "st.h $w0, 2($a0)\n" + "st.w $w0, 4($a0)\n" + "st.d $w0, 8($a0)\n" + "st.b $w0, 511($a0)\n" + "st.d $w0, 512($a0)\n" + "addiu $at, $a0, 513\n" + "st.b $w0, 0($at)\n" + "st.h $w0, 514($a0)\n" + "st.w $w0, 516($a0)\n" + "st.h $w0, 1022($a0)\n" + "st.d $w0, 1024($a0)\n" + "addiu $at, $a0, 1025\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 1026\n" + "st.h $w0, 0($at)\n" + "st.w $w0, 1028($a0)\n" + "st.w $w0, 2044($a0)\n" + "st.d $w0, 2048($a0)\n" + "addiu $at, $a0, 2049\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 2050\n" + "st.h $w0, 0($at)\n" + "addiu $at, $a0, 2052\n" + "st.w $w0, 0($at)\n" + "st.d $w0, 4088($a0)\n" + "addiu $at, $a0, 4096\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, 4097\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 4098\n" + "st.h $w0, 0($at)\n" + "addiu $at, $a0, 4100\n" + "st.w $w0, 0($at)\n" + "addiu $at, $a0, 4104\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, 0x7FFC\n" + "st.w $w0, 0($at)\n" + "addiu $at, $a0, 0x7FF8\n" + "st.d $w0, 8($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 32760\n" + "st.d $w0, 16($at)\n" + "lui $at, 4660\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, 24576\n" + "st.d $w0, -2440($at) # 0xF678\n" + "lui $at, 4661\n" + "addu $at, $at, $a0\n" + "st.d $w0, 120($at)\n" + "st.d $w0, -256($a0)\n" + "st.b $w0, -511($a0)\n" + "addiu $at, $a0, -513\n" + "st.b $w0, 0($at)\n" + "st.h $w0, -1022($a0)\n" + "addiu $at, $a0, -1026\n" + "st.h $w0, 0($at)\n" + "st.w $w0, -2044($a0)\n" + "addiu $at, $a0, -2052\n" + "st.w $w0, 0($at)\n" + "st.d $w0, -4096($a0)\n" + "addiu $at, $a0, -4104\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, -32768\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, -32760\n" + "addiu $at, $at, -4096\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 4096\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, -32760\n" + "addiu $at, $at, -32760\n" + "st.d $w0, -4088($at)\n" + "addiu $at, $a0, 32760\n" + "addiu $at, $at, 32760\n" + "st.d $w0, 4088($at)\n" + "lui $at, 0xABCE\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, -8192 # 0xE000\n" + "st.d $w0, 0xF00($at)\n" + "lui $at, 0x8000\n" + "addu $at, $at, $a0\n" + "addiu $at, $at, -21504 # 0xAC00\n" + "st.b $w0, -51($at) # 0xFFCD\n"; + DriverStr(expected, "StoreQToOffset"); +} + +#undef __ +} // namespace art diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc index d4642607ad..6ee2a5cb79 100644 --- a/compiler/utils/mips/assembler_mips32r6_test.cc +++ b/compiler/utils/mips/assembler_mips32r6_test.cc @@ -627,6 +627,124 @@ TEST_F(AssemblerMIPS32r6Test, LoadDFromOffset) { DriverStr(expected, "LoadDFromOffset"); } +TEST_F(AssemblerMIPS32r6Test, LoadQFromOffset) { + __ LoadQFromOffset(mips::F0, mips::A0, 0); + __ LoadQFromOffset(mips::F0, mips::A0, 1); + __ LoadQFromOffset(mips::F0, mips::A0, 2); + __ LoadQFromOffset(mips::F0, mips::A0, 4); + __ LoadQFromOffset(mips::F0, mips::A0, 8); + __ LoadQFromOffset(mips::F0, mips::A0, 511); + __ LoadQFromOffset(mips::F0, mips::A0, 512); + __ LoadQFromOffset(mips::F0, mips::A0, 513); + __ LoadQFromOffset(mips::F0, mips::A0, 514); + __ LoadQFromOffset(mips::F0, mips::A0, 516); + __ LoadQFromOffset(mips::F0, mips::A0, 1022); + __ LoadQFromOffset(mips::F0, mips::A0, 1024); + __ LoadQFromOffset(mips::F0, mips::A0, 1025); + __ LoadQFromOffset(mips::F0, mips::A0, 1026); + __ LoadQFromOffset(mips::F0, mips::A0, 1028); + __ LoadQFromOffset(mips::F0, mips::A0, 2044); + __ LoadQFromOffset(mips::F0, mips::A0, 2048); + __ LoadQFromOffset(mips::F0, mips::A0, 2049); + __ LoadQFromOffset(mips::F0, mips::A0, 2050); + __ LoadQFromOffset(mips::F0, mips::A0, 2052); + __ LoadQFromOffset(mips::F0, mips::A0, 4088); + __ LoadQFromOffset(mips::F0, mips::A0, 4096); + __ LoadQFromOffset(mips::F0, mips::A0, 4097); + __ LoadQFromOffset(mips::F0, mips::A0, 4098); + __ LoadQFromOffset(mips::F0, mips::A0, 4100); + __ LoadQFromOffset(mips::F0, mips::A0, 4104); + __ LoadQFromOffset(mips::F0, mips::A0, 0x7FFC); + __ LoadQFromOffset(mips::F0, mips::A0, 0x8000); + __ LoadQFromOffset(mips::F0, mips::A0, 0x10000); + __ LoadQFromOffset(mips::F0, mips::A0, 0x12345678); + __ LoadQFromOffset(mips::F0, mips::A0, 0x12350078); + __ LoadQFromOffset(mips::F0, mips::A0, -256); + __ LoadQFromOffset(mips::F0, mips::A0, -511); + __ LoadQFromOffset(mips::F0, mips::A0, -513); + __ LoadQFromOffset(mips::F0, mips::A0, -1022); + __ LoadQFromOffset(mips::F0, mips::A0, -1026); + __ LoadQFromOffset(mips::F0, mips::A0, -2044); + __ LoadQFromOffset(mips::F0, mips::A0, -2052); + __ LoadQFromOffset(mips::F0, mips::A0, -4096); + __ LoadQFromOffset(mips::F0, mips::A0, -4104); + __ LoadQFromOffset(mips::F0, mips::A0, -32768); + __ LoadQFromOffset(mips::F0, mips::A0, 0xABCDEF00); + __ LoadQFromOffset(mips::F0, mips::A0, 0x7FFFABCD); + + const char* expected = + "ld.d $w0, 0($a0)\n" + "ld.b $w0, 1($a0)\n" + "ld.h $w0, 2($a0)\n" + "ld.w $w0, 4($a0)\n" + "ld.d $w0, 8($a0)\n" + "ld.b $w0, 511($a0)\n" + "ld.d $w0, 512($a0)\n" + "addiu $at, $a0, 513\n" + "ld.b $w0, 0($at)\n" + "ld.h $w0, 514($a0)\n" + "ld.w $w0, 516($a0)\n" + "ld.h $w0, 1022($a0)\n" + "ld.d $w0, 1024($a0)\n" + "addiu $at, $a0, 1025\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 1026\n" + "ld.h $w0, 0($at)\n" + "ld.w $w0, 1028($a0)\n" + "ld.w $w0, 2044($a0)\n" + "ld.d $w0, 2048($a0)\n" + "addiu $at, $a0, 2049\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 2050\n" + "ld.h $w0, 0($at)\n" + "addiu $at, $a0, 2052\n" + "ld.w $w0, 0($at)\n" + "ld.d $w0, 4088($a0)\n" + "addiu $at, $a0, 4096\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, 4097\n" + "ld.b $w0, 0($at)\n" + "addiu $at, $a0, 4098\n" + "ld.h $w0, 0($at)\n" + "addiu $at, $a0, 4100\n" + "ld.w $w0, 0($at)\n" + "addiu $at, $a0, 4104\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, 0x7FFC\n" + "ld.w $w0, 0($at)\n" + "addiu $at, $a0, 0x7FF8\n" + "ld.d $w0, 8($at)\n" + "aui $at, $a0, 0x1\n" + "ld.d $w0, 0($at)\n" + "aui $at, $a0, 0x1234\n" + "addiu $at, $at, 0x6000\n" + "ld.d $w0, -2440($at) # 0xF678\n" + "aui $at, $a0, 0x1235\n" + "ld.d $w0, 0x78($at)\n" + "ld.d $w0, -256($a0)\n" + "ld.b $w0, -511($a0)\n" + "addiu $at, $a0, -513\n" + "ld.b $w0, 0($at)\n" + "ld.h $w0, -1022($a0)\n" + "addiu $at, $a0, -1026\n" + "ld.h $w0, 0($at)\n" + "ld.w $w0, -2044($a0)\n" + "addiu $at, $a0, -2052\n" + "ld.w $w0, 0($at)\n" + "ld.d $w0, -4096($a0)\n" + "addiu $at, $a0, -4104\n" + "ld.d $w0, 0($at)\n" + "addiu $at, $a0, -32768\n" + "ld.d $w0, 0($at)\n" + "aui $at, $a0, 0xABCE\n" + "addiu $at, $at, -8192 # 0xE000\n" + "ld.d $w0, 0xF00($at)\n" + "aui $at, $a0, 0x8000\n" + "addiu $at, $at, -21504 # 0xAC00\n" + "ld.b $w0, -51($at) # 0xFFCD\n"; + DriverStr(expected, "LoadQFromOffset"); +} + TEST_F(AssemblerMIPS32r6Test, StoreDToOffset) { __ StoreDToOffset(mips::F0, mips::A0, -0x8000); __ StoreDToOffset(mips::F0, mips::A0, +0); @@ -711,6 +829,124 @@ TEST_F(AssemblerMIPS32r6Test, StoreDToOffset) { DriverStr(expected, "StoreDToOffset"); } +TEST_F(AssemblerMIPS32r6Test, StoreQToOffset) { + __ StoreQToOffset(mips::F0, mips::A0, 0); + __ StoreQToOffset(mips::F0, mips::A0, 1); + __ StoreQToOffset(mips::F0, mips::A0, 2); + __ StoreQToOffset(mips::F0, mips::A0, 4); + __ StoreQToOffset(mips::F0, mips::A0, 8); + __ StoreQToOffset(mips::F0, mips::A0, 511); + __ StoreQToOffset(mips::F0, mips::A0, 512); + __ StoreQToOffset(mips::F0, mips::A0, 513); + __ StoreQToOffset(mips::F0, mips::A0, 514); + __ StoreQToOffset(mips::F0, mips::A0, 516); + __ StoreQToOffset(mips::F0, mips::A0, 1022); + __ StoreQToOffset(mips::F0, mips::A0, 1024); + __ StoreQToOffset(mips::F0, mips::A0, 1025); + __ StoreQToOffset(mips::F0, mips::A0, 1026); + __ StoreQToOffset(mips::F0, mips::A0, 1028); + __ StoreQToOffset(mips::F0, mips::A0, 2044); + __ StoreQToOffset(mips::F0, mips::A0, 2048); + __ StoreQToOffset(mips::F0, mips::A0, 2049); + __ StoreQToOffset(mips::F0, mips::A0, 2050); + __ StoreQToOffset(mips::F0, mips::A0, 2052); + __ StoreQToOffset(mips::F0, mips::A0, 4088); + __ StoreQToOffset(mips::F0, mips::A0, 4096); + __ StoreQToOffset(mips::F0, mips::A0, 4097); + __ StoreQToOffset(mips::F0, mips::A0, 4098); + __ StoreQToOffset(mips::F0, mips::A0, 4100); + __ StoreQToOffset(mips::F0, mips::A0, 4104); + __ StoreQToOffset(mips::F0, mips::A0, 0x7FFC); + __ StoreQToOffset(mips::F0, mips::A0, 0x8000); + __ StoreQToOffset(mips::F0, mips::A0, 0x10000); + __ StoreQToOffset(mips::F0, mips::A0, 0x12345678); + __ StoreQToOffset(mips::F0, mips::A0, 0x12350078); + __ StoreQToOffset(mips::F0, mips::A0, -256); + __ StoreQToOffset(mips::F0, mips::A0, -511); + __ StoreQToOffset(mips::F0, mips::A0, -513); + __ StoreQToOffset(mips::F0, mips::A0, -1022); + __ StoreQToOffset(mips::F0, mips::A0, -1026); + __ StoreQToOffset(mips::F0, mips::A0, -2044); + __ StoreQToOffset(mips::F0, mips::A0, -2052); + __ StoreQToOffset(mips::F0, mips::A0, -4096); + __ StoreQToOffset(mips::F0, mips::A0, -4104); + __ StoreQToOffset(mips::F0, mips::A0, -32768); + __ StoreQToOffset(mips::F0, mips::A0, 0xABCDEF00); + __ StoreQToOffset(mips::F0, mips::A0, 0x7FFFABCD); + + const char* expected = + "st.d $w0, 0($a0)\n" + "st.b $w0, 1($a0)\n" + "st.h $w0, 2($a0)\n" + "st.w $w0, 4($a0)\n" + "st.d $w0, 8($a0)\n" + "st.b $w0, 511($a0)\n" + "st.d $w0, 512($a0)\n" + "addiu $at, $a0, 513\n" + "st.b $w0, 0($at)\n" + "st.h $w0, 514($a0)\n" + "st.w $w0, 516($a0)\n" + "st.h $w0, 1022($a0)\n" + "st.d $w0, 1024($a0)\n" + "addiu $at, $a0, 1025\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 1026\n" + "st.h $w0, 0($at)\n" + "st.w $w0, 1028($a0)\n" + "st.w $w0, 2044($a0)\n" + "st.d $w0, 2048($a0)\n" + "addiu $at, $a0, 2049\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 2050\n" + "st.h $w0, 0($at)\n" + "addiu $at, $a0, 2052\n" + "st.w $w0, 0($at)\n" + "st.d $w0, 4088($a0)\n" + "addiu $at, $a0, 4096\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, 4097\n" + "st.b $w0, 0($at)\n" + "addiu $at, $a0, 4098\n" + "st.h $w0, 0($at)\n" + "addiu $at, $a0, 4100\n" + "st.w $w0, 0($at)\n" + "addiu $at, $a0, 4104\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, 0x7FFC\n" + "st.w $w0, 0($at)\n" + "addiu $at, $a0, 0x7FF8\n" + "st.d $w0, 8($at)\n" + "aui $at, $a0, 0x1\n" + "st.d $w0, 0($at)\n" + "aui $at, $a0, 0x1234\n" + "addiu $at, $at, 0x6000\n" + "st.d $w0, -2440($at) # 0xF678\n" + "aui $at, $a0, 0x1235\n" + "st.d $w0, 0x78($at)\n" + "st.d $w0, -256($a0)\n" + "st.b $w0, -511($a0)\n" + "addiu $at, $a0, -513\n" + "st.b $w0, 0($at)\n" + "st.h $w0, -1022($a0)\n" + "addiu $at, $a0, -1026\n" + "st.h $w0, 0($at)\n" + "st.w $w0, -2044($a0)\n" + "addiu $at, $a0, -2052\n" + "st.w $w0, 0($at)\n" + "st.d $w0, -4096($a0)\n" + "addiu $at, $a0, -4104\n" + "st.d $w0, 0($at)\n" + "addiu $at, $a0, -32768\n" + "st.d $w0, 0($at)\n" + "aui $at, $a0, 0xABCE\n" + "addiu $at, $at, -8192 # 0xE000\n" + "st.d $w0, 0xF00($at)\n" + "aui $at, $a0, 0x8000\n" + "addiu $at, $at, -21504 # 0xAC00\n" + "st.b $w0, -51($at) # 0xFFCD\n"; + DriverStr(expected, "StoreQToOffset"); +} + TEST_F(AssemblerMIPS32r6Test, LoadFarthestNearLabelAddress) { mips::MipsLabel label; __ LoadLabelAddress(mips::V0, mips::ZERO, &label); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 92d60b252b..ca0bae1c8e 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -925,8 +925,6 @@ class Dex2Oat FINAL { break; } - compiler_options_->verbose_methods_ = verbose_methods_.empty() ? nullptr : &verbose_methods_; - if (!IsBootImage() && multi_image_) { Usage("--multi-image can only be used when creating boot images"); } @@ -1262,11 +1260,6 @@ class Dex2Oat FINAL { app_image_file_name_ = option.substr(strlen("--app-image-file=")).data(); } else if (option.starts_with("--app-image-fd=")) { ParseUintOption(option, "--app-image-fd", &app_image_fd_, Usage); - } else if (option.starts_with("--verbose-methods=")) { - // TODO: rather than switch off compiler logging, make all VLOG(compiler) messages - // conditional on having verbost methods. - gLogVerbosity.compiler = false; - Split(option.substr(strlen("--verbose-methods=")).ToString(), ',', &verbose_methods_); } else if (option == "--multi-image") { multi_image_ = true; } else if (option.starts_with("--no-inline-from=")) { @@ -2804,7 +2797,6 @@ class Dex2Oat FINAL { std::vector<const DexFile*> no_inline_from_dex_files_; - std::vector<std::string> verbose_methods_; bool dump_stats_; bool dump_passes_; bool dump_timing_; diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc index df0169f7d0..8437ea5dc0 100644 --- a/dexdump/dexdump.cc +++ b/dexdump/dexdump.cc @@ -1582,31 +1582,53 @@ static void dumpClass(const DexFile* pDexFile, int idx, char** pLastPackage) { static void dumpMethodHandle(const DexFile* pDexFile, u4 idx) { const DexFile::MethodHandleItem& mh = pDexFile->GetMethodHandle(idx); + const char* type = nullptr; + bool is_instance = false; bool is_invoke = false; - const char* type; switch (static_cast<DexFile::MethodHandleType>(mh.method_handle_type_)) { case DexFile::MethodHandleType::kStaticPut: type = "put-static"; + is_instance = false; + is_invoke = false; break; case DexFile::MethodHandleType::kStaticGet: type = "get-static"; + is_instance = false; + is_invoke = false; break; case DexFile::MethodHandleType::kInstancePut: type = "put-instance"; + is_instance = true; + is_invoke = false; break; case DexFile::MethodHandleType::kInstanceGet: type = "get-instance"; + is_instance = true; + is_invoke = false; break; case DexFile::MethodHandleType::kInvokeStatic: type = "invoke-static"; + is_instance = false; is_invoke = true; break; case DexFile::MethodHandleType::kInvokeInstance: type = "invoke-instance"; + is_instance = true; is_invoke = true; break; case DexFile::MethodHandleType::kInvokeConstructor: type = "invoke-constructor"; + is_instance = true; + is_invoke = true; + break; + case DexFile::MethodHandleType::kInvokeDirect: + type = "invoke-direct"; + is_instance = true; + is_invoke = true; + break; + case DexFile::MethodHandleType::kInvokeInterface: + type = "invoke-interface"; + is_instance = true; is_invoke = true; break; } @@ -1614,16 +1636,26 @@ static void dumpMethodHandle(const DexFile* pDexFile, u4 idx) { const char* declaring_class; const char* member; std::string member_type; - if (is_invoke) { - const DexFile::MethodId& method_id = pDexFile->GetMethodId(mh.field_or_method_idx_); - declaring_class = pDexFile->GetMethodDeclaringClassDescriptor(method_id); - member = pDexFile->GetMethodName(method_id); - member_type = pDexFile->GetMethodSignature(method_id).ToString(); + if (type != nullptr) { + if (is_invoke) { + const DexFile::MethodId& method_id = pDexFile->GetMethodId(mh.field_or_method_idx_); + declaring_class = pDexFile->GetMethodDeclaringClassDescriptor(method_id); + member = pDexFile->GetMethodName(method_id); + member_type = pDexFile->GetMethodSignature(method_id).ToString(); + } else { + const DexFile::FieldId& field_id = pDexFile->GetFieldId(mh.field_or_method_idx_); + declaring_class = pDexFile->GetFieldDeclaringClassDescriptor(field_id); + member = pDexFile->GetFieldName(field_id); + member_type = pDexFile->GetFieldTypeDescriptor(field_id); + } + if (is_instance) { + member_type = android::base::StringPrintf("(%s%s", declaring_class, member_type.c_str() + 1); + } } else { - const DexFile::FieldId& field_id = pDexFile->GetFieldId(mh.field_or_method_idx_); - declaring_class = pDexFile->GetFieldDeclaringClassDescriptor(field_id); - member = pDexFile->GetFieldName(field_id); - member_type = pDexFile->GetFieldTypeDescriptor(field_id); + type = "?"; + declaring_class = "?"; + member = "?"; + member_type = "?"; } if (gOptions.outputFormat == OUTPUT_PLAIN) { @@ -1661,12 +1693,12 @@ static void dumpCallSite(const DexFile* pDexFile, u4 idx) { it.Next(); if (gOptions.outputFormat == OUTPUT_PLAIN) { - fprintf(gOutFile, "Call site #%u:\n", idx); + fprintf(gOutFile, "Call site #%u: // offset %u\n", idx, call_site_id.data_off_); fprintf(gOutFile, " link_argument[0] : %u (MethodHandle)\n", method_handle_idx); fprintf(gOutFile, " link_argument[1] : %s (String)\n", method_name); fprintf(gOutFile, " link_argument[2] : %s (MethodType)\n", method_type.c_str()); } else { - fprintf(gOutFile, "<call_site index=\"%u\">\n", idx); + fprintf(gOutFile, "<call_site index=\"%u\" offset=\"%u\">\n", idx, call_site_id.data_off_); fprintf(gOutFile, "<link_argument index=\"0\" type=\"MethodHandle\" value=\"%u\"/>\n", method_handle_idx); diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc index a200d8d9c7..5913832f96 100644 --- a/dexlayout/dex_ir.cc +++ b/dexlayout/dex_ir.cc @@ -793,8 +793,10 @@ void Collections::CreateMethodHandleItem(const DexFile& dex_file, uint32_t i) { static_cast<DexFile::MethodHandleType>(disk_method_handle.method_handle_type_); bool is_invoke = type == DexFile::MethodHandleType::kInvokeStatic || type == DexFile::MethodHandleType::kInvokeInstance || - type == DexFile::MethodHandleType::kInvokeConstructor; - static_assert(DexFile::MethodHandleType::kLast == DexFile::MethodHandleType::kInvokeConstructor, + type == DexFile::MethodHandleType::kInvokeConstructor || + type == DexFile::MethodHandleType::kInvokeDirect || + type == DexFile::MethodHandleType::kInvokeInterface; + static_assert(DexFile::MethodHandleType::kLast == DexFile::MethodHandleType::kInvokeInterface, "Unexpected method handle types."); IndexedItem* field_or_method_id; if (is_invoke) { diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h index 531bc98a0c..ed011d6771 100644 --- a/dexlayout/dexlayout.h +++ b/dexlayout/dexlayout.h @@ -58,7 +58,8 @@ class Options { bool show_section_headers_ = false; bool show_section_statistics_ = false; bool verbose_ = false; - bool verify_output_ = false; + // TODO: Set verify_output_ back to false by default. Was set to true for debugging b/62840842. + bool verify_output_ = true; bool visualize_pattern_ = false; OutputFormat output_format_ = kOutputPlain; const char* output_dex_directory_ = nullptr; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index d8bafc011a..066c66ac93 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -3466,7 +3466,7 @@ struct OatdumpArgs : public CmdlineArgs { " Example: --image=/system/framework/boot.art\n" "\n" " --app-image=<file.art>: specifies an input app image. Must also have a specified\n" - " boot image and app oat file.\n" + " boot image (with --image) and app oat file (with --app-oat).\n" " Example: --app-image=app.art\n" "\n" " --app-oat=<file.odex>: specifies an input app oat.\n" diff --git a/runtime/Android.bp b/runtime/Android.bp index 46307ddde8..0dfc60d88a 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -461,6 +461,7 @@ gensrcs { "object_callbacks.h", "process_state.h", "stack.h", + "suspend_reason.h", "thread.h", "thread_state.h", "ti/agent.h", diff --git a/runtime/arch/arm/context_arm.h b/runtime/arch/arm/context_arm.h index 2623ee9315..fa9aa46d4d 100644 --- a/runtime/arch/arm/context_arm.h +++ b/runtime/arch/arm/context_arm.h @@ -25,7 +25,7 @@ namespace art { namespace arm { -class ArmContext : public Context { +class ArmContext FINAL : public Context { public: ArmContext() { Reset(); diff --git a/runtime/arch/arm64/context_arm64.h b/runtime/arch/arm64/context_arm64.h index 105e78461d..36aded07c4 100644 --- a/runtime/arch/arm64/context_arm64.h +++ b/runtime/arch/arm64/context_arm64.h @@ -25,7 +25,7 @@ namespace art { namespace arm64 { -class Arm64Context : public Context { +class Arm64Context FINAL : public Context { public: Arm64Context() { Reset(); diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index ee91277417..138dbf9495 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -2669,19 +2669,19 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, w29, x29 RESTORE_TWO_REGS x14, x15, 112 RESTORE_TWO_REGS x18, x19, 128 // Skip x16, x17, i.e. IP0, IP1. RESTORE_REG xLR, 144 // Restore return address. - // Save all potentially live caller-save floating-point registers. - stp d0, d1, [sp, #160] - stp d2, d3, [sp, #176] - stp d4, d5, [sp, #192] - stp d6, d7, [sp, #208] - stp d16, d17, [sp, #224] - stp d18, d19, [sp, #240] - stp d20, d21, [sp, #256] - stp d22, d23, [sp, #272] - stp d24, d25, [sp, #288] - stp d26, d27, [sp, #304] - stp d28, d29, [sp, #320] - stp d30, d31, [sp, #336] + // Restore caller-save floating-point registers. + ldp d0, d1, [sp, #160] + ldp d2, d3, [sp, #176] + ldp d4, d5, [sp, #192] + ldp d6, d7, [sp, #208] + ldp d16, d17, [sp, #224] + ldp d18, d19, [sp, #240] + ldp d20, d21, [sp, #256] + ldp d22, d23, [sp, #272] + ldp d24, d25, [sp, #288] + ldp d26, d27, [sp, #304] + ldp d28, d29, [sp, #320] + ldp d30, d31, [sp, #336] ldr x0, [lr, #\ldr_offset] // Load the instruction. adr xIP1, .Lmark_introspection_return_switch diff --git a/runtime/arch/x86/context_x86.h b/runtime/arch/x86/context_x86.h index f482d9ffcb..303dfe361c 100644 --- a/runtime/arch/x86/context_x86.h +++ b/runtime/arch/x86/context_x86.h @@ -25,7 +25,7 @@ namespace art { namespace x86 { -class X86Context : public Context { +class X86Context FINAL : public Context { public: X86Context() { Reset(); diff --git a/runtime/arch/x86_64/context_x86_64.h b/runtime/arch/x86_64/context_x86_64.h index 46f2b63848..f8e2845983 100644 --- a/runtime/arch/x86_64/context_x86_64.h +++ b/runtime/arch/x86_64/context_x86_64.h @@ -25,7 +25,7 @@ namespace art { namespace x86_64 { -class X86_64Context : public Context { +class X86_64Context FINAL : public Context { public: X86_64Context() { Reset(); diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 155498639e..3e7ed9799d 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -61,6 +61,19 @@ DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex, "Wrong runtime-method dex method index"); +ArtMethod* ArtMethod::GetCanonicalMethod(PointerSize pointer_size) { + if (LIKELY(!IsDefault())) { + return this; + } else { + mirror::Class* declaring_class = GetDeclaringClass(); + ArtMethod* ret = declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(), + GetDexMethodIndex(), + pointer_size); + DCHECK(ret != nullptr); + return ret; + } +} + ArtMethod* ArtMethod::GetNonObsoleteMethod() { DCHECK_EQ(kRuntimePointerSize, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); if (LIKELY(!IsObsolete())) { diff --git a/runtime/art_method.h b/runtime/art_method.h index 96306af177..4b3e8efdad 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -483,6 +483,12 @@ class ArtMethod FINAL { } } + // Takes a method and returns a 'canonical' one if the method is default (and therefore + // potentially copied from some other class). For example, this ensures that the debugger does not + // get confused as to which method we are in. + ArtMethod* GetCanonicalMethod(PointerSize pointer_size = kRuntimePointerSize) + REQUIRES_SHARED(Locks::mutator_lock_); + ArtMethod* GetSingleImplementation(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 928645ac0f..a19085f5b5 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -55,6 +55,7 @@ #include "gc_root-inl.h" #include "gc/accounting/card_table-inl.h" #include "gc/accounting/heap_bitmap-inl.h" +#include "gc/accounting/space_bitmap-inl.h" #include "gc/heap.h" #include "gc/scoped_gc_critical_section.h" #include "gc/space/image_space.h" @@ -88,6 +89,7 @@ #include "mirror/method_handles_lookup.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" +#include "mirror/object-refvisitor-inl.h" #include "mirror/proxy.h" #include "mirror/reference-inl.h" #include "mirror/stack_trace_element.h" @@ -1193,6 +1195,63 @@ class VerifyDeclaringClassVisitor : public ArtMethodVisitor { gc::accounting::HeapBitmap* const live_bitmap_; }; +class FixupInternVisitor { + public: + ALWAYS_INLINE ObjPtr<mirror::Object> TryInsertIntern(mirror::Object* obj) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (obj != nullptr && obj->IsString()) { + const auto intern = Runtime::Current()->GetInternTable()->InternStrong(obj->AsString()); + return intern; + } + return obj; + } + + ALWAYS_INLINE void VisitRootIfNonNull( + mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!root->IsNull()) { + VisitRoot(root); + } + } + + ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + root->Assign(TryInsertIntern(root->AsMirrorPtr())); + } + + // Visit Class Fields + ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj, + MemberOffset offset, + bool is_static ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(Locks::mutator_lock_) { + // There could be overlap between ranges, we must avoid visiting the same reference twice. + // Avoid the class field since we already fixed it up in FixupClassVisitor. + if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) { + // Updating images, don't do a read barrier. + // Only string fields are fixed, don't do a verify. + mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( + offset); + obj->SetFieldObject<false, false>(offset, TryInsertIntern(ref)); + } + } + + void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, + ObjPtr<mirror::Reference> ref) const + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { + this->operator()(ref, mirror::Reference::ReferentOffset(), false); + } + + void operator()(mirror::Object* obj) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (obj->IsDexCache()) { + obj->VisitReferences<true, kVerifyNone, kWithoutReadBarrier>(*this, *this); + } else { + // Don't visit native roots for non-dex-cache + obj->VisitReferences<false, kVerifyNone, kWithoutReadBarrier>(*this, *this); + } + } +}; + // Copies data from one array to another array at the same position // if pred returns false. If there is a page of continuous data in // the src array for which pred consistently returns true then @@ -1285,6 +1344,7 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( return false; } } + // Only add the classes to the class loader after the points where we can return false. for (size_t i = 0; i < num_dex_caches; i++) { ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i); @@ -1448,6 +1508,21 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( } } } + { + // Fixup all the literal strings happens at app images which are supposed to be interned. + ScopedTrace timing("Fixup String Intern in image and dex_cache"); + const auto& image_header = space->GetImageHeader(); + const auto bitmap = space->GetMarkBitmap(); // bitmap of objects + const uint8_t* target_base = space->GetMemMap()->Begin(); + const ImageSection& objects_section = + image_header.GetImageSection(ImageHeader::kSectionObjects); + + uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); + uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + + FixupInternVisitor fixup_intern_visitor; + bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_intern_visitor); + } if (*out_forward_dex_cache_array) { ScopedTrace timing("Fixup ArtMethod dex cache arrays"); FixupArtMethodArrayVisitor visitor(header); @@ -2410,74 +2485,121 @@ ClassPathEntry FindInClassPath(const char* descriptor, return ClassPathEntry(nullptr, nullptr); } +// 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) +static bool IsPathOrDexClassLoader(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_PathClassLoader)) || + (class_loader_class == + soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader)); +} + +static bool IsDelegateLastClassLoader(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_DelegateLastClassLoader); +} + bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Thread* self, const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader, ObjPtr<mirror::Class>* result) { - // Termination case: boot class-loader. + // Termination case: boot class loader. if (IsBootClassLoader(soa, class_loader.Get())) { - // The boot class loader, search the boot class path. - ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); - if (pair.second != nullptr) { - ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, nullptr); - if (klass != nullptr) { - *result = EnsureResolved(self, descriptor, klass); - } else { - *result = DefineClass(self, - descriptor, - hash, - ScopedNullHandle<mirror::ClassLoader>(), - *pair.first, - *pair.second); - } - if (*result == nullptr) { - CHECK(self->IsExceptionPending()) << descriptor; - self->ClearException(); - } - } else { - *result = nullptr; - } + *result = FindClassInBootClassLoaderClassPath(self, descriptor, hash); return true; } - // Unsupported class-loader? - if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader) != - class_loader->GetClass()) { - // PathClassLoader is the most common case, so it's the one we check first. For secondary dex - // files, we also check DexClassLoader here. - if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader) != - class_loader->GetClass()) { - *result = nullptr; - return false; + if (IsPathOrDexClassLoader(soa, class_loader)) { + // For regular path or dex class loader the search order is: + // - parent + // - class loader dex files + + // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); + if (!FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result)) { + return false; // One of the parents is not supported. + } + if (*result != nullptr) { + return true; // Found the class up the chain. } + + // Search the current class loader classpath. + *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader); + return true; } - // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). - StackHandleScope<4> hs(self); - Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); - bool recursive_result = FindClassInBaseDexClassLoader(soa, - self, - descriptor, - hash, - h_parent, - result); + if (IsDelegateLastClassLoader(soa, class_loader)) { + // For delegate last, the search order is: + // - boot class path + // - class loader dex files + // - parent + *result = FindClassInBootClassLoaderClassPath(self, descriptor, hash); + if (*result != nullptr) { + return true; // The class is part of the boot class path. + } - if (!recursive_result) { - // Something wrong up the chain. - return false; + *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader); + if (*result != nullptr) { + return true; // Found the class in the current class loader + } + + // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); + return FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result); } - if (*result != nullptr) { - // Found the class up the chain. - return true; + // Unsupported class loader. + *result = nullptr; + return false; +} + +// Finds the class in the boot class loader. +// If the class is found the method returns the resolved class. Otherwise it returns null. +ObjPtr<mirror::Class> ClassLinker::FindClassInBootClassLoaderClassPath(Thread* self, + const char* descriptor, + size_t hash) { + ObjPtr<mirror::Class> result = nullptr; + ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); + if (pair.second != nullptr) { + ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, nullptr); + if (klass != nullptr) { + result = EnsureResolved(self, descriptor, klass); + } else { + result = DefineClass(self, + descriptor, + hash, + ScopedNullHandle<mirror::ClassLoader>(), + *pair.first, + *pair.second); + } + if (result == nullptr) { + CHECK(self->IsExceptionPending()) << descriptor; + self->ClearException(); + } } + return result; +} - // Handle this step. - // Handle as if this is the child PathClassLoader. - // The class loader is a PathClassLoader which inherits from BaseDexClassLoader. - // We need to get the DexPathList and loop through it. +ObjPtr<mirror::Class> ClassLinker::FindClassInBaseDexClassLoaderClassPath( + ScopedObjectAccessAlreadyRunnable& soa, + const char* descriptor, + size_t hash, + Handle<mirror::ClassLoader> class_loader) { + CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader)) + << "Unexpected class loader for descriptor " << descriptor; + + Thread* self = soa.Self(); ArtField* const cookie_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); ArtField* const dex_file_field = @@ -2489,10 +2611,11 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl // DexPathList has an array dexElements of Elements[] which each contain a dex file. ObjPtr<mirror::Object> dex_elements_obj = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> - GetObject(dex_path_list); + GetObject(dex_path_list); // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look // at the mCookie which is a DexFile vector. if (dex_elements_obj != nullptr) { + StackHandleScope<1> hs(self); Handle<mirror::ObjectArray<mirror::Object>> dex_elements = hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()); for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { @@ -2518,19 +2641,18 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl OatDexFile::FindClassDef(*cp_dex_file, descriptor, hash); if (dex_class_def != nullptr) { ObjPtr<mirror::Class> klass = DefineClass(self, - descriptor, - hash, - class_loader, - *cp_dex_file, - *dex_class_def); + descriptor, + hash, + class_loader, + *cp_dex_file, + *dex_class_def); if (klass == nullptr) { CHECK(self->IsExceptionPending()) << descriptor; self->ClearException(); // TODO: Is it really right to break here, and not check the other dex files? - return true; + return nullptr; } - *result = klass; - return true; + return klass; } } } @@ -2538,9 +2660,7 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl } self->AssertNoPendingException(); } - - // Result is still null from the parent call, no need to set it again... - return true; + return nullptr; } mirror::Class* ClassLinker::FindClass(Thread* self, @@ -4064,7 +4184,10 @@ verifier::FailureKind ClassLinker::VerifyClass( while (old_status == mirror::Class::kStatusVerifying || old_status == mirror::Class::kStatusVerifyingAtRuntime) { lock.WaitIgnoringInterrupts(); - CHECK(klass->IsErroneous() || (klass->GetStatus() > old_status)) + // WaitIgnoringInterrupts can still receive an interrupt and return early, in this + // case we may see the same status again. b/62912904. This is why the check is + // greater or equal. + CHECK(klass->IsErroneous() || (klass->GetStatus() >= old_status)) << "Class '" << klass->PrettyClass() << "' performed an illegal verification state transition from " << old_status << " to " << klass->GetStatus(); @@ -8253,65 +8376,139 @@ mirror::MethodHandle* ClassLinker::ResolveMethodHandle(uint32_t method_handle_id const DexFile* const dex_file = referrer->GetDexFile(); const DexFile::MethodHandleItem& mh = dex_file->GetMethodHandle(method_handle_idx); - union { - ArtField* field; - ArtMethod* method; - uintptr_t field_or_method; - } target; - uint32_t num_params; - mirror::MethodHandle::Kind kind; + ArtField* target_field = nullptr; + ArtMethod* target_method = nullptr; + DexFile::MethodHandleType handle_type = static_cast<DexFile::MethodHandleType>(mh.method_handle_type_); switch (handle_type) { case DexFile::MethodHandleType::kStaticPut: { + target_field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */); + break; + } + case DexFile::MethodHandleType::kStaticGet: { + target_field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */); + break; + } + case DexFile::MethodHandleType::kInstancePut: { + target_field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */); + break; + } + case DexFile::MethodHandleType::kInstanceGet: { + target_field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */); + break; + } + case DexFile::MethodHandleType::kInvokeStatic: { + target_method = ResolveMethod<kNoICCECheckForCache>(self, + mh.field_or_method_idx_, + referrer, + InvokeType::kStatic); + break; + } + case DexFile::MethodHandleType::kInvokeInstance: { + target_method = ResolveMethod<kNoICCECheckForCache>(self, + mh.field_or_method_idx_, + referrer, + InvokeType::kVirtual); + break; + } + case DexFile::MethodHandleType::kInvokeConstructor: { + UNIMPLEMENTED(FATAL) << "Invoke constructor is implemented as a transform."; + break; + } + case DexFile::MethodHandleType::kInvokeDirect: { + target_method = ResolveMethod<kNoICCECheckForCache>(self, + mh.field_or_method_idx_, + referrer, + InvokeType::kDirect); + break; + } + case DexFile::MethodHandleType::kInvokeInterface: { + target_method = ResolveMethod<kNoICCECheckForCache>(self, + mh.field_or_method_idx_, + referrer, + InvokeType::kInterface); + break; + } + } + + if (target_field != nullptr) { + ObjPtr<mirror::Class> target_class = target_field->GetDeclaringClass(); + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); + if (!referring_class->CanAccessMember(target_class, target_field->GetAccessFlags())) { + ThrowIllegalAccessErrorField(referring_class, target_field); + return nullptr; + } + } else if (target_method != nullptr) { + ObjPtr<mirror::Class> target_class = target_method->GetDeclaringClass(); + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); + if (!referring_class->CanAccessMember(target_class, target_method->GetAccessFlags())) { + ThrowIllegalAccessErrorMethod(referring_class, target_method); + return nullptr; + } + } else { + // Common check for resolution failure. + DCHECK(Thread::Current()->IsExceptionPending()); + return nullptr; + } + + // Determine the kind and number of parameters after it's safe to + // follow the field or method pointer. + mirror::MethodHandle::Kind kind; + uint32_t num_params; + switch (handle_type) { + case DexFile::MethodHandleType::kStaticPut: { kind = mirror::MethodHandle::Kind::kStaticPut; - target.field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */); num_params = 1; break; } case DexFile::MethodHandleType::kStaticGet: { kind = mirror::MethodHandle::Kind::kStaticGet; - target.field = ResolveField(mh.field_or_method_idx_, referrer, true /* is_static */); num_params = 0; break; } case DexFile::MethodHandleType::kInstancePut: { kind = mirror::MethodHandle::Kind::kInstancePut; - target.field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */); num_params = 2; break; } case DexFile::MethodHandleType::kInstanceGet: { kind = mirror::MethodHandle::Kind::kInstanceGet; - target.field = ResolveField(mh.field_or_method_idx_, referrer, false /* is_static */); num_params = 1; break; } case DexFile::MethodHandleType::kInvokeStatic: { kind = mirror::MethodHandle::Kind::kInvokeStatic; - target.method = ResolveMethod<kNoICCECheckForCache>(self, - mh.field_or_method_idx_, - referrer, - InvokeType::kStatic); uint32_t shorty_length; - target.method->GetShorty(&shorty_length); - num_params = shorty_length - 1; // Remove 1 for return value. + target_method->GetShorty(&shorty_length); + num_params = shorty_length - 1; // Remove 1 for the return value. break; } case DexFile::MethodHandleType::kInvokeInstance: { kind = mirror::MethodHandle::Kind::kInvokeVirtual; - target.method = ResolveMethod<kNoICCECheckForCache>(self, - mh.field_or_method_idx_, - referrer, - InvokeType::kVirtual); uint32_t shorty_length; - target.method->GetShorty(&shorty_length); - num_params = shorty_length - 1; // Remove 1 for return value. + target_method->GetShorty(&shorty_length); + num_params = shorty_length; // Add 1 for the receiver, remove 1 for the return value. break; } case DexFile::MethodHandleType::kInvokeConstructor: { UNIMPLEMENTED(FATAL) << "Invoke constructor is implemented as a transform."; num_params = 0; + break; + } + case DexFile::MethodHandleType::kInvokeDirect: { + kind = mirror::MethodHandle::Kind::kInvokeDirect; + uint32_t shorty_length; + target_method->GetShorty(&shorty_length); + num_params = shorty_length; // Add 1 for the receiver, remove 1 for the return value. + break; + } + case DexFile::MethodHandleType::kInvokeInterface: { + kind = mirror::MethodHandle::Kind::kInvokeInterface; + uint32_t shorty_length; + target_method->GetShorty(&shorty_length); + num_params = shorty_length; // Add 1 for the receiver, remove 1 for the return value. + break; } } @@ -8328,44 +8525,51 @@ mirror::MethodHandle* ClassLinker::ResolveMethodHandle(uint32_t method_handle_id Handle<mirror::Class> return_type; switch (handle_type) { case DexFile::MethodHandleType::kStaticPut: { - method_params->Set(0, target.field->GetType<true>()); + method_params->Set(0, target_field->GetType<true>()); return_type = hs.NewHandle(FindPrimitiveClass('V')); break; } case DexFile::MethodHandleType::kStaticGet: { - return_type = hs.NewHandle(target.field->GetType<true>()); + return_type = hs.NewHandle(target_field->GetType<true>()); break; } case DexFile::MethodHandleType::kInstancePut: { - method_params->Set(0, target.field->GetDeclaringClass()); - method_params->Set(1, target.field->GetType<true>()); + method_params->Set(0, target_field->GetDeclaringClass()); + method_params->Set(1, target_field->GetType<true>()); return_type = hs.NewHandle(FindPrimitiveClass('V')); break; } case DexFile::MethodHandleType::kInstanceGet: { - method_params->Set(0, target.field->GetDeclaringClass()); - return_type = hs.NewHandle(target.field->GetType<true>()); + method_params->Set(0, target_field->GetDeclaringClass()); + return_type = hs.NewHandle(target_field->GetType<true>()); break; } - case DexFile::MethodHandleType::kInvokeStatic: - case DexFile::MethodHandleType::kInvokeInstance: { + case DexFile::MethodHandleType::kInvokeDirect: + case DexFile::MethodHandleType::kInvokeInstance: + case DexFile::MethodHandleType::kInvokeInterface: + case DexFile::MethodHandleType::kInvokeStatic: { // TODO(oth): This will not work for varargs methods as this // requires instantiating a Transformer. This resolution step // would be best done in managed code rather than in the run // time (b/35235705) Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache())); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader())); - DexFileParameterIterator it(*dex_file, target.method->GetPrototype()); - for (int32_t i = 0; it.HasNext(); i++, it.Next()) { + DexFileParameterIterator it(*dex_file, target_method->GetPrototype()); + int32_t index = 0; + if (handle_type != DexFile::MethodHandleType::kInvokeStatic) { + method_params->Set(index++, target_method->GetDeclaringClass()); + } + while (it.HasNext()) { const dex::TypeIndex type_idx = it.GetTypeIdx(); mirror::Class* klass = ResolveType(*dex_file, type_idx, dex_cache, class_loader); if (nullptr == klass) { DCHECK(self->IsExceptionPending()); return nullptr; } - method_params->Set(i, klass); + method_params->Set(index++, klass); + it.Next(); } - return_type = hs.NewHandle(target.method->GetReturnType(true)); + return_type = hs.NewHandle(target_method->GetReturnType(true)); break; } case DexFile::MethodHandleType::kInvokeConstructor: { @@ -8385,7 +8589,14 @@ mirror::MethodHandle* ClassLinker::ResolveMethodHandle(uint32_t method_handle_id DCHECK(self->IsExceptionPending()); return nullptr; } - return mirror::MethodHandleImpl::Create(self, target.field_or_method, kind, mt); + + uintptr_t target; + if (target_field != nullptr) { + target = reinterpret_cast<uintptr_t>(target_field); + } else { + target = reinterpret_cast<uintptr_t>(target_method); + } + return mirror::MethodHandleImpl::Create(self, target, kind, mt); } bool ClassLinker::IsQuickResolutionStub(const void* entry_point) const { @@ -8549,8 +8760,15 @@ const char* ClassLinker::GetClassRootDescriptor(ClassRoot class_root) { return descriptor; } -jobject ClassLinker::CreatePathClassLoader(Thread* self, - const std::vector<const DexFile*>& dex_files) { +jobject ClassLinker::CreateWellKnownClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files, + jclass loader_class, + jobject parent_loader) { + CHECK(self->GetJniEnv()->IsSameObject(loader_class, + WellKnownClasses::dalvik_system_PathClassLoader) || + self->GetJniEnv()->IsSameObject(loader_class, + WellKnownClasses::dalvik_system_DelegateLastClassLoader)); + // SOAAlreadyRunnable is protected, and we need something to add a global reference. // We could move the jobject to the callers, but all call-sites do this... ScopedObjectAccessUnchecked soa(self); @@ -8586,8 +8804,8 @@ jobject ClassLinker::CreatePathClassLoader(Thread* self, for (const DexFile* dex_file : dex_files) { StackHandleScope<4> hs2(self); - // CreatePathClassLoader is only used by gtests. Index 0 of h_long_array is supposed to be the - // oat file but we can leave it null. + // CreateWellKnownClassLoader is only used by gtests and compiler. + // Index 0 of h_long_array is supposed to be the oat file but we can leave it null. Handle<mirror::LongArray> h_long_array = hs2.NewHandle(mirror::LongArray::Alloc( self, kDexFileIndexStart + 1)); @@ -8622,36 +8840,44 @@ jobject ClassLinker::CreatePathClassLoader(Thread* self, // Set elements. dex_elements_field->SetObject<false>(h_dex_path_list.Get(), h_dex_elements.Get()); - // Create PathClassLoader. - Handle<mirror::Class> h_path_class_class = hs.NewHandle( - soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)); - Handle<mirror::Object> h_path_class_loader = hs.NewHandle( - h_path_class_class->AllocObject(self)); - DCHECK(h_path_class_loader != nullptr); + // Create the class loader.. + Handle<mirror::Class> h_loader_class = hs.NewHandle(soa.Decode<mirror::Class>(loader_class)); + Handle<mirror::Object> h_class_loader = hs.NewHandle(h_loader_class->AllocObject(self)); + DCHECK(h_class_loader != nullptr); // Set DexPathList. ArtField* path_list_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList); DCHECK(path_list_field != nullptr); - path_list_field->SetObject<false>(h_path_class_loader.Get(), h_dex_path_list.Get()); + path_list_field->SetObject<false>(h_class_loader.Get(), h_dex_path_list.Get()); // Make a pretend boot-classpath. // TODO: Should we scan the image? ArtField* const parent_field = mirror::Class::FindField(self, - h_path_class_loader->GetClass(), + h_class_loader->GetClass(), "parent", "Ljava/lang/ClassLoader;"); DCHECK(parent_field != nullptr); - ObjPtr<mirror::Object> boot_cl = - soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)->AllocObject(self); - parent_field->SetObject<false>(h_path_class_loader.Get(), boot_cl); + + ObjPtr<mirror::Object> parent = (parent_loader != nullptr) + ? soa.Decode<mirror::ClassLoader>(parent_loader) + : soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)->AllocObject(self); + parent_field->SetObject<false>(h_class_loader.Get(), parent); // Make it a global ref and return. ScopedLocalRef<jobject> local_ref( - soa.Env(), soa.Env()->AddLocalReference<jobject>(h_path_class_loader.Get())); + soa.Env(), soa.Env()->AddLocalReference<jobject>(h_class_loader.Get())); return soa.Env()->NewGlobalRef(local_ref.get()); } +jobject ClassLinker::CreatePathClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files) { + return CreateWellKnownClassLoader(self, + dex_files, + WellKnownClasses::dalvik_system_PathClassLoader, + nullptr); +} + void ClassLinker::DropFindArrayClassCache() { std::fill_n(find_array_class_cache_, kFindArrayCacheSize, GcRoot<mirror::Class>(nullptr)); find_array_class_cache_next_victim_ = 0; diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 1e8125eb05..de1fefd20e 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -553,8 +553,24 @@ class ClassLinker { REQUIRES(!Locks::classlinker_classes_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - // Creates a GlobalRef PathClassLoader that can be used to load classes from the given dex files. + // Creates a GlobalRef PathClassLoader or DelegateLastClassLoader (specified by loader_class) + // that can be used to load classes from the given dex files. The parent of the class loader + // will be set to `parent_loader`. If `parent_loader` is null the parent will be + // the boot class loader. + // If class_loader points to a different class than PathClassLoader or DelegateLastClassLoader + // this method will abort. // Note: the objects are not completely set up. Do not use this outside of tests and the compiler. + jobject CreateWellKnownClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files, + jclass loader_class, + jobject parent_loader) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + + // Calls CreateWellKnownClassLoader(self, + // dex_files, + // WellKnownClasses::dalvik_system_PathClassLoader, + // nullptr) jobject CreatePathClassLoader(Thread* self, const std::vector<const DexFile*>& dex_files) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); @@ -819,6 +835,27 @@ class ClassLinker { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); + // Finds the class in the classpath of the given class loader. It only searches the class loader + // dex files and does not recurse into its parent. + // The method checks that the provided class loader is either a PathClassLoader or a + // DexClassLoader. + // If the class is found the method returns the resolved class. Otherwise it returns null. + ObjPtr<mirror::Class> FindClassInBaseDexClassLoaderClassPath( + ScopedObjectAccessAlreadyRunnable& soa, + const char* descriptor, + size_t hash, + Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + + // Finds the class in the boot class loader. + // If the class is found the method returns the resolved class. Otherwise it returns null. + ObjPtr<mirror::Class> FindClassInBootClassLoaderClassPath(Thread* self, + const char* descriptor, + size_t hash) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + // Finds a class by its descriptor, returning NULL if it isn't wasn't loaded // by the given 'class_loader'. Uses the provided hash for the descriptor. mirror::Class* LookupClass(Thread* self, diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 684a261cca..03cc6c59c4 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -1533,4 +1533,110 @@ TEST_F(ClassLinkerMethodHandlesTest, TestResolveMethodTypes) { ASSERT_TRUE(method1_type.Get() != method2_type.Get()); } +// Verify that ClassLinker's CreateWellknownClassLoader works as expected +// by creating a chain of class loaders with various dex files. +TEST_F(ClassLinkerTest, CreateWellKnownClassLoader) { + // LoadDexIn*ClassLoader methods already assert that the parent loader is the expected one. + // No need to check again. + jobject class_loader_a = LoadDexInPathClassLoader("MyClass", nullptr); + jobject class_loader_b = LoadDexInDelegateLastClassLoader("Nested", class_loader_a); + jobject class_loader_c = LoadDexInPathClassLoader("MultiDex", class_loader_b); + LoadDexInDelegateLastClassLoader("Interfaces", class_loader_c); +} + +class ClassLinkerClassLoaderTest : public ClassLinkerTest { + protected: + // Verifies that the class identified by the given descriptor is loaded with + // the expected_class_loader_obj when search from class_loader_to_search_obj. + // When expected_class_loader_obj is null the check will be done against BootClassLoader. + void VerifyClassResolution(const std::string& descriptor, + jobject class_loader_to_search_obj, + jobject expected_class_loader_obj, + bool should_find = true) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<3> hs(self); + Handle<mirror::ClassLoader> class_loader_to_search( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_to_search_obj))); + + Handle<mirror::Class> klass = hs.NewHandle( + class_linker_->FindClass(soa.Self(), descriptor.c_str(), class_loader_to_search)); + + if (!should_find) { + if (self->IsExceptionPending()) { + self->ClearException(); + } + ASSERT_TRUE(klass == nullptr); + } else if (expected_class_loader_obj == nullptr) { + ASSERT_TRUE(ClassLinker::IsBootClassLoader(soa, klass->GetClassLoader())); + } else { + ASSERT_TRUE(klass != nullptr) << descriptor; + Handle<mirror::ClassLoader> expected_class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(expected_class_loader_obj))); + ASSERT_EQ(klass->GetClassLoader(), expected_class_loader.Get()); + } + } +}; + +TEST_F(ClassLinkerClassLoaderTest, CreatePathClassLoader) { + jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr); + VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a); + VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr); + VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false); +} + +TEST_F(ClassLinkerClassLoaderTest, CreateDelegateLastClassLoader) { + jobject class_loader_a = LoadDexInDelegateLastClassLoader("ForClassLoaderA", nullptr); + VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a); + VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr); + VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false); +} + +TEST_F(ClassLinkerClassLoaderTest, CreateClassLoaderChain) { + // The chain is + // ClassLoaderA (PathClassLoader, defines: A, AB, AC, AD) + // ^ + // | + // ClassLoaderB (DelegateLastClassLoader, defines: B, AB, BC, BD) + // ^ + // | + // ClassLoaderC (PathClassLoader, defines: C, AC, BC, CD) + // ^ + // | + // ClassLoaderD (DelegateLastClassLoader, defines: D, AD, BD, CD) + + jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr); + jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a); + jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b); + jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c); + + // Verify exclusive classes (present in only one class loader). + VerifyClassResolution("LDefinedInD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInC;", class_loader_d, class_loader_c); + VerifyClassResolution("LDefinedInB;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInA;", class_loader_d, class_loader_a); + + // Verify classes that are defined in multiple classloader. + + // Classes defined in B should be found in B even if they are defined in A or C because + // B is a DelegateLastClassLoader. + VerifyClassResolution("LDefinedInAB;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInABC;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInBC;", class_loader_d, class_loader_b); + + // Classes defined in D should be found in D even if they are defined in parent class loaders + // as well because D is a DelegateLastClassLoader. + VerifyClassResolution("LDefinedInAD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInBD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInCD;", class_loader_d, class_loader_d); + + + // Classes not defined in the DelegateLastClassLoaders (i.e. D or B) should be found + // in the top parent. + VerifyClassResolution("LDefinedInAC;", class_loader_d, class_loader_a); + + // Sanity check that we don't find an undefined class. + VerifyClassResolution("LNotDefined;", class_loader_d, nullptr, /*should_find*/ false); +} + } // namespace art diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 6441a44e6e..659c7e4950 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -680,19 +680,66 @@ jobject CommonRuntimeTestImpl::LoadMultiDex(const char* first_dex_name, } jobject CommonRuntimeTestImpl::LoadDex(const char* dex_name) { - std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(dex_name); + jobject class_loader = LoadDexInPathClassLoader(dex_name, nullptr); + Thread::Current()->SetClassLoaderOverride(class_loader); + return class_loader; +} + +jobject CommonRuntimeTestImpl::LoadDexInWellKnownClassLoader(const std::string& dex_name, + jclass loader_class, + jobject parent_loader) { + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(dex_name.c_str()); std::vector<const DexFile*> class_path; CHECK_NE(0U, dex_files.size()); for (auto& dex_file : dex_files) { class_path.push_back(dex_file.get()); loaded_dex_files_.push_back(std::move(dex_file)); } - Thread* self = Thread::Current(); - jobject class_loader = Runtime::Current()->GetClassLinker()->CreatePathClassLoader(self, - class_path); - self->SetClassLoaderOverride(class_loader); - return class_loader; + ScopedObjectAccess soa(self); + + jobject result = Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader( + self, + class_path, + loader_class, + parent_loader); + + { + // Verify we build the correct chain. + + ObjPtr<mirror::ClassLoader> actual_class_loader = soa.Decode<mirror::ClassLoader>(result); + // Verify that the result has the correct class. + CHECK_EQ(soa.Decode<mirror::Class>(loader_class), actual_class_loader->GetClass()); + // Verify that the parent is not null. The boot class loader will be set up as a + // proper object. + ObjPtr<mirror::ClassLoader> actual_parent(actual_class_loader->GetParent()); + CHECK(actual_parent != nullptr); + + if (parent_loader != nullptr) { + // We were given a parent. Verify that it's what we expect. + ObjPtr<mirror::ClassLoader> expected_parent = soa.Decode<mirror::ClassLoader>(parent_loader); + CHECK_EQ(expected_parent, actual_parent); + } else { + // No parent given. The parent must be the BootClassLoader. + CHECK(Runtime::Current()->GetClassLinker()->IsBootClassLoader(soa, actual_parent)); + } + } + + return result; +} + +jobject CommonRuntimeTestImpl::LoadDexInPathClassLoader(const std::string& dex_name, + jobject parent_loader) { + return LoadDexInWellKnownClassLoader(dex_name, + WellKnownClasses::dalvik_system_PathClassLoader, + parent_loader); +} + +jobject CommonRuntimeTestImpl::LoadDexInDelegateLastClassLoader(const std::string& dex_name, + jobject parent_loader) { + return LoadDexInWellKnownClassLoader(dex_name, + WellKnownClasses::dalvik_system_DelegateLastClassLoader, + parent_loader); } std::string CommonRuntimeTestImpl::GetCoreFileLocation(const char* suffix) { diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 3b3e6c5321..5893573bdd 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -134,10 +134,20 @@ class CommonRuntimeTestImpl { std::unique_ptr<const DexFile> OpenTestDexFile(const char* name); + // Loads the test dex file identified by the given dex_name into a PathClassLoader. + // Returns the created class loader. jobject LoadDex(const char* dex_name) REQUIRES_SHARED(Locks::mutator_lock_); + // Loads the test dex file identified by the given first_dex_name and second_dex_name + // into a PathClassLoader. Returns the created class loader. jobject LoadMultiDex(const char* first_dex_name, const char* second_dex_name) REQUIRES_SHARED(Locks::mutator_lock_); + jobject LoadDexInPathClassLoader(const std::string& dex_name, jobject parent_loader); + jobject LoadDexInDelegateLastClassLoader(const std::string& dex_name, jobject parent_loader); + jobject LoadDexInWellKnownClassLoader(const std::string& dex_name, + jclass loader_class, + jobject parent_loader); + std::string android_data_; std::string dalvik_cache_; diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 12bdb32fec..b59511214d 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -18,7 +18,9 @@ #include <sys/uio.h> +#include <memory> #include <set> +#include <vector> #include "android-base/stringprintf.h" @@ -26,6 +28,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/strlcpy.h" #include "base/time_utils.h" #include "class_linker.h" #include "class_linker-inl.h" @@ -77,25 +80,10 @@ static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { return alloc_record_count; } -// Takes a method and returns a 'canonical' one if the method is default (and therefore potentially -// copied from some other class). This ensures that the debugger does not get confused as to which -// method we are in. -static ArtMethod* GetCanonicalMethod(ArtMethod* m) - REQUIRES_SHARED(Locks::mutator_lock_) { - if (LIKELY(!m->IsDefault())) { - return m; - } else { - mirror::Class* declaring_class = m->GetDeclaringClass(); - return declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(), - m->GetDexMethodIndex(), - kRuntimePointerSize); - } -} - class Breakpoint : public ValueObject { public: Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind) - : method_(GetCanonicalMethod(method)), + : method_(method->GetCanonicalMethod(kRuntimePointerSize)), dex_pc_(dex_pc), deoptimization_kind_(deoptimization_kind) { CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing || @@ -125,7 +113,7 @@ class Breakpoint : public ValueObject { // Returns true if the method of this breakpoint and the passed in method should be considered the // same. That is, they are either the same method or they are copied from the same method. bool IsInMethod(ArtMethod* m) const REQUIRES_SHARED(Locks::mutator_lock_) { - return method_ == GetCanonicalMethod(m); + return method_ == m->GetCanonicalMethod(kRuntimePointerSize); } private: @@ -1367,7 +1355,8 @@ JDWP::FieldId Dbg::ToFieldId(const ArtField* f) { static JDWP::MethodId ToMethodId(ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_) { - return static_cast<JDWP::MethodId>(reinterpret_cast<uintptr_t>(GetCanonicalMethod(m))); + return static_cast<JDWP::MethodId>( + reinterpret_cast<uintptr_t>(m->GetCanonicalMethod(kRuntimePointerSize))); } static ArtField* FromFieldId(JDWP::FieldId fid) @@ -2460,7 +2449,7 @@ JDWP::JdwpError Dbg::SuspendThread(JDWP::ObjectId thread_id, bool request_suspen ThreadList* thread_list = Runtime::Current()->GetThreadList(); Thread* thread = thread_list->SuspendThreadByPeer(peer.get(), request_suspension, - /* debug_suspension */ true, + SuspendReason::kForDebugger, &timed_out); if (thread != nullptr) { return JDWP::ERR_NONE; @@ -2491,7 +2480,7 @@ void Dbg::ResumeThread(JDWP::ObjectId thread_id) { needs_resume = thread->GetDebugSuspendCount() > 0; } if (needs_resume) { - Runtime::Current()->GetThreadList()->Resume(thread, true); + Runtime::Current()->GetThreadList()->Resume(thread, SuspendReason::kForDebugger); } } @@ -2887,7 +2876,7 @@ static void SetEventLocation(JDWP::EventLocation* location, ArtMethod* m, uint32 if (m == nullptr) { memset(location, 0, sizeof(*location)); } else { - location->method = GetCanonicalMethod(m); + location->method = m->GetCanonicalMethod(kRuntimePointerSize); location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint32_t>(-1) : dex_pc; } } @@ -3708,7 +3697,7 @@ class ScopedDebuggerThreadSuspension { ThreadList* const thread_list = Runtime::Current()->GetThreadList(); suspended_thread = thread_list->SuspendThreadByPeer(thread_peer, /* request_suspension */ true, - /* debug_suspension */ true, + SuspendReason::kForDebugger, &timed_out); } if (suspended_thread == nullptr) { @@ -3732,7 +3721,7 @@ class ScopedDebuggerThreadSuspension { ~ScopedDebuggerThreadSuspension() { if (other_suspend_) { - Runtime::Current()->GetThreadList()->Resume(thread_, true); + Runtime::Current()->GetThreadList()->Resume(thread_, SuspendReason::kForDebugger); } } @@ -4054,7 +4043,7 @@ JDWP::JdwpError Dbg::PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thr thread_list->UndoDebuggerSuspensions(); } else { VLOG(jdwp) << " Resuming event thread only"; - thread_list->Resume(targetThread, true); + thread_list->Resume(targetThread, SuspendReason::kForDebugger); } return JDWP::ERR_NONE; @@ -4940,11 +4929,20 @@ class StringTable { StringTable() { } - void Add(const std::string& str) { - table_.insert(str); - } + void Add(const char* str, bool copy_string) { + if (UNLIKELY(copy_string)) { + // Check whether it's already there. + if (table_.find(str) != table_.end()) { + return; + } - void Add(const char* str) { + // Make a copy. + size_t str_len = strlen(str); + char* copy = new char[str_len + 1]; + strlcpy(copy, str, str_len + 1); + string_backup_.emplace_back(copy); + str = copy; + } table_.insert(str); } @@ -4961,17 +4959,23 @@ class StringTable { } void WriteTo(std::vector<uint8_t>& bytes) const { - for (const std::string& str : table_) { - const char* s = str.c_str(); - size_t s_len = CountModifiedUtf8Chars(s); + for (const char* str : table_) { + size_t s_len = CountModifiedUtf8Chars(str); std::unique_ptr<uint16_t[]> s_utf16(new uint16_t[s_len]); - ConvertModifiedUtf8ToUtf16(s_utf16.get(), s); + ConvertModifiedUtf8ToUtf16(s_utf16.get(), str); JDWP::AppendUtf16BE(bytes, s_utf16.get(), s_len); } } private: - std::set<std::string> table_; + struct ConstCharStarComparator { + bool operator()(const char *s1, const char *s2) const { + return strcmp(s1, s2) < 0; + } + }; + + std::set<const char*, ConstCharStarComparator> table_; + std::vector<std::unique_ptr<char[]>> string_backup_; DISALLOW_COPY_AND_ASSIGN(StringTable); }; @@ -5058,12 +5062,13 @@ jbyteArray Dbg::GetRecentAllocations() { count > 0 && it != end; count--, it++) { const gc::AllocRecord* record = &it->second; std::string temp; - class_names.Add(record->GetClassDescriptor(&temp)); + const char* class_descr = record->GetClassDescriptor(&temp); + class_names.Add(class_descr, !temp.empty()); for (size_t i = 0, depth = record->GetDepth(); i < depth; i++) { ArtMethod* m = record->StackElement(i).GetMethod(); - class_names.Add(m->GetDeclaringClassDescriptor()); - method_names.Add(m->GetName()); - filenames.Add(GetMethodSourceFile(m)); + class_names.Add(m->GetDeclaringClassDescriptor(), false); + method_names.Add(m->GetName(), false); + filenames.Add(GetMethodSourceFile(m), false); } } diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 81a39afbee..eb3b210cd1 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -272,7 +272,9 @@ class DexFile { // can be any non-static method on any class (or interface) except // for “<init>”. kInvokeConstructor = 0x0006, // an invoker for a given constructor. - kLast = kInvokeConstructor + kInvokeDirect = 0x0007, // an invoker for a direct (special) method. + kInvokeInterface = 0x0008, // an invoker for an interface method. + kLast = kInvokeInterface }; // raw method_handle_item diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc index c18ab47739..c5c4eda98f 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -2483,7 +2483,9 @@ bool DexFileVerifier::CheckInterMethodHandleItem() { } case DexFile::MethodHandleType::kInvokeStatic: case DexFile::MethodHandleType::kInvokeInstance: - case DexFile::MethodHandleType::kInvokeConstructor: { + case DexFile::MethodHandleType::kInvokeConstructor: + case DexFile::MethodHandleType::kInvokeDirect: + case DexFile::MethodHandleType::kInvokeInterface: { LOAD_METHOD(method, index, "method_handle_item method_idx", return false); break; } diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 37734e8afb..6547299853 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -376,6 +376,7 @@ inline ArtField* FindFieldFromCode(uint32_t field_idx, mirror::Class* referring_class = referrer->GetDeclaringClass(); if (UNLIKELY(!referring_class->CheckResolvedFieldAccess(fields_class, resolved_field, + referrer->GetDexCache(), field_idx))) { DCHECK(self->IsExceptionPending()); // Throw exception and unwind. return nullptr; // Failure. @@ -461,9 +462,11 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, } else if (access_check) { mirror::Class* methods_class = resolved_method->GetDeclaringClass(); bool can_access_resolved_method = - referrer->GetDeclaringClass()->CheckResolvedMethodAccess<type>(methods_class, - resolved_method, - method_idx); + referrer->GetDeclaringClass()->CheckResolvedMethodAccess(methods_class, + resolved_method, + referrer->GetDexCache(), + method_idx, + type); if (UNLIKELY(!can_access_resolved_method)) { DCHECK(self->IsExceptionPending()); // Throw exception and unwind. return nullptr; // Failure. @@ -662,7 +665,7 @@ inline ArtField* FindFieldFast(uint32_t field_idx, ArtMethod* referrer, FindFiel return nullptr; } } - mirror::Class* referring_class = referrer->GetDeclaringClass(); + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); if (UNLIKELY(!referring_class->CanAccess(fields_class) || !referring_class->CanAccessMember(fields_class, resolved_field->GetAccessFlags()) || (is_set && resolved_field->IsFinal() && (fields_class != referring_class)))) { @@ -677,18 +680,17 @@ inline ArtField* FindFieldFast(uint32_t field_idx, ArtMethod* referrer, FindFiel } // Fast path method resolution that can't throw exceptions. +template <InvokeType type, bool access_check> inline ArtMethod* FindMethodFast(uint32_t method_idx, ObjPtr<mirror::Object> this_object, - ArtMethod* referrer, - bool access_check, - InvokeType type) { + ArtMethod* referrer) { ScopedAssertNoThreadSuspension ants(__FUNCTION__); if (UNLIKELY(this_object == nullptr && type != kStatic)) { return nullptr; } - mirror::Class* referring_class = referrer->GetDeclaringClass(); - ArtMethod* resolved_method = - referrer->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize); + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); + ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache(); + ArtMethod* resolved_method = dex_cache->GetResolvedMethod(method_idx, kRuntimePointerSize); if (UNLIKELY(resolved_method == nullptr)) { return nullptr; } @@ -698,7 +700,7 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, if (UNLIKELY(icce)) { return nullptr; } - mirror::Class* methods_class = resolved_method->GetDeclaringClass(); + ObjPtr<mirror::Class> methods_class = resolved_method->GetDeclaringClass(); if (UNLIKELY(!referring_class->CanAccess(methods_class) || !referring_class->CanAccessMember(methods_class, resolved_method->GetAccessFlags()))) { @@ -713,7 +715,6 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, return resolved_method; } else if (type == kSuper) { // TODO This lookup is rather slow. - ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache(); dex::TypeIndex method_type_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_; ObjPtr<mirror::Class> method_reference_class = ClassLinker::LookupResolvedType( method_type_idx, dex_cache, referrer->GetClassLoader()); @@ -727,7 +728,7 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, if (!method_reference_class->IsAssignableFrom(referring_class)) { return nullptr; } - mirror::Class* super_class = referring_class->GetSuperClass(); + ObjPtr<mirror::Class> super_class = referring_class->GetSuperClass(); if (resolved_method->GetMethodIndex() >= super_class->GetVTableLength()) { // The super class does not have the method. return nullptr; diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h index eed08aabad..fe85887f05 100644 --- a/runtime/entrypoints/entrypoint_utils.h +++ b/runtime/entrypoints/entrypoint_utils.h @@ -137,11 +137,10 @@ inline ArtField* FindFieldFast(uint32_t field_idx, REQUIRES_SHARED(Locks::mutator_lock_); // Fast path method resolution that can't throw exceptions. +template <InvokeType type, bool access_check> inline ArtMethod* FindMethodFast(uint32_t method_idx, ObjPtr<mirror::Object> this_object, - ArtMethod* referrer, - bool access_check, - InvokeType type) + ArtMethod* referrer) REQUIRES_SHARED(Locks::mutator_lock_); inline mirror::Class* ResolveVerifyAndClinit(dex::TypeIndex type_idx, diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 2c99aeba88..36885d8a1f 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -2363,7 +2363,7 @@ extern "C" uint64_t artQuickGenericJniEndTrampoline(Thread* self, // It is valid to use this, as at the usage points here (returns from C functions) we are assuming // to hold the mutator lock (see REQUIRES_SHARED(Locks::mutator_lock_) annotations). -template<InvokeType type, bool access_check> +template <InvokeType type, bool access_check> static TwoWordReturn artInvokeCommon(uint32_t method_idx, ObjPtr<mirror::Object> this_object, Thread* self, @@ -2371,7 +2371,7 @@ static TwoWordReturn artInvokeCommon(uint32_t method_idx, ScopedQuickEntrypointChecks sqec(self); DCHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs)); ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp); - ArtMethod* method = FindMethodFast(method_idx, this_object, caller_method, access_check, type); + ArtMethod* method = FindMethodFast<type, access_check>(method_idx, this_object, caller_method); if (UNLIKELY(method == nullptr)) { const DexFile* dex_file = caller_method->GetDeclaringClass()->GetDexCache()->GetDexFile(); uint32_t shorty_len; diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h index 060f12db33..bf5cf29f13 100644 --- a/runtime/gc/heap-inl.h +++ b/runtime/gc/heap-inl.h @@ -154,8 +154,13 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, } pre_fence_visitor(obj, usable_size); QuasiAtomic::ThreadFenceForConstructor(); - new_num_bytes_allocated = static_cast<size_t>( - num_bytes_allocated_.FetchAndAddRelaxed(bytes_tl_bulk_allocated)) + bytes_tl_bulk_allocated; + new_num_bytes_allocated = num_bytes_allocated_.FetchAndAddRelaxed(bytes_tl_bulk_allocated) + + bytes_tl_bulk_allocated; + if (bytes_tl_bulk_allocated > 0) { + // Only trace when we get an increase in the number of bytes allocated. This happens when + // obtaining a new TLAB and isn't often enough to hurt performance according to golem. + TraceHeapSize(new_num_bytes_allocated + bytes_tl_bulk_allocated); + } } if (kIsDebugBuild && Runtime::Current()->IsStarted()) { CHECK_LE(obj->SizeOf(), usable_size); diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 880b2d40bd..ad4c0d5b2d 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -2678,6 +2678,10 @@ collector::GarbageCollector* Heap::Compact(space::ContinuousMemMapAllocSpace* ta } } +void Heap::TraceHeapSize(size_t heap_size) { + ATRACE_INT("Heap size (KB)", heap_size / KB); +} + collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, GcCause gc_cause, bool clear_soft_references) { @@ -2726,8 +2730,6 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, ++self->GetStats()->gc_for_alloc_count; } const uint64_t bytes_allocated_before_gc = GetBytesAllocated(); - // Approximate heap size. - ATRACE_INT("Heap size (KB)", bytes_allocated_before_gc / KB); if (gc_type == NonStickyGcType()) { // Move all bytes from new_native_bytes_allocated_ to @@ -3632,6 +3634,8 @@ void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran, // We know what our utilization is at this moment. // This doesn't actually resize any memory. It just lets the heap grow more when necessary. const uint64_t bytes_allocated = GetBytesAllocated(); + // Trace the new heap size after the GC is finished. + TraceHeapSize(bytes_allocated); uint64_t target_size; collector::GcType gc_type = collector_ran->GetGcType(); const double multiplier = HeapGrowthMultiplier(); // Use the multiplier to grow more for diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 3484e0297d..9e55081b63 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -1089,6 +1089,8 @@ class Heap { return growth_limit_ / 2; } + void TraceHeapSize(size_t heap_size); + // All-known continuous spaces, where objects lie within fixed bounds. std::vector<space::ContinuousSpace*> continuous_spaces_ GUARDED_BY(Locks::mutator_lock_); diff --git a/runtime/gc/heap_verification_test.cc b/runtime/gc/heap_verification_test.cc index 8ea0459c89..40ee86ce79 100644 --- a/runtime/gc/heap_verification_test.cc +++ b/runtime/gc/heap_verification_test.cc @@ -54,6 +54,11 @@ TEST_F(VerificationTest, IsValidHeapObjectAddress) { Handle<mirror::String> string( hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test"))); EXPECT_TRUE(v->IsValidHeapObjectAddress(string.Get())); + // Address in the heap that isn't aligned. + const void* unaligned_address = + reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(string.Get()) + 1); + EXPECT_TRUE(v->IsAddressInHeapSpace(unaligned_address)); + EXPECT_FALSE(v->IsValidHeapObjectAddress(unaligned_address)); EXPECT_TRUE(v->IsValidHeapObjectAddress(string->GetClass())); const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass()); // Not actually a valid object but the verification can't know that. Guaranteed to be inside a diff --git a/runtime/gc/space/region_space-inl.h b/runtime/gc/space/region_space-inl.h index fc24fc2974..2e67f34648 100644 --- a/runtime/gc/space/region_space-inl.h +++ b/runtime/gc/space/region_space-inl.h @@ -48,58 +48,34 @@ inline mirror::Object* RegionSpace::AllocNonvirtual(size_t num_bytes, size_t* by mirror::Object* obj; if (LIKELY(num_bytes <= kRegionSize)) { // Non-large object. - if (!kForEvac) { - obj = current_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } else { - DCHECK(evac_region_ != nullptr); - obj = evac_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } + obj = (kForEvac ? evac_region_ : current_region_)->Alloc(num_bytes, + bytes_allocated, + usable_size, + bytes_tl_bulk_allocated); if (LIKELY(obj != nullptr)) { return obj; } MutexLock mu(Thread::Current(), region_lock_); // Retry with current region since another thread may have updated it. - if (!kForEvac) { - obj = current_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } else { - obj = evac_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } + obj = (kForEvac ? evac_region_ : current_region_)->Alloc(num_bytes, + bytes_allocated, + usable_size, + bytes_tl_bulk_allocated); if (LIKELY(obj != nullptr)) { return obj; } - if (!kForEvac) { - // Retain sufficient free regions for full evacuation. - if ((num_non_free_regions_ + 1) * 2 > num_regions_) { - return nullptr; - } - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - r->SetNewlyAllocated(); - ++num_non_free_regions_; - obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); - CHECK(obj != nullptr); - current_region_ = r; - return obj; - } - } - } else { - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - ++num_non_free_regions_; - obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); - CHECK(obj != nullptr); - evac_region_ = r; - return obj; - } + Region* r = AllocateRegion(kForEvac); + if (LIKELY(r != nullptr)) { + obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); + CHECK(obj != nullptr); + // Do our allocation before setting the region, this makes sure no threads race ahead + // and fill in the region before we allocate the object. b/63153464 + if (kForEvac) { + evac_region_ = r; + } else { + current_region_ = r; } + return obj; } } else { // Large object. diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc index 8d8c4885ef..b8f1e8fc71 100644 --- a/runtime/gc/space/region_space.cc +++ b/runtime/gc/space/region_space.cc @@ -29,6 +29,10 @@ namespace space { // value of the region size, evaculate the region. static constexpr uint kEvaculateLivePercentThreshold = 75U; +// If we protect the cleared regions. +// Only protect for target builds to prevent flaky test failures (b/63131961). +static constexpr bool kProtectClearedRegions = kIsTargetBuild; + MemMap* RegionSpace::CreateMemMap(const std::string& name, size_t capacity, uint8_t* requested_begin) { CHECK_ALIGNED(capacity, kRegionSize); @@ -247,6 +251,13 @@ void RegionSpace::SetFromSpace(accounting::ReadBarrierTable* rb_table, bool forc evac_region_ = &full_region_; } +static void ZeroAndProtectRegion(uint8_t* begin, uint8_t* end) { + ZeroAndReleasePages(begin, end - begin); + if (kProtectClearedRegions) { + mprotect(begin, end - begin, PROT_NONE); + } +} + void RegionSpace::ClearFromSpace(uint64_t* cleared_bytes, uint64_t* cleared_objects) { DCHECK(cleared_bytes != nullptr); DCHECK(cleared_objects != nullptr); @@ -265,7 +276,7 @@ void RegionSpace::ClearFromSpace(uint64_t* cleared_bytes, uint64_t* cleared_obje auto clear_region = [&clear_block_begin, &clear_block_end](Region* r) { r->Clear(/*zero_and_release_pages*/false); if (clear_block_end != r->Begin()) { - ZeroAndReleasePages(clear_block_begin, clear_block_end - clear_block_begin); + ZeroAndProtectRegion(clear_block_begin, clear_block_end); clear_block_begin = r->Begin(); } clear_block_end = r->End(); @@ -449,21 +460,14 @@ bool RegionSpace::AllocNewTlab(Thread* self, size_t min_bytes) { MutexLock mu(self, region_lock_); RevokeThreadLocalBuffersLocked(self); // Retain sufficient free regions for full evacuation. - if ((num_non_free_regions_ + 1) * 2 > num_regions_) { - return false; - } - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - ++num_non_free_regions_; - r->SetNewlyAllocated(); - r->SetTop(r->End()); - r->is_a_tlab_ = true; - r->thread_ = self; - self->SetTlab(r->Begin(), r->Begin() + min_bytes, r->End()); - return true; - } + + Region* r = AllocateRegion(/*for_evac*/ false); + if (r != nullptr) { + r->is_a_tlab_ = true; + r->thread_ = self; + r->SetTop(r->End()); + self->SetTlab(r->Begin(), r->Begin() + min_bytes, r->End()); + return true; } return false; } @@ -543,6 +547,65 @@ size_t RegionSpace::AllocationSizeNonvirtual(mirror::Object* obj, size_t* usable return num_bytes; } +void RegionSpace::Region::Clear(bool zero_and_release_pages) { + top_.StoreRelaxed(begin_); + state_ = RegionState::kRegionStateFree; + type_ = RegionType::kRegionTypeNone; + objects_allocated_.StoreRelaxed(0); + alloc_time_ = 0; + live_bytes_ = static_cast<size_t>(-1); + if (zero_and_release_pages) { + ZeroAndProtectRegion(begin_, end_); + } + is_newly_allocated_ = false; + is_a_tlab_ = false; + thread_ = nullptr; +} + +RegionSpace::Region* RegionSpace::AllocateRegion(bool for_evac) { + if (!for_evac && (num_non_free_regions_ + 1) * 2 > num_regions_) { + return nullptr; + } + for (size_t i = 0; i < num_regions_; ++i) { + Region* r = ®ions_[i]; + if (r->IsFree()) { + r->Unfree(this, time_); + ++num_non_free_regions_; + if (!for_evac) { + // Evac doesn't count as newly allocated. + r->SetNewlyAllocated(); + } + return r; + } + } + return nullptr; +} + +void RegionSpace::Region::MarkAsAllocated(RegionSpace* region_space, uint32_t alloc_time) { + DCHECK(IsFree()); + alloc_time_ = alloc_time; + region_space->AdjustNonFreeRegionLimit(idx_); + type_ = RegionType::kRegionTypeToSpace; + if (kProtectClearedRegions) { + mprotect(Begin(), kRegionSize, PROT_READ | PROT_WRITE); + } +} + +void RegionSpace::Region::Unfree(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateAllocated; +} + +void RegionSpace::Region::UnfreeLarge(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateLarge; +} + +void RegionSpace::Region::UnfreeLargeTail(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateLargeTail; +} + } // namespace space } // namespace gc } // namespace art diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h index 323ccdbd74..8907b07bf2 100644 --- a/runtime/gc/space/region_space.h +++ b/runtime/gc/space/region_space.h @@ -284,20 +284,7 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { return type_; } - void Clear(bool zero_and_release_pages) { - top_.StoreRelaxed(begin_); - state_ = RegionState::kRegionStateFree; - type_ = RegionType::kRegionTypeNone; - objects_allocated_.StoreRelaxed(0); - alloc_time_ = 0; - live_bytes_ = static_cast<size_t>(-1); - if (zero_and_release_pages) { - ZeroAndReleasePages(begin_, end_ - begin_); - } - is_newly_allocated_ = false; - is_a_tlab_ = false; - thread_ = nullptr; - } + void Clear(bool zero_and_release_pages); ALWAYS_INLINE mirror::Object* Alloc(size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, @@ -315,31 +302,16 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { // Given a free region, declare it non-free (allocated). void Unfree(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateAllocated; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); void UnfreeLarge(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateLarge; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); void UnfreeLargeTail(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateLargeTail; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); + + void MarkAsAllocated(RegionSpace* region_space, uint32_t alloc_time) + REQUIRES(region_space->region_lock_); void SetNewlyAllocated() { is_newly_allocated_ = true; @@ -539,6 +511,8 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { } } + Region* AllocateRegion(bool for_evac) REQUIRES(region_lock_); + Mutex region_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; uint32_t time_; // The time as the number of collections since the startup. diff --git a/runtime/gc/verification.cc b/runtime/gc/verification.cc index 03b26a0a6b..beb43dfcf5 100644 --- a/runtime/gc/verification.cc +++ b/runtime/gc/verification.cc @@ -26,6 +26,28 @@ namespace art { namespace gc { +std::string Verification::DumpRAMAroundAddress(uintptr_t addr, uintptr_t bytes) const { + const uintptr_t dump_start = addr - bytes; + const uintptr_t dump_end = addr + bytes; + std::ostringstream oss; + if (dump_start < dump_end && + IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_start)) && + IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_end - 1))) { + oss << " adjacent_ram="; + for (uintptr_t p = dump_start; p < dump_end; ++p) { + if (p == addr) { + // Marker of where the address is. + oss << "|"; + } + uint8_t* ptr = reinterpret_cast<uint8_t*>(p); + oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<uintptr_t>(*ptr); + } + } else { + oss << " <invalid address>"; + } + return oss.str(); +} + std::string Verification::DumpObjectInfo(const void* addr, const char* tag) const { std::ostringstream oss; oss << tag << "=" << addr; @@ -51,23 +73,7 @@ std::string Verification::DumpObjectInfo(const void* addr, const char* tag) cons card_table->GetCard(reinterpret_cast<const mirror::Object*>(addr))); } // Dump adjacent RAM. - const uintptr_t uint_addr = reinterpret_cast<uintptr_t>(addr); - static constexpr size_t kBytesBeforeAfter = 2 * kObjectAlignment; - const uintptr_t dump_start = uint_addr - kBytesBeforeAfter; - const uintptr_t dump_end = uint_addr + kBytesBeforeAfter; - if (dump_start < dump_end && - IsValidHeapObjectAddress(reinterpret_cast<const void*>(dump_start)) && - IsValidHeapObjectAddress(reinterpret_cast<const void*>(dump_end - kObjectAlignment))) { - oss << " adjacent_ram="; - for (uintptr_t p = dump_start; p < dump_end; ++p) { - if (p == uint_addr) { - // Marker of where the object is. - oss << "|"; - } - uint8_t* ptr = reinterpret_cast<uint8_t*>(p); - oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<uintptr_t>(*ptr); - } - } + oss << DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(addr), 4 * kObjectAlignment); } else { oss << " <invalid address>"; } @@ -91,12 +97,15 @@ void Verification::LogHeapCorruption(ObjPtr<mirror::Object> holder, if (holder != nullptr) { mirror::Class* holder_klass = holder->GetClass<kVerifyNone, kWithoutReadBarrier>(); if (IsValidClass(holder_klass)) { - oss << "field_offset=" << offset.Uint32Value(); + oss << " field_offset=" << offset.Uint32Value(); ArtField* field = holder->FindFieldByOffset(offset); if (field != nullptr) { oss << " name=" << field->GetName(); } } + mirror::HeapReference<mirror::Object>* addr = holder->GetFieldObjectReferenceAddr(offset); + oss << " reference addr" + << DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(addr), 4 * kObjectAlignment); } if (fatal) { @@ -106,10 +115,7 @@ void Verification::LogHeapCorruption(ObjPtr<mirror::Object> holder, } } -bool Verification::IsValidHeapObjectAddress(const void* addr, space::Space** out_space) const { - if (!IsAligned<kObjectAlignment>(addr)) { - return false; - } +bool Verification::IsAddressInHeapSpace(const void* addr, space::Space** out_space) const { space::Space* const space = heap_->FindSpaceFromAddress(addr); if (space != nullptr) { if (out_space != nullptr) { @@ -120,6 +126,10 @@ bool Verification::IsValidHeapObjectAddress(const void* addr, space::Space** out return false; } +bool Verification::IsValidHeapObjectAddress(const void* addr, space::Space** out_space) const { + return IsAligned<kObjectAlignment>(addr) && IsAddressInHeapSpace(addr, out_space); +} + bool Verification::IsValidClass(const void* addr) const { if (!IsValidHeapObjectAddress(addr)) { return false; diff --git a/runtime/gc/verification.h b/runtime/gc/verification.h index 903e159c5a..6b456fd349 100644 --- a/runtime/gc/verification.h +++ b/runtime/gc/verification.h @@ -49,11 +49,10 @@ class Verification { mirror::Object* ref, bool fatal) const REQUIRES_SHARED(Locks::mutator_lock_); - // Return true if the klass is likely to be a valid mirror::Class. bool IsValidClass(const void* klass) const REQUIRES_SHARED(Locks::mutator_lock_); - // Does not allow null. + // Does not allow null, checks alignment. bool IsValidHeapObjectAddress(const void* addr, space::Space** out_space = nullptr) const REQUIRES_SHARED(Locks::mutator_lock_); @@ -62,6 +61,14 @@ class Verification { std::string FirstPathFromRootSet(ObjPtr<mirror::Object> target) const REQUIRES_SHARED(Locks::mutator_lock_); + // Does not check alignment, used by DumpRAMAroundAddress. + bool IsAddressInHeapSpace(const void* addr, space::Space** out_space = nullptr) const + REQUIRES_SHARED(Locks::mutator_lock_); + + // Dump bytes of RAM before and after an address. + std::string DumpRAMAroundAddress(uintptr_t addr, uintptr_t bytes) const + REQUIRES_SHARED(Locks::mutator_lock_); + private: gc::Heap* const heap_; diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 1b36c3f12b..0687b753d8 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -640,7 +640,7 @@ static ObjPtr<mirror::CallSite> InvokeBootstrapMethod(Thread* self, const DexFile* dex_file = referrer->GetDexFile(); const DexFile::CallSiteIdItem& csi = dex_file->GetCallSiteId(call_site_idx); - StackHandleScope<9> hs(self); + StackHandleScope<10> hs(self); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referrer->GetClassLoader())); Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache())); @@ -836,9 +836,13 @@ static ObjPtr<mirror::CallSite> InvokeBootstrapMethod(Thread* self, return nullptr; } - // Check the target method type matches the method type requested. - if (UNLIKELY(!target->GetMethodType()->IsExactMatch(method_type.Get()))) { - ThrowWrongMethodTypeException(target->GetMethodType(), method_type.Get()); + // Check the target method type matches the method type requested modulo the receiver + // needs to be compatible rather than exact. + Handle<mirror::MethodType> target_method_type = hs.NewHandle(target->GetMethodType()); + if (UNLIKELY(!target_method_type->IsExactMatch(method_type.Get()) && + !IsParameterTypeConvertible(target_method_type->GetPTypes()->GetWithoutChecks(0), + method_type->GetPTypes()->GetWithoutChecks(0)))) { + ThrowWrongMethodTypeException(target_method_type.Get(), method_type.Get()); return nullptr; } @@ -947,13 +951,20 @@ static inline bool DoCallCommon(ArtMethod* called_method, // Test whether to use the interpreter or compiler entrypoint, and save that result to pass to // PerformCall. A deoptimization could occur at any time, and we shouldn't change which // entrypoint to use once we start building the shadow frame. - bool use_interpreter_entrypoint = ClassLinker::ShouldUseInterpreterEntrypoint( - called_method, called_method->GetEntryPointFromQuickCompiledCode()); + + // For unstarted runtimes, always use the interpreter entrypoint. This fixes the case where we are + // doing cross compilation. Note that GetEntryPointFromQuickCompiledCode doesn't use the image + // pointer size here and this may case an overflow if it is called from the compiler. b/62402160 + const bool use_interpreter_entrypoint = !Runtime::Current()->IsStarted() || + ClassLinker::ShouldUseInterpreterEntrypoint( + called_method, + called_method->GetEntryPointFromQuickCompiledCode()); if (LIKELY(code_item != nullptr)) { // When transitioning to compiled code, space only needs to be reserved for the input registers. // The rest of the frame gets discarded. This also prevents accessing the called method's code // item, saving memory by keeping code items of compiled code untouched. - if (Runtime::Current()->IsStarted() && !use_interpreter_entrypoint) { + if (!use_interpreter_entrypoint) { + DCHECK(!Runtime::Current()->IsAotCompiler()) << "Compiler should use interpreter entrypoint"; num_regs = number_of_inputs; } else { num_regs = code_item->registers_size_; diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 45788e7617..0a2705d5f7 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -64,13 +64,22 @@ namespace interpreter { } // Code to run before each dex instruction. -#define PREAMBLE() \ - do { \ - if (UNLIKELY(instrumentation->HasDexPcListeners())) { \ - instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), \ - shadow_frame.GetMethod(), dex_pc); \ +#define PREAMBLE_SAVE(save_ref) \ + { \ + if (UNLIKELY(instrumentation->HasDexPcListeners()) && \ + UNLIKELY(!DoDexPcMoveEvent(self, \ + code_item, \ + shadow_frame, \ + dex_pc, \ + instrumentation, \ + save_ref))) { \ + HANDLE_PENDING_EXCEPTION(); \ + break; \ } \ - } while (false) + } \ + do {} while (false) + +#define PREAMBLE() PREAMBLE_SAVE(nullptr) #define BRANCH_INSTRUMENTATION(offset) \ do { \ @@ -104,6 +113,43 @@ namespace interpreter { } \ } while (false) +// Unlike most other events the DexPcMovedEvent can be sent when there is a pending exception (if +// the next instruction is MOVE_EXCEPTION). This means it needs to be handled carefully to be able +// to detect exceptions thrown by the DexPcMovedEvent itself. These exceptions could be thrown by +// jvmti-agents while handling breakpoint or single step events. We had to move this into its own +// function because it was making ExecuteSwitchImpl have too large a stack. +NO_INLINE static bool DoDexPcMoveEvent(Thread* self, + const DexFile::CodeItem* code_item, + const ShadowFrame& shadow_frame, + uint32_t dex_pc, + const instrumentation::Instrumentation* instrumentation, + JValue* save_ref) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(instrumentation->HasDexPcListeners()); + StackHandleScope<2> hs(self); + Handle<mirror::Throwable> thr(hs.NewHandle(self->GetException())); + mirror::Object* null_obj = nullptr; + HandleWrapper<mirror::Object> h( + hs.NewHandleWrapper(LIKELY(save_ref == nullptr) ? &null_obj : save_ref->GetGCRoot())); + self->ClearException(); + instrumentation->DexPcMovedEvent(self, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + dex_pc); + if (UNLIKELY(self->IsExceptionPending())) { + // We got a new exception in the dex-pc-moved event. We just let this exception replace the old + // one. + // TODO It would be good to add the old exception to the suppressed exceptions of the new one if + // possible. + return false; + } else { + if (UNLIKELY(!thr.IsNull())) { + self->SetException(thr.Get()); + } + return true; + } +} + template<bool do_access_check, bool transaction_active> JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register, @@ -198,7 +244,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, inst = inst->Next_1xx(); break; case Instruction::MOVE_RESULT_OBJECT: - PREAMBLE(); + PREAMBLE_SAVE(&result_register); shadow_frame.SetVRegReference(inst->VRegA_11x(inst_data), result_register.GetL()); inst = inst->Next_1xx(); break; diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index b41bc78170..10dddaefc8 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -43,6 +43,33 @@ namespace art { ProfileSaver* ProfileSaver::instance_ = nullptr; pthread_t ProfileSaver::profiler_pthread_ = 0U; +// At what priority to schedule the saver threads. 9 is the lowest foreground priority on device. +static constexpr int kProfileSaverPthreadPriority = 9; + +static void SetProfileSaverThreadPriority(pthread_t thread, int priority) { +#if defined(ART_TARGET_ANDROID) + int result = setpriority(PRIO_PROCESS, pthread_gettid_np(thread), priority); + if (result != 0) { + LOG(ERROR) << "Failed to setpriority to :" << priority; + } +#else + UNUSED(thread); + UNUSED(priority); +#endif +} + +static int GetDefaultThreadPriority() { +#if defined(ART_TARGET_ANDROID) + pthread_attr_t attr; + sched_param param; + pthread_attr_init(&attr); + pthread_attr_getschedparam(&attr, ¶m); + return param.sched_priority; +#else + return 0; +#endif +} + ProfileSaver::ProfileSaver(const ProfileSaverOptions& options, const std::string& output_filename, jit::JitCodeCache* jit_code_cache, @@ -241,6 +268,20 @@ class GetClassesAndMethodsVisitor : public ClassVisitor { const bool profile_boot_class_path_; }; +class ScopedDefaultPriority { + public: + explicit ScopedDefaultPriority(pthread_t thread) : thread_(thread) { + SetProfileSaverThreadPriority(thread_, GetDefaultThreadPriority()); + } + + ~ScopedDefaultPriority() { + SetProfileSaverThreadPriority(thread_, kProfileSaverPthreadPriority); + } + + private: + const pthread_t thread_; +}; + void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() { ScopedTrace trace(__PRETTY_FUNCTION__); const uint64_t start_time = NanoTime(); @@ -257,7 +298,15 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() { TypeReferenceCollection resolved_classes(allocator.Adapter(), allocator.Adapter()); const bool is_low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode(); const size_t hot_threshold = options_.GetHotStartupMethodSamples(is_low_ram); + pthread_t profiler_pthread; { + MutexLock mu(self, *Locks::profiler_lock_); + profiler_pthread = profiler_pthread_; + } + { + // Restore profile saver thread priority during the GC critical section. This helps prevent + // priority inversions blocking the GC for long periods of time. + ScopedDefaultPriority sdp(profiler_pthread); ScopedObjectAccess soa(self); gc::ScopedGCCriticalSection sgcs(self, gc::kGcCauseProfileSaver, @@ -543,15 +592,7 @@ void ProfileSaver::Start(const ProfileSaverOptions& options, (&profiler_pthread_, nullptr, &RunProfileSaverThread, reinterpret_cast<void*>(instance_)), "Profile saver thread"); -#if defined(ART_TARGET_ANDROID) - // At what priority to schedule the saver threads. 9 is the lowest foreground priority on device. - static constexpr int kProfileSaverPthreadPriority = 9; - int result = setpriority( - PRIO_PROCESS, pthread_gettid_np(profiler_pthread_), kProfileSaverPthreadPriority); - if (result != 0) { - PLOG(ERROR) << "Failed to setpriority to :" << kProfileSaverPthreadPriority; - } -#endif + SetProfileSaverThreadPriority(profiler_pthread_, kProfileSaverPthreadPriority); } void ProfileSaver::Stop(bool dump_info) { diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 12baf387d2..419a4db0fc 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -99,7 +99,7 @@ inline DexCache* Class::GetDexCache() { inline uint32_t Class::GetCopiedMethodsStartOffset() { // Object::GetFieldShort returns an int16_t value, but // Class::copied_methods_offset_ is an uint16_t value; cast the - // latter to int16_t before returning it as an uint32_t value, so + // latter to uint16_t before returning it as an uint32_t value, so // that uint16_t values between 2^15 and 2^16-1 are correctly // handled. return static_cast<uint16_t>( @@ -113,7 +113,7 @@ inline uint32_t Class::GetDirectMethodsStartOffset() { inline uint32_t Class::GetVirtualMethodsStartOffset() { // Object::GetFieldShort returns an int16_t value, but // Class::virtual_method_offset_ is an uint16_t value; cast the - // latter to int16_t before returning it as an uint32_t value, so + // latter to uint16_t before returning it as an uint32_t value, so // that uint16_t values between 2^15 and 2^16-1 are correctly // handled. return static_cast<uint16_t>( @@ -410,25 +410,24 @@ inline bool Class::IsAssignableFromArray(ObjPtr<Class> src) { return IsArrayAssignableFromArray(src); } -template <bool throw_on_failure, bool use_referrers_cache> +template <bool throw_on_failure> inline bool Class::ResolvedFieldAccessTest(ObjPtr<Class> access_to, ArtField* field, - uint32_t field_idx, - ObjPtr<DexCache> dex_cache) { - DCHECK_EQ(use_referrers_cache, dex_cache == nullptr); + ObjPtr<DexCache> dex_cache, + uint32_t field_idx) { + DCHECK(dex_cache != nullptr); if (UNLIKELY(!this->CanAccess(access_to))) { // The referrer class can't access the field's declaring class but may still be able // to access the field if the FieldId specifies an accessible subclass of the declaring // class rather than the declaring class itself. - ObjPtr<DexCache> referrer_dex_cache = use_referrers_cache ? this->GetDexCache() : dex_cache; - dex::TypeIndex class_idx = referrer_dex_cache->GetDexFile()->GetFieldId(field_idx).class_idx_; + dex::TypeIndex class_idx = dex_cache->GetDexFile()->GetFieldId(field_idx).class_idx_; // The referenced class has already been resolved with the field, but may not be in the dex // cache. Use LookupResolveType here to search the class table if it is not in the dex cache. // should be no thread suspension due to the class being resolved. ObjPtr<Class> dex_access_to = Runtime::Current()->GetClassLinker()->LookupResolvedType( - *referrer_dex_cache->GetDexFile(), + *dex_cache->GetDexFile(), class_idx, - referrer_dex_cache, + dex_cache, access_to->GetClassLoader()); DCHECK(dex_access_to != nullptr); if (UNLIKELY(!this->CanAccess(dex_access_to))) { @@ -447,25 +446,25 @@ inline bool Class::ResolvedFieldAccessTest(ObjPtr<Class> access_to, return false; } -template <bool throw_on_failure, bool use_referrers_cache, InvokeType throw_invoke_type> +template <bool throw_on_failure> inline bool Class::ResolvedMethodAccessTest(ObjPtr<Class> access_to, ArtMethod* method, + ObjPtr<DexCache> dex_cache, uint32_t method_idx, - ObjPtr<DexCache> dex_cache) { - static_assert(throw_on_failure || throw_invoke_type == kStatic, "Non-default throw invoke type"); - DCHECK_EQ(use_referrers_cache, dex_cache == nullptr); + InvokeType throw_invoke_type) { + DCHECK(throw_on_failure || throw_invoke_type == kStatic); + DCHECK(dex_cache != nullptr); if (UNLIKELY(!this->CanAccess(access_to))) { // The referrer class can't access the method's declaring class but may still be able // to access the method if the MethodId specifies an accessible subclass of the declaring // class rather than the declaring class itself. - ObjPtr<DexCache> referrer_dex_cache = use_referrers_cache ? this->GetDexCache() : dex_cache; - dex::TypeIndex class_idx = referrer_dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_; + dex::TypeIndex class_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_; // The referenced class has already been resolved with the method, but may not be in the dex // cache. ObjPtr<Class> dex_access_to = Runtime::Current()->GetClassLinker()->LookupResolvedType( - *referrer_dex_cache->GetDexFile(), + *dex_cache->GetDexFile(), class_idx, - referrer_dex_cache, + dex_cache, access_to->GetClassLoader()); DCHECK(dex_access_to != nullptr); if (UNLIKELY(!this->CanAccess(dex_access_to))) { @@ -491,30 +490,30 @@ inline bool Class::CanAccessResolvedField(ObjPtr<Class> access_to, ArtField* field, ObjPtr<DexCache> dex_cache, uint32_t field_idx) { - return ResolvedFieldAccessTest<false, false>(access_to, field, field_idx, dex_cache); + return ResolvedFieldAccessTest<false>(access_to, field, dex_cache, field_idx); } inline bool Class::CheckResolvedFieldAccess(ObjPtr<Class> access_to, ArtField* field, + ObjPtr<DexCache> dex_cache, uint32_t field_idx) { - return ResolvedFieldAccessTest<true, true>(access_to, field, field_idx, nullptr); + return ResolvedFieldAccessTest<true>(access_to, field, dex_cache, field_idx); } inline bool Class::CanAccessResolvedMethod(ObjPtr<Class> access_to, ArtMethod* method, ObjPtr<DexCache> dex_cache, uint32_t method_idx) { - return ResolvedMethodAccessTest<false, false, kStatic>(access_to, method, method_idx, dex_cache); + return ResolvedMethodAccessTest<false>(access_to, method, dex_cache, method_idx, kStatic); } -template <InvokeType throw_invoke_type> inline bool Class::CheckResolvedMethodAccess(ObjPtr<Class> access_to, ArtMethod* method, - uint32_t method_idx) { - return ResolvedMethodAccessTest<true, true, throw_invoke_type>(access_to, - method, - method_idx, - nullptr); + ObjPtr<DexCache> dex_cache, + uint32_t method_idx, + InvokeType throw_invoke_type) { + return ResolvedMethodAccessTest<true>( + access_to, method, dex_cache, method_idx, throw_invoke_type); } inline bool Class::IsSubClass(ObjPtr<Class> klass) { diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 61d6e05416..00498bc30a 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -643,7 +643,10 @@ class MANAGED Class FINAL : public Object { ObjPtr<DexCache> dex_cache, uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_); - bool CheckResolvedFieldAccess(ObjPtr<Class> access_to, ArtField* field, uint32_t field_idx) + bool CheckResolvedFieldAccess(ObjPtr<Class> access_to, + ArtField* field, + ObjPtr<DexCache> dex_cache, + uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_); // Can this class access a resolved method? @@ -654,10 +657,11 @@ class MANAGED Class FINAL : public Object { ObjPtr<DexCache> dex_cache, uint32_t method_idx) REQUIRES_SHARED(Locks::mutator_lock_); - template <InvokeType throw_invoke_type> bool CheckResolvedMethodAccess(ObjPtr<Class> access_to, ArtMethod* resolved_method, - uint32_t method_idx) + ObjPtr<DexCache> dex_cache, + uint32_t method_idx, + InvokeType throw_invoke_type) REQUIRES_SHARED(Locks::mutator_lock_); bool IsSubClass(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); @@ -1352,18 +1356,19 @@ class MANAGED Class FINAL : public Object { uint32_t end_offset) REQUIRES_SHARED(Locks::mutator_lock_); - template <bool throw_on_failure, bool use_referrers_cache> + template <bool throw_on_failure> bool ResolvedFieldAccessTest(ObjPtr<Class> access_to, ArtField* field, - uint32_t field_idx, - ObjPtr<DexCache> dex_cache) + ObjPtr<DexCache> dex_cache, + uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_); - template <bool throw_on_failure, bool use_referrers_cache, InvokeType throw_invoke_type> + template <bool throw_on_failure> bool ResolvedMethodAccessTest(ObjPtr<Class> access_to, ArtMethod* resolved_method, + ObjPtr<DexCache> dex_cache, uint32_t method_idx, - ObjPtr<DexCache> dex_cache) + InvokeType throw_invoke_type) REQUIRES_SHARED(Locks::mutator_lock_); bool Implements(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/mirror/dex_cache_test.cc b/runtime/mirror/dex_cache_test.cc index a110ed7f6b..5b1ba8d010 100644 --- a/runtime/mirror/dex_cache_test.cc +++ b/runtime/mirror/dex_cache_test.cc @@ -106,12 +106,12 @@ TEST_F(DexCacheTest, TestResolvedFieldAccess) { EXPECT_NE(klass1->NumStaticFields(), 0u); for (ArtField& field : klass2->GetSFields()) { - EXPECT_FALSE(( - klass1->ResolvedFieldAccessTest</*throw_on_failure*/ false, - /*use_referrers_cache*/ false>(klass2.Get(), - &field, - field.GetDexFieldIndex(), - klass1->GetDexCache()))); + EXPECT_FALSE( + klass1->ResolvedFieldAccessTest</*throw_on_failure*/ false>( + klass2.Get(), + &field, + klass1->GetDexCache(), + field.GetDexFieldIndex())); } } diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 940afc8448..3e3eaae13a 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -898,7 +898,9 @@ void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWo Thread* owner; { ScopedThreadSuspension sts(self, kBlocked); - owner = thread_list->SuspendThreadByThreadId(owner_thread_id, false, &timed_out); + owner = thread_list->SuspendThreadByThreadId(owner_thread_id, + SuspendReason::kInternal, + &timed_out); } if (owner != nullptr) { // We succeeded in suspending the thread, check the lock's status didn't change. @@ -908,7 +910,7 @@ void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWo // Go ahead and inflate the lock. Inflate(self, owner, obj.Get(), hash_code); } - thread_list->Resume(owner, false); + thread_list->Resume(owner, SuspendReason::kInternal); } self->SetMonitorEnterObject(nullptr); } diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc index e86e64ed6a..7d2d0e5bb9 100644 --- a/runtime/native/dalvik_system_VMStack.cc +++ b/runtime/native/dalvik_system_VMStack.cc @@ -51,7 +51,10 @@ static jobject GetThreadStack(const ScopedFastNativeObjectAccess& soa, jobject p ScopedThreadSuspension sts(soa.Self(), kNative); ThreadList* thread_list = Runtime::Current()->GetThreadList(); bool timed_out; - Thread* thread = thread_list->SuspendThreadByPeer(peer, true, false, &timed_out); + Thread* thread = thread_list->SuspendThreadByPeer(peer, + /* request_suspension */ true, + SuspendReason::kInternal, + &timed_out); if (thread != nullptr) { // Must be runnable to create returned array. { @@ -59,7 +62,7 @@ static jobject GetThreadStack(const ScopedFastNativeObjectAccess& soa, jobject p trace = thread->CreateInternalStackTrace<false>(soa); } // Restart suspended thread. - thread_list->Resume(thread, false); + thread_list->Resume(thread, SuspendReason::kInternal); } else if (timed_out) { LOG(ERROR) << "Trying to get thread's stack failed as the thread failed to suspend within a " "generous timeout."; diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc index e4d1705d28..8b76327fa8 100644 --- a/runtime/native/java_lang_Thread.cc +++ b/runtime/native/java_lang_Thread.cc @@ -146,13 +146,16 @@ static void Thread_nativeSetName(JNIEnv* env, jobject peer, jstring java_name) { ThreadList* thread_list = Runtime::Current()->GetThreadList(); bool timed_out; // Take suspend thread lock to avoid races with threads trying to suspend this one. - Thread* thread = thread_list->SuspendThreadByPeer(peer, true, false, &timed_out); + Thread* thread = thread_list->SuspendThreadByPeer(peer, + /* request_suspension */ true, + SuspendReason::kInternal, + &timed_out); if (thread != nullptr) { { ScopedObjectAccess soa(env); thread->SetThreadName(name.c_str()); } - thread_list->Resume(thread, false); + thread_list->Resume(thread, SuspendReason::kInternal); } else if (timed_out) { LOG(ERROR) << "Trying to set thread name to '" << name.c_str() << "' failed as the thread " "failed to suspend within a generous timeout."; diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc index 0a254aca54..c516b66d93 100644 --- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc +++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc @@ -66,7 +66,9 @@ static jobjectArray DdmVmInternal_getStackTraceById(JNIEnv* env, jclass, jint th } // Suspend thread to build stack trace. - Thread* thread = thread_list->SuspendThreadByThreadId(thin_lock_id, false, &timed_out); + Thread* thread = thread_list->SuspendThreadByThreadId(thin_lock_id, + SuspendReason::kInternal, + &timed_out); if (thread != nullptr) { { ScopedObjectAccess soa(env); @@ -74,7 +76,7 @@ static jobjectArray DdmVmInternal_getStackTraceById(JNIEnv* env, jclass, jint th trace = Thread::InternalStackTraceToStackTraceElementArray(soa, internal_trace); } // Restart suspended thread. - thread_list->Resume(thread, false); + thread_list->Resume(thread, SuspendReason::kInternal); } else { if (timed_out) { LOG(ERROR) << "Trying to get thread's stack by id failed as the thread failed to suspend " diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/runtime/openjdkjvm/OpenjdkJvm.cc index 0b93b079d5..4560dda4dd 100644 --- a/runtime/openjdkjvm/OpenjdkJvm.cc +++ b/runtime/openjdkjvm/OpenjdkJvm.cc @@ -422,14 +422,17 @@ JNIEXPORT void JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring jav // Take suspend thread lock to avoid races with threads trying to suspend this one. art::Thread* thread; { - thread = thread_list->SuspendThreadByPeer(jthread, true, false, &timed_out); + thread = thread_list->SuspendThreadByPeer(jthread, + true, + art::SuspendReason::kInternal, + &timed_out); } if (thread != NULL) { { art::ScopedObjectAccess soa(env); thread->SetThreadName(name.c_str()); } - thread_list->Resume(thread, false); + thread_list->Resume(thread, art::SuspendReason::kInternal); } else if (timed_out) { LOG(ERROR) << "Trying to set thread name to '" << name.c_str() << "' failed as the thread " "failed to suspend within a generous timeout."; diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index e38f265c5a..aec1bd0f37 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -27,6 +27,8 @@ cc_defaults { "fixed_up_dex_file.cc", "object_tagging.cc", "OpenjdkJvmTi.cc", + "ti_allocator.cc", + "ti_breakpoint.cc", "ti_class.cc", "ti_class_definition.cc", "ti_class_loader.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 0896210f1c..d3e8798bd6 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -48,6 +48,8 @@ #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_allocator.h" +#include "ti_breakpoint.h" #include "ti_class.h" #include "ti_dump.h" #include "ti_field.h" @@ -108,22 +110,12 @@ class JvmtiFunctions { static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr) { ENSURE_VALID_ENV(env); ENSURE_NON_NULL(mem_ptr); - if (size < 0) { - return ERR(ILLEGAL_ARGUMENT); - } else if (size == 0) { - *mem_ptr = nullptr; - return OK; - } - *mem_ptr = static_cast<unsigned char*>(malloc(size)); - return (*mem_ptr != nullptr) ? OK : ERR(OUT_OF_MEMORY); + return AllocUtil::Allocate(env, size, mem_ptr); } static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem) { ENSURE_VALID_ENV(env); - if (mem != nullptr) { - free(mem); - } - return OK; + return AllocUtil::Deallocate(env, mem); } static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr) { @@ -619,20 +611,17 @@ class JvmtiFunctions { return ERR(NOT_IMPLEMENTED); } - static jvmtiError SetBreakpoint(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jlocation location ATTRIBUTE_UNUSED) { + + static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_breakpoint_events); - return ERR(NOT_IMPLEMENTED); + return BreakpointUtil::SetBreakpoint(env, method, location); } - static jvmtiError ClearBreakpoint(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jlocation location ATTRIBUTE_UNUSED) { + static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_breakpoint_events); - return ERR(NOT_IMPLEMENTED); + return BreakpointUtil::ClearBreakpoint(env, method, location); } static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { @@ -924,12 +913,12 @@ class JvmtiFunctions { } static jvmtiError GetBytecodes(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jint* bytecode_count_ptr ATTRIBUTE_UNUSED, - unsigned char** bytecodes_ptr ATTRIBUTE_UNUSED) { + jmethodID method, + jint* bytecode_count_ptr, + unsigned char** bytecodes_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_bytecodes); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetBytecodes(env, method, bytecode_count_ptr, bytecodes_ptr); } static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr) { @@ -1219,6 +1208,23 @@ class JvmtiFunctions { return error; } + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(AllocUtil::GetGlobalJvmtiAllocationState), + "com.android.art.alloc.get_global_jvmti_allocation_state", + "Returns the total amount of memory currently allocated by all jvmtiEnvs through the" + " 'Allocate' jvmti function. This does not include any memory that has been deallocated" + " through the 'Deallocate' function. This number is approximate and might not correspond" + " exactly to the sum of the sizes of all not freed allocations.", + 1, + { // NOLINT [whitespace/braces] [4] + { "currently_allocated", JVMTI_KIND_OUT, JVMTI_TYPE_JLONG, false}, + }, + 1, + { ERR(NULL_POINTER) }); + if (error != ERR(NONE)) { + return error; + } + // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index b5f12191e6..c63e50252b 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -34,6 +34,7 @@ #include <memory> #include <type_traits> +#include <unordered_map> #include <unordered_set> #include <jni.h> @@ -46,10 +47,12 @@ #include "java_vm_ext.h" #include "jni_env_ext.h" #include "jvmti.h" +#include "ti_breakpoint.h" namespace art { class ArtField; -} +class ArtMethod; +} // namespace art namespace openjdkjvmti { @@ -76,6 +79,9 @@ struct ArtJvmTiEnv : public jvmtiEnv { std::unordered_set<art::ArtField*> access_watched_fields; std::unordered_set<art::ArtField*> modify_watched_fields; + // Set of breakpoints is unique to each jvmtiEnv. + std::unordered_set<Breakpoint> breakpoints; + ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler); static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) { @@ -210,7 +216,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_tag_objects = 1, .can_generate_field_modification_events = 1, .can_generate_field_access_events = 1, - .can_get_bytecodes = 0, + .can_get_bytecodes = 1, .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, @@ -223,10 +229,10 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_source_debug_extension = 1, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, - .can_generate_single_step_events = 0, + .can_generate_single_step_events = 1, .can_generate_exception_events = 0, .can_generate_frame_pop_events = 0, - .can_generate_breakpoint_events = 0, + .can_generate_breakpoint_events = 1, .can_suspend = 0, .can_redefine_any_class = 0, .can_get_current_thread_cpu_time = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index af99233f90..f30d7cecb3 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -22,6 +22,7 @@ #include "events.h" #include "jni_internal.h" #include "ScopedLocalRef.h" +#include "ti_breakpoint.h" #include "art_jvmti.h" @@ -217,6 +218,32 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A } } +// Need to give custom specializations for Breakpoint since it needs to filter out which particular +// methods/dex_pcs agents get notified on. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID jmethod, + jlocation location) const { + art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); + for (ArtJvmTiEnv* env : envs) { + // Search for a breakpoint on this particular method and location. + if (env != nullptr && + ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) && + env->breakpoints.find({method, location}) != env->breakpoints.end()) { + // We temporarily clear any pending exceptions so the event can call back into java code. + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env); + (*callback)(env, jnienv, jni_thread, jmethod, location); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } +} + // Need to give custom specializations for FieldAccess and FieldModification since they need to // filter out which particular fields agents want to get notified on. // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 989b9af591..f749daa918 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -423,14 +423,30 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat } } - // Call-back for when the dex pc moves in a method. We don't currently have any events associated - // with this. - void DexPcMoved(art::Thread* self ATTRIBUTE_UNUSED, + // Call-back for when the dex pc moves in a method. + void DexPcMoved(art::Thread* self, art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, - art::ArtMethod* method ATTRIBUTE_UNUSED, - uint32_t new_dex_pc ATTRIBUTE_UNUSED) + art::ArtMethod* method, + uint32_t new_dex_pc) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - return; + DCHECK(!method->IsRuntimeMethod()); + // Default methods might be copied to multiple classes. We need to get the canonical version of + // this method so that we can check for breakpoints correctly. + // TODO We should maybe do this on other events to ensure that we are consistent WRT default + // methods. This could interact with obsolete methods if we ever let interface redefinition + // happen though. + method = method->GetCanonicalMethod(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + jmethodID jmethod = art::jni::EncodeArtMethod(method); + jlocation location = static_cast<jlocation>(new_dex_pc); + // Step event is reported first according to the spec. + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) { + RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location); + } + // Next we do the Breakpoint events. The Dispatch code will filter the individual + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) { + RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location); + } } // Call-back for when we read from a field. @@ -563,6 +579,9 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { return art::instrumentation::Instrumentation::kFieldWritten; case ArtJvmtiEvent::kFieldAccess: return art::instrumentation::Instrumentation::kFieldRead; + case ArtJvmtiEvent::kBreakpoint: + case ArtJvmtiEvent::kSingleStep: + return art::instrumentation::Instrumentation::kDexPcMoved; default: LOG(FATAL) << "Unknown event "; return 0; @@ -580,6 +599,8 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, art::gc::kCollectorTypeInstrumentation); art::ScopedSuspendAll ssa("jvmti method tracing installation"); if (enable) { + // TODO Depending on the features being used we should be able to avoid deoptimizing everything + // like we do here. if (!instr->AreAllMethodsDeoptimized()) { instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true); } @@ -601,6 +622,17 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { SetupGcPauseTracking(gc_pause_listener_.get(), event, enable); return; + case ArtJvmtiEvent::kBreakpoint: + case ArtJvmtiEvent::kSingleStep: { + ArtJvmtiEvent other = (event == ArtJvmtiEvent::kBreakpoint) ? ArtJvmtiEvent::kSingleStep + : ArtJvmtiEvent::kBreakpoint; + // We only need to do anything if there isn't already a listener installed/held-on by the + // other jvmti event that uses DexPcMoved. + if (!IsEventEnabledAnywhere(other)) { + SetupTraceListener(method_trace_listener_.get(), event, enable); + } + return; + } case ArtJvmtiEvent::kMethodEntry: case ArtJvmtiEvent::kMethodExit: case ArtJvmtiEvent::kFieldAccess: diff --git a/runtime/openjdkjvmti/jvmti_allocator.h b/runtime/openjdkjvmti/jvmti_allocator.h index 1225c143f9..44b1cb1c20 100644 --- a/runtime/openjdkjvmti/jvmti_allocator.h +++ b/runtime/openjdkjvmti/jvmti_allocator.h @@ -36,6 +36,8 @@ #include "base/macros.h" #include "jvmti.h" +#include "ti_allocator.h" + namespace openjdkjvmti { template <typename T> class JvmtiAllocator; @@ -53,6 +55,7 @@ class JvmtiAllocator<void> { }; explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {} + explicit JvmtiAllocator() : env_(nullptr) {} template <typename U> JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit @@ -89,6 +92,7 @@ class JvmtiAllocator { }; explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {} + explicit JvmtiAllocator() : env_(nullptr) {} template <typename U> JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit @@ -108,8 +112,8 @@ class JvmtiAllocator { pointer allocate(size_type n, JvmtiAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) { DCHECK_LE(n, max_size()); if (env_ == nullptr) { - T* result = reinterpret_cast<T*>(malloc(n * sizeof(T))); - CHECK(result != nullptr || n == 0u); // Abort if malloc() fails. + T* result = reinterpret_cast<T*>(AllocUtil::AllocateImpl(n * sizeof(T))); + CHECK(result != nullptr || n == 0u); // Abort if AllocateImpl() fails. return result; } else { unsigned char* result; @@ -120,7 +124,7 @@ class JvmtiAllocator { } void deallocate(pointer p, size_type n ATTRIBUTE_UNUSED) { if (env_ == nullptr) { - free(p); + AllocUtil::DeallocateImpl(reinterpret_cast<unsigned char*>(p)); } else { jvmtiError dealloc_error = env_->Deallocate(reinterpret_cast<unsigned char*>(p)); CHECK(dealloc_error == JVMTI_ERROR_NONE); diff --git a/runtime/openjdkjvmti/jvmti_weak_table.h b/runtime/openjdkjvmti/jvmti_weak_table.h index 01c24b1917..a5175a42ba 100644 --- a/runtime/openjdkjvmti/jvmti_weak_table.h +++ b/runtime/openjdkjvmti/jvmti_weak_table.h @@ -40,6 +40,7 @@ #include "gc_root-inl.h" #include "globals.h" #include "jvmti.h" +#include "jvmti_allocator.h" #include "mirror/object.h" #include "thread-current-inl.h" @@ -191,7 +192,7 @@ class JvmtiWeakTable : public art::gc::SystemWeakHolder { REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(allow_disallow_lock_); - template <typename Storage, class Allocator = std::allocator<T>> + template <typename Storage, class Allocator = JvmtiAllocator<T>> struct ReleasableContainer; struct HashGcRoot { @@ -209,10 +210,12 @@ class JvmtiWeakTable : public art::gc::SystemWeakHolder { } }; + using TagAllocator = JvmtiAllocator<std::pair<const art::GcRoot<art::mirror::Object>, T>>; std::unordered_map<art::GcRoot<art::mirror::Object>, T, HashGcRoot, - EqGcRoot> tagged_objects_ + EqGcRoot, + TagAllocator> tagged_objects_ GUARDED_BY(allow_disallow_lock_) GUARDED_BY(art::Locks::mutator_lock_); // To avoid repeatedly scanning the whole table, remember if we did that since the last sweep. diff --git a/runtime/openjdkjvmti/ti_allocator.cc b/runtime/openjdkjvmti/ti_allocator.cc new file mode 100644 index 0000000000..8a0237d6c3 --- /dev/null +++ b/runtime/openjdkjvmti/ti_allocator.cc @@ -0,0 +1,89 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_allocator.h" + +#include <malloc.h> +#include <atomic> + +#include "art_jvmti.h" +#include "base/enums.h" + +namespace openjdkjvmti { + +std::atomic<jlong> AllocUtil::allocated; + +jvmtiError AllocUtil::GetGlobalJvmtiAllocationState(jvmtiEnv* env ATTRIBUTE_UNUSED, + jlong* allocated_ptr) { + if (allocated_ptr == nullptr) { + return ERR(NULL_POINTER); + } + *allocated_ptr = allocated.load(); + return OK; +} + +jvmtiError AllocUtil::Allocate(jvmtiEnv* env ATTRIBUTE_UNUSED, + jlong size, + unsigned char** mem_ptr) { + if (size < 0) { + return ERR(ILLEGAL_ARGUMENT); + } else if (size == 0) { + *mem_ptr = nullptr; + return OK; + } + *mem_ptr = AllocateImpl(size); + if (UNLIKELY(*mem_ptr == nullptr)) { + return ERR(OUT_OF_MEMORY); + } + return OK; +} + +unsigned char* AllocUtil::AllocateImpl(jlong size) { + unsigned char* ret = size != 0 ? reinterpret_cast<unsigned char*>(malloc(size)) : nullptr; + if (LIKELY(ret != nullptr)) { + allocated += malloc_usable_size(ret); + } + return ret; +} + +jvmtiError AllocUtil::Deallocate(jvmtiEnv* env ATTRIBUTE_UNUSED, unsigned char* mem) { + DeallocateImpl(mem); + return OK; +} + +void AllocUtil::DeallocateImpl(unsigned char* mem) { + if (mem != nullptr) { + allocated -= malloc_usable_size(mem); + free(mem); + } +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_allocator.h b/runtime/openjdkjvmti/ti_allocator.h new file mode 100644 index 0000000000..35575c3884 --- /dev/null +++ b/runtime/openjdkjvmti/ti_allocator.h @@ -0,0 +1,65 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_ + +#include "jni.h" +#include "jvmti.h" + +#include <atomic> +#include <memory> + +namespace openjdkjvmti { + +template<typename T> +class JvmtiAllocator; + +class AllocUtil { + public: + static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr); + static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem); + static jvmtiError GetGlobalJvmtiAllocationState(jvmtiEnv* env, jlong* total_allocated); + + private: + static void DeallocateImpl(unsigned char* mem); + static unsigned char* AllocateImpl(jlong size); + + static std::atomic<jlong> allocated; + + template <typename T> + friend class JvmtiAllocator; +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_ + diff --git a/runtime/openjdkjvmti/ti_breakpoint.cc b/runtime/openjdkjvmti/ti_breakpoint.cc new file mode 100644 index 0000000000..6d0e2c60c1 --- /dev/null +++ b/runtime/openjdkjvmti/ti_breakpoint.cc @@ -0,0 +1,114 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <functional> + +#include "ti_breakpoint.h" + +#include "art_jvmti.h" +#include "art_method-inl.h" +#include "base/enums.h" +#include "dex_file_annotations.h" +#include "events-inl.h" +#include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/object_array-inl.h" +#include "modifiers.h" +#include "runtime_callbacks.h" +#include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" +#include "thread-current-inl.h" +#include "thread_list.h" +#include "ti_phase.h" + +namespace openjdkjvmti { + +size_t Breakpoint::hash() const { + return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_)) + ^ std::hash<jlocation> {}(location_); +} + +Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_(loc) { + DCHECK(!m->IsDefault() || !m->IsCopied() || !m->IsInvokable()) + << "Flags are: 0x" << std::hex << m->GetAccessFlags(); +} + +void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) { + std::vector<Breakpoint> to_remove; + for (const Breakpoint& b : env->breakpoints) { + if (b.GetMethod()->GetDeclaringClass() == klass) { + to_remove.push_back(b); + } + } + for (const Breakpoint& b : to_remove) { + auto it = env->breakpoints.find(b); + DCHECK(it != env->breakpoints.end()); + env->breakpoints.erase(it); + } +} + +jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + // Need to get mutator_lock_ so we can find the interface version of any default methods. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod(); + if (location < 0 || static_cast<uint32_t>(location) >= + art_method->GetCodeItem()->insns_size_in_code_units_) { + return ERR(INVALID_LOCATION); + } + auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location}); + if (!res_pair.second) { + // Didn't get inserted because it's already present! + return ERR(DUPLICATE); + } + return OK; +} + +jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + // Need to get mutator_lock_ so we can find the interface version of any default methods. + art::ScopedObjectAccess soa(art::Thread::Current()); + auto pos = env->breakpoints.find( + /* Breakpoint */ {art::jni::DecodeArtMethod(method)->GetCanonicalMethod(), location}); + if (pos == env->breakpoints.end()) { + return ERR(NOT_FOUND); + } + env->breakpoints.erase(pos); + return OK; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_breakpoint.h b/runtime/openjdkjvmti/ti_breakpoint.h new file mode 100644 index 0000000000..c3dbef7baf --- /dev/null +++ b/runtime/openjdkjvmti/ti_breakpoint.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ + +#include "jni.h" +#include "jvmti.h" + +#include "base/mutex.h" + +namespace art { +class ArtMethod; +namespace mirror { +class Class; +} // namespace mirror +} // namespace art + +namespace openjdkjvmti { + +struct ArtJvmTiEnv; + +class Breakpoint { + public: + Breakpoint(art::ArtMethod* m, jlocation loc); + + // Get the hash code of this breakpoint. + size_t hash() const; + + bool operator==(const Breakpoint& other) const { + return method_ == other.method_ && location_ == other.location_; + } + + art::ArtMethod* GetMethod() const { + return method_; + } + + jlocation GetLocation() const { + return location_; + } + + private: + art::ArtMethod* method_; + jlocation location_; +}; + +class BreakpointUtil { + public: + static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location); + static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location); + // Used by class redefinition to remove breakpoints on redefined classes. + static void RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) + REQUIRES(art::Locks::mutator_lock_); +}; + +} // namespace openjdkjvmti + +namespace std { +template<> struct hash<openjdkjvmti::Breakpoint> { + size_t operator()(const openjdkjvmti::Breakpoint& b) const { + return b.hash(); + } +}; + +} // namespace std +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 0ac08d9cb8..b8e79555ae 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -598,6 +598,13 @@ jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env, return ERR(INVALID_CLASS); } + // Check if this class is a temporary class object used for loading. Since we are seeing it the + // class must not have been prepared yet since otherwise the fixup would have gotten the jobject + // to point to the final class object. + if (klass->IsTemp() || klass->IsRetired()) { + return ERR(CLASS_NOT_PREPARED); + } + if (field_count_ptr == nullptr || fields_ptr == nullptr) { return ERR(NULL_POINTER); } @@ -639,6 +646,13 @@ jvmtiError ClassUtil::GetClassMethods(jvmtiEnv* env, return ERR(INVALID_CLASS); } + // Check if this class is a temporary class object used for loading. Since we are seeing it the + // class must not have been prepared yet since otherwise the fixup would have gotten the jobject + // to point to the final class object. + if (klass->IsTemp() || klass->IsRetired()) { + return ERR(CLASS_NOT_PREPARED); + } + if (method_count_ptr == nullptr || methods_ptr == nullptr) { return ERR(NULL_POINTER); } diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc index beb639e208..9b5b964a4d 100644 --- a/runtime/openjdkjvmti/ti_method.cc +++ b/runtime/openjdkjvmti/ti_method.cc @@ -91,6 +91,40 @@ void MethodUtil::Unregister() { runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback); } +jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env, + jmethodID method, + jint* size_ptr, + unsigned char** bytecode_ptr) { + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + art::ArtMethod* art_method = art::jni::DecodeArtMethod(method); + + if (art_method->IsNative()) { + return ERR(NATIVE_METHOD); + } + + if (size_ptr == nullptr || bytecode_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + const art::DexFile::CodeItem* code_item = art_method->GetCodeItem(); + if (code_item == nullptr) { + *size_ptr = 0; + *bytecode_ptr = nullptr; + return OK; + } + // 2 bytes per instruction for dex code. + *size_ptr = code_item->insns_size_in_code_units_ * 2; + jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr); + if (err != OK) { + return err; + } + memcpy(*bytecode_ptr, code_item->insns_, *size_ptr); + return OK; +} + jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED, jmethodID method, jint* size_ptr) { diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h index cc161c8fed..d95a81b63b 100644 --- a/runtime/openjdkjvmti/ti_method.h +++ b/runtime/openjdkjvmti/ti_method.h @@ -44,6 +44,11 @@ class MethodUtil { static void Register(EventHandler* event_handler); static void Unregister(); + static jvmtiError GetBytecodes(jvmtiEnv* env, + jmethodID method, + jint* count_ptr, + unsigned char** bytecodes); + static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr); static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr); diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 5422f48664..debee913ee 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -64,6 +64,7 @@ #include "object_lock.h" #include "runtime.h" #include "ScopedLocalRef.h" +#include "ti_breakpoint.h" #include "ti_class_loader.h" #include "transform.h" #include "verifier/method_verifier.h" @@ -380,7 +381,7 @@ jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, art::jit::ScopedJitSuspend suspend_jit; // Get shared mutator lock so we can lock all the classes. art::ScopedObjectAccess soa(self); - Redefiner r(runtime, self, error_msg); + Redefiner r(env, runtime, self, error_msg); for (const ArtClassDefinition& def : definitions) { // Only try to transform classes that have been modified. if (def.IsModified()) { @@ -1200,6 +1201,10 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( return true; } +void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() { + BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass()); +} + void Redefiner::ClassRedefinition::UnregisterBreakpoints() { DCHECK(art::Dbg::IsDebuggerActive()); art::JDWP::JdwpState* state = art::Dbg::GetJdwpState(); @@ -1342,6 +1347,7 @@ jvmtiError Redefiner::Run() { // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile()); + redef.UnregisterJvmtiBreakpoints(); } RestoreObsoleteMethodMapsIfUnneeded(holder); // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index ec4a8b2789..27d7c3d726 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -199,6 +199,8 @@ class Redefiner { void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_); + // This should be done with all threads suspended. + void UnregisterJvmtiBreakpoints() REQUIRES(art::Locks::mutator_lock_); private: Redefiner* driver_; @@ -208,6 +210,7 @@ class Redefiner { art::ArrayRef<const unsigned char> original_dex_file_; }; + ArtJvmTiEnv* env_; jvmtiError result_; art::Runtime* runtime_; art::Thread* self_; @@ -216,10 +219,12 @@ class Redefiner { // mirror::Class difficult and confusing. std::string* error_msg_; - Redefiner(art::Runtime* runtime, + Redefiner(ArtJvmTiEnv* env, + art::Runtime* runtime, art::Thread* self, std::string* error_msg) - : result_(ERR(INTERNAL)), + : env_(env), + result_(ERR(INTERNAL)), runtime_(runtime), self_(self), redefinitions_(), diff --git a/runtime/runtime_linux.cc b/runtime/runtime_linux.cc index 424dcf85cf..6313553255 100644 --- a/runtime/runtime_linux.cc +++ b/runtime/runtime_linux.cc @@ -20,6 +20,7 @@ #include <iostream> +#include "base/memory_tool.h" #include "runtime_common.h" namespace art { @@ -63,6 +64,16 @@ void HandleUnexpectedSignalLinux(int signal_number, siginfo_t* info, void* raw_c } void Runtime::InitPlatformSignalHandlers() { + constexpr bool kIsASAN = +#ifdef ADDRESS_SANITIZER + true; +#else + false; +#endif + if (!kIsTargetBuild && kIsASAN) { + // (Temporarily) try and let ASAN print abort stacks, as our code sometimes fails. b/31098551 + return; + } // On the host, we don't have debuggerd to dump a stack for us when something unexpected happens. InitPlatformSignalHandlersCommon(HandleUnexpectedSignalLinux, nullptr, diff --git a/runtime/suspend_reason.h b/runtime/suspend_reason.h new file mode 100644 index 0000000000..27c4d3207b --- /dev/null +++ b/runtime/suspend_reason.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ART_RUNTIME_SUSPEND_REASON_H_ +#define ART_RUNTIME_SUSPEND_REASON_H_ + +#include <ostream> + +namespace art { + +// The various reasons that we might be suspending a thread. +enum class SuspendReason { + // Suspending for internal reasons (e.g. GC, stack trace, etc.). + // TODO Split this into more descriptive sections. + kInternal, + // Suspending for debugger (code in Dbg::*, runtime/jdwp/, etc.). + kForDebugger, +}; + +std::ostream& operator<<(std::ostream& os, const SuspendReason& thread); + +} // namespace art + +#endif // ART_RUNTIME_SUSPEND_REASON_H_ diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h index 7da15d9f4c..95608b5f63 100644 --- a/runtime/thread-inl.h +++ b/runtime/thread-inl.h @@ -330,12 +330,12 @@ inline void Thread::PoisonObjectPointersIfDebug() { inline bool Thread::ModifySuspendCount(Thread* self, int delta, AtomicInteger* suspend_barrier, - bool for_debugger) { + SuspendReason reason) { if (delta > 0 && ((kUseReadBarrier && this != self) || suspend_barrier != nullptr)) { // When delta > 0 (requesting a suspend), ModifySuspendCountInternal() may fail either if // active_suspend_barriers is full or we are in the middle of a thread flip. Retry in a loop. while (true) { - if (LIKELY(ModifySuspendCountInternal(self, delta, suspend_barrier, for_debugger))) { + if (LIKELY(ModifySuspendCountInternal(self, delta, suspend_barrier, reason))) { return true; } else { // Failure means the list of active_suspend_barriers is full or we are in the middle of a @@ -354,7 +354,7 @@ inline bool Thread::ModifySuspendCount(Thread* self, } } } else { - return ModifySuspendCountInternal(self, delta, suspend_barrier, for_debugger); + return ModifySuspendCountInternal(self, delta, suspend_barrier, reason); } } diff --git a/runtime/thread.cc b/runtime/thread.cc index be1614b3cc..36ecd3398c 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -549,27 +549,47 @@ void Thread::InstallImplicitProtection() { // // We map in the stack by reading every page from the stack bottom (highest address) // to the stack top. (We then madvise this away.) This must be done by reading from the - // current stack pointer downwards. Any access more than a page below the current SP - // might cause a segv. - // TODO: This comment may be out of date. It seems possible to speed this up. As - // this is normally done once in the zygote on startup, ignore for now. + // current stack pointer downwards. // - // AddressSanitizer does not like the part of this functions that reads every stack page. - // Looks a lot like an out-of-bounds access. + // Accesses too far below the current machine register corresponding to the stack pointer (e.g., + // ESP on x86[-32], SP on ARM) might cause a SIGSEGV (at least on x86 with newer kernels). We + // thus have to move the stack pointer. We do this portably by using a recursive function with a + // large stack frame size. - // (Defensively) first remove the protection on the protected region as will want to read + // (Defensively) first remove the protection on the protected region as we'll want to read // and write it. Ignore errors. UnprotectStack(); VLOG(threads) << "Need to map in stack for thread at " << std::hex << static_cast<void*>(pregion); - // Read every page from the high address to the low. - volatile uint8_t dont_optimize_this; - UNUSED(dont_optimize_this); - for (uint8_t* p = stack_top; p >= pregion; p -= kPageSize) { - dont_optimize_this = *p; - } + struct RecurseDownStack { + // This function has an intentionally large stack size. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + NO_INLINE + static void Touch(uintptr_t target) { + volatile size_t zero = 0; + // Use a large local volatile array to ensure a large frame size. Do not use anything close + // to a full page for ASAN. It would be nice to ensure the frame size is at most a page, but + // there is no pragma support for this. + // Note: for ASAN we need to shrink the array a bit, as there's other overhead. + constexpr size_t kAsanMultiplier = +#ifdef ADDRESS_SANITIZER + 2u; +#else + 1u; +#endif + volatile char space[kPageSize - (kAsanMultiplier * 256)]; + char sink ATTRIBUTE_UNUSED = space[zero]; + if (reinterpret_cast<uintptr_t>(space) >= target + kPageSize) { + Touch(target); + } + zero *= 2; // Try to avoid tail recursion. + } +#pragma GCC diagnostic pop + }; + RecurseDownStack::Touch(reinterpret_cast<uintptr_t>(pregion)); VLOG(threads) << "(again) installing stack protected region at " << std::hex << static_cast<void*>(pregion) << " to " << @@ -1178,10 +1198,10 @@ static void UnsafeLogFatalForSuspendCount(Thread* self, Thread* thread) NO_THREA bool Thread::ModifySuspendCountInternal(Thread* self, int delta, AtomicInteger* suspend_barrier, - bool for_debugger) { + SuspendReason reason) { if (kIsDebugBuild) { DCHECK(delta == -1 || delta == +1 || delta == -tls32_.debug_suspend_count) - << delta << " " << tls32_.debug_suspend_count << " " << this; + << reason << " " << delta << " " << tls32_.debug_suspend_count << " " << this; DCHECK_GE(tls32_.suspend_count, tls32_.debug_suspend_count) << this; Locks::thread_suspend_count_lock_->AssertHeld(self); if (this != self && !IsSuspended()) { @@ -1217,8 +1237,12 @@ bool Thread::ModifySuspendCountInternal(Thread* self, } tls32_.suspend_count += delta; - if (for_debugger) { - tls32_.debug_suspend_count += delta; + switch (reason) { + case SuspendReason::kForDebugger: + tls32_.debug_suspend_count += delta; + break; + case SuspendReason::kInternal: + break; } if (tls32_.suspend_count == 0) { @@ -1458,7 +1482,7 @@ bool Thread::RequestSynchronousCheckpoint(Closure* function) { { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); - if (!ModifySuspendCount(self, +1, nullptr, false)) { + if (!ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal)) { // Just retry the loop. sched_yield(); continue; @@ -1483,7 +1507,7 @@ bool Thread::RequestSynchronousCheckpoint(Closure* function) { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); DCHECK_NE(GetState(), ThreadState::kRunnable); - bool updated = ModifySuspendCount(self, -1, nullptr, false); + bool updated = ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); } diff --git a/runtime/thread.h b/runtime/thread.h index 770173e47e..e785ddc803 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -40,6 +40,7 @@ #include "managed_stack.h" #include "offsets.h" #include "runtime_stats.h" +#include "suspend_reason.h" #include "thread_state.h" class BacktraceMap; @@ -244,7 +245,7 @@ class Thread { bool ModifySuspendCount(Thread* self, int delta, AtomicInteger* suspend_barrier, - bool for_debugger) + SuspendReason reason) WARN_UNUSED REQUIRES(Locks::thread_suspend_count_lock_); @@ -1300,7 +1301,7 @@ class Thread { bool ModifySuspendCountInternal(Thread* self, int delta, AtomicInteger* suspend_barrier, - bool for_debugger) + SuspendReason reason) WARN_UNUSED REQUIRES(Locks::thread_suspend_count_lock_); diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index 95aba79ed7..fc767ed899 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -332,7 +332,7 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function, Closure* callback // Spurious fail, try again. continue; } - bool updated = thread->ModifySuspendCount(self, +1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal); DCHECK(updated); suspended_count_modified_threads.push_back(thread); break; @@ -375,7 +375,7 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function, Closure* callback checkpoint_function->Run(thread); { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); - bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); } } @@ -583,7 +583,7 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, if ((state == kWaitingForGcThreadFlip || thread->IsTransitioningToRunnable()) && thread->GetSuspendCount() == 1) { // The thread will resume right after the broadcast. - bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); ++runnable_thread_count; } else { @@ -617,7 +617,7 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, TimingLogger::ScopedTiming split4("ResumeOtherThreads", collector->GetTimings()); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (const auto& thread : other_threads) { - bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); } Thread::resume_cond_->Broadcast(self); @@ -688,7 +688,7 @@ void ThreadList::SuspendAll(const char* cause, bool long_suspend) { void ThreadList::SuspendAllInternal(Thread* self, Thread* ignore1, Thread* ignore2, - bool debug_suspend) { + SuspendReason reason) { Locks::mutator_lock_->AssertNotExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); Locks::thread_suspend_count_lock_->AssertNotHeld(self); @@ -718,7 +718,7 @@ void ThreadList::SuspendAllInternal(Thread* self, MutexLock mu2(self, *Locks::thread_suspend_count_lock_); // Update global suspend all state for attaching threads. ++suspend_all_count_; - if (debug_suspend) { + if (reason == SuspendReason::kForDebugger) { ++debug_suspend_all_count_; } pending_threads.StoreRelaxed(list_.size() - num_ignored); @@ -728,7 +728,7 @@ void ThreadList::SuspendAllInternal(Thread* self, continue; } VLOG(threads) << "requesting thread suspend: " << *thread; - bool updated = thread->ModifySuspendCount(self, +1, &pending_threads, debug_suspend); + bool updated = thread->ModifySuspendCount(self, +1, &pending_threads, reason); DCHECK(updated); // Must install the pending_threads counter first, then check thread->IsSuspend() and clear @@ -807,7 +807,7 @@ void ThreadList::ResumeAll() { if (thread == self) { continue; } - bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); } @@ -828,14 +828,13 @@ void ThreadList::ResumeAll() { } } -void ThreadList::Resume(Thread* thread, bool for_debugger) { +void ThreadList::Resume(Thread* thread, SuspendReason reason) { // This assumes there was an ATRACE_BEGIN when we suspended the thread. ATRACE_END(); Thread* self = Thread::Current(); DCHECK_NE(thread, self); - VLOG(threads) << "Resume(" << reinterpret_cast<void*>(thread) << ") starting..." - << (for_debugger ? " (debugger)" : ""); + VLOG(threads) << "Resume(" << reinterpret_cast<void*>(thread) << ") starting..." << reason; { // To check Contains. @@ -850,7 +849,7 @@ void ThreadList::Resume(Thread* thread, bool for_debugger) { << ") thread not within thread list"; return; } - bool updated = thread->ModifySuspendCount(self, -1, nullptr, for_debugger); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, reason); DCHECK(updated); } @@ -882,7 +881,7 @@ static void ThreadSuspendByPeerWarning(Thread* self, Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool request_suspension, - bool debug_suspension, + SuspendReason reason, bool* timed_out) { const uint64_t start_time = NanoTime(); useconds_t sleep_us = kThreadSuspendInitialSleepUs; @@ -910,7 +909,7 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool updated = suspended_thread->ModifySuspendCount(soa.Self(), -1, nullptr, - debug_suspension); + reason); DCHECK(updated); } ThreadSuspendByPeerWarning(self, @@ -937,7 +936,7 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, } CHECK(suspended_thread == nullptr); suspended_thread = thread; - bool updated = suspended_thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + bool updated = suspended_thread->ModifySuspendCount(self, +1, nullptr, reason); DCHECK(updated); request_suspension = false; } else { @@ -973,7 +972,7 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool updated = suspended_thread->ModifySuspendCount(soa.Self(), -1, nullptr, - debug_suspension); + reason); DCHECK(updated); } *timed_out = true; @@ -1002,7 +1001,7 @@ static void ThreadSuspendByThreadIdWarning(LogSeverity severity, } Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, - bool debug_suspension, + SuspendReason reason, bool* timed_out) { const uint64_t start_time = NanoTime(); useconds_t sleep_us = kThreadSuspendInitialSleepUs; @@ -1047,7 +1046,7 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, // which will allow this thread to be suspended. continue; } - bool updated = thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, reason); DCHECK(updated); suspended_thread = thread; } else { @@ -1079,7 +1078,7 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, "Thread suspension timed out", thread_id); if (suspended_thread != nullptr) { - bool updated = thread->ModifySuspendCount(soa.Self(), -1, nullptr, debug_suspension); + bool updated = thread->ModifySuspendCount(soa.Self(), -1, nullptr, reason); DCHECK(updated); } *timed_out = true; @@ -1114,7 +1113,7 @@ void ThreadList::SuspendAllForDebugger() { VLOG(threads) << *self << " SuspendAllForDebugger starting..."; - SuspendAllInternal(self, self, debug_thread, true); + SuspendAllInternal(self, self, debug_thread, SuspendReason::kForDebugger); // Block on the mutator lock until all Runnable threads release their share of access then // immediately unlock again. #if HAVE_TIMED_RWLOCK @@ -1157,7 +1156,7 @@ void ThreadList::SuspendSelfForDebugger() { // to ensure that we're the only one fiddling with the suspend count // though. MutexLock mu(self, *Locks::thread_suspend_count_lock_); - bool updated = self->ModifySuspendCount(self, +1, nullptr, true); + bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kForDebugger); DCHECK(updated); CHECK_GT(self->GetSuspendCount(), 0); @@ -1242,7 +1241,7 @@ void ThreadList::ResumeAllForDebugger() { continue; } VLOG(threads) << "requesting thread resume: " << *thread; - bool updated = thread->ModifySuspendCount(self, -1, nullptr, true); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kForDebugger); DCHECK(updated); } } @@ -1275,7 +1274,7 @@ void ThreadList::UndoDebuggerSuspensions() { bool suspended = thread->ModifySuspendCount(self, -thread->GetDebugSuspendCount(), nullptr, - true); + SuspendReason::kForDebugger); DCHECK(suspended); } } @@ -1333,7 +1332,7 @@ void ThreadList::SuspendAllDaemonThreadsForShutdown() { // daemons. CHECK(thread->IsDaemon()) << *thread; if (thread != self) { - bool updated = thread->ModifySuspendCount(self, +1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal); DCHECK(updated); ++daemons_left; } @@ -1394,11 +1393,11 @@ void ThreadList::Register(Thread* self) { // Modify suspend count in increments of 1 to maintain invariants in ModifySuspendCount. While // this isn't particularly efficient the suspend counts are most commonly 0 or 1. for (int delta = debug_suspend_all_count_; delta > 0; delta--) { - bool updated = self->ModifySuspendCount(self, +1, nullptr, true); + bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kForDebugger); DCHECK(updated); } for (int delta = suspend_all_count_ - debug_suspend_all_count_; delta > 0; delta--) { - bool updated = self->ModifySuspendCount(self, +1, nullptr, false); + bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal); DCHECK(updated); } CHECK(!Contains(self)); @@ -1495,12 +1494,12 @@ void ThreadList::VisitRootsForSuspendedThreads(RootVisitor* visitor) { MutexLock mu(self, *Locks::thread_list_lock_); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (Thread* thread : list_) { - bool suspended = thread->ModifySuspendCount(self, +1, nullptr, false); + bool suspended = thread->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal); DCHECK(suspended); if (thread == self || thread->IsSuspended()) { threads_to_visit.push_back(thread); } else { - bool resumed = thread->ModifySuspendCount(self, -1, nullptr, false); + bool resumed = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(resumed); } } @@ -1516,7 +1515,7 @@ void ThreadList::VisitRootsForSuspendedThreads(RootVisitor* visitor) { { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (Thread* thread : threads_to_visit) { - bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal); DCHECK(updated); } } diff --git a/runtime/thread_list.h b/runtime/thread_list.h index 92702c6498..41c5e328b0 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -23,6 +23,7 @@ #include "base/time_utils.h" #include "base/value_object.h" #include "jni.h" +#include "suspend_reason.h" #include <bitset> #include <list> @@ -64,7 +65,7 @@ class ThreadList { void ResumeAll() REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_) UNLOCK_FUNCTION(Locks::mutator_lock_); - void Resume(Thread* thread, bool for_debugger = false) + void Resume(Thread* thread, SuspendReason reason = SuspendReason::kInternal) REQUIRES(!Locks::thread_suspend_count_lock_); // Suspends all threads and gets exclusive access to the mutator_lock_. @@ -81,7 +82,9 @@ class ThreadList { // If the thread should be suspended then value of request_suspension should be true otherwise // the routine will wait for a previous suspend request. If the suspension times out then *timeout // is set to true. - Thread* SuspendThreadByPeer(jobject peer, bool request_suspension, bool debug_suspension, + Thread* SuspendThreadByPeer(jobject peer, + bool request_suspension, + SuspendReason reason, bool* timed_out) REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, @@ -91,7 +94,7 @@ class ThreadList { // thread on success else null. The thread id is used to identify the thread to avoid races with // the thread terminating. Note that as thread ids are recycled this may not suspend the expected // thread, that may be terminating. If the suspension times out then *timeout is set to true. - Thread* SuspendThreadByThreadId(uint32_t thread_id, bool debug_suspension, bool* timed_out) + Thread* SuspendThreadByThreadId(uint32_t thread_id, SuspendReason reason, bool* timed_out) REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); @@ -198,7 +201,7 @@ class ThreadList { void SuspendAllInternal(Thread* self, Thread* ignore1, Thread* ignore2 = nullptr, - bool debug_suspend = false) + SuspendReason reason = SuspendReason::kInternal) REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); void AssertThreadsAreSuspended(Thread* self, Thread* ignore1, Thread* ignore2 = nullptr) diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 9b652553df..efb02f6205 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -4159,7 +4159,8 @@ bool MethodVerifier::CheckCallSite(uint32_t call_site_idx) { const DexFile::MethodHandleItem& mh = dex_file_->GetMethodHandle(method_handle_idx); if (mh.method_handle_type_ != static_cast<uint16_t>(DexFile::MethodHandleType::kInvokeStatic)) { Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx - << " argument 0 method handle type is not InvokeStatic"; + << " argument 0 method handle type is not InvokeStatic: " + << mh.method_handle_type_; return false; } diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index 24f194b5ee..8d505e2582 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -37,6 +37,7 @@ namespace art { jclass WellKnownClasses::dalvik_annotation_optimization_CriticalNative; jclass WellKnownClasses::dalvik_annotation_optimization_FastNative; jclass WellKnownClasses::dalvik_system_BaseDexClassLoader; +jclass WellKnownClasses::dalvik_system_DelegateLastClassLoader; jclass WellKnownClasses::dalvik_system_DexClassLoader; jclass WellKnownClasses::dalvik_system_DexFile; jclass WellKnownClasses::dalvik_system_DexPathList; @@ -270,6 +271,7 @@ void WellKnownClasses::Init(JNIEnv* env) { CacheClass(env, "dalvik/annotation/optimization/CriticalNative"); dalvik_annotation_optimization_FastNative = CacheClass(env, "dalvik/annotation/optimization/FastNative"); dalvik_system_BaseDexClassLoader = CacheClass(env, "dalvik/system/BaseDexClassLoader"); + dalvik_system_DelegateLastClassLoader = CacheClass(env, "dalvik/system/DelegateLastClassLoader"); dalvik_system_DexClassLoader = CacheClass(env, "dalvik/system/DexClassLoader"); dalvik_system_DexFile = CacheClass(env, "dalvik/system/DexFile"); dalvik_system_DexPathList = CacheClass(env, "dalvik/system/DexPathList"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index c18473197b..c5a16c1c76 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -47,6 +47,7 @@ struct WellKnownClasses { static jclass dalvik_annotation_optimization_CriticalNative; static jclass dalvik_annotation_optimization_FastNative; static jclass dalvik_system_BaseDexClassLoader; + static jclass dalvik_system_DelegateLastClassLoader; static jclass dalvik_system_DexClassLoader; static jclass dalvik_system_DexFile; static jclass dalvik_system_DexPathList; diff --git a/runtime/zip_archive.cc b/runtime/zip_archive.cc index df1012ea3c..f3d4d77214 100644 --- a/runtime/zip_archive.cc +++ b/runtime/zip_archive.cc @@ -25,6 +25,8 @@ #include <vector> #include "android-base/stringprintf.h" +#include "ziparchive/zip_archive.h" + #include "base/bit_utils.h" #include "base/unix_file/fd_file.h" diff --git a/runtime/zip_archive.h b/runtime/zip_archive.h index 18584447e8..821cc5ceb1 100644 --- a/runtime/zip_archive.h +++ b/runtime/zip_archive.h @@ -18,7 +18,6 @@ #define ART_RUNTIME_ZIP_ARCHIVE_H_ #include <stdint.h> -#include <ziparchive/zip_archive.h> #include <memory> #include <string> @@ -29,6 +28,10 @@ #include "os.h" #include "safe_map.h" +// system/core/zip_archive definitions. +struct ZipEntry; +typedef void* ZipArchiveHandle; + namespace art { class ZipArchive; diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java index 72c5a28203..e9a11d76c2 100644 --- a/test/098-ddmc/src/Main.java +++ b/test/098-ddmc/src/Main.java @@ -14,8 +14,11 @@ * limitations under the License. */ +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.nio.ByteBuffer; +import java.util.ArrayList; public class Main { public static void main(String[] args) throws Exception { @@ -27,6 +30,8 @@ public class Main { testRecentAllocationTracking(); } + private static ArrayList<Object> staticHolder = new ArrayList<>(100000); + private static void testRecentAllocationTracking() throws Exception { System.out.println("Confirm empty"); Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations()); @@ -44,18 +49,15 @@ public class Main { System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248"); final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value. for (int i = 0; i < overflowAllocations; i++) { - new Object() { - // Add a finalizer so that the allocation won't be eliminated. - public void finalize() { - System.out.print(""); - } - }; + allocate(i, 0); } Allocations after = new Allocations(DdmVmInternal.getRecentAllocations()); System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations)); System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries)); System.out.println("after.numberOfEntries=" + after.numberOfEntries); + staticHolder.clear(); // Free the allocated objects. + System.out.println("Disable and confirm back to empty"); DdmVmInternal.enableRecentAllocations(false); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); @@ -72,7 +74,7 @@ public class Main { DdmVmInternal.enableRecentAllocations(true); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); for (int i = 0; i < 16 * 1024; i++) { - new String("fnord"); + staticHolder.add(new String("fnord")); } Allocations first = new Allocations(DdmVmInternal.getRecentAllocations()); DdmVmInternal.enableRecentAllocations(true); @@ -86,6 +88,50 @@ public class Main { System.out.println("goodbye=" + goodbye); } + // Allocate a simple object. Use depth for a reasonably deep stack. + private static final int ALLOCATE1_DEPTH = 50; + + private static Object createProxy() { + try { + InvocationHandler handler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) { + // Don't expect to be invoked. + return null; + } + }; + return Proxy.newProxyInstance(Main.class.getClassLoader(), + new Class[] { Runnable.class }, handler); + } catch (Exception e) { + // We don't really expect exceptions here. + throw new RuntimeException(e); + } + } + + private static void allocate(int i, int depth) { + if (depth >= ALLOCATE1_DEPTH) { + // Mix proxies, int arrays and Objects to test the different descriptor paths. + switch (i) { + case 0: + staticHolder.add(createProxy()); + break; + + case 1: + staticHolder.add(new int[0]); + break; + + case 2: + staticHolder.add(new Object[0]); + break; + + default: + staticHolder.add(new Object()); + break; + } + } else { + allocate(i, depth + 1); + } + } + private static class Allocations { final int messageHeaderLen; final int entryHeaderLen; diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java index 9072c8b538..3cfe0064fc 100644 --- a/test/141-class-unload/src/Main.java +++ b/test/141-class-unload/src/Main.java @@ -65,7 +65,8 @@ public class Main { String line; int count = 0; while ((line = reader.readLine()) != null) { - if (line.contains("@141-class-unload-ex.jar")) { + if (line.contains("141-class-unload-ex.odex") || + line.contains("141-class-unload-ex.vdex")) { System.out.println(line); ++count; } diff --git a/test/163-app-image-methods/expected.txt b/test/163-app-image-methods/expected.txt new file mode 100644 index 0000000000..f63e8e302f --- /dev/null +++ b/test/163-app-image-methods/expected.txt @@ -0,0 +1,3 @@ +Eating all memory. +memoryWasAllocated = true +match: true diff --git a/test/163-app-image-methods/info.txt b/test/163-app-image-methods/info.txt new file mode 100644 index 0000000000..7b42ebc2bb --- /dev/null +++ b/test/163-app-image-methods/info.txt @@ -0,0 +1,3 @@ +Regression test for erroneously storing an ArtMethod* in the app image DexCache +when the class from the corresponding MethodId is not in the app image, only the +declaring class is. diff --git a/test/163-app-image-methods/profile b/test/163-app-image-methods/profile new file mode 100644 index 0000000000..6585b94292 --- /dev/null +++ b/test/163-app-image-methods/profile @@ -0,0 +1,2 @@ +LAAA/Base; +LMain; diff --git a/test/163-app-image-methods/run b/test/163-app-image-methods/run new file mode 100644 index 0000000000..7cc107a1ba --- /dev/null +++ b/test/163-app-image-methods/run @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Use a profile to put specific classes in the app image. +# Also run the compiler with -j1 to ensure specific class verification order. +exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile \ + -Xcompiler-option -j1 diff --git a/test/163-app-image-methods/src/AAA/Base.java b/test/163-app-image-methods/src/AAA/Base.java new file mode 100644 index 0000000000..7ba71ad4fe --- /dev/null +++ b/test/163-app-image-methods/src/AAA/Base.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 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 AAA; + +class Base { + // The method is public but the class is package-private. + public static int foo() { return 42; } +} diff --git a/test/163-app-image-methods/src/AAA/Derived.java b/test/163-app-image-methods/src/AAA/Derived.java new file mode 100644 index 0000000000..66e156fe34 --- /dev/null +++ b/test/163-app-image-methods/src/AAA/Derived.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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 AAA; + +public class Derived extends Base { + // Allows public access to Base.foo() (Base is package-private) referenced as Derived.foo(). +} diff --git a/test/163-app-image-methods/src/Main.java b/test/163-app-image-methods/src/Main.java new file mode 100644 index 0000000000..a995bb8412 --- /dev/null +++ b/test/163-app-image-methods/src/Main.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AAA.Derived; + +public class Main { + public static void main(String[] args) { + try { + // Allocate memory for the "AAA.Derived" class name before eating memory. + String aaaDerivedName = "AAA.Derived"; + System.out.println("Eating all memory."); + Object memory = eatAllMemory(); + + // This test assumes that Derived is not yet resolved. In some configurations + // (notably interp-ac), Derived is already resolved by verifying Main at run + // time. Therefore we cannot assume that we get a certain `value` and need to + // simply check for consistency, i.e. `value == another_value`. + int value = 0; + try { + // If the ArtMethod* is erroneously left in the DexCache, this + // shall succeed despite the class Derived being unresolved so + // far. Otherwise, we shall throw OOME trying to resolve it. + value = Derived.foo(); + } catch (OutOfMemoryError e) { + value = -1; + } + int another_value = 0; + try { + // For comparison, try to resolve the class Derived directly. + Class.forName(aaaDerivedName, false, Main.class.getClassLoader()); + another_value = 42; + } catch (OutOfMemoryError e) { + another_value = -1; + } + boolean memoryWasAllocated = (memory != null); + memory = null; + System.out.println("memoryWasAllocated = " + memoryWasAllocated); + System.out.println("match: " + (value == another_value)); + if (value != another_value || (value != -1 && value != 42)) { + // Mismatch or unexpected value, print additional debugging information. + System.out.println("value: " + value); + System.out.println("another_value: " + another_value); + } + } catch (Throwable t) { + t.printStackTrace(System.out); + } + } + + public static Object eatAllMemory() { + Object[] result = null; + int size = 1000000; + while (result == null && size != 0) { + try { + result = new Object[size]; + } catch (OutOfMemoryError oome) { + size /= 2; + } + } + if (result != null) { + int index = 0; + while (index != result.length && size != 0) { + try { + result[index] = new byte[size]; + ++index; + } catch (OutOfMemoryError oome) { + size /= 2; + } + } + } + return result; + } +} diff --git a/test/1900-track-alloc/alloc.cc b/test/1900-track-alloc/alloc.cc new file mode 100644 index 0000000000..db5617c54c --- /dev/null +++ b/test/1900-track-alloc/alloc.cc @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace Test1900TrackAlloc { + +typedef jvmtiError (*GetGlobalState)(jvmtiEnv* env, jlong* allocated); + +struct AllocTrackingData { + GetGlobalState get_global_state; +}; + +template <typename T> +static void Dealloc(T* t) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t)); +} + +template <typename T, typename ...Rest> +static void Dealloc(T* t, Rest... rs) { + Dealloc(t); + Dealloc(rs...); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1900_doDeallocate(JNIEnv* env, + jclass, + jlong jvmti_env_ptr, + jlong ptr) { + JvmtiErrorToException(env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr), + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Deallocate( + reinterpret_cast<unsigned char*>(static_cast<intptr_t>(ptr)))); +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_doAllocate(JNIEnv* env, + jclass, + jlong jvmti_env_ptr, + jlong size) { + unsigned char* res = nullptr; + JvmtiErrorToException(env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr), + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Allocate(size, &res)); + return static_cast<jlong>(reinterpret_cast<intptr_t>(res)); +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getAmountAllocated(JNIEnv* env, jclass) { + AllocTrackingData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return -1; + } + if (data == nullptr || data->get_global_state == nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Alloc tracking data not initialized."); + return -1; + } + jlong allocated = -1; + JvmtiErrorToException(env, jvmti_env, data->get_global_state(jvmti_env, &allocated)); + return allocated; +} + +static void DeallocParams(jvmtiParamInfo* params, jint n_params) { + for (jint i = 0; i < n_params; i++) { + Dealloc(params[i].name); + } +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getDefaultJvmtiEnv(JNIEnv*, jclass) { + return static_cast<jlong>(reinterpret_cast<intptr_t>(jvmti_env)); +} + +extern "C" JNIEXPORT void Java_art_Test1900_destroyJvmtiEnv(JNIEnv* env, + jclass, + jlong jvmti_env_ptr) { + JvmtiErrorToException(env, + jvmti_env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->DisposeEnvironment()); +} + +extern "C" JNIEXPORT jlong Java_art_Test1900_newJvmtiEnv(JNIEnv* env, jclass) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to get JavaVM"); + return -1; + } + jvmtiEnv* new_env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&new_env), JVMTI_VERSION_1_0) != 0) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to create new jvmtiEnv"); + return -1; + } + return static_cast<jlong>(reinterpret_cast<intptr_t>(new_env)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1900_initializeTest(JNIEnv* env, jclass) { + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + AllocTrackingData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(AllocTrackingData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(AllocTrackingData)); + // Get the extensions. + jint n_ext = 0; + jvmtiExtensionFunctionInfo* infos = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) { + return; + } + for (jint i = 0; i < n_ext; i++) { + jvmtiExtensionFunctionInfo* cur_info = &infos[i]; + if (strcmp("com.android.art.alloc.get_global_jvmti_allocation_state", cur_info->id) == 0) { + data->get_global_state = reinterpret_cast<GetGlobalState>(cur_info->func); + } + // Cleanup the cur_info + DeallocParams(cur_info->params, cur_info->param_count); + Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors); + } + // Cleanup the array. + Dealloc(infos); + if (data->get_global_state == nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to find memory tracking extensions."); + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data)); + return; +} + +} // namespace Test1900TrackAlloc +} // namespace art diff --git a/test/988-redefine-use-after-free/expected.txt b/test/1900-track-alloc/expected.txt index e69de29bb2..e69de29bb2 100644 --- a/test/988-redefine-use-after-free/expected.txt +++ b/test/1900-track-alloc/expected.txt diff --git a/test/1900-track-alloc/info.txt b/test/1900-track-alloc/info.txt new file mode 100644 index 0000000000..e1d35ae026 --- /dev/null +++ b/test/1900-track-alloc/info.txt @@ -0,0 +1 @@ +Tests the jvmti-extension to get allocated memory snapshot. diff --git a/test/988-redefine-use-after-free/run b/test/1900-track-alloc/run index c6e62ae6cd..c6e62ae6cd 100755 --- a/test/988-redefine-use-after-free/run +++ b/test/1900-track-alloc/run diff --git a/test/1900-track-alloc/src/Main.java b/test/1900-track-alloc/src/Main.java new file mode 100644 index 0000000000..0dab4ef726 --- /dev/null +++ b/test/1900-track-alloc/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1900.run(); + } +} diff --git a/test/1900-track-alloc/src/art/Main.java b/test/1900-track-alloc/src/art/Main.java new file mode 100644 index 0000000000..aa5498bd62 --- /dev/null +++ b/test/1900-track-alloc/src/art/Main.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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 art; + +// Binder class so the agent's C code has something that can be bound and exposed to tests. +// In a package to separate cleanly and work around CTS reference issues (though this class +// should be replaced in the CTS version). +public class Main { + // Load the given class with the given classloader, and bind all native methods to corresponding + // C methods in the agent. Will abort if any of the steps fail. + public static native void bindAgentJNI(String className, ClassLoader classLoader); + // Same as above, giving the class directly. + public static native void bindAgentJNIForClass(Class<?> klass); + + // Common infrastructure. + public static native void setTag(Object o, long tag); + public static native long getTag(Object o); +} diff --git a/test/1900-track-alloc/src/art/Test1900.java b/test/1900-track-alloc/src/art/Test1900.java new file mode 100644 index 0000000000..becee1b15c --- /dev/null +++ b/test/1900-track-alloc/src/art/Test1900.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; + +public class Test1900 { + public static void checkLE(long exp, long o) { + if (exp > o) { + throw new Error("Expected: " + exp + " Got: " + o); + } + } + public static void checkEq(long exp, long o) { + if (exp != o) { + throw new Error("Expected: " + exp + " Got: " + o); + } + } + + public static void runConcurrent(Runnable... rs) throws Exception { + final CountDownLatch latch = new CountDownLatch(rs.length); + Thread[] thrs = new Thread[rs.length]; + for (int i = 0; i < rs.length; i++) { + final Runnable r = rs[i]; + thrs[i] = new Thread(() -> { + latch.countDown(); + r.run(); + }); + thrs[i].start(); + } + for (Thread thr : thrs) { + thr.join(); + } + } + static class Holder { + public long val; + } + + public static void run() throws Exception { + initializeTest(); + // Get the overhead for the native part of this test. + final long base_state = getAmountAllocated(); + + // Basic alloc-dealloc + checkEq(base_state + 0, getAmountAllocated()); + long abc = doAllocate(10); + checkLE(base_state + 10, getAmountAllocated()); + long def = doAllocate(10); + checkLE(base_state + 20, getAmountAllocated()); + doDeallocate(abc); + checkLE(base_state + 10, getAmountAllocated()); + + doDeallocate(def); + + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it concurrently. + Runnable add10 = () -> { long x = doAllocate(10); doDeallocate(x); }; + Runnable[] rs = new Runnable[100]; + Arrays.fill(rs, add10); + runConcurrent(rs); + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it concurrently with different threads to allocate and deallocate. + final Semaphore sem = new Semaphore(0); + final Holder h = new Holder(); + runConcurrent( + () -> { + try { + h.val = doAllocate(100); + checkLE(base_state + 100, getAmountAllocated()); + sem.release(); + } catch (Exception e) { throw new Error("exception!", e); } + }, + () -> { + try { + sem.acquire(); + long after_acq = getAmountAllocated(); + doDeallocate(h.val); + checkLE(base_state + 100, after_acq); + } catch (Exception e) { throw new Error("exception!", e); } + } + ); + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it with multiple jvmtienvs. + long env1 = newJvmtiEnv(); + long env2 = newJvmtiEnv(); + + final long new_base_state = getAmountAllocated(); + // new jvmtienvs shouldn't save us memory. + checkLE(base_state, new_base_state); + // Make sure we track both. + abc = doAllocate(env1, 10); + checkLE(new_base_state + 10, getAmountAllocated()); + def = doAllocate(env2, 10); + checkLE(new_base_state + 20, getAmountAllocated()); + doDeallocate(env1, abc); + checkLE(new_base_state + 10, getAmountAllocated()); + + doDeallocate(env2, def); + + checkEq(new_base_state + 0, getAmountAllocated()); + + destroyJvmtiEnv(env1); + destroyJvmtiEnv(env2); + + // Back to normal after getting rid of the envs. + checkEq(base_state + 0, getAmountAllocated()); + + // Try adding some tags + Object a = new Object(); + Object b = new Object(); + Main.setTag(a, 100); + Main.setTag(b, 200); + + // tags should be counted and should have some data associated with them. + checkLE(base_state + 1, getAmountAllocated()); + } + + private static native long doAllocate(long jvmtienv, long size); + private static long doAllocate(long size) { + return doAllocate(getDefaultJvmtiEnv(), size); + } + + private static native void doDeallocate(long jvmtienv, long ptr); + private static void doDeallocate(long size) { + doDeallocate(getDefaultJvmtiEnv(), size); + } + + private static native long getDefaultJvmtiEnv(); + private static native long newJvmtiEnv(); + private static native void destroyJvmtiEnv(long jvmtienv); + private static native long getAmountAllocated(); + private static native void initializeTest(); +} diff --git a/test/1901-get-bytecodes/bytecodes.cc b/test/1901-get-bytecodes/bytecodes.cc new file mode 100644 index 0000000000..edcb734788 --- /dev/null +++ b/test/1901-get-bytecodes/bytecodes.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1901Bytecodes { + +extern "C" JNIEXPORT jbyteArray JNICALL Java_art_Test1901_getBytecodes(JNIEnv* env, + jclass, + jobject jmethod) { + jmethodID method = env->FromReflectedMethod(jmethod); + if (env->ExceptionCheck()) { + return nullptr; + } + unsigned char* bytecodes = nullptr; + jint bytecodes_size = 0; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetBytecodes(method, &bytecodes_size, &bytecodes))) { + return nullptr; + } + jbyteArray out = env->NewByteArray(bytecodes_size); + if (env->ExceptionCheck()) { + return nullptr; + } else if (bytecodes_size == 0) { + return out; + } + jbyte* bytes = env->GetByteArrayElements(out, /* is_copy */ nullptr); + memcpy(bytes, bytecodes, bytecodes_size); + env->ReleaseByteArrayElements(out, bytes, 0); + return out; +} + +} // namespace Test1901Bytecodes +} // namespace art diff --git a/test/1901-get-bytecodes/expected.txt b/test/1901-get-bytecodes/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/1901-get-bytecodes/expected.txt diff --git a/test/1901-get-bytecodes/info.txt b/test/1901-get-bytecodes/info.txt new file mode 100644 index 0000000000..c8c91893e5 --- /dev/null +++ b/test/1901-get-bytecodes/info.txt @@ -0,0 +1,3 @@ +Tests basic functions in the jvmti plugin. + +Tests that the GetBytecodes function works as expected. diff --git a/test/1901-get-bytecodes/run b/test/1901-get-bytecodes/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1901-get-bytecodes/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-run "$@" --jvmti diff --git a/test/1901-get-bytecodes/src/Main.java b/test/1901-get-bytecodes/src/Main.java new file mode 100644 index 0000000000..d37fcdb0cb --- /dev/null +++ b/test/1901-get-bytecodes/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1901.run(); + } +} diff --git a/test/1901-get-bytecodes/src/art/Test1901.java b/test/1901-get-bytecodes/src/art/Test1901.java new file mode 100644 index 0000000000..6940491e3c --- /dev/null +++ b/test/1901-get-bytecodes/src/art/Test1901.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; + +public class Test1901 { + // Class & Dex file containing the following class. + // Using this representation to prevent any changes to the compiler or the file formats from + // changing the output of this test. + // + // package art; + // public class Target { + // public void doNothing() { + // return; + // } + // + // public static void staticNothing() { + // return; + // } + // + // public void sayHi() { + // System.out.println("hello"); + // } + // } + public static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHgoABgAQCQARABIIABMKABQAFQcAFgcAFwEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAlkb05vdGhpbmcBAA1zdGF0aWNOb3RoaW5nAQAFc2F5SGkBAApT" + + "b3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMAAcACAcAGAwAGQAaAQAFaGVsbG8HABsMABwAHQEACmFy" + + "dC9UYXJnZXQBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxq" + + "YXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExq" + + "YXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAAEAAEABwAIAAEACQAAAB0AAQABAAAABSq3AAGx" + + "AAAAAQAKAAAABgABAAAAAgABAAsACAABAAkAAAAZAAAAAQAAAAGxAAAAAQAKAAAABgABAAAABAAJ" + + "AAwACAABAAkAAAAZAAAAAAAAAAGxAAAAAQAKAAAABgABAAAACAABAA0ACAABAAkAAAAlAAIAAQAA" + + "AAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAMAAgADQABAA4AAAACAA8="); + public static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAbYkxNjiZ8a+fNWF4smR2+uXbrq88/FNoYAwAAcAAAAHhWNBIAAAAAAAAAAHgCAAAP" + + "AAAAcAAAAAYAAACsAAAAAgAAAMQAAAABAAAA3AAAAAYAAADkAAAAAQAAABQBAADkAQAANAEAAJoB" + + "AACiAQAAsAEAAMcBAADbAQAA7wEAAAMCAAAQAgAAEwIAABcCAAAiAgAAKQIAAC4CAAA3AgAAPgIA" + + "AAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAcAAAAFAAAAAAAAAAgAAAAFAAAAlAEAAAQAAQALAAAA" + + "AAAAAAAAAAAAAAAACQAAAAAAAAANAAAAAAAAAA4AAAABAAEADAAAAAIAAAAAAAAAAAAAAAEAAAAC" + + "AAAAAAAAAAYAAAAAAAAAYgIAAAAAAAABAAEAAQAAAE0CAAAEAAAAcBAFAAAADgAAAAAAAAAAAFIC" + + "AAABAAAADgAAAAEAAQAAAAAAVwIAAAEAAAAOAAAAAwABAAIAAABcAgAACAAAAGIAAAAaAQoAbiAE" + + "ABAADgABAAAAAwAGPGluaXQ+AAxMYXJ0L1RhcmdldDsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAS" + + "TGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" + + "OwALVGFyZ2V0LmphdmEAAVYAAlZMAAlkb05vdGhpbmcABWhlbGxvAANvdXQAB3ByaW50bG4ABXNh" + + "eUhpAA1zdGF0aWNOb3RoaW5nAAIABw4ACAAHDgAEAAcOAAwABw54AAAAAgIAgYAEtAIDCcwCAQHg" + + "AgEB9AINAAAAAAAAAAEAAAAAAAAAAQAAAA8AAABwAAAAAgAAAAYAAACsAAAAAwAAAAIAAADEAAAA" + + "BAAAAAEAAADcAAAABQAAAAYAAADkAAAABgAAAAEAAAAUAQAAASAAAAQAAAA0AQAAARAAAAEAAACU" + + "AQAAAiAAAA8AAACaAQAAAyAAAAQAAABNAgAAACAAAAEAAABiAgAAABAAAAEAAAB4AgAA"); + + public static byte[][] DO_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for doNothing + // 0e00 |0000: return-void + new byte[] { 14, 0 }, + // Java bytecodes + // 0: return + new byte[] { -79 }, + }; + + public static byte[][] STATIC_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for staticNothing + // 0e00 |0000: return-void + new byte[] { 14, 0 }, + // Java bytecodes + // 0: return + new byte[] { -79 }, + }; + + public static byte[][] SAY_HI_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for sayHi + // 6200 0000 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000 + // 1a01 0a00 |0002: const-string v1, "hello" // string@000a + // 6e20 0400 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0004 + // 0e00 |0007: return-void + new byte[] { 98, 0, 0, 0, 26, 1, 10, 0, 110, 32, 4, 0, 16, 0, 14, 0 }, + // Java bytecodes + // 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; + // 3: ldc #3 // String hello + // 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + // 8: return + new byte[] { -78, 0, 2, 18, 3, -74, 0, 4, -79 }, + }; + + public static ClassLoader getClassLoader() throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + // We are on art since we got the InMemoryDexClassLoader. + return (ClassLoader)ctor.newInstance( + ByteBuffer.wrap(DEX_BYTES), Test1901.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. + return new ClassLoader(Test1901.class.getClassLoader()) { + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals("art.Target")) { + return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length); + } else { + return super.findClass(name); + } + } + }; + } + } + + public static void CheckMethodBytes(Method m, byte[][] possible_bytecodes) { + byte[] real_codes = getBytecodes(m); + for (byte[] pos : possible_bytecodes) { + if (Arrays.equals(pos, real_codes)) { + return; + } + } + System.out.println("Unexpected bytecodes for " + m); + System.out.println("Received: " + Arrays.toString(real_codes)); + System.out.println("Expected one of:"); + for (byte[] pos : possible_bytecodes) { + System.out.println("\t" + Arrays.toString(pos)); + } + } + + public static void run() throws Exception { + Class<?> target = getClassLoader().loadClass("art.Target"); + CheckMethodBytes(target.getDeclaredMethod("doNothing"), DO_NOTHING_BYTECODES); + CheckMethodBytes(target.getDeclaredMethod("staticNothing"), STATIC_NOTHING_BYTECODES); + CheckMethodBytes(target.getDeclaredMethod("sayHi"), SAY_HI_NOTHING_BYTECODES); + } + + public static native byte[] getBytecodes(Method m); +} diff --git a/test/458-checker-instruct-simplification/build b/test/458-checker-instruct-simplification/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/458-checker-instruct-simplification/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/458-checker-instruct-simplification/smali/SmaliTests.smali b/test/458-checker-instruct-simplification/smali/SmaliTests.smali index 6845961f39..a8d7d94e46 100644 --- a/test/458-checker-instruct-simplification/smali/SmaliTests.smali +++ b/test/458-checker-instruct-simplification/smali/SmaliTests.smali @@ -327,3 +327,144 @@ return v0 .end method + +# Test simplification of the `~~var` pattern. +# The transformation tested is implemented in `InstructionSimplifierVisitor::VisitNot`. + +## CHECK-START: long SmaliTests.NotNot1(long) instruction_simplifier (before) +## CHECK-DAG: <<Arg:j\d+>> ParameterValue +## CHECK-DAG: <<Not1:j\d+>> Not [<<Arg>>] +## CHECK-DAG: <<Not2:j\d+>> Not [<<Not1>>] +## CHECK-DAG: Return [<<Not2>>] + +## CHECK-START: long SmaliTests.NotNot1(long) instruction_simplifier (after) +## CHECK-DAG: <<Arg:j\d+>> ParameterValue +## CHECK-DAG: Return [<<Arg>>] + +## CHECK-START: long SmaliTests.NotNot1(long) instruction_simplifier (after) +## CHECK-NOT: Not + +.method public static NotNot1(J)J + .registers 4 + .param p0, "arg" # J + + .prologue + sget-boolean v0, LMain;->doThrow:Z + + # if (doThrow) throw new Error(); + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return ~~arg + not-long v0, p0 + not-long v0, v0 + return-wide v0 +.end method + +## CHECK-START: int SmaliTests.NotNot2(int) instruction_simplifier (before) +## CHECK-DAG: <<Arg:i\d+>> ParameterValue +## CHECK-DAG: <<Not1:i\d+>> Not [<<Arg>>] +## CHECK-DAG: <<Not2:i\d+>> Not [<<Not1>>] +## CHECK-DAG: <<Add:i\d+>> Add [<<Not2>>,<<Not1>>] +## CHECK-DAG: Return [<<Add>>] + +## CHECK-START: int SmaliTests.NotNot2(int) instruction_simplifier (after) +## CHECK-DAG: <<Arg:i\d+>> ParameterValue +## CHECK-DAG: <<Not:i\d+>> Not [<<Arg>>] +## CHECK-DAG: <<Add:i\d+>> Add [<<Arg>>,<<Not>>] +## CHECK-DAG: Return [<<Add>>] + +## CHECK-START: int SmaliTests.NotNot2(int) instruction_simplifier (after) +## CHECK: Not +## CHECK-NOT: Not + +.method public static NotNot2(I)I + .registers 3 + .param p0, "arg" # I + + .prologue + sget-boolean v1, LMain;->doThrow:Z + + # if (doThrow) throw new Error(); + if-eqz v1, :cond_a + new-instance v1, Ljava/lang/Error; + invoke-direct {v1}, Ljava/lang/Error;-><init>()V + throw v1 + + :cond_a + # temp = ~arg; return temp + ~temp; + not-int v0, p0 + not-int v1, v0 + add-int/2addr v1, v0 + + return v1 +.end method + +# Test simplification of double Boolean negation. Note that sometimes +# both negations can be removed but we only expect the simplifier to +# remove the second. + +## CHECK-START: boolean SmaliTests.NotNotBool(boolean) instruction_simplifier (before) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 +## CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect +## CHECK-DAG: <<NotResult:i\d+>> Xor [<<Result>>,<<Const1>>] +## CHECK-DAG: Return [<<NotResult>>] + +## CHECK-START: boolean SmaliTests.NotNotBool(boolean) instruction_simplifier (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect +## CHECK-DAG: <<NotResult:z\d+>> BooleanNot [<<Result>>] +## CHECK-DAG: Return [<<NotResult>>] + +## CHECK-START: boolean SmaliTests.NotNotBool(boolean) instruction_simplifier$after_inlining (before) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<NotArg:z\d+>> BooleanNot [<<Arg>>] +## CHECK-DAG: <<NotNotArg:z\d+>> BooleanNot [<<NotArg>>] +## CHECK-DAG: Return [<<NotNotArg>>] + +## CHECK-START: boolean SmaliTests.NotNotBool(boolean) instruction_simplifier$after_inlining (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<NotArg:z\d+>> BooleanNot [<<Arg>>] +## CHECK-DAG: Return [<<Arg>>] + +## CHECK-START: boolean SmaliTests.NotNotBool(boolean) dead_code_elimination$final (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: Return [<<Arg>>] + +.method public static NegateValue(Z)Z + .registers 2 + .param p0, "arg" # Z + + .prologue + + # return !arg + xor-int/lit8 v0, p0, 0x1 + return v0 +.end method + + +.method public static NotNotBool(Z)Z + .registers 2 + .param p0, "arg" # Z + + .prologue + sget-boolean v0, LMain;->doThrow:Z + + # if (doThrow) throw new Error(); + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return !Negate(arg) + invoke-static {p0}, LSmaliTests;->NegateValue(Z)Z + move-result v0 + xor-int/lit8 v0, v0, 0x1 + + return v0 +.end method diff --git a/test/458-checker-instruct-simplification/src/Main.java b/test/458-checker-instruct-simplification/src/Main.java index 529ea5ba9c..5c36ce9982 100644 --- a/test/458-checker-instruct-simplification/src/Main.java +++ b/test/458-checker-instruct-simplification/src/Main.java @@ -982,17 +982,18 @@ public class Main { */ /// CHECK-START: long Main.$noinline$NotNot1(long) instruction_simplifier (before) - /// CHECK-DAG: <<Arg:j\d+>> ParameterValue - /// CHECK-DAG: <<Not1:j\d+>> Not [<<Arg>>] - /// CHECK-DAG: <<Not2:j\d+>> Not [<<Not1>>] - /// CHECK-DAG: Return [<<Not2>>] + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<ConstNeg1:j\d+>> LongConstant -1 + /// CHECK-DAG: <<Not1:j\d+>> Xor [<<Arg>>,<<ConstNeg1>>] + /// CHECK-DAG: <<Not2:j\d+>> Xor [<<Not1>>,<<ConstNeg1>>] + /// CHECK-DAG: Return [<<Not2>>] /// CHECK-START: long Main.$noinline$NotNot1(long) instruction_simplifier (after) /// CHECK-DAG: <<Arg:j\d+>> ParameterValue /// CHECK-DAG: Return [<<Arg>>] /// CHECK-START: long Main.$noinline$NotNot1(long) instruction_simplifier (after) - /// CHECK-NOT: Not + /// CHECK-NOT: Xor public static long $noinline$NotNot1(long arg) { if (doThrow) { throw new Error(); } @@ -1000,11 +1001,12 @@ public class Main { } /// CHECK-START: int Main.$noinline$NotNot2(int) instruction_simplifier (before) - /// CHECK-DAG: <<Arg:i\d+>> ParameterValue - /// CHECK-DAG: <<Not1:i\d+>> Not [<<Arg>>] - /// CHECK-DAG: <<Not2:i\d+>> Not [<<Not1>>] - /// CHECK-DAG: <<Add:i\d+>> Add [<<Not2>>,<<Not1>>] - /// CHECK-DAG: Return [<<Add>>] + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<ConstNeg1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Not1:i\d+>> Xor [<<Arg>>,<<ConstNeg1>>] + /// CHECK-DAG: <<Not2:i\d+>> Xor [<<Not1>>,<<ConstNeg1>>] + /// CHECK-DAG: <<Add:i\d+>> Add [<<Not2>>,<<Not1>>] + /// CHECK-DAG: Return [<<Add>>] /// CHECK-START: int Main.$noinline$NotNot2(int) instruction_simplifier (after) /// CHECK-DAG: <<Arg:i\d+>> ParameterValue @@ -1016,6 +1018,9 @@ public class Main { /// CHECK: Not /// CHECK-NOT: Not + /// CHECK-START: int Main.$noinline$NotNot2(int) instruction_simplifier (after) + /// CHECK-NOT: Xor + public static int $noinline$NotNot2(int arg) { if (doThrow) { throw new Error(); } int temp = ~arg; @@ -1180,30 +1185,46 @@ public class Main { /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier (before) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 - /// CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect - /// CHECK-DAG: <<NotResult:i\d+>> Xor [<<Result>>,<<Const1>>] - /// CHECK-DAG: Return [<<NotResult>>] + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect method_name:Main.NegateValue + /// CHECK-DAG: <<NotResult:z\d+>> NotEqual [<<Result>>,<<Const1>>] + /// CHECK-DAG: If [<<NotResult>>] + + /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier (after) + /// CHECK-NOT: NotEqual /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier (after) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect - /// CHECK-DAG: <<NotResult:z\d+>> BooleanNot [<<Result>>] - /// CHECK-DAG: Return [<<NotResult>>] + /// CHECK-DAG: <<Result:z\d+>> InvokeStaticOrDirect method_name:Main.NegateValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Const1>>,<<Const0>>] + /// CHECK-DAG: Return [<<Phi>>] /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier$after_inlining (before) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<NotArg:z\d+>> BooleanNot [<<Arg>>] - /// CHECK-DAG: <<NotNotArg:z\d+>> BooleanNot [<<NotArg>>] - /// CHECK-DAG: Return [<<NotNotArg>>] + /// CHECK-NOT: BooleanNot [<<Arg>>] + /// CHECK-NOT: Phi + + /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier$after_inlining (before) + /// CHECK-DAG: <<Arg:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Sel:i\d+>> Select [<<Const1>>,<<Const0>>,<<Arg>>] + /// CHECK-DAG: <<Sel2:i\d+>> Select [<<Const1>>,<<Const0>>,<<Sel>>] + /// CHECK-DAG: Return [<<Sel2>>] /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier$after_inlining (after) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<NotArg:z\d+>> BooleanNot [<<Arg>>] - /// CHECK-DAG: Return [<<Arg>>] + /// CHECK: BooleanNot [<<Arg>>] + /// CHECK-NEXT: Goto + + /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) instruction_simplifier$after_inlining (after) + /// CHECK-NOT: Select /// CHECK-START: boolean Main.$noinline$NotNotBool(boolean) dead_code_elimination$final (after) /// CHECK-DAG: <<Arg:z\d+>> ParameterValue + /// CHECK-NOT: BooleanNot [<<Arg>>] /// CHECK-DAG: Return [<<Arg>>] public static boolean NegateValue(boolean arg) { @@ -1882,7 +1903,18 @@ public class Main { } } - public static int $noinline$runSmaliTestConst(String name, int arg) { + public static boolean $noinline$runSmaliTestBoolean(String name, boolean input) { + if (doThrow) { throw new Error(); } + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, boolean.class); + return (Boolean) m.invoke(null, input); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static int $noinline$runSmaliTestInt(String name, int arg) { if (doThrow) { throw new Error(); } try { Class<?> c = Class.forName("SmaliTests"); @@ -1893,6 +1925,17 @@ public class Main { } } + public static long $noinline$runSmaliTestLong(String name, long arg) { + if (doThrow) { throw new Error(); } + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, long.class); + return (Long) m.invoke(null, arg); + } catch (Exception ex) { + throw new Error(ex); + } + } + /// CHECK-START: int Main.$noinline$intUnnecessaryShiftMasking(int, int) instruction_simplifier (before) /// CHECK: <<Value:i\d+>> ParameterValue /// CHECK: <<Shift:i\d+>> ParameterValue @@ -2188,7 +2231,9 @@ public class Main { assertIntEquals(1, $noinline$NegSub1(arg, arg + 1)); assertIntEquals(1, $noinline$NegSub2(arg, arg + 1)); assertLongEquals(arg, $noinline$NotNot1(arg)); + assertLongEquals(arg, $noinline$runSmaliTestLong("NotNot1", arg)); assertIntEquals(-1, $noinline$NotNot2(arg)); + assertIntEquals(-1, $noinline$runSmaliTestInt("NotNot2", arg)); assertIntEquals(-(arg + arg + 1), $noinline$SubNeg1(arg, arg + 1)); assertIntEquals(-(arg + arg + 1), $noinline$SubNeg2(arg, arg + 1)); assertLongEquals(-(2 * arg + 1), $noinline$SubNeg3(arg, arg + 1)); @@ -2197,7 +2242,9 @@ public class Main { assertBooleanEquals(false, $noinline$NotEqualBoolVsIntConst(false)); assertBooleanEquals(false, $noinline$NotEqualBoolVsIntConst(false)); assertBooleanEquals(true, $noinline$NotNotBool(true)); + assertBooleanEquals(true, $noinline$runSmaliTestBoolean("NotNotBool", true)); assertBooleanEquals(false, $noinline$NotNotBool(false)); + assertBooleanEquals(false, $noinline$runSmaliTestBoolean("NotNotBool", false)); assertFloatEquals(50.0f, $noinline$Div2(100.0f)); assertDoubleEquals(75.0, $noinline$Div2(150.0)); assertFloatEquals(-400.0f, $noinline$DivMP25(100.0f)); @@ -2309,11 +2356,11 @@ public class Main { } } - assertIntEquals(0, $noinline$runSmaliTestConst("AddSubConst", 1)); - assertIntEquals(3, $noinline$runSmaliTestConst("SubAddConst", 2)); - assertIntEquals(-16, $noinline$runSmaliTestConst("SubSubConst1", 3)); - assertIntEquals(-5, $noinline$runSmaliTestConst("SubSubConst2", 4)); - assertIntEquals(26, $noinline$runSmaliTestConst("SubSubConst3", 5)); + assertIntEquals(0, $noinline$runSmaliTestInt("AddSubConst", 1)); + assertIntEquals(3, $noinline$runSmaliTestInt("SubAddConst", 2)); + assertIntEquals(-16, $noinline$runSmaliTestInt("SubSubConst1", 3)); + assertIntEquals(-5, $noinline$runSmaliTestInt("SubSubConst2", 4)); + assertIntEquals(26, $noinline$runSmaliTestInt("SubSubConst3", 5)); assertIntEquals(0x5e6f7808, $noinline$intUnnecessaryShiftMasking(0xabcdef01, 3)); assertIntEquals(0x5e6f7808, $noinline$intUnnecessaryShiftMasking(0xabcdef01, 3 + 32)); assertLongEquals(0xffffffffffffeaf3L, $noinline$longUnnecessaryShiftMasking(0xabcdef0123456789L, 50)); diff --git a/test/536-checker-intrinsic-optimization/build b/test/536-checker-intrinsic-optimization/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/536-checker-intrinsic-optimization/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/536-checker-intrinsic-optimization/smali/SmaliTests.smali b/test/536-checker-intrinsic-optimization/smali/SmaliTests.smali new file mode 100644 index 0000000000..6612faeb77 --- /dev/null +++ b/test/536-checker-intrinsic-optimization/smali/SmaliTests.smali @@ -0,0 +1,64 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +## CHECK-START: char SmaliTests.stringCharAtCatch(java.lang.String, int) instruction_simplifier (before) +## CHECK-DAG: <<Char:c\d+>> InvokeVirtual intrinsic:StringCharAt +## CHECK-DAG: Return [<<Char>>] + +## CHECK-START: char SmaliTests.stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) +## CHECK-DAG: <<String:l\d+>> ParameterValue +## CHECK-DAG: <<Pos:i\d+>> ParameterValue +## CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] +## CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true +## CHECK-DAG: <<Bounds:i\d+>> BoundsCheck [<<Pos>>,<<Length>>] is_string_char_at:true +## CHECK-DAG: <<Char:c\d+>> ArrayGet [<<NullCk>>,<<Bounds>>] is_string_char_at:true +## CHECK-DAG: Return [<<Char>>] + +## CHECK-START: char SmaliTests.stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) +## CHECK-NOT: InvokeVirtual intrinsic:StringCharAt +.method public static stringCharAtCatch(Ljava/lang/String;I)C + .registers 4 + .param p0, "s" # Ljava/lang/String; + .param p1, "pos" # I + + .prologue + + # if (doThrow) { throw new Error(); } + sget-boolean v1, LMain;->doThrow:Z + if-eqz v1, :doThrow_false + new-instance v1, Ljava/lang/Error; + invoke-direct {v1}, Ljava/lang/Error;-><init>()V + throw v1 + + :doThrow_false + :try_start + # tmp = s.charAt(pos) + invoke-virtual {p0, p1}, Ljava/lang/String;->charAt(I)C + :try_end + .catch Ljava/lang/StringIndexOutOfBoundsException; {:try_start .. :try_end} :catch + + # return tmp + move-result v1 + return v1 + + :catch + # return '\0' + move-exception v0 + const/4 v1, 0x0 + return v1 +.end method + diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java index e395e283e0..3dce23fb31 100644 --- a/test/536-checker-intrinsic-optimization/src/Main.java +++ b/test/536-checker-intrinsic-optimization/src/Main.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import java.lang.reflect.Method; public class Main { public static boolean doThrow = false; @@ -83,7 +84,9 @@ public class Main { } assertCharEquals('7', $opt$noinline$stringCharAtCatch("0123456789", 7)); + assertCharEquals('7', $noinline$runSmaliTest("stringCharAtCatch", "0123456789", 7)); assertCharEquals('\0', $opt$noinline$stringCharAtCatch("0123456789", 10)); + assertCharEquals('\0', $noinline$runSmaliTest("stringCharAtCatch","0123456789", 10)); assertIntEquals('a' + 'b' + 'c', $opt$noinline$stringSumChars("abc")); assertIntEquals('a' + 'b' + 'c', $opt$noinline$stringSumLeadingChars("abcdef", 3)); @@ -166,17 +169,21 @@ public class Main { } /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (before) + /// CHECK-DAG: <<Int:i\d+>> IntConstant 0 /// CHECK-DAG: <<Char:c\d+>> InvokeVirtual intrinsic:StringCharAt - /// CHECK-DAG: Return [<<Char>>] + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Char>>,<<Int>>] + /// CHECK-DAG: Return [<<Phi>>] /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) /// CHECK-DAG: <<String:l\d+>> ParameterValue /// CHECK-DAG: <<Pos:i\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> IntConstant 0 /// CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] /// CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true /// CHECK-DAG: <<Bounds:i\d+>> BoundsCheck [<<Pos>>,<<Length>>] is_string_char_at:true /// CHECK-DAG: <<Char:c\d+>> ArrayGet [<<NullCk>>,<<Bounds>>] is_string_char_at:true - /// CHECK-DAG: Return [<<Char>>] + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Char>>,<<Int>>] + /// CHECK-DAG: Return [<<Phi>>] /// CHECK-START: char Main.$opt$noinline$stringCharAtCatch(java.lang.String, int) instruction_simplifier (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringCharAt @@ -421,4 +428,14 @@ public class Main { static String myString; static Object myObject; + + public static char $noinline$runSmaliTest(String name, String str, int pos) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, String.class, int.class); + return (Character) m.invoke(null, str, pos); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/test/565-checker-doublenegbitwise/build b/test/565-checker-doublenegbitwise/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/565-checker-doublenegbitwise/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/565-checker-doublenegbitwise/smali/SmaliTests.smali b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali new file mode 100644 index 0000000000..2e0802276e --- /dev/null +++ b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali @@ -0,0 +1,405 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# +# Test transformation of Not/Not/And into Or/Not. +# + +## CHECK-START: int SmaliTests.$opt$noinline$andToOr(int, int) instruction_simplifier (before) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Not1:i\d+>> Not [<<P1>>] +## CHECK-DAG: <<Not2:i\d+>> Not [<<P2>>] +## CHECK-DAG: <<And:i\d+>> And [<<Not1>>,<<Not2>>] +## CHECK-DAG: Return [<<And>>] + +## CHECK-START: int SmaliTests.$opt$noinline$andToOr(int, int) instruction_simplifier (after) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Or:i\d+>> Or [<<P1>>,<<P2>>] +## CHECK-DAG: <<Not:i\d+>> Not [<<Or>>] +## CHECK-DAG: Return [<<Not>>] + +## CHECK-START: int SmaliTests.$opt$noinline$andToOr(int, int) instruction_simplifier (after) +## CHECK-DAG: Not +## CHECK-NOT: Not + +## CHECK-START: int SmaliTests.$opt$noinline$andToOr(int, int) instruction_simplifier (after) +## CHECK-NOT: And +.method public static $opt$noinline$andToOr(II)I + .registers 4 + .param p0, "a" # I + .param p1, "b" # I + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return ~a & ~b; + not-int v0, p0 + not-int v1, p1 + and-int/2addr v0, v1 + + return v0 +.end method + +# Test transformation of Not/Not/And into Or/Not for boolean negations. +# Note that the graph before this instruction simplification pass does not +# contain `HBooleanNot` instructions. This is because this transformation +# follows the optimization of `HSelect` to `HBooleanNot` occurring in the +# same pass. + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier (before) +## CHECK-DAG: <<P1:z\d+>> ParameterValue +## CHECK-DAG: <<P2:z\d+>> ParameterValue +## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 +## CHECK-DAG: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] +## CHECK-DAG: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] +## CHECK-DAG: <<And:i\d+>> And [<<NotP1>>,<<NotP2>>] +## CHECK-DAG: Return [<<And>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier (after) +## CHECK-DAG: <<Cond1:z\d+>> ParameterValue +## CHECK-DAG: <<Cond2:z\d+>> ParameterValue +## CHECK-DAG: <<Or:i\d+>> Or [<<Cond1>>,<<Cond2>>] +## CHECK-DAG: <<BooleanNot:z\d+>> BooleanNot [<<Or>>] +## CHECK-DAG: Return [<<BooleanNot>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-DAG: BooleanNot +## CHECK-NOT: BooleanNot + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-NOT: And +.method public static $opt$noinline$booleanAndToOr(ZZ)Z + .registers 4 + .param p0, "a" # Z + .param p1, "b" # Z + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return !a & !b; + xor-int/lit8 v0, p0, 0x1 + xor-int/lit8 v1, p1, 0x1 + and-int/2addr v0, v1 + return v0 +.end method + +# Test transformation of Not/Not/Or into And/Not. + +## CHECK-START: long SmaliTests.$opt$noinline$orToAnd(long, long) instruction_simplifier (before) +## CHECK-DAG: <<P1:j\d+>> ParameterValue +## CHECK-DAG: <<P2:j\d+>> ParameterValue +## CHECK-DAG: <<Not1:j\d+>> Not [<<P1>>] +## CHECK-DAG: <<Not2:j\d+>> Not [<<P2>>] +## CHECK-DAG: <<Or:j\d+>> Or [<<Not1>>,<<Not2>>] +## CHECK-DAG: Return [<<Or>>] + +## CHECK-START: long SmaliTests.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) +## CHECK-DAG: <<P1:j\d+>> ParameterValue +## CHECK-DAG: <<P2:j\d+>> ParameterValue +## CHECK-DAG: <<And:j\d+>> And [<<P1>>,<<P2>>] +## CHECK-DAG: <<Not:j\d+>> Not [<<And>>] +## CHECK-DAG: Return [<<Not>>] + +## CHECK-START: long SmaliTests.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) +## CHECK-DAG: Not +## CHECK-NOT: Not + +## CHECK-START: long SmaliTests.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) +## CHECK-NOT: Or +.method public static $opt$noinline$orToAnd(JJ)J + .registers 8 + .param p0, "a" # J + .param p2, "b" # J + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return ~a | ~b; + not-long v0, p0 + not-long v2, p2 + or-long/2addr v0, v2 + return-wide v0 +.end method + +# Test transformation of Not/Not/Or into Or/And for boolean negations. +# Note that the graph before this instruction simplification pass does not +# contain `HBooleanNot` instructions. This is because this transformation +# follows the optimization of `HSelect` to `HBooleanNot` occurring in the +# same pass. + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier (before) +## CHECK-DAG: <<P1:z\d+>> ParameterValue +## CHECK-DAG: <<P2:z\d+>> ParameterValue +## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 +## CHECK-DAG: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] +## CHECK-DAG: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] +## CHECK-DAG: <<Or:i\d+>> Or [<<NotP1>>,<<NotP2>>] +## CHECK-DAG: Return [<<Or>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier (after) +## CHECK-DAG: <<Cond1:z\d+>> ParameterValue +## CHECK-DAG: <<Cond2:z\d+>> ParameterValue +## CHECK-DAG: <<And:i\d+>> And [<<Cond1>>,<<Cond2>>] +## CHECK-DAG: <<BooleanNot:z\d+>> BooleanNot [<<And>>] +## CHECK-DAG: Return [<<BooleanNot>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-DAG: BooleanNot +## CHECK-NOT: BooleanNot + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-NOT: Or +.method public static $opt$noinline$booleanOrToAnd(ZZ)Z + .registers 4 + .param p0, "a" # Z + .param p1, "b" # Z + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return !a | !b; + xor-int/lit8 v0, p0, 0x1 + xor-int/lit8 v1, p1, 0x1 + or-int/2addr v0, v1 + return v0 +.end method + +# Test that the transformation copes with inputs being separated from the +# bitwise operations. +# This is a regression test. The initial logic was inserting the new bitwise +# operation incorrectly. + +## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (before) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 +## CHECK-DAG: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] +## CHECK-DAG: <<Not1:i\d+>> Not [<<AddP1>>] +## CHECK-DAG: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] +## CHECK-DAG: <<Not2:i\d+>> Not [<<AddP2>>] +## CHECK-DAG: <<Or:i\d+>> Or [<<Not1>>,<<Not2>>] +## CHECK-DAG: Return [<<Or>>] + +## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 +## CHECK-DAG: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] +## CHECK-DAG: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] +## CHECK-DAG: <<And:i\d+>> And [<<AddP1>>,<<AddP2>>] +## CHECK-DAG: <<Not:i\d+>> Not [<<And>>] +## CHECK-DAG: Return [<<Not>>] + +## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) +## CHECK-DAG: Not +## CHECK-NOT: Not + +## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) +## CHECK-NOT: Or +.method public static $opt$noinline$regressInputsAway(II)I + .registers 7 + .param p0, "a" # I + .param p1, "b" # I + + .prologue + sget-boolean v4, LSmaliTests;->doThrow:Z + if-eqz v4, :cond_a + new-instance v4, Ljava/lang/Error; + invoke-direct {v4}, Ljava/lang/Error;-><init>()V + throw v4 + + :cond_a + # int a1 = a + 1; + add-int/lit8 v0, p0, 0x1 + # int not_a1 = ~a1; + not-int v2, v0 + # int b1 = b + 1; + add-int/lit8 v1, p1, 0x1 + # int not_b1 = ~b1; + not-int v3, v1 + + # return not_a1 | not_b1 + or-int v4, v2, v3 + return v4 +.end method + +# Test transformation of Not/Not/Xor into Xor. + +# See first note above. +## CHECK-START: int SmaliTests.$opt$noinline$notXorToXor(int, int) instruction_simplifier (before) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Not1:i\d+>> Not [<<P1>>] +## CHECK-DAG: <<Not2:i\d+>> Not [<<P2>>] +## CHECK-DAG: <<Xor:i\d+>> Xor [<<Not1>>,<<Not2>>] +## CHECK-DAG: Return [<<Xor>>] + +## CHECK-START: int SmaliTests.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<Xor:i\d+>> Xor [<<P1>>,<<P2>>] +## CHECK-DAG: Return [<<Xor>>] + +## CHECK-START: int SmaliTests.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after) +## CHECK-NOT: Not +.method public static $opt$noinline$notXorToXor(II)I + .registers 4 + .param p0, "a" # I + .param p1, "b" # I + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return ~a ^ ~b; + not-int v0, p0 + not-int v1, p1 + xor-int/2addr v0, v1 + return v0 +.end method + +# Test transformation of Not/Not/Xor into Xor for boolean negations. +# Note that the graph before this instruction simplification pass does not +# contain `HBooleanNot` instructions. This is because this transformation +# follows the optimization of `HSelect` to `HBooleanNot` occurring in the +# same pass. + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier (before) +## CHECK-DAG: <<P1:z\d+>> ParameterValue +## CHECK-DAG: <<P2:z\d+>> ParameterValue +## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 +## CHECK-DAG: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] +## CHECK-DAG: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] +## CHECK-DAG: <<Xor:i\d+>> Xor [<<NotP1>>,<<NotP2>>] +## CHECK-DAG: Return [<<Xor>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier (after) +## CHECK-DAG: <<Cond1:z\d+>> ParameterValue +## CHECK-DAG: <<Cond2:z\d+>> ParameterValue +## CHECK-DAG: <<Xor:i\d+>> Xor [<<Cond1>>,<<Cond2>>] +## CHECK-DAG: Return [<<Xor>>] + +## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_bce (after) +## CHECK-NOT: BooleanNot +.method public static $opt$noinline$booleanNotXorToXor(ZZ)Z + .registers 4 + .param p0, "a" # Z + .param p1, "b" # Z + + .prologue + sget-boolean v0, LSmaliTests;->doThrow:Z + if-eqz v0, :cond_a + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :cond_a + # return !a ^ !b; + xor-int/lit8 v0, p0, 0x1 + xor-int/lit8 v1, p1, 0x1 + xor-int/2addr v0, v1 + return v0 +.end method + +# Check that no transformation is done when one Not has multiple uses. + +## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (before) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Not2:i\d+>> Not [<<P2>>] +## CHECK-DAG: <<And2:i\d+>> And [<<Not2>>,<<One>>] +## CHECK-DAG: <<Not1:i\d+>> Not [<<P1>>] +## CHECK-DAG: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] +## CHECK-DAG: <<Add:i\d+>> Add [<<And2>>,<<And1>>] +## CHECK-DAG: Return [<<Add>>] + +## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after) +## CHECK-DAG: <<P1:i\d+>> ParameterValue +## CHECK-DAG: <<P2:i\d+>> ParameterValue +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Not2:i\d+>> Not [<<P2>>] +## CHECK-DAG: <<And2:i\d+>> And [<<Not2>>,<<One>>] +## CHECK-DAG: <<Not1:i\d+>> Not [<<P1>>] +## CHECK-DAG: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] +## CHECK-DAG: <<Add:i\d+>> Add [<<And2>>,<<And1>>] +## CHECK-DAG: Return [<<Add>>] + +## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after) +## CHECK-NOT: Or +.method public static $opt$noinline$notMultipleUses(II)I + .registers 5 + .param p0, "a" # I + .param p1, "b" # I + + .prologue + sget-boolean v1, LSmaliTests;->doThrow:Z + if-eqz v1, :cond_a + new-instance v1, Ljava/lang/Error; + invoke-direct {v1}, Ljava/lang/Error;-><init>()V + throw v1 + + :cond_a + # int tmp = ~b; + not-int v0, p1 + # return (tmp & 0x1) + (~a & tmp); + and-int/lit8 v1, v0, 0x1 + not-int v2, p0 + and-int/2addr v2, v0 + add-int/2addr v1, v2 + return v1 +.end method + +# static fields +.field static doThrow:Z # boolean + +# direct methods +.method static constructor <clinit>()V + .registers 1 + + .prologue + # doThrow = false + const/4 v0, 0x0 + sput-boolean v0, LSmaliTests;->doThrow:Z + return-void +.end method diff --git a/test/565-checker-doublenegbitwise/src/Main.java b/test/565-checker-doublenegbitwise/src/Main.java index 5ccc648076..816cafc2ba 100644 --- a/test/565-checker-doublenegbitwise/src/Main.java +++ b/test/565-checker-doublenegbitwise/src/Main.java @@ -14,6 +14,8 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { // A dummy value to defeat inlining of these routines. @@ -31,31 +33,53 @@ public class Main { } } + public static void assertEquals(boolean expected, boolean result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static <T> T $noinline$runSmaliTest(String name, Class<T> klass, T input1, T input2) { + if (doThrow) { throw new Error(); } + + Class<T> inputKlass = (Class<T>)input1.getClass(); + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, klass, klass); + return inputKlass.cast(m.invoke(null, input1, input2)); + } catch (Exception ex) { + throw new Error(ex); + } + } + /** * Test transformation of Not/Not/And into Or/Not. */ + // Note: before the instruction_simplifier pass, Xor's are used instead of + // Not's (the simplification happens during the same pass). /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (before) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Not1:i\d+>> Not [<<P1>>] - /// CHECK: <<Not2:i\d+>> Not [<<P2>>] - /// CHECK: <<And:i\d+>> And [<<Not1>>,<<Not2>>] - /// CHECK: Return [<<And>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<CstM1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Not1:i\d+>> Xor [<<P1>>,<<CstM1>>] + /// CHECK-DAG: <<Not2:i\d+>> Xor [<<P2>>,<<CstM1>>] + /// CHECK-DAG: <<And:i\d+>> And [<<Not1>>,<<Not2>>] + /// CHECK-DAG: Return [<<And>>] /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Or:i\d+>> Or [<<P1>>,<<P2>>] - /// CHECK: <<Not:i\d+>> Not [<<Or>>] - /// CHECK: Return [<<Not>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<Or:i\d+>> Or [<<P1>>,<<P2>>] + /// CHECK-DAG: <<Not:i\d+>> Not [<<Or>>] + /// CHECK-DAG: Return [<<Not>>] /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after) - /// CHECK: Not - /// CHECK-NOT: Not + /// CHECK-DAG: Not + /// CHECK-NOT: Not /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after) - /// CHECK-NOT: And + /// CHECK-NOT: And public static int $opt$noinline$andToOr(int a, int b) { if (doThrow) throw new Error(); @@ -70,28 +94,29 @@ public class Main { * same pass. */ - /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier (before) - /// CHECK: <<P1:z\d+>> ParameterValue - /// CHECK: <<P2:z\d+>> ParameterValue - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 - /// CHECK-DAG: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] - /// CHECK-DAG: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] - /// CHECK: <<And:i\d+>> And [<<NotP1>>,<<NotP2>>] - /// CHECK: Return [<<And>>] - - /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier (after) - /// CHECK: <<Cond1:z\d+>> ParameterValue - /// CHECK: <<Cond2:z\d+>> ParameterValue - /// CHECK: <<Or:i\d+>> Or [<<Cond1>>,<<Cond2>>] - /// CHECK: <<BooleanNot:z\d+>> BooleanNot [<<Or>>] - /// CHECK: Return [<<BooleanNot>>] + /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_inlining (before) + /// CHECK-DAG: <<P1:z\d+>> ParameterValue + /// CHECK-DAG: <<P2:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Select1:i\d+>> Select [<<Const1>>,<<Const0>>,<<P1>>] + /// CHECK-DAG: <<Select2:i\d+>> Select [<<Const1>>,<<Const0>>,<<P2>>] + /// CHECK-DAG: <<And:i\d+>> And [<<Select2>>,<<Select1>>] + /// CHECK-DAG: Return [<<And>>] + + /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_inlining (after) + /// CHECK-DAG: <<Cond1:z\d+>> ParameterValue + /// CHECK-DAG: <<Cond2:z\d+>> ParameterValue + /// CHECK-DAG: <<Or:i\d+>> Or [<<Cond2>>,<<Cond1>>] + /// CHECK-DAG: <<BooleanNot:z\d+>> BooleanNot [<<Or>>] + /// CHECK-DAG: Return [<<BooleanNot>>] /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after) - /// CHECK: BooleanNot - /// CHECK-NOT: BooleanNot + /// CHECK-DAG: BooleanNot + /// CHECK-NOT: BooleanNot /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after) - /// CHECK-NOT: And + /// CHECK-NOT: And public static boolean $opt$noinline$booleanAndToOr(boolean a, boolean b) { if (doThrow) throw new Error(); @@ -102,27 +127,30 @@ public class Main { * Test transformation of Not/Not/Or into And/Not. */ + // See note above. + // The second Xor has its arguments reversed for no obvious reason. /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (before) - /// CHECK: <<P1:j\d+>> ParameterValue - /// CHECK: <<P2:j\d+>> ParameterValue - /// CHECK: <<Not1:j\d+>> Not [<<P1>>] - /// CHECK: <<Not2:j\d+>> Not [<<P2>>] - /// CHECK: <<Or:j\d+>> Or [<<Not1>>,<<Not2>>] - /// CHECK: Return [<<Or>>] + /// CHECK-DAG: <<P1:j\d+>> ParameterValue + /// CHECK-DAG: <<P2:j\d+>> ParameterValue + /// CHECK-DAG: <<CstM1:j\d+>> LongConstant -1 + /// CHECK-DAG: <<Not1:j\d+>> Xor [<<P1>>,<<CstM1>>] + /// CHECK-DAG: <<Not2:j\d+>> Xor [<<CstM1>>,<<P2>>] + /// CHECK-DAG: <<Or:j\d+>> Or [<<Not1>>,<<Not2>>] + /// CHECK-DAG: Return [<<Or>>] /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) - /// CHECK: <<P1:j\d+>> ParameterValue - /// CHECK: <<P2:j\d+>> ParameterValue - /// CHECK: <<And:j\d+>> And [<<P1>>,<<P2>>] - /// CHECK: <<Not:j\d+>> Not [<<And>>] - /// CHECK: Return [<<Not>>] + /// CHECK-DAG: <<P1:j\d+>> ParameterValue + /// CHECK-DAG: <<P2:j\d+>> ParameterValue + /// CHECK-DAG: <<And:j\d+>> And [<<P1>>,<<P2>>] + /// CHECK-DAG: <<Not:j\d+>> Not [<<And>>] + /// CHECK-DAG: Return [<<Not>>] /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) - /// CHECK: Not - /// CHECK-NOT: Not + /// CHECK-DAG: Not + /// CHECK-NOT: Not /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after) - /// CHECK-NOT: Or + /// CHECK-NOT: Or public static long $opt$noinline$orToAnd(long a, long b) { if (doThrow) throw new Error(); @@ -137,28 +165,29 @@ public class Main { * same pass. */ - /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier (before) - /// CHECK: <<P1:z\d+>> ParameterValue - /// CHECK: <<P2:z\d+>> ParameterValue - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 - /// CHECK: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] - /// CHECK: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] - /// CHECK: <<Or:i\d+>> Or [<<NotP1>>,<<NotP2>>] - /// CHECK: Return [<<Or>>] - - /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier (after) - /// CHECK: <<Cond1:z\d+>> ParameterValue - /// CHECK: <<Cond2:z\d+>> ParameterValue - /// CHECK: <<And:i\d+>> And [<<Cond1>>,<<Cond2>>] - /// CHECK: <<BooleanNot:z\d+>> BooleanNot [<<And>>] - /// CHECK: Return [<<BooleanNot>>] + /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_inlining (before) + /// CHECK-DAG: <<P1:z\d+>> ParameterValue + /// CHECK-DAG: <<P2:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Select1:i\d+>> Select [<<Const1>>,<<Const0>>,<<P1>>] + /// CHECK-DAG: <<Select2:i\d+>> Select [<<Const1>>,<<Const0>>,<<P2>>] + /// CHECK-DAG: <<Or:i\d+>> Or [<<Select2>>,<<Select1>>] + /// CHECK-DAG: Return [<<Or>>] + + /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_inlining (after) + /// CHECK-DAG: <<Cond1:z\d+>> ParameterValue + /// CHECK-DAG: <<Cond2:z\d+>> ParameterValue + /// CHECK-DAG: <<And:i\d+>> And [<<Cond2>>,<<Cond1>>] + /// CHECK-DAG: <<BooleanNot:z\d+>> BooleanNot [<<And>>] + /// CHECK-DAG: Return [<<BooleanNot>>] /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after) - /// CHECK: BooleanNot - /// CHECK-NOT: BooleanNot + /// CHECK-DAG: BooleanNot + /// CHECK-NOT: BooleanNot /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after) - /// CHECK-NOT: Or + /// CHECK-NOT: Or public static boolean $opt$noinline$booleanOrToAnd(boolean a, boolean b) { if (doThrow) throw new Error(); @@ -173,32 +202,33 @@ public class Main { */ /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (before) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Cst1:i\d+>> IntConstant 1 - /// CHECK: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] - /// CHECK: <<Not1:i\d+>> Not [<<AddP1>>] - /// CHECK: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] - /// CHECK: <<Not2:i\d+>> Not [<<AddP2>>] - /// CHECK: <<Or:i\d+>> Or [<<Not1>>,<<Not2>>] - /// CHECK: Return [<<Or>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<CstM1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] + /// CHECK-DAG: <<Not1:i\d+>> Xor [<<AddP1>>,<<CstM1>>] + /// CHECK-DAG: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] + /// CHECK-DAG: <<Not2:i\d+>> Xor [<<AddP2>>,<<CstM1>>] + /// CHECK-DAG: <<Or:i\d+>> Or [<<Not1>>,<<Not2>>] + /// CHECK-DAG: Return [<<Or>>] /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Cst1:i\d+>> IntConstant 1 - /// CHECK: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] - /// CHECK: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] - /// CHECK: <<And:i\d+>> And [<<AddP1>>,<<AddP2>>] - /// CHECK: <<Not:i\d+>> Not [<<And>>] - /// CHECK: Return [<<Not>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<AddP1:i\d+>> Add [<<P1>>,<<Cst1>>] + /// CHECK-DAG: <<AddP2:i\d+>> Add [<<P2>>,<<Cst1>>] + /// CHECK-DAG: <<And:i\d+>> And [<<AddP1>>,<<AddP2>>] + /// CHECK-DAG: <<Not:i\d+>> Not [<<And>>] + /// CHECK-DAG: Return [<<Not>>] /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) - /// CHECK: Not - /// CHECK-NOT: Not + /// CHECK-DAG: Not + /// CHECK-NOT: Not /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after) - /// CHECK-NOT: Or + /// CHECK-NOT: Or public static int $opt$noinline$regressInputsAway(int a, int b) { if (doThrow) throw new Error(); @@ -215,21 +245,22 @@ public class Main { // See first note above. /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (before) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Not1:i\d+>> Not [<<P1>>] - /// CHECK: <<Not2:i\d+>> Not [<<P2>>] - /// CHECK: <<Xor:i\d+>> Xor [<<Not1>>,<<Not2>>] - /// CHECK: Return [<<Xor>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<CstM1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Not1:i\d+>> Xor [<<P1>>,<<CstM1>>] + /// CHECK-DAG: <<Not2:i\d+>> Xor [<<P2>>,<<CstM1>>] + /// CHECK-DAG: <<Xor:i\d+>> Xor [<<Not1>>,<<Not2>>] + /// CHECK-DAG: Return [<<Xor>>] /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<Xor:i\d+>> Xor [<<P1>>,<<P2>>] - /// CHECK: Return [<<Xor>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<Xor:i\d+>> Xor [<<P1>>,<<P2>>] + /// CHECK-DAG: Return [<<Xor>>] /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after) - /// CHECK-NOT: Not + /// CHECK-NOT: Not public static int $opt$noinline$notXorToXor(int a, int b) { if (doThrow) throw new Error(); @@ -244,23 +275,24 @@ public class Main { * same pass. */ - /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier (before) - /// CHECK: <<P1:z\d+>> ParameterValue - /// CHECK: <<P2:z\d+>> ParameterValue - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 - /// CHECK: <<NotP1:i\d+>> Xor [<<P1>>,<<Const1>>] - /// CHECK: <<NotP2:i\d+>> Xor [<<P2>>,<<Const1>>] - /// CHECK: <<Xor:i\d+>> Xor [<<NotP1>>,<<NotP2>>] - /// CHECK: Return [<<Xor>>] - - /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier (after) - /// CHECK: <<Cond1:z\d+>> ParameterValue - /// CHECK: <<Cond2:z\d+>> ParameterValue - /// CHECK: <<Xor:i\d+>> Xor [<<Cond1>>,<<Cond2>>] - /// CHECK: Return [<<Xor>>] + /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_inlining (before) + /// CHECK-DAG: <<P1:z\d+>> ParameterValue + /// CHECK-DAG: <<P2:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Select1:i\d+>> Select [<<Const1>>,<<Const0>>,<<P1>>] + /// CHECK-DAG: <<Select2:i\d+>> Select [<<Const1>>,<<Const0>>,<<P2>>] + /// CHECK-DAG: <<Xor:i\d+>> Xor [<<Select2>>,<<Select1>>] + /// CHECK-DAG: Return [<<Xor>>] + + /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_inlining (after) + /// CHECK-DAG: <<Cond1:z\d+>> ParameterValue + /// CHECK-DAG: <<Cond2:z\d+>> ParameterValue + /// CHECK-DAG: <<Xor:i\d+>> Xor [<<Cond2>>,<<Cond1>>] + /// CHECK-DAG: Return [<<Xor>>] /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_bce (after) - /// CHECK-NOT: BooleanNot + /// CHECK-NOT: BooleanNot public static boolean $opt$noinline$booleanNotXorToXor(boolean a, boolean b) { if (doThrow) throw new Error(); @@ -272,29 +304,30 @@ public class Main { */ /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (before) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<One:i\d+>> IntConstant 1 - /// CHECK: <<Not2:i\d+>> Not [<<P2>>] - /// CHECK: <<And2:i\d+>> And [<<Not2>>,<<One>>] - /// CHECK: <<Not1:i\d+>> Not [<<P1>>] - /// CHECK: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] - /// CHECK: <<Add:i\d+>> Add [<<And2>>,<<And1>>] - /// CHECK: Return [<<Add>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<CstM1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<One:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Not2:i\d+>> Xor [<<P2>>,<<CstM1>>] + /// CHECK-DAG: <<And2:i\d+>> And [<<Not2>>,<<One>>] + /// CHECK-DAG: <<Not1:i\d+>> Xor [<<P1>>,<<CstM1>>] + /// CHECK-DAG: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] + /// CHECK-DAG: <<Add:i\d+>> Add [<<And2>>,<<And1>>] + /// CHECK-DAG: Return [<<Add>>] /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after) - /// CHECK: <<P1:i\d+>> ParameterValue - /// CHECK: <<P2:i\d+>> ParameterValue - /// CHECK: <<One:i\d+>> IntConstant 1 - /// CHECK: <<Not2:i\d+>> Not [<<P2>>] - /// CHECK: <<And2:i\d+>> And [<<Not2>>,<<One>>] - /// CHECK: <<Not1:i\d+>> Not [<<P1>>] - /// CHECK: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] - /// CHECK: <<Add:i\d+>> Add [<<And2>>,<<And1>>] - /// CHECK: Return [<<Add>>] + /// CHECK-DAG: <<P1:i\d+>> ParameterValue + /// CHECK-DAG: <<P2:i\d+>> ParameterValue + /// CHECK-DAG: <<One:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Not2:i\d+>> Not [<<P2>>] + /// CHECK-DAG: <<And2:i\d+>> And [<<Not2>>,<<One>>] + /// CHECK-DAG: <<Not1:i\d+>> Not [<<P1>>] + /// CHECK-DAG: <<And1:i\d+>> And [<<Not1>>,<<Not2>>] + /// CHECK-DAG: <<Add:i\d+>> Add [<<And2>>,<<And1>>] + /// CHECK-DAG: Return [<<Add>>] /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after) - /// CHECK-NOT: Or + /// CHECK-NOT: Or public static int $opt$noinline$notMultipleUses(int a, int b) { if (doThrow) throw new Error(); @@ -304,8 +337,20 @@ public class Main { public static void main(String[] args) { assertIntEquals(~0xff, $opt$noinline$andToOr(0xf, 0xff)); + assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$andToOr", int.class, 0xf, 0xff)); + assertEquals(true, $opt$noinline$booleanAndToOr(false, false)); + assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanAndToOr", boolean.class, false, false)); assertLongEquals(~0xf, $opt$noinline$orToAnd(0xf, 0xff)); + assertLongEquals(~0xf, $noinline$runSmaliTest("$opt$noinline$orToAnd", long.class, 0xfL, 0xffL)); + assertEquals(false, $opt$noinline$booleanOrToAnd(true, true)); + assertEquals(false, $noinline$runSmaliTest("$opt$noinline$booleanOrToAnd", boolean.class, true, true)); + assertIntEquals(-1, $opt$noinline$regressInputsAway(0xf, 0xff)); + assertIntEquals(-1, $noinline$runSmaliTest("$opt$noinline$regressInputsAway", int.class, 0xf, 0xff)); assertIntEquals(0xf0, $opt$noinline$notXorToXor(0xf, 0xff)); + assertIntEquals(0xf0, $noinline$runSmaliTest("$opt$noinline$notXorToXor", int.class, 0xf, 0xff)); + assertEquals(true, $opt$noinline$booleanNotXorToXor(true, false)); + assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanNotXorToXor", boolean.class, true, false)); assertIntEquals(~0xff, $opt$noinline$notMultipleUses(0xf, 0xff)); + assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$notMultipleUses", int.class, 0xf, 0xff)); } } diff --git a/test/586-checker-null-array-get/build b/test/586-checker-null-array-get/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/586-checker-null-array-get/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/586-checker-null-array-get/smali/SmaliTests.smali b/test/586-checker-null-array-get/smali/SmaliTests.smali new file mode 100644 index 0000000000..f58af360fc --- /dev/null +++ b/test/586-checker-null-array-get/smali/SmaliTests.smali @@ -0,0 +1,96 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +## CHECK-START: void SmaliTests.bar() load_store_elimination (after) +## CHECK-DAG: <<Null:l\d+>> NullConstant +## CHECK-DAG: <<BoundType:l\d+>> BoundType [<<Null>>] +## CHECK-DAG: <<CheckL:l\d+>> NullCheck [<<BoundType>>] +## CHECK-DAG: <<GetL0:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] +## CHECK-DAG: <<GetL1:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] +## CHECK-DAG: <<GetL2:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] +## CHECK-DAG: <<GetL3:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] +## CHECK-DAG: <<CheckJ:l\d+>> NullCheck [<<Null>>] +## CHECK-DAG: <<GetJ0:j\d+>> ArrayGet [<<CheckJ>>,{{i\d+}}] +## CHECK-DAG: <<GetJ1:j\d+>> ArrayGet [<<CheckJ>>,{{i\d+}}] +## CHECK-DAG: <<GetJ2:j\d+>> ArrayGet [<<CheckJ>>,{{i\d+}}] +## CHECK-DAG: <<GetJ3:j\d+>> ArrayGet [<<CheckJ>>,{{i\d+}}] +.method public static bar()V + .registers 7 + + .prologue + const/4 v6, 0x3 + const/4 v5, 0x2 + const/4 v4, 0x1 + const/4 v3, 0x0 + + # We create multiple accesses that will lead the bounds check + # elimination pass to add a HDeoptimize. Not having the bounds check helped + # the load store elimination think it could merge two ArrayGet with different + # types. + + # String[] array = (String[])getNull(); + invoke-static {}, LMain;->getNull()Ljava/lang/Object; + move-result-object v0 + check-cast v0, [Ljava/lang/String; + + # objectField = array[0]; + aget-object v2, v0, v3 + sput-object v2, LMain;->objectField:Ljava/lang/Object; + # objectField = array[1]; + aget-object v2, v0, v4 + sput-object v2, LMain;->objectField:Ljava/lang/Object; + # objectField = array[2]; + aget-object v2, v0, v5 + sput-object v2, LMain;->objectField:Ljava/lang/Object; + # objectField = array[3]; + aget-object v2, v0, v6 + sput-object v2, LMain;->objectField:Ljava/lang/Object; + + # long[] longArray = getLongArray(); + invoke-static {}, LMain;->getLongArray()[J + move-result-object v1 + + # longField = longArray[0]; + aget-wide v2, v1, v3 + sput-wide v2, LMain;->longField:J + # longField = longArray[1]; + aget-wide v2, v1, v4 + sput-wide v2, LMain;->longField:J + # longField = longArray[2]; + aget-wide v2, v1, v5 + sput-wide v2, LMain;->longField:J + # longField = longArray[3]; + aget-wide v2, v1, v6 + sput-wide v2, LMain;->longField:J + + return-void +.end method + + +# static fields +.field static doThrow:Z # boolean + +# direct methods +.method static constructor <clinit>()V + .registers 1 + + .prologue + # doThrow = false + const/4 v0, 0x0 + sput-boolean v0, LSmaliTests;->doThrow:Z + return-void +.end method diff --git a/test/586-checker-null-array-get/src/Main.java b/test/586-checker-null-array-get/src/Main.java index 0ea7d34043..09ebff16c2 100644 --- a/test/586-checker-null-array-get/src/Main.java +++ b/test/586-checker-null-array-get/src/Main.java @@ -14,6 +14,9 @@ * limitations under the License. */ +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + class Test1 { int[] iarr; } @@ -29,6 +32,18 @@ public class Main { public static Test1 getNullTest1() { return null; } public static Test2 getNullTest2() { return null; } + public static void $noinline$runSmaliTest(String name) throws Throwable { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name); + m.invoke(null); + } catch (InvocationTargetException ex) { + throw ex.getCause(); // re-raise expected exception. + } catch (Exception ex) { + throw new Error(ex); + } + } + public static void main(String[] args) { try { foo(); @@ -43,6 +58,15 @@ public class Main { // Expected. } try { + $noinline$runSmaliTest("bar"); + throw new Error("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected. + } catch (Throwable t) { + throw new Error("Unexpected Throwable", t); + } + + try { test1(); throw new Error("Expected NullPointerException"); } catch (NullPointerException e) { @@ -62,7 +86,8 @@ public class Main { /// CHECK-START: void Main.bar() load_store_elimination (after) /// CHECK-DAG: <<Null:l\d+>> NullConstant - /// CHECK-DAG: <<BoundType:l\d+>> BoundType [<<Null>>] + /// CHECK-DAG: <<BoundFirst:l\d+>> BoundType [<<Null>>] + /// CHECK-DAG: <<BoundType:l\d+>> BoundType [<<BoundFirst>>] /// CHECK-DAG: <<CheckL:l\d+>> NullCheck [<<BoundType>>] /// CHECK-DAG: <<GetL0:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] /// CHECK-DAG: <<GetL1:l\d+>> ArrayGet [<<CheckL>>,{{i\d+}}] diff --git a/test/593-checker-boolean-2-integral-conv/build b/test/593-checker-boolean-2-integral-conv/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/593-checker-boolean-2-integral-conv/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali b/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali new file mode 100644 index 0000000000..00ebaaf451 --- /dev/null +++ b/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali @@ -0,0 +1,119 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# static fields +.field public static booleanField:Z + +.method static constructor <clinit>()V + .registers 1 + + .prologue + const/4 v0, 0x1 + + # booleanField = true + sput-boolean v0, LSmaliTests;->booleanField:Z + + return-void +.end method + +## CHECK-START: long SmaliTests.booleanToLong(boolean) builder (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Cond:z\d+>> Equal [<<Arg>>,<<Zero>>] +## CHECK-DAG: If [<<Cond>>] +## CHECK-DAG: <<Phi:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Phi>>] +## CHECK-DAG: Return [<<IToJ>>] + +## CHECK-START: long SmaliTests.booleanToLong(boolean) select_generator (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Sel:i\d+>> Select [<<Zero>>,<<One>>,<<Arg>>] +## CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Sel>>] +## CHECK-DAG: Return [<<IToJ>>] + +## CHECK-START: long SmaliTests.booleanToLong(boolean) instruction_simplifier$after_bce (after) +## CHECK-DAG: <<Arg:z\d+>> ParameterValue +## CHECK-DAG: <<ZToJ:j\d+>> TypeConversion [<<Arg>>] +## CHECK-DAG: Return [<<ZToJ>>] +.method public static booleanToLong(Z)J + .registers 3 + .param p0, "b" # Z + .prologue + + # return b ? 1 : 0; + if-eqz p0, :b_is_zero + +# :b_is_one + const/4 v0, 0x1 + + :l_return + int-to-long v0, v0 + return-wide v0 + + :b_is_zero + const/4 v0, 0x0 + goto :l_return +.end method + +## CHECK-START: int SmaliTests.longToIntOfBoolean() builder (after) +## CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod +## CHECK-DAG: <<Sget:z\d+>> StaticFieldGet +## CHECK-DAG: <<ZToJ:j\d+>> InvokeStaticOrDirect [<<Sget>>,<<Method>>] +## CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<ZToJ>>] +## CHECK-DAG: Return [<<JToI>>] + +## CHECK-START: int SmaliTests.longToIntOfBoolean() inliner (after) +## CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Sget:z\d+>> StaticFieldGet +## CHECK-DAG: If [<<Sget>>] +## CHECK-DAG: <<Phi:i\d+>> Phi [<<One>>,<<Zero>>] +## CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Phi>>] +## CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<IToJ>>] +## CHECK-DAG: Return [<<JToI>>] + +## CHECK-START: int SmaliTests.longToIntOfBoolean() select_generator (after) +## CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod +## CHECK-DAG: <<Zero:i\d+>> IntConstant 0 +## CHECK-DAG: <<One:i\d+>> IntConstant 1 +## CHECK-DAG: <<Sget:z\d+>> StaticFieldGet +## CHECK-DAG: <<Sel:i\d+>> Select [<<Zero>>,<<One>>,<<Sget>>] +## CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Sel>>] +## CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<IToJ>>] +## CHECK-DAG: Return [<<JToI>>] + +## CHECK-START: int SmaliTests.longToIntOfBoolean() instruction_simplifier$after_bce (after) +## CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod +## CHECK-DAG: <<Sget:z\d+>> StaticFieldGet +## CHECK-DAG: Return [<<Sget>>] +.method public static longToIntOfBoolean()I + .registers 3 + .prologue + + # long l = booleanToLong(booleanField); + sget-boolean v2, LSmaliTests;->booleanField:Z + invoke-static {v2}, LSmaliTests;->booleanToLong(Z)J + move-result-wide v0 + + # return (int) l; + long-to-int v2, v0 + return v2 +.end method diff --git a/test/593-checker-boolean-2-integral-conv/src/Main.java b/test/593-checker-boolean-2-integral-conv/src/Main.java index b4c91c8db6..3503b2e877 100644 --- a/test/593-checker-boolean-2-integral-conv/src/Main.java +++ b/test/593-checker-boolean-2-integral-conv/src/Main.java @@ -14,6 +14,8 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { public static void main(String args[]) { @@ -22,8 +24,10 @@ public class Main { expectEqualsChar((char)1, booleanToChar(true)); expectEqualsInt(1, booleanToInt(true)); expectEqualsLong(1L, booleanToLong(true)); + expectEqualsLong(1L, $noinline$runSmaliTest("booleanToLong", true)); expectEqualsInt(1, longToIntOfBoolean()); + expectEqualsInt(1, $noinline$runSmaliTest("longToIntOfBoolean")); System.out.println("passed"); } @@ -132,26 +136,34 @@ public class Main { /// CHECK-START: long Main.booleanToLong(boolean) builder (after) /// CHECK: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 - /// CHECK-DAG: <<Cond:z\d+>> Equal [<<Arg>>,<<Zero>>] + /// CHECK-DAG: <<IZero:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Zero:j\d+>> LongConstant 0 + /// CHECK-DAG: <<One:j\d+>> LongConstant 1 + /// CHECK-DAG: <<Cond:z\d+>> Equal [<<Arg>>,<<IZero>>] /// CHECK-DAG: If [<<Cond>>] - /// CHECK-DAG: <<Phi:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Phi>>] - /// CHECK-DAG: Return [<<IToJ>>] + /// CHECK-DAG: <<Phi:j\d+>> Phi [<<One>>,<<Zero>>] + /// CHECK-DAG: Return [<<Phi>>] /// CHECK-START: long Main.booleanToLong(boolean) select_generator (after) - /// CHECK: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 - /// CHECK-DAG: <<Sel:i\d+>> Select [<<Zero>>,<<One>>,<<Arg>>] - /// CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Sel>>] - /// CHECK-DAG: Return [<<IToJ>>] + /// CHECK-NOT: IntConstant + /// CHECK-NOT: Equal + /// CHECK-NOT: If + /// CHECK-NOT: Phi - /// CHECK-START: long Main.booleanToLong(boolean) instruction_simplifier$after_bce (after) + /// CHECK-START: long Main.booleanToLong(boolean) select_generator (after) /// CHECK: <<Arg:z\d+>> ParameterValue - /// CHECK-DAG: <<ZToJ:j\d+>> TypeConversion [<<Arg>>] - /// CHECK-DAG: Return [<<ZToJ>>] + /// CHECK-DAG: <<Zero:j\d+>> LongConstant 0 + /// CHECK-DAG: <<One:j\d+>> LongConstant 1 + /// CHECK-DAG: <<Sel:j\d+>> Select [<<Zero>>,<<One>>,<<Arg>>] + /// CHECK-DAG: Return [<<Sel>>] + + // As of now, the code is not optimized any further than the above. + // TODO: Re-enable checks below after simplifier is updated to handle this pattern: b/63064517 + + // CHECK-START: long Main.booleanToLong(boolean) instruction_simplifier$after_bce (after) + // CHECK: <<Arg:z\d+>> ParameterValue + // CHECK-DAG: <<ZToJ:j\d+>> TypeConversion [<<Arg>>] + // CHECK-DAG: Return [<<ZToJ>>] static long booleanToLong(boolean b) { return b ? 1 : 0; @@ -166,29 +178,36 @@ public class Main { /// CHECK-START: int Main.longToIntOfBoolean() inliner (after) /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Zero:j\d+>> LongConstant 0 + /// CHECK-DAG: <<One:j\d+>> LongConstant 1 /// CHECK-DAG: <<Sget:z\d+>> StaticFieldGet /// CHECK-DAG: If [<<Sget>>] - /// CHECK-DAG: <<Phi:i\d+>> Phi [<<One>>,<<Zero>>] - /// CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Phi>>] - /// CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<IToJ>>] + /// CHECK-DAG: <<Phi:j\d+>> Phi [<<One>>,<<Zero>>] + /// CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<Phi>>] /// CHECK-DAG: Return [<<JToI>>] + /// CHECK-START: long Main.booleanToLong(boolean) select_generator (after) + /// CHECK-NOT: IntConstant + /// CHECK-NOT: Equal + /// CHECK-NOT: If + /// CHECK-NOT: Phi + /// CHECK-START: int Main.longToIntOfBoolean() select_generator (after) /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod - /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 - /// CHECK-DAG: <<One:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Zero:j\d+>> LongConstant 0 + /// CHECK-DAG: <<One:j\d+>> LongConstant 1 /// CHECK-DAG: <<Sget:z\d+>> StaticFieldGet - /// CHECK-DAG: <<Sel:i\d+>> Select [<<Zero>>,<<One>>,<<Sget>>] - /// CHECK-DAG: <<IToJ:j\d+>> TypeConversion [<<Sel>>] - /// CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<IToJ>>] + /// CHECK-DAG: <<Sel:j\d+>> Select [<<Zero>>,<<One>>,<<Sget>>] + /// CHECK-DAG: <<JToI:i\d+>> TypeConversion [<<Sel>>] /// CHECK-DAG: Return [<<JToI>>] - /// CHECK-START: int Main.longToIntOfBoolean() instruction_simplifier$after_bce (after) - /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod - /// CHECK-DAG: <<Sget:z\d+>> StaticFieldGet - /// CHECK-DAG: Return [<<Sget>>] + // As of now, the code is not optimized any further than the above. + // TODO: Re-enable checks below after simplifier is updated to handle this pattern: b/63064517 + + // CHECK-START: int Main.longToIntOfBoolean() instruction_simplifier$after_bce (after) + // CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod + // CHECK-DAG: <<Sget:z\d+>> StaticFieldGet + // CHECK-DAG: Return [<<Sget>>] static int longToIntOfBoolean() { long l = booleanToLong(booleanField); @@ -226,6 +245,26 @@ public class Main { } } + public static long $noinline$runSmaliTest(String name, boolean input) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, boolean.class); + return (Long) m.invoke(null, input); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static int $noinline$runSmaliTest(String name) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name); + return (Integer) m.invoke(null); + } catch (Exception ex) { + throw new Error(ex); + } + } + public static boolean booleanField = true; diff --git a/test/593-checker-shift-and-simplifier/expected.txt b/test/593-checker-shift-and-simplifier/expected.txt index b0aad4deb5..f8d85db6ac 100644 --- a/test/593-checker-shift-and-simplifier/expected.txt +++ b/test/593-checker-shift-and-simplifier/expected.txt @@ -1 +1,2 @@ passed +passed diff --git a/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali b/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali new file mode 100644 index 0000000000..6b0d68306b --- /dev/null +++ b/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali @@ -0,0 +1,58 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# A very particular set of operations that caused a double removal by the +# ARM64 simplifier doing "forward" removals (b/27851582). + +## CHECK-START-ARM: int SmaliTests.operations() instruction_simplifier_arm (before) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] +## CHECK-DAG: And [<<Not>>,<<Shl>>] + +## CHECK-START-ARM: int SmaliTests.operations() instruction_simplifier_arm (after) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 + +## CHECK-START-ARM64: int SmaliTests.operations() instruction_simplifier_arm64 (before) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] +## CHECK-DAG: And [<<Not>>,<<Shl>>] + +## CHECK-START-ARM64: int SmaliTests.operations() instruction_simplifier_arm64 (after) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 +.method public static operations()I + .registers 6 + .prologue + + # int r = a[0]; + sget-object v4, LMain;->a:[I + const/4 v5, 0x0 + aget v2, v4, v5 + # int n = ~r; + not-int v1, v2 + # int s = r << 2; + shl-int/lit8 v3, v2, 0x2 + # int a = s & n; + and-int v0, v3, v1 + # return a + return v0 +.end method diff --git a/test/593-checker-shift-and-simplifier/src/Main.java b/test/593-checker-shift-and-simplifier/src/Main.java index c9826bc546..f0ef0e6949 100644 --- a/test/593-checker-shift-and-simplifier/src/Main.java +++ b/test/593-checker-shift-and-simplifier/src/Main.java @@ -14,30 +14,20 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { - private static int[] a = { 10 }; + static int[] a = { 10 }; // A very particular set of operations that caused a double removal by the // ARM64 simplifier doing "forward" removals (b/27851582). - /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (before) - /// CHECK-DAG: <<Get:i\d+>> ArrayGet - /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] - /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] - /// CHECK-DAG: And [<<Not>>,<<Shl>>] - // /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (after) /// CHECK-DAG: <<Get:i\d+>> ArrayGet /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] /// CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 - /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (before) - /// CHECK-DAG: <<Get:i\d+>> ArrayGet - /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] - /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] - /// CHECK-DAG: And [<<Not>>,<<Shl>>] - // /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (after) /// CHECK-DAG: <<Get:i\d+>> ArrayGet /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] @@ -56,5 +46,21 @@ public class Main { } else { System.out.println("passed"); } + + if ($noinline$runSmaliTest("operations") != 32) { + System.out.println("failed"); + } else { + System.out.println("passed"); + } + } + + public static int $noinline$runSmaliTest(String name) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name); + return (Integer) m.invoke(null); + } catch (Exception ex) { + throw new Error(ex); + } } } diff --git a/test/596-app-images/src/Main.java b/test/596-app-images/src/Main.java index 8ee3c888b0..88d95f4162 100644 --- a/test/596-app-images/src/Main.java +++ b/test/596-app-images/src/Main.java @@ -14,6 +14,10 @@ * limitations under the License. */ +import java.lang.reflect.Field; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + class Main { static class Inner { final public static int abc = 10; @@ -46,13 +50,76 @@ class Main { if (!checkInitialized(StaticFieldsInit.class)) System.out.println("StaticFieldsInit class is not initialized!"); - if (checkInitialized(StaticInternString.class)) - System.out.println("StaticInternString class is initialized!"); + if (!checkInitialized(StaticInternString.class)) + System.out.println("StaticInternString class is not initialized!"); + + StringBuffer sb = new StringBuffer(); + sb.append("java."); + sb.append("abc."); + sb.append("Action"); + + String tmp = sb.toString(); + String intern = tmp.intern(); + + assertNotEqual(tmp, intern, "Dynamically constructed String, not interned."); + assertEqual(intern, StaticInternString.intent, "Static encoded literal String not interned."); + assertEqual(BootInternedString.boot, BootInternedString.boot.intern(), + "Static encoded literal String not moved back to runtime intern table."); + + try { + Field f = StaticInternString.class.getDeclaredField("intent"); + assertEqual(intern, f.get(null), "String Literals are not interned properly."); + + } catch (Exception e) { + System.out.println("Exception"); + } + + assertEqual(StaticInternString.getIntent(), StaticInternString2.getIntent(), + "String Literals are not intenred properly, App image static strings duplicated."); + + // reload the class StaticInternString, check whether static strings interned properly + final String DEX_FILE = System.getenv("DEX_LOCATION") + "/596-app-images.jar"; + final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path"); + + try { + Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new AssertionError("Counldn't find path class loader class"); + } + Constructor<?> ctor = + pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class); + ClassLoader loader = (ClassLoader) ctor.newInstance( + DEX_FILE, LIBRARY_SEARCH_PATH, null); + + Class<?> staticInternString = loader.loadClass("StaticInternString"); + + if (!checkAppImageContains(staticInternString)) { + System.out.println("Not loaded again."); + } + Method getIntent = staticInternString.getDeclaredMethod("getIntent"); + + assertEqual(StaticInternString.getIntent(), getIntent.invoke(staticInternString), + "Dynamically loaded app image's literal strings not interned properly."); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } public static native boolean checkAppImageLoaded(); public static native boolean checkAppImageContains(Class<?> klass); public static native boolean checkInitialized(Class<?> klass); + + public static void assertEqual(Object a, Object b, String msg) { + if (a != b) + System.out.println(msg); + } + + public static void assertNotEqual(Object a, Object b, String msg) { + if (a == b) + System.out.println(msg); + } + } class StaticFields{ @@ -68,6 +135,21 @@ class StaticFieldsInit{ } class StaticInternString { - final public static String intern = "java.abc.Action"; + final public static String intent = "java.abc.Action"; + static public String getIntent() { + return intent; + } +} + +class BootInternedString { + final public static String boot = "double"; +} + +class StaticInternString2 { + final public static String intent = "java.abc.Action"; + + static String getIntent() { + return intent; + } } diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java index af205b074f..aca997e982 100644 --- a/test/623-checker-loop-regressions/src/Main.java +++ b/test/623-checker-loop-regressions/src/Main.java @@ -285,6 +285,9 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.string2Bytes(char[], java.lang.String) loop_optimization (after) + /// CHECK-NOT: VecLoad + // /// CHECK-START-ARM64: void Main.string2Bytes(char[], java.lang.String) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -329,6 +332,13 @@ public class Main { /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<One>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<One>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.oneBoth(short[], char[]) loop_optimization (after) + /// CHECK-DAG: <<One:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<One>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Repl>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Repl>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.oneBoth(short[], char[]) loop_optimization (after) /// CHECK-DAG: <<One:i\d+>> IntConstant 1 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<One>>] loop:none @@ -369,6 +379,19 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Add>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.typeConv(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<One:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<One>>] loop:none + /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop1:B\d+>> outer_loop:none + /// CHECK-DAG: <<Load:d\d+>> VecLoad [{{l\d+}},<<Phi1>>] loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: <<Vadd:d\d+>> VecAdd [<<Load>>,<<Repl>>] loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi1>>,<<Vadd>>] loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop2:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:b\d+>> ArrayGet [{{l\d+}},<<Phi2>>] loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<One>>] loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Add>>] loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi2>>,<<Cnv>>] loop:<<Loop2>> outer_loop:none + // /// CHECK-START-ARM64: void Main.typeConv(byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<One:i\d+>> IntConstant 1 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<One>>] loop:none diff --git a/test/633-checker-rtp-getclass/build b/test/633-checker-rtp-getclass/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/633-checker-rtp-getclass/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/633-checker-rtp-getclass/smali/SmaliTests.smali b/test/633-checker-rtp-getclass/smali/SmaliTests.smali new file mode 100644 index 0000000000..9ea04498d7 --- /dev/null +++ b/test/633-checker-rtp-getclass/smali/SmaliTests.smali @@ -0,0 +1,65 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# Checker test to make sure the only inlined instruction is SubMain.bar. + +## CHECK-START: int SmaliTests.$opt$noinline$foo(Main) inliner (after) +## CHECK-DAG: InvokeVirtual method_name:Main.foo +## CHECK-DAG: <<Const:i\d+>> IntConstant 3 +## CHECK: begin_block +## CHECK: BoundType klass:SubMain +## CHECK: Return [<<Const>>] +## CHECK-NOT: begin_block +## CHECK: end_block +.method public static $opt$noinline$foo(LMain;)I + .registers 3 + .param p0, "o" # LMain; + .prologue + + # if (doThrow) { throw new Error(); } + sget-boolean v0, LMain;->doThrow:Z + if-eqz v0, :doThrow_false + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :doThrow_false + # if (o.getClass() == Main.class || o.getClass() != SubMain.class) + invoke-virtual {p0}, LMain;->getClass()Ljava/lang/Class; + move-result-object v0 + const-class v1, LMain; + if-eq v0, v1, :class_is_Main_and_not_SubMain + + invoke-virtual {p0}, LMain;->getClass()Ljava/lang/Class; + move-result-object v0 + const-class v1, LSubMain; + if-eq v0, v1, :else + + :class_is_Main_and_not_SubMain + # return o.foo() + invoke-virtual {p0}, LMain;->foo()I + move-result v0 + return v0 + + :else + # return o.bar() + invoke-virtual {p0}, LMain;->bar()I + move-result v0 + return v0 +.end method + + diff --git a/test/633-checker-rtp-getclass/src/Main.java b/test/633-checker-rtp-getclass/src/Main.java index f29c139f63..d1145af2a5 100644 --- a/test/633-checker-rtp-getclass/src/Main.java +++ b/test/633-checker-rtp-getclass/src/Main.java @@ -14,34 +14,13 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { public static void main(String[] args) { - System.out.println($opt$noinline$foo(new Main())); - System.out.println($opt$noinline$foo(new SubMain())); - System.out.println($opt$noinline$foo(new SubSubMain())); - } - - - // Checker test to make sure the only inlined instruction is - // SubMain.bar. - /// CHECK-START: int Main.$opt$noinline$foo(Main) inliner (after) - /// CHECK-DAG: InvokeVirtual method_name:Main.foo - /// CHECK-DAG: <<Const:i\d+>> IntConstant 3 - /// CHECK: begin_block - /// CHECK: BoundType klass:SubMain - /// CHECK: Return [<<Const>>] - /// CHECK-NOT: begin_block - /// CHECK: end_block - public static int $opt$noinline$foo(Main o) { - if (doThrow) { throw new Error(); } - // To exercise the bug on Jack, we need two getClass compares. - if (o.getClass() == Main.class || o.getClass() != SubMain.class) { - return o.foo(); - } else { - // We used to wrongly bound the type of o to `Main` here and then realize that's - // impossible and mark this branch as dead. - return o.bar(); - } + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new Main())); + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new SubMain())); + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new SubSubMain())); } public int bar() { @@ -53,6 +32,16 @@ public class Main { } public static boolean doThrow = false; + + public static int $noinline$runSmaliTest(String name, Main input) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, Main.class); + return (Integer) m.invoke(null, input); + } catch (Exception ex) { + throw new Error(ex); + } + } } class SubMain extends Main { diff --git a/test/640-checker-boolean-simd/src/Main.java b/test/640-checker-boolean-simd/src/Main.java index 64b76f8516..c337ef4fed 100644 --- a/test/640-checker-boolean-simd/src/Main.java +++ b/test/640-checker-boolean-simd/src/Main.java @@ -30,6 +30,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.and(boolean) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecAnd loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.and(boolean) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -51,6 +57,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.or(boolean) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecOr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.or(boolean) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +84,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.xor(boolean) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecXor loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.xor(boolean) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -93,6 +111,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.not() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNot loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.not() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/640-checker-byte-simd/src/Main.java b/test/640-checker-byte-simd/src/Main.java index 283c2c907d..dc7aaf7f05 100644 --- a/test/640-checker-byte-simd/src/Main.java +++ b/test/640-checker-byte-simd/src/Main.java @@ -30,6 +30,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.add(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecAdd loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.add(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -51,6 +57,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sub(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecSub loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sub(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +84,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.mul(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecMul loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.mul(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -107,6 +125,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.neg() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNeg loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.neg() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -128,6 +152,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.not() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNot loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.not() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -149,6 +179,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shl4() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShl loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shl4() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -170,6 +206,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sar2() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sar2() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/640-checker-char-simd/src/Main.java b/test/640-checker-char-simd/src/Main.java index dd879b40cd..0ba596389d 100644 --- a/test/640-checker-char-simd/src/Main.java +++ b/test/640-checker-char-simd/src/Main.java @@ -30,6 +30,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.add(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecAdd loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.add(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -51,6 +57,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sub(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecSub loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sub(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +84,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.mul(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecMul loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.mul(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -107,6 +125,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.neg() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNeg loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.neg() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -128,6 +152,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.not() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNot loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.not() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -149,6 +179,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shl4() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShl loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shl4() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -183,6 +219,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shr2() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecUShr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shr2() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/640-checker-int-simd/src/Main.java b/test/640-checker-int-simd/src/Main.java index 9abf60d6fa..10dd340129 100644 --- a/test/640-checker-int-simd/src/Main.java +++ b/test/640-checker-int-simd/src/Main.java @@ -30,6 +30,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.add(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecAdd loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.add(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -51,6 +57,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sub(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecSub loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sub(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +84,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.mul(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecMul loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.mul(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -108,6 +126,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.neg() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNeg loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.neg() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -129,6 +153,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.not() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNot loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.not() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -150,6 +180,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shl4() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShl loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shl4() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -170,7 +206,13 @@ public class Main { /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none - // + // + /// CHECK-START-ARM: void Main.sar2() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sar2() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -192,6 +234,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shr2() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecUShr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shr2() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -229,6 +277,11 @@ public class Main { /// CHECK-DAG: <<Get:i\d+>> ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<Get>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shr32() loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Get>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shr32() loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -258,6 +311,13 @@ public class Main { /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Get>>,<<Dist>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<UShr>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shr33() loop_optimization (after) + /// CHECK-DAG: <<Dist:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:d\d+>> VecUShr [<<Get>>,<<Dist>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<UShr>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shr33() loop_optimization (after) /// CHECK-DAG: <<Dist:i\d+>> IntConstant 1 loop:none /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none @@ -290,6 +350,13 @@ public class Main { /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Get>>,<<Dist>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<UShr>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shrMinus254() loop_optimization (after) + /// CHECK-DAG: <<Dist:i\d+>> IntConstant 2 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:d\d+>> VecUShr [<<Get>>,<<Dist>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<UShr>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shrMinus254() loop_optimization (after) /// CHECK-DAG: <<Dist:i\d+>> IntConstant 2 loop:none /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none diff --git a/test/640-checker-short-simd/src/Main.java b/test/640-checker-short-simd/src/Main.java index 4cca837efb..9dc084d1df 100644 --- a/test/640-checker-short-simd/src/Main.java +++ b/test/640-checker-short-simd/src/Main.java @@ -30,6 +30,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.add(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecAdd loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.add(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -51,6 +57,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sub(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecSub loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sub(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +84,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.mul(int) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecMul loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.mul(int) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -107,6 +125,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.neg() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNeg loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.neg() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -128,6 +152,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.not() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecNot loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.not() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -149,6 +179,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.shl4() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShl loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.shl4() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none @@ -170,6 +206,12 @@ public class Main { /// CHECK-DAG: ArrayGet loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.sar2() loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecShr loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.sar2() loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/645-checker-abs-simd/src/Main.java b/test/645-checker-abs-simd/src/Main.java index 9714a46630..c49d85d33f 100644 --- a/test/645-checker-abs-simd/src/Main.java +++ b/test/645-checker-abs-simd/src/Main.java @@ -28,6 +28,18 @@ public class Main { /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitByte(byte[]) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecAbs loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop2:B\d+>> outer_loop:none + /// CHECK-DAG: ArrayGet loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: ArraySet loop:<<Loop2>> outer_loop:none + // + /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>" + // /// CHECK-START-ARM64: void Main.doitByte(byte[]) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none @@ -78,6 +90,18 @@ public class Main { /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitShort(short[]) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecAbs loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop2:B\d+>> outer_loop:none + /// CHECK-DAG: ArrayGet loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: ArraySet loop:<<Loop2>> outer_loop:none + // + /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>" + // /// CHECK-START-ARM64: void Main.doitShort(short[]) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none @@ -113,6 +137,18 @@ public class Main { /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitInt(int[]) loop_optimization (after) + /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none + /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecAbs loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: VecStore loop:<<Loop1>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop2:B\d+>> outer_loop:none + /// CHECK-DAG: ArrayGet loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: InvokeStaticOrDirect intrinsic:MathAbsInt loop:<<Loop2>> outer_loop:none + /// CHECK-DAG: ArraySet loop:<<Loop2>> outer_loop:none + // + /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>" + // /// CHECK-START-ARM64: void Main.doitInt(int[]) loop_optimization (after) /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none /// CHECK-DAG: VecLoad loop:<<Loop1>> outer_loop:none diff --git a/test/646-checker-hadd-alt-byte/src/Main.java b/test/646-checker-hadd-alt-byte/src/Main.java index 9cc68287c3..7be3151c6c 100644 --- a/test/646-checker-hadd-alt-byte/src/Main.java +++ b/test/646-checker-hadd-alt-byte/src/Main.java @@ -39,6 +39,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +79,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -103,6 +117,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -137,6 +158,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -167,6 +195,14 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none @@ -200,6 +236,14 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none diff --git a/test/646-checker-hadd-alt-char/src/Main.java b/test/646-checker-hadd-alt-char/src/Main.java index 3f81299476..2799ea7483 100644 --- a/test/646-checker-hadd-alt-char/src/Main.java +++ b/test/646-checker-hadd-alt-char/src/Main.java @@ -39,6 +39,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +79,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -106,6 +120,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -140,6 +161,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -173,6 +201,14 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none diff --git a/test/646-checker-hadd-alt-short/src/Main.java b/test/646-checker-hadd-alt-short/src/Main.java index 150626cdd7..6cd102f035 100644 --- a/test/646-checker-hadd-alt-short/src/Main.java +++ b/test/646-checker-hadd-alt-short/src/Main.java @@ -39,6 +39,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -72,6 +79,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -103,6 +117,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -137,6 +158,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -167,6 +195,14 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none @@ -200,6 +236,14 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none diff --git a/test/646-checker-hadd-byte/src/Main.java b/test/646-checker-hadd-byte/src/Main.java index 5a615a429e..a9e844cf17 100644 --- a/test/646-checker-hadd-byte/src/Main.java +++ b/test/646-checker-hadd-byte/src/Main.java @@ -36,6 +36,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -69,6 +76,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -100,6 +114,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -134,6 +155,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -164,6 +192,14 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none @@ -197,6 +233,14 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none diff --git a/test/646-checker-hadd-char/src/Main.java b/test/646-checker-hadd-char/src/Main.java index bb8a01f2cc..22eb7cb3cc 100644 --- a/test/646-checker-hadd-char/src/Main.java +++ b/test/646-checker-hadd-char/src/Main.java @@ -36,6 +36,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -69,6 +76,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -103,6 +117,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -137,6 +158,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -170,6 +198,14 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none @@ -203,6 +239,14 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (after) /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none diff --git a/test/646-checker-hadd-short/src/Main.java b/test/646-checker-hadd-short/src/Main.java index 07845a6038..756f8a8ae9 100644 --- a/test/646-checker-hadd-short/src/Main.java +++ b/test/646-checker-hadd-short/src/Main.java @@ -36,6 +36,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -70,6 +77,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed_alt(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed_alt(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -104,6 +118,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -135,6 +156,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -166,6 +194,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed_alt(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed_alt(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -201,6 +236,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_signed_alt2(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_signed_alt2(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -236,6 +278,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -270,6 +319,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.rounding_halving_add_unsigned_alt(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned_alt(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -301,6 +357,14 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none @@ -334,6 +398,14 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none diff --git a/test/651-checker-byte-simd-minmax/src/Main.java b/test/651-checker-byte-simd-minmax/src/Main.java index 4711214c9d..e018b56dd5 100644 --- a/test/651-checker-byte-simd-minmax/src/Main.java +++ b/test/651-checker-byte-simd-minmax/src/Main.java @@ -27,6 +27,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Min>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMin(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMin(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -58,6 +65,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Min>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMinUnsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMinUnsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -86,6 +100,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Max>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMax(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMax(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -117,6 +138,13 @@ public class Main { /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Max>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMaxUnsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMaxUnsigned(byte[], byte[], byte[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/651-checker-char-simd-minmax/src/Main.java b/test/651-checker-char-simd-minmax/src/Main.java index 79795ee0bd..57cad9b34a 100644 --- a/test/651-checker-char-simd-minmax/src/Main.java +++ b/test/651-checker-char-simd-minmax/src/Main.java @@ -27,6 +27,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Min>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMin(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMin(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -55,6 +62,13 @@ public class Main { /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Max>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMax(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMax(char[], char[], char[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/651-checker-int-simd-minmax/src/Main.java b/test/651-checker-int-simd-minmax/src/Main.java index 2a97009ae9..11b67b84d3 100644 --- a/test/651-checker-int-simd-minmax/src/Main.java +++ b/test/651-checker-int-simd-minmax/src/Main.java @@ -26,6 +26,13 @@ public class Main { /// CHECK-DAG: <<Min:i\d+>> InvokeStaticOrDirect [<<Get1>>,<<Get2>>] intrinsic:MathMinIntInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMin(int[], int[], int[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMin(int[], int[], int[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -53,6 +60,13 @@ public class Main { /// CHECK-DAG: <<Max:i\d+>> InvokeStaticOrDirect [<<Get1>>,<<Get2>>] intrinsic:MathMaxIntInt loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMax(int[], int[], int[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMax(int[], int[], int[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/651-checker-short-simd-minmax/src/Main.java b/test/651-checker-short-simd-minmax/src/Main.java index 3bd1305e3e..4f2a7a4440 100644 --- a/test/651-checker-short-simd-minmax/src/Main.java +++ b/test/651-checker-short-simd-minmax/src/Main.java @@ -27,6 +27,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Min>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMin(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMin(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -58,6 +65,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Min>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMinUnsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMinUnsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -86,6 +100,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Max>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMax(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMax(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none @@ -117,6 +138,13 @@ public class Main { /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Max>>] loop:<<Loop>> outer_loop:none /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none // + /// CHECK-START-ARM: void Main.doitMaxUnsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // /// CHECK-START-ARM64: void Main.doitMaxUnsigned(short[], short[], short[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none diff --git a/test/652-deopt-intrinsic/run b/test/652-deopt-intrinsic/run new file mode 100755 index 0000000000..97d1ff16bb --- /dev/null +++ b/test/652-deopt-intrinsic/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ensure this test is not subject to code collection. +exec ${RUN} "$@" --runtime-option -Xjitinitialsize:32M diff --git a/test/656-checker-simd-opt/expected.txt b/test/656-checker-simd-opt/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/656-checker-simd-opt/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/656-checker-simd-opt/info.txt b/test/656-checker-simd-opt/info.txt new file mode 100644 index 0000000000..185d2b1b95 --- /dev/null +++ b/test/656-checker-simd-opt/info.txt @@ -0,0 +1 @@ +Tests around optimizations of SIMD code. diff --git a/test/656-checker-simd-opt/src/Main.java b/test/656-checker-simd-opt/src/Main.java new file mode 100644 index 0000000000..794c9b6c0d --- /dev/null +++ b/test/656-checker-simd-opt/src/Main.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * Tests for SIMD related optimizations. + */ +public class Main { + + /// CHECK-START: void Main.unroll(float[], float[]) loop_optimization (before) + /// CHECK-DAG: <<Cons:f\d+>> FloatConstant 2.5 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:f\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul:f\d+>> Mul [<<Get>>,<<Cons>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Mul>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.unroll(float[], float[]) loop_optimization (after) + /// CHECK-DAG: <<Cons:f\d+>> FloatConstant 2.5 loop:none + /// CHECK-DAG: <<Incr:i\d+>> IntConstant 4 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<Cons>>] loop:none + /// CHECK-NOT: VecReplicateScalar + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul1:d\d+>> VecMul [<<Get1>>,<<Repl>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Mul1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Phi>>,<<Incr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul2:d\d+>> VecMul [<<Get2>>,<<Repl>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Add>>,<<Mul2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Add [<<Add>>,<<Incr>>] loop:<<Loop>> outer_loop:none + private static void unroll(float[] x, float[] y) { + for (int i = 0; i < 100; i++) { + x[i] = y[i] * 2.5f; + } + } + + /// CHECK-START: void Main.stencil(int[], int[], int) loop_optimization (before) + /// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<CM1:i\d+>> IntConstant -1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CM1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add4:i\d+>> Add [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add4>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.stencil(int[], int[], int) loop_optimization (after) + /// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:d\d+>> VecAdd [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get3:d\d+>> VecLoad [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add4:d\d+>> VecAdd [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Add1>>,<<Add4>>] loop:<<Loop>> outer_loop:none + private static void stencil(int[] a, int[] b, int n) { + for (int i = 1; i < n - 1; i++) { + a[i] = b[i - 1] + b[i] + b[i + 1]; + } + } + + public static void main(String[] args) { + float[] x = new float[100]; + float[] y = new float[100]; + for (int i = 0; i < 100; i++) { + x[i] = 0.0f; + y[i] = 2.0f; + } + unroll(x, y); + for (int i = 0; i < 100; i++) { + expectEquals(5.0f, x[i]); + expectEquals(2.0f, y[i]); + } + int[] a = new int[100]; + int[] b = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + b[i] = i; + } + stencil(a, b, 100); + for (int i = 1; i < 99; i++) { + int e = i + i + i; + expectEquals(e, a[i]); + expectEquals(i, b[i]); + } + System.out.println("passed"); + } + + private static void expectEquals(float expected, float result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/658-fp-read-barrier/expected.txt b/test/658-fp-read-barrier/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/658-fp-read-barrier/expected.txt diff --git a/test/658-fp-read-barrier/info.txt b/test/658-fp-read-barrier/info.txt new file mode 100644 index 0000000000..26ecb60aaf --- /dev/null +++ b/test/658-fp-read-barrier/info.txt @@ -0,0 +1,2 @@ +Regression test for the read barrier implementation in ARM64, +which used to not restore floating point registers. diff --git a/test/658-fp-read-barrier/src/Main.java b/test/658-fp-read-barrier/src/Main.java new file mode 100644 index 0000000000..eed3c6116b --- /dev/null +++ b/test/658-fp-read-barrier/src/Main.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + static volatile boolean done = false; + + public static void main(String[] args) { + // Run a thread for 30 seconds, allocating memory and triggering garbage + // collection. + // Time is limited to 30 seconds to not make this test too long. The test used + // to trigger the failure around 1 every 10 runs. + Thread t = new Thread() { + public void run() { + long time = System.currentTimeMillis(); + while (System.currentTimeMillis() - time < 30000) { + for (int j = 0; j < 10000; j++) { + o = new Object[1000]; + } + Runtime.getRuntime().gc(); + Thread.yield(); + } + Main.done = true; + } + Object o; + }; + // Make the thread a daemon to quit early in case of an + // exception thrown below. + t.setDaemon(true); + t.start(); + + // Run 'foo' as long as the test runs. + while (!done) { + double res = foo(staticMain); + if (res != 529.0) { + throw new Error("Unexpected result " + res); + } + } + } + + public static double foo(Main main) { + // Use up all D registers on arm64. + double d1 = main.field1; + double d2 = main.field2; + double d3 = main.field3; + double d4 = main.field4; + double d5 = main.field5; + double d6 = main.field6; + double d7 = main.field7; + double d8 = main.field8; + double d9 = main.field9; + double d10 = main.field10; + double d11 = main.field11; + double d12 = main.field12; + double d13 = main.field13; + double d14 = main.field14; + double d15 = main.field15; + double d16 = main.field16; + double d17 = main.field17; + double d18 = main.field18; + double d19 = main.field19; + double d20 = main.field20; + double d21 = main.field21; + double d22 = main.field22; + double d23 = main.field23; + double d24 = main.field24; + double d25 = main.field25; + double d26 = main.field26; + double d27 = main.field27; + double d28 = main.field28; + double d29 = main.field29; + double d30 = main.field30; + double d31 = main.field31; + double d32 = main.field32; + + // Trigger a read barrier. This used to make the test trip on ARM64 as + // the read barrier stub used to not restore the D registers. + double p = main.objectField.field1; + + return p + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9 + d10 + d11 + d12 + + d13 + d14 + d15 + d16 + d17 + d18 + d19 + d20 + d21 + d22 + d23 + d24 + + d25 + d26 + d27 + d28 + d29 + d30 + d31 + d32; + } + + // Initialize objects here and not in 'main' to avoid having + // these objects in roots. + public static Main staticMain = new Main(); + static { + staticMain.objectField = new Main(); + } + + public Main objectField; + + public double field1 = 1.0; + public double field2 = 2.0; + public double field3 = 3.0; + public double field4 = 4.0; + public double field5 = 5.0; + public double field6 = 6.0; + public double field7 = 7.0; + public double field8 = 8.0; + public double field9 = 9.0; + public double field10 = 10.0; + public double field11 = 11.0; + public double field12 = 12.0; + public double field13 = 13.0; + public double field14 = 14.0; + public double field15 = 15.0; + public double field16 = 16.0; + public double field17 = 17.0; + public double field18 = 18.0; + public double field19 = 19.0; + public double field20 = 20.0; + public double field21 = 21.0; + public double field22 = 22.0; + public double field23 = 23.0; + public double field24 = 24.0; + public double field25 = 25.0; + public double field26 = 26.0; + public double field27 = 27.0; + public double field28 = 28.0; + public double field29 = 29.0; + public double field30 = 30.0; + public double field31 = 31.0; + public double field32 = 32.0; +} diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java index 1721e4294e..a68565b552 100644 --- a/test/706-checker-scheduler/src/Main.java +++ b/test/706-checker-scheduler/src/Main.java @@ -16,8 +16,22 @@ public class Main { + public class ExampleObj { + int n1; + int n2; + + public ExampleObj(int n1, int n2) { + this.n1 = n1; + this.n2 = n2; + } + } + static int static_variable = 0; + public ExampleObj my_obj; + public static int number1; + public static int number2; + /// CHECK-START-ARM64: int Main.arrayAccess() scheduler (before) /// CHECK: <<Const1:i\d+>> IntConstant 1 /// CHECK: <<i0:i\d+>> Phi @@ -50,6 +64,282 @@ public class Main { return res; } + /// CHECK-START-ARM: void Main.arrayAccessVariable(int) scheduler (before) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant -1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Add2:i\d+>> Add [<<Param>>,<<Const2>>] + /// CHECK: <<Add3:i\d+>> Add [<<Param>>,<<Const3>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const1>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Add2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const1>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>] + /// CHECK: <<ArrayGet3:i\d+>> ArrayGet [<<Array>>,<<Add3>>] + /// CHECK: <<AddArray3:i\d+>> Add [<<ArrayGet3>>,<<Const1>>] + /// CHECK: <<ArraySet3:v\d+>> ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>] + + /// CHECK-START-ARM: void Main.arrayAccessVariable(int) scheduler (after) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant -1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Add2:i\d+>> Add [<<Param>>,<<Const2>>] + /// CHECK: <<Add3:i\d+>> Add [<<Param>>,<<Const3>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK: ArraySet + /// CHECK: ArraySet + /// CHECK: ArraySet + + /// CHECK-START-ARM64: void Main.arrayAccessVariable(int) scheduler (before) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant -1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Add2:i\d+>> Add [<<Param>>,<<Const2>>] + /// CHECK: <<Add3:i\d+>> Add [<<Param>>,<<Const3>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const1>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Add2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const1>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>] + /// CHECK: <<ArrayGet3:i\d+>> ArrayGet [<<Array>>,<<Add3>>] + /// CHECK: <<AddArray3:i\d+>> Add [<<ArrayGet3>>,<<Const1>>] + /// CHECK: <<ArraySet3:v\d+>> ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>] + + /// CHECK-START-ARM64: void Main.arrayAccessVariable(int) scheduler (after) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant -1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Add2:i\d+>> Add [<<Param>>,<<Const2>>] + /// CHECK: <<Add3:i\d+>> Add [<<Param>>,<<Const3>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: ArrayGet [<<Array>>,{{i\d+}}] + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK: ArraySet + /// CHECK: ArraySet + /// CHECK: ArraySet + public static void arrayAccessVariable(int i) { + int [] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (int j = 0; j < 100; j++) { + array[i + 1]++; + array[i + 2]++; + array[i - 1]++; + } + } + + /// CHECK-START-ARM: void Main.arrayAccessSub(int) scheduler (before) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 9 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant 1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Sub2:i\d+>> Sub [<<Const2>>,<<Param>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const3>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Sub2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const3>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Sub2>>,<<AddArray2>>] + + /// CHECK-START-ARM: void Main.arrayAccessSub(int) scheduler (after) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 9 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant 1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Sub2:i\d+>> Sub [<<Const2>>,<<Param>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const3>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Sub2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const3>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Sub2>>,<<AddArray2>>] + + /// CHECK-START-ARM64: void Main.arrayAccessSub(int) scheduler (before) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 9 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant 1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Sub2:i\d+>> Sub [<<Const2>>,<<Param>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const3>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Sub2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const3>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Sub2>>,<<AddArray2>>] + + /// CHECK-START-ARM64: void Main.arrayAccessSub(int) scheduler (after) + /// CHECK: <<Param:i\d+>> ParameterValue + /// CHECK-DAG: <<Const1:i\d+>> IntConstant -1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 9 + /// CHECK-DAG: <<Const3:i\d+>> IntConstant 1 + /// CHECK: <<Add1:i\d+>> Add [<<Param>>,<<Const1>>] + /// CHECK: <<Sub2:i\d+>> Sub [<<Const2>>,<<Param>>] + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<Add1>>] + /// CHECK: <<AddArray1:i\d+>> Add [<<ArrayGet1>>,<<Const3>>] + /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<Sub2>>] + /// CHECK: <<AddArray2:i\d+>> Add [<<ArrayGet2>>,<<Const3>>] + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Array>>,<<Sub2>>,<<AddArray2>>] + public static void arrayAccessSub(int i) { + int [] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (int j = 0; j < 100; j++) { + // These two accesses MAY ALIAS + array[i - 1]++; + array[9 - i]++; + } + } + + /// CHECK-START-ARM: void Main.arrayAccessLoopVariable() scheduler (before) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Phi:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet + /// CHECK: <<AddArray1:i\d+>> Add + /// CHECK: <<ArraySet1:v\d+>> ArraySet + /// CHECK: <<AddVar:i\d+>> Add + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet + /// CHECK: <<AddArray2:i\d+>> Add + /// CHECK: <<ArraySet2:v\d+>> ArraySet + + /// CHECK-START-ARM: void Main.arrayAccessLoopVariable() scheduler (after) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Phi:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<AddVar:i\d+>> Add + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet + /// CHECK: <<AddArray1:i\d+>> Add + /// CHECK: <<AddArray2:i\d+>> Add + /// CHECK: <<ArraySet1:v\d+>> ArraySet + /// CHECK: <<ArraySet2:v\d+>> ArraySet + + /// CHECK-START-ARM64: void Main.arrayAccessLoopVariable() scheduler (before) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Phi:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet + /// CHECK: <<AddArray1:i\d+>> Add + /// CHECK: <<ArraySet1:v\d+>> ArraySet + /// CHECK: <<AddVar:i\d+>> Add + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet + /// CHECK: <<AddArray2:i\d+>> Add + /// CHECK: <<ArraySet2:v\d+>> ArraySet + + /// CHECK-START-ARM64: void Main.arrayAccessLoopVariable() scheduler (after) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Phi:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<AddVar:i\d+>> Add + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet + /// CHECK: <<AddArray1:i\d+>> Add + /// CHECK: <<AddArray2:i\d+>> Add + /// CHECK: <<ArraySet1:v\d+>> ArraySet + /// CHECK: <<ArraySet2:v\d+>> ArraySet + public static void arrayAccessLoopVariable() { + int [] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (int j = 0; j < 9; j++) { + array[j]++; + array[j + 1]++; + } + } + + /// CHECK-START-ARM: void Main.accessFields() scheduler (before) + /// CHECK: InstanceFieldGet + /// CHECK: Add + /// CHECK: InstanceFieldSet + /// CHECK: InstanceFieldGet + /// CHECK: Add + /// CHECK: InstanceFieldSet + /// CHECK: StaticFieldGet + /// CHECK: Add + /// CHECK: StaticFieldSet + /// CHECK: StaticFieldGet + /// CHECK: Add + /// CHECK: StaticFieldSet + + /// CHECK-START-ARM: void Main.accessFields() scheduler (after) + /// CHECK-DAG: InstanceFieldGet + /// CHECK-DAG: InstanceFieldGet + /// CHECK-DAG: StaticFieldGet + /// CHECK-DAG: StaticFieldGet + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK-DAG: InstanceFieldSet + /// CHECK-DAG: InstanceFieldSet + /// CHECK-DAG: StaticFieldSet + /// CHECK-DAG: StaticFieldSet + + /// CHECK-START-ARM64: void Main.accessFields() scheduler (before) + /// CHECK: InstanceFieldGet + /// CHECK: Add + /// CHECK: InstanceFieldSet + /// CHECK: InstanceFieldGet + /// CHECK: Add + /// CHECK: InstanceFieldSet + /// CHECK: StaticFieldGet + /// CHECK: Add + /// CHECK: StaticFieldSet + /// CHECK: StaticFieldGet + /// CHECK: Add + /// CHECK: StaticFieldSet + + /// CHECK-START-ARM64: void Main.accessFields() scheduler (after) + /// CHECK-DAG: InstanceFieldGet + /// CHECK-DAG: InstanceFieldGet + /// CHECK-DAG: StaticFieldGet + /// CHECK-DAG: StaticFieldGet + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK: Add + /// CHECK-DAG: InstanceFieldSet + /// CHECK-DAG: InstanceFieldSet + /// CHECK-DAG: StaticFieldSet + /// CHECK-DAG: StaticFieldSet + public void accessFields() { + my_obj = new ExampleObj(1, 2); + for (int i = 0; i < 10; i++) { + my_obj.n1++; + my_obj.n2++; + number1++; + number2++; + } + } + /// CHECK-START-ARM64: int Main.intDiv(int) scheduler (before) /// CHECK: Sub /// CHECK: DivZeroCheck diff --git a/test/952-invoke-custom-kinds/build b/test/952-invoke-custom-kinds/build new file mode 100644 index 0000000000..a02cdc3769 --- /dev/null +++ b/test/952-invoke-custom-kinds/build @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Stop if something fails. +set -e + +${DX} --dex --min-sdk-version=26 --output=classes.dex classes + +zip $TEST_NAME.jar classes.dex diff --git a/test/952-invoke-custom-kinds/classes/Main.class b/test/952-invoke-custom-kinds/classes/Main.class Binary files differnew file mode 100644 index 0000000000..6bc04e326a --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/Main.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class Binary files differnew file mode 100644 index 0000000000..5dfe958795 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class Binary files differnew file mode 100644 index 0000000000..a11ee696bf --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class Binary files differnew file mode 100644 index 0000000000..e233febbf4 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class Binary files differnew file mode 100644 index 0000000000..41e1d431f2 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class Binary files differnew file mode 100644 index 0000000000..4f0f497c72 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Super.class b/test/952-invoke-custom-kinds/classes/invokecustom/Super.class Binary files differnew file mode 100644 index 0000000000..5990f28d47 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/Super.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class Binary files differnew file mode 100644 index 0000000000..c3266e49f7 --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class Binary files differnew file mode 100644 index 0000000000..711db2343b --- /dev/null +++ b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class diff --git a/test/952-invoke-custom-kinds/expected.txt b/test/952-invoke-custom-kinds/expected.txt new file mode 100644 index 0000000000..c72da25c99 --- /dev/null +++ b/test/952-invoke-custom-kinds/expected.txt @@ -0,0 +1,38 @@ +bsmLookupStatic [] +Hello World! +bsmLookupStatic [] +true +127 +c +1024 +123456 +1.2 +123456789 +3.5123456789 +String +bsmLookupStaticWithExtraArgs [1, 123456789, 123.456, 123456.789123] +targetMethodTest3 from InvokeCustom +bsmCreateCallSite [MethodHandle(Super)void] +targetMethodTest4 from Super +bsmLookupStatic [] +targetMethodTest5 1000 + -923 = 77 +targetMethodTest5 returned: 77 +bsmLookupStatic [] +targetMethodTest6 8209686820727 + -1172812402961 = 7036874417766 +targetMethodTest6 returned: 7036874417766 +bsmLookupStatic [] +targetMethodTest7 0.50097656 * -0.50097656 = -0.2509775161743164 +targetMethodTest6 returned: -0.2509775161743164 +bsmLookupStatic [] +targetMethodTest8 First invokedynamic invocation +bsmLookupStatic [] +targetMethodTest8 Second invokedynamic invocation +bsmLookupStatic [] +targetMethodTest8 Dupe first invokedynamic invocation +bsmLookupTest9 [MethodHandle()int, MethodHandle(int)void, MethodHandle(InvokeCustom)float, MethodHandle(InvokeCustom,float)void] +targetMethodTest9 ()void +checkStaticFieldTest9: old 0 new 1985229328 expected 1985229328 OK +checkFieldTest9: old 0.0 new 1.99E-19 expected 1.99E-19 OK +helperMethodTest9 in class invokecustom.InvokeCustom +run() for Test9 +targetMethodTest9() diff --git a/test/952-invoke-custom-kinds/info.txt b/test/952-invoke-custom-kinds/info.txt new file mode 100644 index 0000000000..cddb96dcf0 --- /dev/null +++ b/test/952-invoke-custom-kinds/info.txt @@ -0,0 +1,7 @@ +This test checks call sites and constant method handles in DEX files used +by invoke-custom. + +It is derived from a dx test (dalvik/dx/tests/135-invoke-custom) which +generates the invoke-custom using ASM to generate class files. The +test here differs as it not include an instance a constant method +handle with a constructor kind (see b/62774190). diff --git a/test/988-method-trace/check b/test/988-method-trace/check new file mode 100644 index 0000000000..4c583eb051 --- /dev/null +++ b/test/988-method-trace/check @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Jack uses @hide API which gives it wrong method trace in the expected.txt +if [[ "$USE_JACK" == true ]]; then + patch -p0 expected.txt < expected_jack.diff >/dev/null +fi + +./default-check "$@" diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index 30ad532f6c..574d5b0772 100644 --- a/test/988-method-trace/expected.txt +++ b/test/988-method-trace/expected.txt @@ -1,4 +1,4 @@ -.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +.<= public static void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> <= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> => art.Test988$IterOp() .=> public java.lang.Object() @@ -130,8 +130,8 @@ fibonacci(5)=5 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...=> public java.lang.String java.lang.StringBuilder.toString() -....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> ...=> public java.lang.Error(java.lang.String) ....=> public java.lang.Throwable(java.lang.String) @@ -140,14 +140,14 @@ fibonacci(5)=5 .....=> public static final java.util.List java.util.Collections.emptyList() .....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> .....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:209) - at art.Test988$IterOp.applyAsInt(Test988.java:204) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:267) - at Main.main(Main.java:19) + art.Test988.iter_fibonacci(Test988.java:235) + art.Test988$IterOp.applyAsInt(Test988.java:230) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:304) + <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> ...<= public java.lang.Error(java.lang.String) -> <null: null> @@ -163,11 +163,11 @@ fibonacci(5)=5 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:209) - at art.Test988$IterOp.applyAsInt(Test988.java:204) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:267) - at Main.main(Main.java:19) + art.Test988.iter_fibonacci(Test988.java:235) + art.Test988$IterOp.applyAsInt(Test988.java:230) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:304) + <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> @@ -231,8 +231,8 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...=> public java.lang.String java.lang.StringBuilder.toString() -....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> ...=> public java.lang.Error(java.lang.String) ....=> public java.lang.Throwable(java.lang.String) @@ -241,14 +241,14 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 .....=> public static final java.util.List java.util.Collections.emptyList() .....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> .....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:231) - at art.Test988$RecurOp.applyAsInt(Test988.java:226) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:268) - at Main.main(Main.java:19) + art.Test988.fibonacci(Test988.java:257) + art.Test988$RecurOp.applyAsInt(Test988.java:252) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:305) + <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> ...<= public java.lang.Error(java.lang.String) -> <null: null> @@ -264,14 +264,194 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:231) - at art.Test988$RecurOp.applyAsInt(Test988.java:226) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:268) - at Main.main(Main.java:19) + art.Test988.fibonacci(Test988.java:257) + art.Test988$RecurOp.applyAsInt(Test988.java:252) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:305) + <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> -=> public static native java.lang.Thread java.lang.Thread.currentThread() -<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>> -=> public static native void art.Trace.disableTracing(java.lang.Thread) +=> static void art.Test988$IntrinsicsTest.doTest() +.=> static void art.Test988Intrinsics.test() +..=> public static long java.lang.Double.doubleToRawLongBits(double) +..<= public static long java.lang.Double.doubleToRawLongBits(double) -> <class java.lang.Long: 0> +..=> public static long java.lang.Double.doubleToLongBits(double) +..<= public static long java.lang.Double.doubleToLongBits(double) -> <class java.lang.Long: 0> +..=> public static boolean java.lang.Double.isInfinite(double) +..<= public static boolean java.lang.Double.isInfinite(double) -> <class java.lang.Boolean: false> +..=> public static boolean java.lang.Double.isNaN(double) +..<= public static boolean java.lang.Double.isNaN(double) -> <class java.lang.Boolean: false> +..=> public static double java.lang.Double.longBitsToDouble(long) +..<= public static double java.lang.Double.longBitsToDouble(long) -> <class java.lang.Double: 0.0> +..=> public static int java.lang.Float.floatToRawIntBits(float) +..<= public static int java.lang.Float.floatToRawIntBits(float) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Float.floatToIntBits(float) +..<= public static int java.lang.Float.floatToIntBits(float) -> <class java.lang.Integer: 0> +..=> public static boolean java.lang.Float.isInfinite(float) +..<= public static boolean java.lang.Float.isInfinite(float) -> <class java.lang.Boolean: false> +..=> public static boolean java.lang.Float.isNaN(float) +..<= public static boolean java.lang.Float.isNaN(float) -> <class java.lang.Boolean: false> +..=> public static float java.lang.Float.intBitsToFloat(int) +..<= public static float java.lang.Float.intBitsToFloat(int) -> <class java.lang.Float: 0.0> +..=> public static int java.lang.Integer.reverse(int) +..<= public static int java.lang.Integer.reverse(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.reverseBytes(int) +..<= public static int java.lang.Integer.reverseBytes(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.bitCount(int) +..<= public static int java.lang.Integer.bitCount(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.compare(int,int) +..<= public static int java.lang.Integer.compare(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.highestOneBit(int) +..<= public static int java.lang.Integer.highestOneBit(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.lowestOneBit(int) +..<= public static int java.lang.Integer.lowestOneBit(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.numberOfLeadingZeros(int) +..<= public static int java.lang.Integer.numberOfLeadingZeros(int) -> <class java.lang.Integer: 32> +..=> public static int java.lang.Integer.numberOfTrailingZeros(int) +..<= public static int java.lang.Integer.numberOfTrailingZeros(int) -> <class java.lang.Integer: 32> +..=> public static int java.lang.Integer.rotateRight(int,int) +..<= public static int java.lang.Integer.rotateRight(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.rotateLeft(int,int) +..<= public static int java.lang.Integer.rotateLeft(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.signum(int) +..<= public static int java.lang.Integer.signum(int) -> <class java.lang.Integer: 0> +..=> public static long java.lang.Long.reverse(long) +..<= public static long java.lang.Long.reverse(long) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.reverseBytes(long) +..<= public static long java.lang.Long.reverseBytes(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.bitCount(long) +..<= public static int java.lang.Long.bitCount(long) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Long.compare(long,long) +..<= public static int java.lang.Long.compare(long,long) -> <class java.lang.Integer: 0> +..=> public static long java.lang.Long.highestOneBit(long) +..<= public static long java.lang.Long.highestOneBit(long) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.lowestOneBit(long) +..<= public static long java.lang.Long.lowestOneBit(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.numberOfLeadingZeros(long) +..<= public static int java.lang.Long.numberOfLeadingZeros(long) -> <class java.lang.Integer: 64> +..=> public static int java.lang.Long.numberOfTrailingZeros(long) +..<= public static int java.lang.Long.numberOfTrailingZeros(long) -> <class java.lang.Integer: 64> +..=> public static long java.lang.Long.rotateRight(long,int) +..<= public static long java.lang.Long.rotateRight(long,int) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.rotateLeft(long,int) +..<= public static long java.lang.Long.rotateLeft(long,int) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.signum(long) +..<= public static int java.lang.Long.signum(long) -> <class java.lang.Integer: 0> +..=> public static short java.lang.Short.reverseBytes(short) +..<= public static short java.lang.Short.reverseBytes(short) -> <class java.lang.Short: 0> +..=> public static double java.lang.Math.abs(double) +..<= public static double java.lang.Math.abs(double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.abs(float) +..<= public static float java.lang.Math.abs(float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.abs(long) +..<= public static long java.lang.Math.abs(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.abs(int) +..<= public static int java.lang.Math.abs(int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.min(double,double) +..<= public static double java.lang.Math.min(double,double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.min(float,float) +..<= public static float java.lang.Math.min(float,float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.min(long,long) +..<= public static long java.lang.Math.min(long,long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.min(int,int) +..<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.max(double,double) +..<= public static double java.lang.Math.max(double,double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.max(float,float) +..<= public static float java.lang.Math.max(float,float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.max(long,long) +..<= public static long java.lang.Math.max(long,long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.max(int,int) +..<= public static int java.lang.Math.max(int,int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.cos(double) +..<= public static double java.lang.Math.cos(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.sin(double) +..<= public static double java.lang.Math.sin(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.acos(double) +..<= public static double java.lang.Math.acos(double) -> <class java.lang.Double: 1.5707963267948966> +..=> public static double java.lang.Math.asin(double) +..<= public static double java.lang.Math.asin(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.atan(double) +..<= public static double java.lang.Math.atan(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.atan2(double,double) +..<= public static double java.lang.Math.atan2(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.cbrt(double) +..<= public static double java.lang.Math.cbrt(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.cosh(double) +..<= public static double java.lang.Math.cosh(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.exp(double) +..<= public static double java.lang.Math.exp(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.expm1(double) +..<= public static double java.lang.Math.expm1(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.hypot(double,double) +..<= public static double java.lang.Math.hypot(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.log(double) +..<= public static double java.lang.Math.log(double) -> <class java.lang.Double: -Infinity> +..=> public static double java.lang.Math.log10(double) +..<= public static double java.lang.Math.log10(double) -> <class java.lang.Double: -Infinity> +..=> public static double java.lang.Math.nextAfter(double,double) +..<= public static double java.lang.Math.nextAfter(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.sinh(double) +..<= public static double java.lang.Math.sinh(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.tan(double) +..<= public static double java.lang.Math.tan(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.tanh(double) +..<= public static double java.lang.Math.tanh(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.sqrt(double) +..<= public static double java.lang.Math.sqrt(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.ceil(double) +..<= public static double java.lang.Math.ceil(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.floor(double) +..<= public static double java.lang.Math.floor(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.rint(double) +..<= public static double java.lang.Math.rint(double) -> <class java.lang.Double: 0.0> +..=> public static long java.lang.Math.round(double) +..<= public static long java.lang.Math.round(double) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.round(float) +..<= public static int java.lang.Math.round(float) -> <class java.lang.Integer: 0> +..=> public static java.lang.Thread java.lang.Thread.currentThread() +..<= public static java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> +..=> public char java.lang.String.charAt(int) +..<= public char java.lang.String.charAt(int) -> <class java.lang.Character: s> +..=> public int java.lang.String.compareTo(java.lang.String) +..<= public int java.lang.String.compareTo(java.lang.String) -> <class java.lang.Integer: 11> +..=> public boolean java.lang.String.equals(java.lang.Object) +..<= public boolean java.lang.String.equals(java.lang.Object) -> <class java.lang.Boolean: false> +..=> public int java.lang.String.indexOf(int) +..<= public int java.lang.String.indexOf(int) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(int,int) +..<= public int java.lang.String.indexOf(int,int) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(java.lang.String) +..<= public int java.lang.String.indexOf(java.lang.String) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(java.lang.String,int) +..<= public int java.lang.String.indexOf(java.lang.String,int) -> <class java.lang.Integer: -1> +..=> public boolean java.lang.String.isEmpty() +..<= public boolean java.lang.String.isEmpty() -> <class java.lang.Boolean: false> +..=> public int java.lang.String.length() +..<= public int java.lang.String.length() -> <class java.lang.Integer: 17> +..=> public synchronized java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String) +..<= public synchronized java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String) -> <class java.lang.StringBuffer: some large string bufferhello> +..=> public synchronized int java.lang.StringBuffer.length() +..<= public synchronized int java.lang.StringBuffer.length() -> <class java.lang.Integer: 29> +..=> public synchronized java.lang.String java.lang.StringBuffer.toString() +..<= public synchronized java.lang.String java.lang.StringBuffer.toString() -> <class java.lang.String: some large string bufferhello> +..=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +..<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: some large string builderhello> +..=> public int java.lang.StringBuilder.length() +..<= public int java.lang.StringBuilder.length() -> <class java.lang.Integer: 30> +..=> public java.lang.String java.lang.StringBuilder.toString() +..<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: some large string builderhello> +..=> public static java.lang.Integer java.lang.Integer.valueOf(int) +..<= public static java.lang.Integer java.lang.Integer.valueOf(int) -> <class java.lang.Integer: 0> +..=> public static boolean java.lang.Thread.interrupted() +..<= public static boolean java.lang.Thread.interrupted() -> <class java.lang.Boolean: false> +.<= static void art.Test988Intrinsics.test() -> <null: null> +.=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +.<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +.=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +.<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +<= static void art.Test988$IntrinsicsTest.doTest() -> <null: null> +=> public static java.lang.Thread java.lang.Thread.currentThread() +<= public static java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> +=> public static void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/expected_jack.diff b/test/988-method-trace/expected_jack.diff new file mode 100644 index 0000000000..11364a0539 --- /dev/null +++ b/test/988-method-trace/expected_jack.diff @@ -0,0 +1,10 @@ +450,453c450,453 +< .=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +< .<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +< .=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +< .<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +--- +> .=> public static void java.lang.System.arraycopy(int[],int,int[],int,int) +> .<= public static void java.lang.System.arraycopy(int[],int,int[],int,int) -> <null: null> +> .=> public static void java.lang.System.arraycopy(char[],int,char[],int,int) +> .<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null> diff --git a/test/988-method-trace/gen_srcs.py b/test/988-method-trace/gen_srcs.py new file mode 100755 index 0000000000..c1ce35c278 --- /dev/null +++ b/test/988-method-trace/gen_srcs.py @@ -0,0 +1,321 @@ +#!/usr/bin/python3 +# Copyright (C) 2017 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. + +# +# Generates the src/art/Test988Intrinsics.java file. +# Re-run this every time art/compiler/intrinics_list.h is modified. +# +# $> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +# + +import argparse +import os +import re +import collections +import sys + +from string import Template + +# Relative path to art/compiler/intrinsics_list.h +INTRINSICS_LIST_H = os.path.dirname(os.path.realpath(__file__)) + "/../../compiler/intrinsics_list.h" + +# Macro parameter index to V(). Negative means from the end. +IDX_STATIC_OR_VIRTUAL = 1 +IDX_SIGNATURE = -1 +IDX_METHOD_NAME = -2 +IDX_CLASS_NAME = -3 + +# Exclude all hidden API. +KLASS_BLACK_LIST = ['sun.misc.Unsafe', 'libcore.io.Memory', 'java.lang.StringFactory'] +METHOD_BLACK_LIST = [('java.lang.ref.Reference', 'getReferent'), + ('java.lang.String', 'getCharsNoCheck'), + ('java.lang.System', 'arraycopy')] # arraycopy has a manual test. + +# When testing a virtual function, it needs to operate on an instance. +# These instances will be created with the following values, +# otherwise a default 'new T()' is used. +KLASS_INSTANCE_INITIALIZERS = { + 'java.lang.String' : '"some large string"', + 'java.lang.StringBuffer' : 'new java.lang.StringBuffer("some large string buffer")', + 'java.lang.StringBuilder' : 'new java.lang.StringBuilder("some large string builder")', + 'java.lang.ref.Reference' : 'new java.lang.ref.WeakReference(new Object())' +}; + +OUTPUT_TPL = Template(""" +/* + * Copyright (C) 2017 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. + */ + +// AUTO-GENENERATED by gen_srcs.py: DO NOT EDIT HERE DIRECTLY. +// +// $$> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +// +// RUN ABOVE COMMAND TO REGENERATE THIS FILE. + +package art; + +class Test988Intrinsics { + // Pre-initialize *all* instance variables used so that their constructors are not in the trace. +$static_fields + + static void initialize() { + // Ensure all static variables are initialized. + // In addition, pre-load classes here so that we don't see diverging class loading traces. +$initialize_classes + } + + static void test() { + // Call each intrinsic from art/compiler/intrinsics_list.h to make sure they are traced. +$test_body + } +} +""") + +JNI_TYPES = { + 'Z' : 'boolean', + 'B' : 'byte', + 'C' : 'char', + 'S' : 'short', + 'I' : 'int', + 'J' : 'long', + 'F' : 'float', + 'D' : 'double', + 'L' : 'object' +}; + +debug_printing_enabled = False + +def debug_print(x): + if debug_printing_enabled: + print(x, file=sys.stderr) + +# Parse JNI sig into a list, e.g. "II" -> ['I', 'I'], '[[IJ' -> ['[[I', 'J'], etc. +def sig_to_parameter_type_list(sig): + sig = re.sub(r'[(](.*)[)].*', r'\1', sig) + + lst = [] + obj = "" + is_obj = False + is_array = False + for x in sig: + if is_obj: + obj = obj + x + if x == ";": + is_obj = False + lst.append(obj) + obj = "" + elif is_array: + obj = obj + x + if x != "[": + is_array = False + lst.append(obj) + obj = "" + else: + if x == "[": + obj = "[" + is_array = True + elif x == "L": + obj = "L" + is_obj = True + else: + lst.append(x) + + return lst + +# Convert a single JNI descriptor into a pretty java name, e.g. "[I" -> "int[]", etc. +def javafy_name(kls_name): + if kls_name.startswith("L"): + kls_name = kls_name.lstrip("L").rstrip(";") + return kls_name.replace("/", ".") + elif kls_name.startswith("["): + array_count = kls_name.count("[") + non_array = javafy_name(kls_name.lstrip("[")) + return non_array + ("[]" * array_count) + + return JNI_TYPES.get(kls_name, kls_name) + +def extract_staticness(static_or_virtual): + if static_or_virtual == "kStatic": + return 'static' + return 'virtual' # kVirtual, kDirect + +class MethodInfo: + def __init__(self, staticness, pretty_params, method, kls): + # 'virtual' or 'static' + self.staticness = staticness + # list of e.g. ['int', 'double', 'java.lang.String'] etc + self.parameters = pretty_params + # e.g. 'toString' + self.method_name = method + # e.g. 'java.lang.String' + self.klass = kls + + def __str__(self): + return "MethodInfo " + str(self.__dict__) + + def dummy_parameters(self): + dummy_values = { + 'boolean' : 'false', + 'byte' : '(byte)0', + 'char' : "'x'", + 'short' : '(short)0', + 'int' : '0', + 'long' : '0L', + 'float' : '0.0f', + 'double' : '0.0' + } + + def object_dummy(name): + if name == "java.lang.String": + return '"hello"' + else: + return "(%s)null" %(name) + return [ dummy_values.get(param, object_dummy(param)) for param in self.parameters ] + + def dummy_instance_value(self): + return KLASS_INSTANCE_INITIALIZERS.get(self.klass, 'new %s()' %(self.klass)) + + def is_blacklisted(self): + for blk in KLASS_BLACK_LIST: + if self.klass.startswith(blk): + return True + + return (self.klass, self.method_name) in METHOD_BLACK_LIST + +# parse the V(...) \ list of items into a MethodInfo +def parse_method_info(items): + def get_item(idx): + return items[idx].strip().strip("\"") + + staticness = get_item(IDX_STATIC_OR_VIRTUAL) + sig = get_item(IDX_SIGNATURE) + method = get_item(IDX_METHOD_NAME) + kls = get_item(IDX_CLASS_NAME) + + debug_print ((sig, method, kls)) + + staticness = extract_staticness(staticness) + kls = javafy_name(kls) + param_types = sig_to_parameter_type_list(sig) + pretty_params = param_types + pretty_params = [javafy_name(i) for i in param_types] + + return MethodInfo(staticness, pretty_params, method, kls) + +# parse a line containing ' V(...)' into a MethodInfo +def parse_line(line): + line = line.strip() + if not line.startswith("V("): + return None + + line = re.sub(r'V[(](.*)[)]', r'\1', line) + debug_print(line) + + items = line.split(",") + + method_info = parse_method_info(items) + return method_info + +# Generate all the MethodInfo that we parse from intrinsics_list.h +def parse_all_method_infos(): + with open(INTRINSICS_LIST_H) as f: + for line in f: + s = parse_line(line) + if s is not None: + yield s + +# Format a receiver name. For statics, it's the class name, for receivers, it's an instance variable +def format_receiver_name(method_info): + receiver = method_info.klass + if method_info.staticness == 'virtual': + receiver = "instance_" + method_info.klass.replace(".", "_") + return receiver + +# Format a dummy call with dummy method parameters to the requested method. +def format_call_to(method_info): + dummy_args = ", ".join(method_info.dummy_parameters()) + receiver = format_receiver_name(method_info) + + return ("%s.%s(%s);" %(receiver, method_info.method_name, dummy_args)) + +# Format a static variable with an instance that could be used as the receiver +# (or None for non-static methods). +def format_instance_variable(method_info): + if method_info.staticness == 'static': + return None + return "static %s %s = %s;" %(method_info.klass, format_receiver_name(method_info), method_info.dummy_instance_value()) + +def format_initialize_klass(method_info): + return "%s.class.toString();" %(method_info.klass) + +def indent_list(lst, indent): + return [' ' * indent + i for i in lst] + +def main(): + global debug_printing_enabled + parser = argparse.ArgumentParser(description='Generate art/test/988-method-trace/src/art/Test988Intrinsics.java') + parser.add_argument('-d', '--debug', action='store_true', dest='debug', help='Print extra debugging information to stderr.') + parser.add_argument('output_file', nargs='?', metavar='<output-file>', default=sys.stdout, type=argparse.FileType('w'), help='Destination file to write to (default: stdout).') + args = parser.parse_args() + + debug_printing_enabled = args.debug + + ##### + + call_str_list = [] + instance_variable_dict = collections.OrderedDict() + initialize_klass_dict = collections.OrderedDict() + for i in parse_all_method_infos(): + debug_print(i) + if i.is_blacklisted(): + debug_print("Blacklisted: " + str(i)) + continue + + call_str = format_call_to(i) + debug_print(call_str) + + call_str_list.append(call_str) + + instance_variable = format_instance_variable(i) + if instance_variable is not None: + debug_print(instance_variable) + instance_variable_dict[i.klass] = instance_variable + + initialize_klass_dict[i.klass] = format_initialize_klass(i) + + static_fields = indent_list([ value for (key, value) in instance_variable_dict.items() ], 2) + test_body = indent_list(call_str_list, 4) + initialize_classes = indent_list([ value for (key, value) in initialize_klass_dict.items() ], 4) + + print(OUTPUT_TPL.substitute(static_fields="\n".join(static_fields), + test_body="\n".join(test_body), + initialize_classes="\n".join(initialize_classes)). + strip("\n"), \ + file=args.output_file) + +if __name__ == '__main__': + main() diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index 6a45c0eaa2..d7eda524c4 100644 --- a/test/988-method-trace/src/art/Test988.java +++ b/test/988-method-trace/src/art/Test988.java @@ -31,6 +31,7 @@ public class Test988 { // Methods with non-deterministic output that should not be printed. static Set<Method> NON_DETERMINISTIC_OUTPUT_METHODS = new HashSet<>(); + static Set<Method> NON_DETERMINISTIC_OUTPUT_TYPE_METHODS = new HashSet<>(); static { try { @@ -39,6 +40,7 @@ public class Test988 { } catch (Exception e) {} try { NON_DETERMINISTIC_OUTPUT_METHODS.add(Thread.class.getDeclaredMethod("currentThread")); + NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.add(Thread.class.getDeclaredMethod("currentThread")); } catch (Exception e) {} } @@ -55,7 +57,7 @@ public class Test988 { } @Override public void Print() { - System.out.println(whitespace(cnt) + "=> " + m); + System.out.println(whitespace(cnt) + "=> " + methodToString(m)); } } @@ -66,7 +68,16 @@ public class Test988 { return arrayToString(val); } else if (val instanceof Throwable) { StringWriter w = new StringWriter(); - ((Throwable) val).printStackTrace(new PrintWriter(w)); + Throwable thr = ((Throwable) val); + w.write(thr.getClass().getName() + ": " + thr.getMessage() + "\n"); + for (StackTraceElement e : thr.getStackTrace()) { + if (e.getClassName().startsWith("art.")) { + w.write("\t" + e + "\n"); + } else { + w.write("\t<additional hidden frames>\n"); + break; + } + } return w.toString(); } else { return val.toString(); @@ -113,6 +124,13 @@ public class Test988 { } } + static String methodToString(Object m) { + // Make the output more similar between ART and RI, + // by removing the 'native' specifier from methods. + String methodStr = m.toString(); + return methodStr.replaceFirst(" native", ""); + } + static final class MethodReturn implements Printable { private Object m; private Object val; @@ -134,8 +152,16 @@ public class Test988 { if (val != null) { klass = val.getClass(); } + String klass_print; + if (klass == null) { + klass_print = "null"; + } else if (NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.contains(m)) { + klass_print = "<non-deterministic>"; + } else { + klass_print = klass.toString(); + } System.out.println( - whitespace(cnt) + "<= " + m + " -> <" + klass + ": " + print + ">"); + whitespace(cnt) + "<= " + methodToString(m) + " -> <" + klass_print + ": " + print + ">"); } } @@ -148,7 +174,7 @@ public class Test988 { } @Override public void Print() { - System.out.println(whitespace(cnt) + "<= " + m + " EXCEPTION"); + System.out.println(whitespace(cnt) + "<= " + methodToString(m) + " EXCEPTION"); } } @@ -236,15 +262,26 @@ public class Test988 { } } + static final int METHOD_TRACING_IGNORE_DEPTH = 2; + static boolean sMethodTracingIgnore = false; + public static void notifyMethodEntry(Object m) { // Called by native code when a method is entered. This method is ignored by the native // entry and exit hooks. - results.add(new MethodEntry(m, cnt)); cnt++; + if ((cnt - 1) > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) { + return; + } + results.add(new MethodEntry(m, cnt - 1)); } public static void notifyMethodExit(Object m, boolean exception, Object result) { cnt--; + + if (cnt > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) { + return; + } + if (exception) { results.add(new MethodThrownThrough(m, cnt)); } else { @@ -266,6 +303,10 @@ public class Test988 { doFibTest(5, new RecurOp()); doFibTest(-19, new IterOp()); doFibTest(-19, new RecurOp()); + + sMethodTracingIgnore = true; + IntrinsicsTest.doTest(); + sMethodTracingIgnore = false; // Turn off method tracing so we don't have to deal with print internals. Trace.disableTracing(Thread.currentThread()); printResults(); @@ -284,6 +325,7 @@ public class Test988 { RecurOp.class.toString(); IterOp.class.toString(); StringBuilder.class.toString(); + IntrinsicsTest.initialize(); // ensure <clinit> is executed prior to tracing. } public static void printResults() { @@ -300,4 +342,30 @@ public class Test988 { results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t)); } } + + static class IntrinsicsTest { + static int[] sSourceArray = { 0, 1, 2, 3, 4, 5 }; + static int[] sDestArray = { 5, 6, 7, 8, 9, 10 }; + + static char[] sSourceArrayChar = { '0', '1', '2', '3', '4', '5' }; + static char[] sDestArrayChar = { '5', '6', '7', '8', '9', 'a' }; + + static void initialize() { + Test988Intrinsics.initialize(); + + // Pre-load all classes used in #doTest manual intrinsics. + java.lang.System.class.toString(); + } + static void doTest() { + // Ensure that the ART intrinsics in intrinsics_list.h are also being traced, + // since in non-tracing operation they are effectively inlined by the optimizing compiler. + + // Auto-generated test file that uses null/0s as default parameters. + Test988Intrinsics.test(); + + // Manual list here for functions that require special non-null/non-zero parameters: + System.arraycopy(sSourceArray, 0, sDestArray, 0, 1); + System.arraycopy(sSourceArrayChar, 0, sDestArrayChar, 0, 1); + } + } } diff --git a/test/988-method-trace/src/art/Test988Intrinsics.java b/test/988-method-trace/src/art/Test988Intrinsics.java new file mode 100644 index 0000000000..099fbf2ce8 --- /dev/null +++ b/test/988-method-trace/src/art/Test988Intrinsics.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2017 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. + */ + +// AUTO-GENENERATED by gen_srcs.py: DO NOT EDIT HERE DIRECTLY. +// +// $> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +// +// RUN ABOVE COMMAND TO REGENERATE THIS FILE. + +package art; + +class Test988Intrinsics { + // Pre-initialize *all* instance variables used so that their constructors are not in the trace. + static java.lang.String instance_java_lang_String = "some large string"; + static java.lang.StringBuffer instance_java_lang_StringBuffer = new java.lang.StringBuffer("some large string buffer"); + static java.lang.StringBuilder instance_java_lang_StringBuilder = new java.lang.StringBuilder("some large string builder"); + + static void initialize() { + // Ensure all static variables are initialized. + // In addition, pre-load classes here so that we don't see diverging class loading traces. + java.lang.Double.class.toString(); + java.lang.Float.class.toString(); + java.lang.Integer.class.toString(); + java.lang.Long.class.toString(); + java.lang.Short.class.toString(); + java.lang.Math.class.toString(); + java.lang.Thread.class.toString(); + java.lang.String.class.toString(); + java.lang.StringBuffer.class.toString(); + java.lang.StringBuilder.class.toString(); + } + + static void test() { + // Call each intrinsic from art/compiler/intrinsics_list.h to make sure they are traced. + java.lang.Double.doubleToRawLongBits(0.0); + java.lang.Double.doubleToLongBits(0.0); + java.lang.Double.isInfinite(0.0); + java.lang.Double.isNaN(0.0); + java.lang.Double.longBitsToDouble(0L); + java.lang.Float.floatToRawIntBits(0.0f); + java.lang.Float.floatToIntBits(0.0f); + java.lang.Float.isInfinite(0.0f); + java.lang.Float.isNaN(0.0f); + java.lang.Float.intBitsToFloat(0); + java.lang.Integer.reverse(0); + java.lang.Integer.reverseBytes(0); + java.lang.Integer.bitCount(0); + java.lang.Integer.compare(0, 0); + java.lang.Integer.highestOneBit(0); + java.lang.Integer.lowestOneBit(0); + java.lang.Integer.numberOfLeadingZeros(0); + java.lang.Integer.numberOfTrailingZeros(0); + java.lang.Integer.rotateRight(0, 0); + java.lang.Integer.rotateLeft(0, 0); + java.lang.Integer.signum(0); + java.lang.Long.reverse(0L); + java.lang.Long.reverseBytes(0L); + java.lang.Long.bitCount(0L); + java.lang.Long.compare(0L, 0L); + java.lang.Long.highestOneBit(0L); + java.lang.Long.lowestOneBit(0L); + java.lang.Long.numberOfLeadingZeros(0L); + java.lang.Long.numberOfTrailingZeros(0L); + java.lang.Long.rotateRight(0L, 0); + java.lang.Long.rotateLeft(0L, 0); + java.lang.Long.signum(0L); + java.lang.Short.reverseBytes((short)0); + java.lang.Math.abs(0.0); + java.lang.Math.abs(0.0f); + java.lang.Math.abs(0L); + java.lang.Math.abs(0); + java.lang.Math.min(0.0, 0.0); + java.lang.Math.min(0.0f, 0.0f); + java.lang.Math.min(0L, 0L); + java.lang.Math.min(0, 0); + java.lang.Math.max(0.0, 0.0); + java.lang.Math.max(0.0f, 0.0f); + java.lang.Math.max(0L, 0L); + java.lang.Math.max(0, 0); + java.lang.Math.cos(0.0); + java.lang.Math.sin(0.0); + java.lang.Math.acos(0.0); + java.lang.Math.asin(0.0); + java.lang.Math.atan(0.0); + java.lang.Math.atan2(0.0, 0.0); + java.lang.Math.cbrt(0.0); + java.lang.Math.cosh(0.0); + java.lang.Math.exp(0.0); + java.lang.Math.expm1(0.0); + java.lang.Math.hypot(0.0, 0.0); + java.lang.Math.log(0.0); + java.lang.Math.log10(0.0); + java.lang.Math.nextAfter(0.0, 0.0); + java.lang.Math.sinh(0.0); + java.lang.Math.tan(0.0); + java.lang.Math.tanh(0.0); + java.lang.Math.sqrt(0.0); + java.lang.Math.ceil(0.0); + java.lang.Math.floor(0.0); + java.lang.Math.rint(0.0); + java.lang.Math.round(0.0); + java.lang.Math.round(0.0f); + java.lang.Thread.currentThread(); + instance_java_lang_String.charAt(0); + instance_java_lang_String.compareTo("hello"); + instance_java_lang_String.equals((java.lang.Object)null); + instance_java_lang_String.indexOf(0); + instance_java_lang_String.indexOf(0, 0); + instance_java_lang_String.indexOf("hello"); + instance_java_lang_String.indexOf("hello", 0); + instance_java_lang_String.isEmpty(); + instance_java_lang_String.length(); + instance_java_lang_StringBuffer.append("hello"); + instance_java_lang_StringBuffer.length(); + instance_java_lang_StringBuffer.toString(); + instance_java_lang_StringBuilder.append("hello"); + instance_java_lang_StringBuilder.length(); + instance_java_lang_StringBuilder.toString(); + java.lang.Integer.valueOf(0); + java.lang.Thread.interrupted(); + } +} diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/988-method-trace/src/art/Trace.java +++ b/test/988-method-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/989-method-trace-throw/src/art/Trace.java +++ b/test/989-method-trace-throw/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/990-field-trace/src/art/Trace.java b/test/990-field-trace/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/990-field-trace/src/art/Trace.java +++ b/test/990-field-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/991-field-trace-2/src/art/Trace.java b/test/991-field-trace-2/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/991-field-trace-2/src/art/Trace.java +++ b/test/991-field-trace-2/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/992-source-data/expected.txt b/test/992-source-data/expected.txt index 480d8a4fe7..4db8df0ada 100644 --- a/test/992-source-data/expected.txt +++ b/test/992-source-data/expected.txt @@ -1,6 +1,6 @@ class art.Test992 is defined in file "Test992.java" class art.Test992$Target1 is defined in file "Test992.java" -class art.Test2 is defined in file "Test2.java" +class art.Target2 is defined in file "Target2.java" int does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION class java.lang.Integer is defined in file "Integer.java" class java.lang.Object is defined in file "Object.java" diff --git a/test/992-source-data/src/art/Test2.java b/test/992-source-data/src/art/Target2.java index dbb1089c5e..7c29d88871 100644 --- a/test/992-source-data/src/art/Test2.java +++ b/test/992-source-data/src/art/Target2.java @@ -16,4 +16,4 @@ package art; -public class Test2 {} +public class Target2 {} diff --git a/test/992-source-data/src/art/Test992.java b/test/992-source-data/src/art/Test992.java index db6ea73856..d9ab112726 100644 --- a/test/992-source-data/src/art/Test992.java +++ b/test/992-source-data/src/art/Test992.java @@ -25,7 +25,7 @@ public class Test992 { public static void run() { doTest(Test992.class); doTest(Target1.class); - doTest(Test2.class); + doTest(Target2.class); doTest(Integer.TYPE); doTest(Integer.class); doTest(Object.class); diff --git a/test/993-breakpoints/breakpoints.cc b/test/993-breakpoints/breakpoints.cc new file mode 100644 index 0000000000..129207098d --- /dev/null +++ b/test/993-breakpoints/breakpoints.cc @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test993Breakpoints { + +extern "C" JNIEXPORT +jobject JNICALL Java_art_Test993_constructNative(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return nullptr; + } + return env->NewObject(clazz, method); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test993_invokeNative(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz, + jobject thizz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + if (thizz == nullptr) { + env->CallStaticVoidMethod(clazz, method); + } else { + env->CallVoidMethod(thizz, method); + } +} + +} // namespace Test993Breakpoints +} // namespace art + diff --git a/test/993-breakpoints/expected.txt b/test/993-breakpoints/expected.txt new file mode 100644 index 0000000000..962154734b --- /dev/null +++ b/test/993-breakpoints/expected.txt @@ -0,0 +1,613 @@ +Running static invoke + Breaking on [] + Native invoking: public static void art.Test993.breakpoint() args: [this: null] + Reflective invoking: public static void art.Test993.breakpoint() args: [this: null] + Invoking "Test993::breakpoint" + Breaking on [public static void art.Test993.breakpoint() @ 41] + Native invoking: public static void art.Test993.breakpoint() args: [this: null] + Breakpoint: public static void art.Test993.breakpoint() @ line=41 + Reflective invoking: public static void art.Test993.breakpoint() args: [this: null] + Breakpoint: public static void art.Test993.breakpoint() @ line=41 + Invoking "Test993::breakpoint" + Breakpoint: public static void art.Test993.breakpoint() @ line=41 +Running private static invoke + Breaking on [] + Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null] + Invoking "Test993::privateBreakpoint" + Breaking on [private static void art.Test993.privateBreakpoint() @ 45] + Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null] + Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45 + Invoking "Test993::privateBreakpoint" + Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45 +Running interface static invoke + Breaking on [] + Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Invoking "Breakable::iBreakpoint" + Breaking on [public static void art.Test993$Breakable.iBreakpoint() @ 51] + Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 + Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 + Invoking "Breakable::iBreakpoint" + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 +Running TestClass1 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Invoking "((Breakable)new TestClass1()).breakit()" + Invoking "new TestClass1().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1().breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass1ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Invoking "((Breakable)new TestClass1ext()).breakit()" + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Invoking "new TestClass1ext().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass1ext.breakit() @ 74] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass1ext.breakit() @ 74] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass2 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Invoking "((Breakable)new TestClass2()).breakit()" + Invoking "new TestClass2().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Invoking "((Breakable)new TestClass2()).breakit()" + Invoking "new TestClass2().breakit()" + Breaking on [public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2().breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2().breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 +Running TestClass2ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Invoking "((Breakable)new TestClass2ext()).breakit()" + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Invoking "new TestClass2ext().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Invoking "((Breakable)new TestClass2ext()).breakit()" + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Invoking "new TestClass2ext().breakit())" + Breaking on [public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breaking on [public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 +Running TestClass3 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Invoking "((Breakable)new TestClass3()).breakit()" + Invoking "new TestClass3().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3().breakit())" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass3ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Invoking "((Breakable)new TestClass3ext()).breakit()" + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Invoking "new TestClass3ext().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running private instance invoke + Breaking on [] + Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4] + Invoking "new TestClass4().callPrivateMethod()" + Breaking on [private void art.Test993$TestClass4.privateMethod() @ 118] + Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4] + Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 + Invoking "new TestClass4().callPrivateMethod()" + Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 +Running TestClass1 constructor + Breaking on [] + Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1 + Created: TestClass1 + Reflective constructor: public art.Test993$TestClass1() + Created: TestClass1 + Constructing: new TestClass1() + Created: TestClass1 + Breaking on [public art.Test993$TestClass1() @ 62] + Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 + Reflective constructor: public art.Test993$TestClass1() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 + Constructing: new TestClass1() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 +Running TestClass1ext constructor + Breaking on [] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Created: TestClass1Ext + Constructing: new TestClass1ext() + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1() @ 62] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1ext() @ 70] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1() @ 62, public art.Test993$TestClass1ext() @ 70] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext diff --git a/test/993-breakpoints/info.txt b/test/993-breakpoints/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/993-breakpoints/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/993-breakpoints/run b/test/993-breakpoints/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/993-breakpoints/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/993-breakpoints/src/Main.java b/test/993-breakpoints/src/Main.java new file mode 100644 index 0000000000..b11f6f8174 --- /dev/null +++ b/test/993-breakpoints/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test993.run(); + } +} diff --git a/test/993-breakpoints/src/art/Breakpoint.java b/test/993-breakpoints/src/art/Breakpoint.java new file mode 100644 index 0000000000..2a370ebd40 --- /dev/null +++ b/test/993-breakpoints/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/993-breakpoints/src/art/Test993.java b/test/993-breakpoints/src/art/Test993.java new file mode 100644 index 0000000000..781ebffc0f --- /dev/null +++ b/test/993-breakpoints/src/art/Test993.java @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2017 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 art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Supplier; + +public class Test993 { + + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + + // A function we can use as a start breakpoint. + public static void breakpoint() { + return; + } + + private static void privateBreakpoint() { + return; + } + + // An interface with a default method we can break on. + static interface Breakable { + public static void iBreakpoint() { + return; + } + + public default void breakit() { + return; + } + } + + // A class that has a default method we breakpoint on. + public static class TestClass1 implements Breakable { + public TestClass1() { + super(); + } + public String toString() { return "TestClass1"; } + } + + // A class that overrides a default method that we can breakpoint on and calls super. + public static class TestClass1ext extends TestClass1 { + public TestClass1ext() { + super(); + } + public String toString() { return "TestClass1Ext"; } + public void breakit() { + super.breakit(); + } + } + + + // A class that overrides a default method that we can breakpoint on. + public static class TestClass2 implements Breakable { + public String toString() { return "TestClass2"; } + public void breakit() { + return; + } + } + + // A class that overrides a default method that we can breakpoint on and calls super. + public static class TestClass2ext extends TestClass2 { + public String toString() { return "TestClass2ext"; } + public void breakit() { + super.breakit(); + } + } + + // A class that overrides a default method and calls it directly with interface invoke-super + public static class TestClass3 implements Breakable { + public String toString() { return "TestClass3"; } + public void breakit() { + Breakable.super.breakit(); + } + } + + // A class that overrides a default method that we can breakpoint on and calls super to a class + // that uses interface-invoke-super. + public static class TestClass3ext extends TestClass3 { + public String toString() { return "TestClass3ext"; } + public void breakit() { + super.breakit(); + } + } + + public static class TestClass4 { + public String toString() { return "TestClass4"; } + public void callPrivateMethod() { + privateMethod(); + } + private void privateMethod() { + return; + } + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + } + + public static interface ThrowRunnable extends Runnable { + public default void run() { + try { + runThrow(); + } catch (Exception e) { + throw new Error("Caught error while running " + this, e); + } + } + public void runThrow() throws Exception; + } + + public static class InvokeDirect implements Runnable { + String msg; + Runnable r; + public InvokeDirect(String msg, Runnable r) { + this.msg = msg; + this.r = r; + } + @Override + public void run() { + System.out.println("\t\tInvoking \"" + msg + "\""); + r.run(); + } + } + + public static class InvokeReflect implements ThrowRunnable { + Method m; + Object this_arg; + public InvokeReflect(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void runThrow() throws Exception { + System.out.println("\t\tReflective invoking: " + m + " args: [this: " + this_arg + "]"); + m.invoke(this_arg); + } + } + + public static class InvokeNative implements Runnable { + Method m; + Object this_arg; + public InvokeNative(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void run() { + System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]"); + invokeNative(m, m.getDeclaringClass(), this_arg); + } + } + + public static native void invokeNative(Method m, Class<?> clazz, Object thizz); + + public static class ConstructDirect implements Runnable { + String msg; + Supplier<Object> s; + public ConstructDirect(String msg, Supplier<Object> s) { + this.msg = msg; + this.s = s; + } + + @Override + public void run() { + System.out.println("\t\tConstructing: " + msg); + System.out.println("\t\t\tCreated: " + s.get()); + } + } + + public static class ConstructReflect implements ThrowRunnable { + Constructor<?> m; + public ConstructReflect(Constructor<?> m) { + this.m = m; + } + + @Override + public void runThrow() throws Exception { + System.out.println("\t\tReflective constructor: " + m); + System.out.println("\t\t\tCreated: " + m.newInstance()); + } + } + + public static class ConstructNative implements Runnable { + Constructor<?> m; + Class type; + public ConstructNative(Constructor<?> m) { + this.m = m; + this.type = m.getDeclaringClass(); + } + + @Override + public void run() { + System.out.println("\t\tNative constructor: " + m + ", type: " + type); + System.out.println("\t\t\tCreated: " + constructNative(m, type)); + } + } + + public static native Object constructNative(Constructor m, Class<?> clazz); + + private static <T> List<List<T>> combinations(List<T> items, int len) { + if (len > items.size()) { + throw new Error("Bad length" + len + " " + items); + } + if (len == 1) { + List<List<T>> out = new ArrayList<>(); + for (T t : items) { + out.add(Arrays.asList(t)); + } + return out; + } + List<List<T>> out = new ArrayList<>(); + for (int rem = 0; rem <= items.size() - len; rem++) { + for (List<T> others : combinations(items.subList(rem + 1, items.size()), len - 1)) { + List<T> newone = new ArrayList<>(); + newone.add(items.get(rem)); + newone.addAll(others); + out.add(newone); + } + } + return out; + } + + private static <T> List<List<T>> allCombinations(List<T> items) { + List<List<T>> out = new ArrayList<List<T>>(); + out.add(new ArrayList<>()); + for (int i = 0; i < items.size(); i++) { + out.addAll(combinations(items, i + 1)); + } + return out; + } + + private static Breakpoint.Manager.BP BP(Executable m) { + return new Breakpoint.Manager.BP(m); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test993.class, + Test993.class.getDeclaredMethod("notifyBreakpointReached", + Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + runMethodTests(); + runConstructorTests(); + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } + + public static void runConstructorTests() throws Exception { + // The constructors we will be breaking on. + Constructor<?> tc1_construct = TestClass1.class.getConstructor(); + Constructor<?> tc1ext_construct = TestClass1ext.class.getConstructor(); + + Runnable[] tc1_constructors = new Runnable[] { + new ConstructNative(tc1_construct), + new ConstructReflect(tc1_construct), + new ConstructDirect("new TestClass1()", TestClass1::new), + }; + Breakpoint.Manager.BP[] tc1_bps = new Breakpoint.Manager.BP[] { + BP(tc1_construct), + }; + runTestGroups("TestClass1 constructor", tc1_constructors, tc1_bps); + + Runnable[] tc1ext_constructors = new Runnable[] { + new ConstructNative(tc1ext_construct), + new ConstructReflect(tc1ext_construct), + new ConstructDirect("new TestClass1ext()", TestClass1ext::new), + }; + Breakpoint.Manager.BP[] tc1ext_bps = new Breakpoint.Manager.BP[] { + BP(tc1_construct), BP(tc1ext_construct), + }; + runTestGroups("TestClass1ext constructor", tc1ext_constructors, tc1ext_bps); + } + + public static void runMethodTests() throws Exception { + // The methods we will be breaking on. + Method breakpoint_method = Test993.class.getDeclaredMethod("breakpoint"); + Method private_breakpoint_method = Test993.class.getDeclaredMethod("privateBreakpoint"); + Method i_breakpoint_method = Breakable.class.getDeclaredMethod("iBreakpoint"); + Method breakit_method = Breakable.class.getDeclaredMethod("breakit"); + Method breakit_method_tc1ext = TestClass1ext.class.getDeclaredMethod("breakit"); + Method breakit_method_tc2 = TestClass2.class.getDeclaredMethod("breakit"); + Method breakit_method_tc2ext = TestClass2ext.class.getDeclaredMethod("breakit"); + Method breakit_method_tc3 = TestClass3.class.getDeclaredMethod("breakit"); + Method breakit_method_tc3ext = TestClass3ext.class.getDeclaredMethod("breakit"); + Method private_method = TestClass4.class.getDeclaredMethod("privateMethod"); + + // Static class function + Runnable[] static_invokes = new Runnable[] { + new InvokeNative(breakpoint_method, null), + + new InvokeReflect(breakpoint_method, null), + + new InvokeDirect("Test993::breakpoint", Test993::breakpoint), + }; + Breakpoint.Manager.BP[] static_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakpoint_method) + }; + runTestGroups("static invoke", static_invokes, static_breakpoints); + + // Static private class function + Runnable[] private_static_invokes = new Runnable[] { + new InvokeNative(private_breakpoint_method, null), + + new InvokeDirect("Test993::privateBreakpoint", Test993::privateBreakpoint), + }; + Breakpoint.Manager.BP[] private_static_breakpoints = new Breakpoint.Manager.BP[] { + BP(private_breakpoint_method) + }; + runTestGroups("private static invoke", private_static_invokes, private_static_breakpoints); + + // Static interface function. + Runnable[] i_static_invokes = new Runnable[] { + new InvokeNative(i_breakpoint_method, null), + + new InvokeReflect(i_breakpoint_method, null), + + new InvokeDirect("Breakable::iBreakpoint", Breakable::iBreakpoint), + }; + Breakpoint.Manager.BP[] i_static_breakpoints = new Breakpoint.Manager.BP[] { + BP(i_breakpoint_method) + }; + runTestGroups("interface static invoke", i_static_invokes, i_static_breakpoints); + + // Call default method through a class. + Runnable[] tc1_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass1()), + + new InvokeReflect(breakit_method, new TestClass1()), + + new InvokeDirect("((Breakable)new TestClass1()).breakit()", + () -> ((Breakable)new TestClass1()).breakit()), + new InvokeDirect("new TestClass1().breakit()", + () -> new TestClass1().breakit()), + }; + Breakpoint.Manager.BP[] tc1_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method) + }; + runTestGroups("TestClass1 invokes", tc1_invokes, tc1_breakpoints); + + // Call default method through an override and normal invoke-super + Runnable[] tc1ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass1ext()), + new InvokeNative(breakit_method_tc1ext, new TestClass1ext()), + + new InvokeReflect(breakit_method, new TestClass1ext()), + new InvokeReflect(breakit_method_tc1ext, new TestClass1ext()), + + new InvokeDirect("((Breakable)new TestClass1ext()).breakit()", + () -> ((Breakable)new TestClass1ext()).breakit()), + new InvokeDirect("((TestClass1)new TestClass1ext()).breakit()", + () -> ((TestClass1)new TestClass1ext()).breakit()), + new InvokeDirect("new TestClass1ext().breakit()", + () -> new TestClass1ext().breakit()), + }; + Breakpoint.Manager.BP[] tc1ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc1ext) + }; + runTestGroups("TestClass1ext invokes", tc1ext_invokes, tc1ext_breakpoints); + + // Override default/interface method. + Runnable[] tc2_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass2()), + new InvokeNative(breakit_method_tc2, new TestClass2()), + + new InvokeReflect(breakit_method, new TestClass2()), + new InvokeReflect(breakit_method_tc2, new TestClass2()), + + new InvokeDirect("((Breakable)new TestClass2()).breakit()", + () -> ((Breakable)new TestClass2()).breakit()), + new InvokeDirect("new TestClass2().breakit()", + () -> new TestClass2().breakit()), + }; + Breakpoint.Manager.BP[] tc2_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc2) + }; + runTestGroups("TestClass2 invokes", tc2_invokes, tc2_breakpoints); + + // Call overridden method using invoke-super + Runnable[] tc2ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass2ext()), + new InvokeNative(breakit_method_tc2, new TestClass2ext()), + new InvokeNative(breakit_method_tc2ext, new TestClass2ext()), + + new InvokeReflect(breakit_method, new TestClass2ext()), + new InvokeReflect(breakit_method_tc2, new TestClass2ext()), + new InvokeReflect(breakit_method_tc2ext, new TestClass2ext()), + + new InvokeDirect("((Breakable)new TestClass2ext()).breakit()", + () -> ((Breakable)new TestClass2ext()).breakit()), + new InvokeDirect("((TestClass2)new TestClass2ext()).breakit()", + () -> ((TestClass2)new TestClass2ext()).breakit()), + new InvokeDirect("new TestClass2ext().breakit())", + () -> new TestClass2ext().breakit()), + }; + Breakpoint.Manager.BP[] tc2ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc2), BP(breakit_method_tc2ext) + }; + runTestGroups("TestClass2ext invokes", tc2ext_invokes, tc2ext_breakpoints); + + // Override default method and call it using interface-invoke-super + Runnable[] tc3_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass3()), + new InvokeNative(breakit_method_tc3, new TestClass3()), + + new InvokeReflect(breakit_method, new TestClass3()), + new InvokeReflect(breakit_method_tc3, new TestClass3()), + + new InvokeDirect("((Breakable)new TestClass3()).breakit()", + () -> ((Breakable)new TestClass3()).breakit()), + new InvokeDirect("new TestClass3().breakit())", + () -> new TestClass3().breakit()), + }; + Breakpoint.Manager.BP[] tc3_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc3) + }; + runTestGroups("TestClass3 invokes", tc3_invokes, tc3_breakpoints); + + // Call overridden method using invoke-super + Runnable[] tc3ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass3ext()), + new InvokeNative(breakit_method_tc3, new TestClass3ext()), + new InvokeNative(breakit_method_tc3ext, new TestClass3ext()), + + new InvokeReflect(breakit_method, new TestClass3ext()), + new InvokeReflect(breakit_method_tc3, new TestClass3ext()), + new InvokeReflect(breakit_method_tc3ext, new TestClass3ext()), + + new InvokeDirect("((Breakable)new TestClass3ext()).breakit()", + () -> ((Breakable)new TestClass3ext()).breakit()), + new InvokeDirect("((TestClass3)new TestClass3ext()).breakit()", + () -> ((TestClass3)new TestClass3ext()).breakit()), + new InvokeDirect("new TestClass3ext().breakit())", + () -> new TestClass3ext().breakit()), + }; + Breakpoint.Manager.BP[] tc3ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc3), BP(breakit_method_tc3ext) + }; + runTestGroups("TestClass3ext invokes", tc3ext_invokes, tc3ext_breakpoints); + + // private instance method. + Runnable[] private_instance_invokes = new Runnable[] { + new InvokeNative(private_method, new TestClass4()), + + new InvokeDirect("new TestClass4().callPrivateMethod()", + () -> new TestClass4().callPrivateMethod()), + }; + Breakpoint.Manager.BP[] private_instance_breakpoints = new Breakpoint.Manager.BP[] { + BP(private_method) + }; + runTestGroups( + "private instance invoke", private_instance_invokes, private_instance_breakpoints); + } + + private static void runTestGroups(String name, + Runnable[] invokes, + Breakpoint.Manager.BP[] breakpoints) throws Exception { + System.out.println("Running " + name); + for (List<Breakpoint.Manager.BP> bps : allCombinations(Arrays.asList(breakpoints))) { + System.out.println("\tBreaking on " + bps); + for (Runnable test : invokes) { + MANAGER.clearAllBreakpoints(); + MANAGER.setBreakpoints(bps.toArray(new Breakpoint.Manager.BP[0])); + test.run(); + } + } + } +} diff --git a/test/994-breakpoint-line/expected.txt b/test/994-breakpoint-line/expected.txt new file mode 100644 index 0000000000..5899659b3c --- /dev/null +++ b/test/994-breakpoint-line/expected.txt @@ -0,0 +1,34 @@ +Breaking on line: 29 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29 + argument was true +Breaking on line: 29 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29 + argument was false +Breaking on line: 30 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30 + argument was true +Breaking on line: 30 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30 + argument was false +Breaking on line: 31 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=31 + argument was true +Breaking on line: 31 calling with arg: false + argument was false +Breaking on line: 33 calling with arg: true + argument was true +Breaking on line: 33 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=33 + argument was false +Breaking on line: 35 calling with arg: true + argument was true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35 +Breaking on line: 35 calling with arg: false + argument was false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35 +Breaking on line: 36 calling with arg: true + argument was true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36 +Breaking on line: 36 calling with arg: false + argument was false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36 diff --git a/test/994-breakpoint-line/info.txt b/test/994-breakpoint-line/info.txt new file mode 100644 index 0000000000..210dea0471 --- /dev/null +++ b/test/994-breakpoint-line/info.txt @@ -0,0 +1,5 @@ +Test basic JVMTI breakpoint functionality. + +This test ensures we can place breakpoints on particular lines of a method. It +sets breakpoints on each line in turn of a function with multiple execution +paths and then runs the function, receiving the breakpoint events. diff --git a/test/994-breakpoint-line/run b/test/994-breakpoint-line/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/994-breakpoint-line/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/994-breakpoint-line/src/Main.java b/test/994-breakpoint-line/src/Main.java new file mode 100644 index 0000000000..39cfeb3ee4 --- /dev/null +++ b/test/994-breakpoint-line/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test994.run(); + } +} diff --git a/test/994-breakpoint-line/src/art/Breakpoint.java b/test/994-breakpoint-line/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/994-breakpoint-line/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/994-breakpoint-line/src/art/Test994.java b/test/994-breakpoint-line/src/art/Test994.java new file mode 100644 index 0000000000..6a1c354b39 --- /dev/null +++ b/test/994-breakpoint-line/src/art/Test994.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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 art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test994 { + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + public static void doNothing() {} + + // Method with multiple paths we can break on. + public static void doMultiPath(boolean bit) { + doNothing(); + if (bit) { + System.out.println("\targument was true"); + } else { + System.out.println("\targument was false"); + } + doNothing(); + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println( + "\tBreakpoint reached: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test994.class, + Test994.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Method multipath_method = Test994.class.getDeclaredMethod("doMultiPath", Boolean.TYPE); + + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(multipath_method); + + // Make sure everything is in the same order. + Arrays.sort(lines); + + boolean[] values = new boolean[] { true, false }; + + for (Breakpoint.LineNumber line : lines) { + MANAGER.clearAllBreakpoints(); + MANAGER.setBreakpoint(multipath_method, line.location); + for (boolean arg : values) { + System.out.println("Breaking on line: " + line.line + " calling with arg: " + arg); + doMultiPath(arg); + } + } + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } +} diff --git a/test/995-breakpoints-throw/expected.txt b/test/995-breakpoints-throw/expected.txt new file mode 100644 index 0000000000..a565b7cf14 --- /dev/null +++ b/test/995-breakpoints-throw/expected.txt @@ -0,0 +1,34 @@ +Test "call Test995::breakpoint": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "call Test995::breakpoint": No error caught with handler "do nothing" +Test "call Test995::breakpoint": Finished running with handler "do nothing" +Test "call Test995::breakpointCatch": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpointCatch() @ line=48 +Test "call Test995::breakpointCatch": No error caught with handler "do nothing" +Test "call Test995::breakpointCatch": Finished running with handler "do nothing" +Test "call Test995::breakpointCatchLate": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38 +Test "call Test995::breakpointCatchLate": No error caught with handler "do nothing" +Test "call Test995::breakpointCatchLate": Finished running with handler "do nothing" +Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "catch subroutine Test995::breakpoint": No error caught with handler "do nothing" +Test "catch subroutine Test995::breakpoint": Finished running with handler "do nothing" +Test "call Test995::breakpoint": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "call Test995::breakpoint": Caught error java.lang.Error:"throwing error!" with handler "throw" +Test "call Test995::breakpoint": Finished running with handler "throw" +Test "call Test995::breakpointCatch": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpointCatch() @ line=48 +Caught java.lang.Error: "throwing error!" +Test "call Test995::breakpointCatch": No error caught with handler "throw" +Test "call Test995::breakpointCatch": Finished running with handler "throw" +Test "call Test995::breakpointCatchLate": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38 +Test "call Test995::breakpointCatchLate": Caught error java.lang.Error:"throwing error!" with handler "throw" +Test "call Test995::breakpointCatchLate": Finished running with handler "throw" +Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Caught java.lang.Error:"throwing error!" +Test "catch subroutine Test995::breakpoint": No error caught with handler "throw" +Test "catch subroutine Test995::breakpoint": Finished running with handler "throw" diff --git a/test/995-breakpoints-throw/info.txt b/test/995-breakpoints-throw/info.txt new file mode 100644 index 0000000000..80f9cf94bf --- /dev/null +++ b/test/995-breakpoints-throw/info.txt @@ -0,0 +1,6 @@ +Test basic JVMTI breakpoint functionality. + +Tests that it is possible to throw exceptions while handling breakpoint events +and that they are handled appropriately. This includes checking that it is +possible for the method being breakpointed to catch exceptions thrown by the +handler. diff --git a/test/995-breakpoints-throw/run b/test/995-breakpoints-throw/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/995-breakpoints-throw/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/995-breakpoints-throw/src/Main.java b/test/995-breakpoints-throw/src/Main.java new file mode 100644 index 0000000000..6f80b43255 --- /dev/null +++ b/test/995-breakpoints-throw/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test995.run(); + } +} diff --git a/test/995-breakpoints-throw/src/art/Breakpoint.java b/test/995-breakpoints-throw/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/995-breakpoints-throw/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/995-breakpoints-throw/src/art/Test995.java b/test/995-breakpoints-throw/src/art/Test995.java new file mode 100644 index 0000000000..a4023fb80a --- /dev/null +++ b/test/995-breakpoints-throw/src/art/Test995.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 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 art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test995 { + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + public static BreakpointHandler HANDLER = null; + + public static void doNothing() { } + + public static interface BreakpointHandler { + public void breakpointReached(Executable e, long loc); + } + + public static void breakpoint() { + return; + } + + public static void breakpointCatchLate() { + doNothing(); + try { + doNothing(); + } catch (Throwable t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + } + } + + public static void breakpointCatch() { + try { + doNothing(); + } catch (Throwable t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + } + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println("\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + HANDLER.breakpointReached(e, loc); + } + + + public static BreakpointHandler makeHandler(String name, BreakpointHandler h) { + return new BreakpointHandler() { + public String toString() { + return name; + } + public void breakpointReached(Executable e, long loc) { + h.breakpointReached(e, loc); + } + }; + } + + public static Runnable makeTest(String name, Runnable test) { + return new Runnable() { + public String toString() { return name; } + public void run() { test.run(); } + }; + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test995.class, + Test995.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Method breakpoint_method = Test995.class.getDeclaredMethod("breakpoint"); + Method breakpoint_catch_method = Test995.class.getDeclaredMethod("breakpointCatch"); + Method breakpoint_catch_late_method = Test995.class.getDeclaredMethod("breakpointCatchLate"); + MANAGER.setBreakpoint(breakpoint_method, Breakpoint.getStartLocation(breakpoint_method)); + MANAGER.setBreakpoint( + breakpoint_catch_method, Breakpoint.getStartLocation(breakpoint_catch_method)); + MANAGER.setBreakpoint( + breakpoint_catch_late_method, Breakpoint.getStartLocation(breakpoint_catch_late_method)); + + BreakpointHandler[] handlers = new BreakpointHandler[] { + makeHandler("do nothing", (e, l) -> {}), + makeHandler("throw", (e, l) -> { throw new Error("throwing error!"); }), + }; + + Runnable[] tests = new Runnable[] { + makeTest("call Test995::breakpoint", Test995::breakpoint), + makeTest("call Test995::breakpointCatch", Test995::breakpointCatch), + makeTest("call Test995::breakpointCatchLate", Test995::breakpointCatchLate), + makeTest("catch subroutine Test995::breakpoint", + () -> { + try { + breakpoint(); + } catch (Throwable t) { + System.out.printf("Caught %s:\"%s\"\n", t.getClass().getName(), t.getMessage()); + } + }), + }; + + for (BreakpointHandler handler : handlers) { + for (Runnable test : tests) { + try { + HANDLER = handler; + System.out.printf("Test \"%s\": Running breakpoint with handler \"%s\"\n", + test, handler); + test.run(); + System.out.printf("Test \"%s\": No error caught with handler \"%s\"\n", + test, handler); + } catch (Throwable e) { + System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n", + test, e.getClass().getName(), e.getMessage(), handler); + } + System.out.printf("Test \"%s\": Finished running with handler \"%s\"\n", test, handler); + HANDLER = null; + } + } + + MANAGER.clearAllBreakpoints(); + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } +} diff --git a/test/996-breakpoint-obsolete/expected.txt b/test/996-breakpoint-obsolete/expected.txt new file mode 100644 index 0000000000..e0d419e3f5 --- /dev/null +++ b/test/996-breakpoint-obsolete/expected.txt @@ -0,0 +1,14 @@ +Initially setting breakpoint to line 42 +Running transform without redefinition. +Should be after first breakpoint. +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=42 +Running transform with redefinition. +Redefining calling function! +Setting breakpoint on now obsolete method to line 40 +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=40 +Should be after first breakpoint. +Running transform post redefinition. Should not hit any breakpoints. +Doing nothing transformed +Setting initial breakpoint on redefined method. +Doing nothing transformed +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=8 diff --git a/test/996-breakpoint-obsolete/info.txt b/test/996-breakpoint-obsolete/info.txt new file mode 100644 index 0000000000..58536acece --- /dev/null +++ b/test/996-breakpoint-obsolete/info.txt @@ -0,0 +1,4 @@ +Test JVMTI breakpoint/obsolete method interaction. + +This checks that redefining a class will clear breakpoints on the class's +methods and that it is possible to set breakpoints on obsolete methods. diff --git a/test/996-breakpoint-obsolete/obsolete_breakpoints.cc b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc new file mode 100644 index 0000000000..b6a67e4a08 --- /dev/null +++ b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test996ObsoleteBreakpoints { + +static constexpr jint kNumFrames = 10; + +static jmethodID GetFirstObsoleteMethod(JNIEnv* env, jvmtiEnv* jvmti_env) { + jint frame_count; + jvmtiFrameInfo frames[kNumFrames]; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetStackTrace(nullptr, // current thread + 0, + kNumFrames, + frames, + &frame_count))) { + return nullptr; + } + for (jint i = 0; i < frame_count; i++) { + jboolean is_obsolete = false; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->IsMethodObsolete(frames[i].method, &is_obsolete))) { + return nullptr; + } + if (is_obsolete) { + return frames[i].method; + } + } + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to find obsolete method!"); + return nullptr; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test996_setBreakpointOnObsoleteMethod( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jlong loc) { + jmethodID method = GetFirstObsoleteMethod(env, jvmti_env); + if (method == nullptr) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, loc)); +} + +} // namespace Test996ObsoleteBreakpoints +} // namespace art diff --git a/test/996-breakpoint-obsolete/run b/test/996-breakpoint-obsolete/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/996-breakpoint-obsolete/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/996-breakpoint-obsolete/src/Main.java b/test/996-breakpoint-obsolete/src/Main.java new file mode 100644 index 0000000000..1b9b0a9b4b --- /dev/null +++ b/test/996-breakpoint-obsolete/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test996.run(); + } +} diff --git a/test/996-breakpoint-obsolete/src/art/Breakpoint.java b/test/996-breakpoint-obsolete/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/996-breakpoint-obsolete/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/988-redefine-use-after-free/src-ex/art/Redefinition.java b/test/996-breakpoint-obsolete/src/art/Redefinition.java index 56d2938a01..56d2938a01 100644 --- a/test/988-redefine-use-after-free/src-ex/art/Redefinition.java +++ b/test/996-breakpoint-obsolete/src/art/Redefinition.java diff --git a/test/996-breakpoint-obsolete/src/art/Test996.java b/test/996-breakpoint-obsolete/src/art/Test996.java new file mode 100644 index 0000000000..f3166c33c7 --- /dev/null +++ b/test/996-breakpoint-obsolete/src/art/Test996.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Base64; + +public class Test996 { + // The line we are going to break on. This should be the println in the Transform class. We set a + // breakpoint here after we have redefined the class. + public static final int TRANSFORM_BREAKPOINT_REDEFINED_LINE = 40; + + // The line we initially set a breakpoint on. This should be the doNothing call. This should be + // cleared by the redefinition and should only be caught on the initial run. + public static final int TRANSFORM_BREAKPOINT_INITIAL_LINE = 42; + + // A function that doesn't do anything. Used for giving places to break on in a function. + public static void doNothing() {} + + public static final class Transform { + public void run(Runnable r) { + r.run(); + // Make sure we don't change anything above this line to keep all the breakpoint stuff + // working. We will be putting a breakpoint before this line in the runnable. + System.out.println("Should be after first breakpoint."); + // This is set as a breakpoint prior to redefinition. It should not be hit. + doNothing(); + } + } + + /* ******************************************************************************************** */ + // Try to keep all edits to this file below the above line. If edits need to be made above this + // line be sure to update the TRANSFORM_BREAKPOINT_REDEFINED_LINE and + // TRANSFORM_BREAKPOINT_INITIAL_LINE to their appropriate values. + + public static final int TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE = 8; + + // The base64 encoding of the following class. The redefined 'run' method should have the same + // instructions as the original. This means that the locations of each line should stay the same + // and the set of valid locations will not change. We use this to ensure that breakpoints are + // removed from the redefined method. + // public static final class Transform { + // public void run(Runnable r) { + // r.run(); + // System.out.println("Doing nothing transformed"); + // doNothing(); // try to catch non-removed breakpoints + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAKAoACAARCwASABMJABQAFQgAFgoAFwAYCgAZABoHABsHAB4BAAY8aW5pdD4BAAMo" + + "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQADcnVuAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAApTb3VyY2VGaWxlAQAMVGVzdDk5Ni5qYXZhDAAJAAoHAB8MAA0ACgcAIAwAIQAiAQAZRG9p" + + "bmcgbm90aGluZyB0cmFuc2Zvcm1lZAcAIwwAJAAlBwAmDAAnAAoBABVhcnQvVGVzdDk5NiRUcmFu" + + "c2Zvcm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQASamF2" + + "YS9sYW5nL1J1bm5hYmxlAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3Ry" + + "aW5nOylWAQALYXJ0L1Rlc3Q5OTYBAAlkb05vdGhpbmcAMQAHAAgAAAAAAAIAAQAJAAoAAQALAAAA" + + "HQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAAEAAEADQAOAAEACwAAADYAAgACAAAAEiu5AAIB" + + "ALIAAxIEtgAFuAAGsQAAAAEADAAAABIABAAAAAYABgAHAA4ACAARAAkAAgAPAAAAAgAQAB0AAAAK" + + "AAEABwAZABwAGQ=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBzn3TiKGAiM0fubj25v816W0k+niqj+SQcBAAAcAAAAHhWNBIAAAAAAAAAAFgDAAAW" + + "AAAAcAAAAAoAAADIAAAAAwAAAPAAAAABAAAAFAEAAAYAAAAcAQAAAQAAAEwBAACwAgAAbAEAANoB" + + "AADiAQAA/QEAABYCAAAlAgAASQIAAGkCAACAAgAAlAIAAKoCAAC+AgAA0gIAAOACAADrAgAA7gIA" + + "APICAAD/AgAACgMAABADAAAVAwAAHgMAACMDAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" + + "CQAAAAoAAAANAAAADQAAAAkAAAAAAAAADgAAAAkAAADMAQAADgAAAAkAAADUAQAACAAEABIAAAAA" + + "AAAAAAAAAAAAAQAUAAAAAQAAABAAAAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAEQAAAAUA" + + "AAAAAAAACwAAALwBAABHAwAAAAAAAAIAAAA4AwAAPgMAAAEAAQABAAAAKgMAAAQAAABwEAQAAAAO" + + "AAQAAgACAAAALwMAAA4AAAByEAUAAwBiAAAAGgEBAG4gAwAQAHEAAgAAAA4AbAEAAAAAAAAAAAAA" + + "AAAAAAEAAAAGAAAAAQAAAAcABjxpbml0PgAZRG9pbmcgbm90aGluZyB0cmFuc2Zvcm1lZAAXTGFy" + + "dC9UZXN0OTk2JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk5NjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9F" + + "bmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8v" + + "UHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJM" + + "amF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAMVGVzdDk5Ni5qYXZhAAlUcmFu" + + "c2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAJZG9Ob3RoaW5nAARuYW1lAANvdXQAB3ByaW50bG4A" + + "A3J1bgAFdmFsdWUABAAHDgAGAQAHDjx4PAACAgEVGAECAwIPBBkRFwwAAAEBAIGABPgCAQGQAwAA" + + "ABAAAAAAAAAAAQAAAAAAAAABAAAAFgAAAHAAAAACAAAACgAAAMgAAAADAAAAAwAAAPAAAAAEAAAA" + + "AQAAABQBAAAFAAAABgAAABwBAAAGAAAAAQAAAEwBAAADEAAAAQAAAGwBAAABIAAAAgAAAHgBAAAG" + + "IAAAAQAAALwBAAABEAAAAgAAAMwBAAACIAAAFgAAANoBAAADIAAAAgAAACoDAAAEIAAAAgAAADgD" + + "AAAAIAAAAQAAAEcDAAAAEAAAAQAAAFgDAAA="); + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + int line = Breakpoint.locationToLine(e, loc); + if (line == -1 && e.getName().equals("run") && e.getDeclaringClass().equals(Transform.class)) { + // RI always reports line = -1 for obsolete methods. Just replace it with the real line for + // consistency. + line = TRANSFORM_BREAKPOINT_REDEFINED_LINE; + } + System.out.println("Breakpoint reached: " + e + " @ line=" + line); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test996.class, + Test996.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Transform t = new Transform(); + Method non_obsolete_run_method = Transform.class.getDeclaredMethod("run", Runnable.class); + final long obsolete_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_REDEFINED_LINE); + + System.out.println("Initially setting breakpoint to line " + TRANSFORM_BREAKPOINT_INITIAL_LINE); + long initial_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_INITIAL_LINE); + Breakpoint.setBreakpoint(non_obsolete_run_method, initial_breakpoint_location); + + System.out.println("Running transform without redefinition."); + t.run(() -> {}); + + System.out.println("Running transform with redefinition."); + t.run(() -> { + System.out.println("Redefining calling function!"); + // This should clear the breakpoint set to TRANSFORM_BREAKPOINT_INITIAL_LINE + Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + System.out.println("Setting breakpoint on now obsolete method to line " + + TRANSFORM_BREAKPOINT_REDEFINED_LINE); + setBreakpointOnObsoleteMethod(obsolete_breakpoint_location); + }); + System.out.println("Running transform post redefinition. Should not hit any breakpoints."); + t.run(() -> {}); + + System.out.println("Setting initial breakpoint on redefined method."); + long final_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, + TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE); + Breakpoint.setBreakpoint(non_obsolete_run_method, final_breakpoint_location); + t.run(() -> {}); + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } + + public static native void setBreakpointOnObsoleteMethod(long location); +} diff --git a/test/997-single-step/expected.txt b/test/997-single-step/expected.txt new file mode 100644 index 0000000000..69c554ca7f --- /dev/null +++ b/test/997-single-step/expected.txt @@ -0,0 +1,12 @@ +Stepping through doMultiPath(true) +Single step: public static void art.Test997.doMultiPath(boolean) @ line=41 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=42 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=43 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=47 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=48 +Stepping through doMultiPath(false) +Single step: public static void art.Test997.doMultiPath(boolean) @ line=41 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=42 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=45 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=47 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=48 diff --git a/test/997-single-step/info.txt b/test/997-single-step/info.txt new file mode 100644 index 0000000000..e4a584e46f --- /dev/null +++ b/test/997-single-step/info.txt @@ -0,0 +1,3 @@ +Test basic JVMTI single step functionality. + +Ensures that we can receive single step events from JVMTI. diff --git a/test/997-single-step/run b/test/997-single-step/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/997-single-step/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/997-single-step/src/Main.java b/test/997-single-step/src/Main.java new file mode 100644 index 0000000000..1927f04d50 --- /dev/null +++ b/test/997-single-step/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test997.run(); + } +} diff --git a/test/997-single-step/src/art/Breakpoint.java b/test/997-single-step/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/997-single-step/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/997-single-step/src/art/Test997.java b/test/997-single-step/src/art/Test997.java new file mode 100644 index 0000000000..a7a522dcca --- /dev/null +++ b/test/997-single-step/src/art/Test997.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 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 art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test997 { + static final int NO_LAST_LINE_NUMBER = -1; + static int LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER; + static Method DO_MULTIPATH_METHOD; + + static { + try { + DO_MULTIPATH_METHOD = Test997.class.getDeclaredMethod("doMultiPath", Boolean.TYPE); + } catch (Exception e) { + throw new Error("could not find method doMultiPath", e); + } + } + + // Function that acts simply to ensure there are multiple lines. + public static void doNothing() {} + + // Method with multiple paths we can break on. + public static void doMultiPath(boolean bit) { + doNothing(); + if (bit) { + doNothing(); + } else { + doNothing(); + } + doNothing(); + } + + public static void notifySingleStep(Thread thr, Executable e, long loc) { + if (!e.equals(DO_MULTIPATH_METHOD)) { + // Only report steps in doMultiPath + return; + } + int cur_line = Breakpoint.locationToLine(e, loc); + // Only report anything when the line number changes. This is so we can run this test against + // both the RI and ART and also to prevent front-end compiler changes from affecting output. + if (LAST_LINE_NUMBER == NO_LAST_LINE_NUMBER || LAST_LINE_NUMBER != cur_line) { + LAST_LINE_NUMBER = cur_line; + System.out.println("Single step: " + e + " @ line=" + cur_line); + } + } + + public static void resetTest() { + LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER; + } + + public static void run() throws Exception { + boolean[] values = new boolean[] { true, false }; + Trace.enableSingleStepTracing(Test997.class, + Test997.class.getDeclaredMethod( + "notifySingleStep", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + for (boolean arg : values) { + System.out.println("Stepping through doMultiPath(" + arg + ")"); + resetTest(); + doMultiPath(arg); + } + + Trace.disableTracing(Thread.currentThread()); + } +} diff --git a/test/997-single-step/src/art/Trace.java b/test/997-single-step/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/997-single-step/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 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 art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/998-redefine-use-after-free/expected.txt b/test/998-redefine-use-after-free/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/998-redefine-use-after-free/expected.txt diff --git a/test/988-redefine-use-after-free/info.txt b/test/998-redefine-use-after-free/info.txt index 2b683dd75e..2b683dd75e 100644 --- a/test/988-redefine-use-after-free/info.txt +++ b/test/998-redefine-use-after-free/info.txt diff --git a/test/998-redefine-use-after-free/run b/test/998-redefine-use-after-free/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/998-redefine-use-after-free/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-run "$@" --jvmti diff --git a/test/988-redefine-use-after-free/src-ex/DexCacheSmash.java b/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java index 2193a631cd..2193a631cd 100644 --- a/test/988-redefine-use-after-free/src-ex/DexCacheSmash.java +++ b/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java diff --git a/test/998-redefine-use-after-free/src-ex/art/Redefinition.java b/test/998-redefine-use-after-free/src-ex/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/998-redefine-use-after-free/src-ex/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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 art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/988-redefine-use-after-free/src/Main.java b/test/998-redefine-use-after-free/src/Main.java index d88c471a07..cd3babf46f 100644 --- a/test/988-redefine-use-after-free/src/Main.java +++ b/test/998-redefine-use-after-free/src/Main.java @@ -17,7 +17,7 @@ import java.lang.reflect.*; public class Main { - public static final String TEST_NAME = "988-redefine-use-after-free"; + public static final String TEST_NAME = "998-redefine-use-after-free"; public static final int REPS = 1000; public static final int STEP = 100; diff --git a/test/Android.bp b/test/Android.bp index 23ffc7e5a3..591684b887 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -250,7 +250,10 @@ art_cc_defaults { "ti-agent/jni_binder.cc", "ti-agent/jvmti_helper.cc", "ti-agent/test_env.cc", + "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/redefinition_helper.cc", + "ti-agent/trace_helper.cc", // This is the list of non-special OnLoad things and excludes BCI and anything that depends // on ART internals. "903-hello-tagging/tagging.cc", @@ -281,6 +284,10 @@ art_cc_defaults { "989-method-trace-throw/method_trace.cc", "991-field-trace-2/field_trace.cc", "992-source-data/source_file.cc", + "993-breakpoints/breakpoints.cc", + "996-breakpoint-obsolete/obsolete_breakpoints.cc", + "1900-track-alloc/alloc.cc", + "1901-get-bytecodes/bytecodes.cc", ], shared_libs: [ "libbase", @@ -440,6 +447,7 @@ art_cc_test_library { "art_debug_defaults", "art_defaults", ], + header_libs: ["libnativebridge-dummy-headers"], srcs: ["115-native-bridge/nativebridge.cc"], target: { android: { diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk deleted file mode 100644 index 60ce6c7003..0000000000 --- a/test/Android.run-test-jvmti-java-library.mk +++ /dev/null @@ -1,159 +0,0 @@ -# -# Copyright (C) 2017 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -# shim classes. We use one that exposes the common functionality. -LOCAL_SHIM_CLASSES := \ - 902-hello-transformation/src/art/Redefinition.java \ - 903-hello-tagging/src/art/Main.java \ - -LOCAL_SRC_FILES := $(LOCAL_SHIM_CLASSES) - -# Actual test classes. -LOCAL_SRC_FILES += \ - 901-hello-ti-agent/src/art/Test901.java \ - 902-hello-transformation/src/art/Test902.java \ - 903-hello-tagging/src/art/Test903.java \ - 904-object-allocation/src/art/Test904.java \ - 905-object-free/src/art/Test905.java \ - 906-iterate-heap/src/art/Test906.java \ - 907-get-loaded-classes/src/art/Test907.java \ - 907-get-loaded-classes/src/art/Cerr.java \ - 908-gc-start-finish/src/art/Test908.java \ - 910-methods/src/art/Test910.java \ - 911-get-stack-trace/src/art/Test911.java \ - 911-get-stack-trace/src/art/AllTraces.java \ - 911-get-stack-trace/src/art/ControlData.java \ - 911-get-stack-trace/src/art/Frames.java \ - 911-get-stack-trace/src/art/OtherThread.java \ - 911-get-stack-trace/src/art/PrintThread.java \ - 911-get-stack-trace/src/art/Recurse.java \ - 911-get-stack-trace/src/art/SameThread.java \ - 911-get-stack-trace/src/art/ThreadListTraces.java \ - 912-classes/src-art/art/Test912.java \ - 912-classes/src-art/art/DexData.java \ - 913-heaps/src/art/Test913.java \ - 914-hello-obsolescence/src/art/Test914.java \ - 915-obsolete-2/src/art/Test915.java \ - 917-fields-transformation/src/art/Test917.java \ - 918-fields/src/art/Test918.java \ - 919-obsolete-fields/src/art/Test919.java \ - 920-objects/src/art/Test920.java \ - 922-properties/src/art/Test922.java \ - 923-monitors/src/art/Test923.java \ - 924-threads/src/art/Test924.java \ - 925-threadgroups/src/art/Test925.java \ - 926-multi-obsolescence/src/art/Test926.java \ - 927-timers/src/art/Test927.java \ - 928-jni-table/src/art/Test928.java \ - 930-hello-retransform/src/art/Test930.java \ - 931-agent-thread/src/art/Test931.java \ - 932-transform-saves/src/art/Test932.java \ - 933-misc-events/src/art/Test933.java \ - 940-recursive-obsolete/src/art/Test940.java \ - 942-private-recursive/src/art/Test942.java \ - 944-transform-classloaders/src/art/Test944.java \ - 945-obsolete-native/src/art/Test945.java \ - 947-reflect-method/src/art/Test947.java \ - 951-threaded-obsolete/src/art/Test951.java \ - 981-dedup-original-dex/src-art/art/Test981.java \ - 982-ok-no-retransform/src/art/Test982.java \ - 984-obsolete-invoke/src/art/Test984.java \ - 985-re-obsolete/src/art/Test985.java \ - 986-native-method-bind/src/art/Test986.java \ - -JVMTI_RUN_TEST_GENERATED_NUMBERS := \ - 901 \ - 902 \ - 903 \ - 904 \ - 905 \ - 906 \ - 907 \ - 908 \ - 910 \ - 911 \ - 912 \ - 913 \ - 914 \ - 915 \ - 917 \ - 918 \ - 919 \ - 920 \ - 922 \ - 923 \ - 924 \ - 925 \ - 926 \ - 927 \ - 928 \ - 930 \ - 931 \ - 932 \ - 933 \ - 940 \ - 942 \ - 944 \ - 945 \ - 947 \ - 951 \ - 981 \ - 982 \ - 984 \ - 985 \ - 986 \ - -# Try to enforce that the directories correspond to the Java files we pull in. -JVMTI_RUN_TEST_DIR_CHECK := $(sort $(foreach DIR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS), \ - $(filter $(DIR)%,$(LOCAL_SRC_FILES)))) -ifneq ($(sort $(LOCAL_SRC_FILES)),$(JVMTI_RUN_TEST_DIR_CHECK)) - $(error Missing file, compare $(sort $(LOCAL_SRC_FILES)) with $(JVMTI_RUN_TEST_DIR_CHECK)) -endif - -LOCAL_MODULE_CLASS := JAVA_LIBRARIES -LOCAL_MODULE_TAGS := optional -LOCAL_JAVA_LANGUAGE_VERSION := 1.8 -LOCAL_MODULE := run-test-jvmti-java - -GENERATED_SRC_DIR := $(call local-generated-sources-dir) -JVMTI_RUN_TEST_GENERATED_FILES := \ - $(foreach NR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS),$(GENERATED_SRC_DIR)/results.$(NR).expected.txt) - -define GEN_JVMTI_RUN_TEST_GENERATED_FILE - -GEN_INPUT := $(wildcard $(LOCAL_PATH)/$(1)*/expected.txt) -GEN_OUTPUT := $(GENERATED_SRC_DIR)/results.$(1).expected.txt -$$(GEN_OUTPUT): $$(GEN_INPUT) - cp $$< $$@ - -GEN_INPUT := -GEN_OUTPUT := - -endef - -$(foreach NR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS),\ - $(eval $(call GEN_JVMTI_RUN_TEST_GENERATED_FILE,$(NR)))) -LOCAL_JAVA_RESOURCE_FILES := $(JVMTI_RUN_TEST_GENERATED_FILES) - -# We only want to depend on libcore. -LOCAL_NO_STANDARD_LIBRARIES := true -LOCAL_JAVA_LIBRARIES := core-all - -include $(BUILD_JAVA_LIBRARY) diff --git a/test/ForClassLoaderA/Classes.java b/test/ForClassLoaderA/Classes.java new file mode 100644 index 0000000000..a65ef64161 --- /dev/null +++ b/test/ForClassLoaderA/Classes.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInA { +} + +class DefinedInAB { +} + +class DefinedInABC { +} + +class DefinedInAC { +} + +class DefinedInAD { +} + diff --git a/test/ForClassLoaderB/Classes.java b/test/ForClassLoaderB/Classes.java new file mode 100644 index 0000000000..8c85ed5fc0 --- /dev/null +++ b/test/ForClassLoaderB/Classes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInB { +} + +class DefinedInAB { +} + +class DefinedInABC { +} + +class DefinedInBC { +} + +class DefinedInBD { +} diff --git a/test/ForClassLoaderC/Classes.java b/test/ForClassLoaderC/Classes.java new file mode 100644 index 0000000000..7b9e83ffff --- /dev/null +++ b/test/ForClassLoaderC/Classes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInC { +} + +class DefinedInAC { +} + +class DefinedInABC { +} + +class DefinedInBC { +} + +class DefinedInCD { +} diff --git a/test/ForClassLoaderD/Classes.java b/test/ForClassLoaderD/Classes.java new file mode 100644 index 0000000000..b34177f05f --- /dev/null +++ b/test/ForClassLoaderD/Classes.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInD { +} + +class DefinedInAD { +} + +class DefinedInBD { +} + +class DefinedInCD { +} diff --git a/test/dexdump/invoke-custom.dex b/test/dexdump/invoke-custom.dex Binary files differindex 67261cad79..dab6f0f0d6 100644 --- a/test/dexdump/invoke-custom.dex +++ b/test/dexdump/invoke-custom.dex diff --git a/test/dexdump/invoke-custom.lst b/test/dexdump/invoke-custom.lst index 3540bd10d5..9037c28990 100644 --- a/test/dexdump/invoke-custom.lst +++ b/test/dexdump/invoke-custom.lst @@ -1,6 +1,35 @@ #invoke-custom.dex -0x000003fc 8 com.android.jack.java7.invokecustom.test004.Tests <init> ()V Tests.java 35 -0x00000414 6 com.android.jack.java7.invokecustom.test004.Tests add (II)I Tests.java 55 -0x0000042c 166 com.android.jack.java7.invokecustom.test004.Tests linkerMethod (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;ZBCSIFDLjava/lang/String;Ljava/lang/Class;J)Ljava/lang/invoke/CallSite; Tests.java 62 -0x000004e4 24 com.android.jack.java7.invokecustom.test004.Tests main ([Ljava/lang/String;)V Tests.java 82 -0x0000050c 22 com.android.jack.java7.invokecustom.test004.Tests test ()V Tests.java 78 +0x000009a0 8 invokecustom.Super <init> ()V InvokeCustom.java 29 +0x000009b8 16 invokecustom.Super targetMethodTest4 ()V InvokeCustom.java 31 +0x000009d8 8 invokecustom.InvokeCustom <clinit> ()V InvokeCustom.java 102 +0x000009f0 14 invokecustom.InvokeCustom <init> ()V InvokeCustom.java 39 +0x00000a10 74 invokecustom.InvokeCustom <init> (I)V InvokeCustom.java 40 +0x00000a6c 72 invokecustom.InvokeCustom bsmCreateCallSite (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; InvokeCustom.java 160 +0x00000ac4 58 invokecustom.InvokeCustom bsmLookupStatic (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; InvokeCustom.java 142 +0x00000b10 164 invokecustom.InvokeCustom bsmLookupStaticWithExtraArgs (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IJFD)Ljava/lang/invoke/CallSite; InvokeCustom.java 151 +0x00000bc4 270 invokecustom.InvokeCustom bsmLookupTest9 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; InvokeCustom.java 170 +0x00000ce4 164 invokecustom.InvokeCustom checkFieldTest9 (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V InvokeCustom.java 120 +0x00000d98 160 invokecustom.InvokeCustom checkStaticFieldTest9 (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V InvokeCustom.java 107 +0x00000e48 22 invokecustom.InvokeCustom lambda$lambdaTest$0 (Ljava/lang/String;)Z InvokeCustom.java 192 +0x00000e70 142 invokecustom.InvokeCustom lambdaTest ()V InvokeCustom.java 191 +0x00000f10 56 invokecustom.InvokeCustom main ([Ljava/lang/String;)V InvokeCustom.java -1 +0x00000f58 16 invokecustom.InvokeCustom targetMethodTest1 ()V InvokeCustom.java 45 +0x00000f78 92 invokecustom.InvokeCustom targetMethodTest2 (ZBCSIFJDLjava/lang/String;)V InvokeCustom.java 50 +0x00000fe4 16 invokecustom.InvokeCustom targetMethodTest3 ()V InvokeCustom.java 62 +0x00001004 166 invokecustom.InvokeCustom targetMethodTest5 (III)I InvokeCustom.java 72 +0x000010bc 170 invokecustom.InvokeCustom targetMethodTest6 (JJJ)J InvokeCustom.java 81 +0x00001178 172 invokecustom.InvokeCustom targetMethodTest7 (FFD)D InvokeCustom.java 90 +0x00001234 50 invokecustom.InvokeCustom targetMethodTest8 (Ljava/lang/String;)V InvokeCustom.java 99 +0x00001278 16 invokecustom.InvokeCustom targetMethodTest9 ()V InvokeCustom.java 133 +0x00001298 8 invokecustom.InvokeCustom test1 ()V InvokeCustom.java -1 +0x000012b0 54 invokecustom.InvokeCustom test2 ()V InvokeCustom.java -1 +0x000012f8 8 invokecustom.InvokeCustom test3 ()V InvokeCustom.java -1 +0x00001310 18 invokecustom.InvokeCustom test4 ()V InvokeCustom.java -1 +0x00001334 70 invokecustom.InvokeCustom test5 ()V InvokeCustom.java -1 +0x0000138c 88 invokecustom.InvokeCustom test6 ()V InvokeCustom.java -1 +0x000013f4 80 invokecustom.InvokeCustom test7 ()V InvokeCustom.java -1 +0x00001454 32 invokecustom.InvokeCustom test8 ()V InvokeCustom.java -1 +0x00001484 8 invokecustom.InvokeCustom test9 ()V InvokeCustom.java -1 +0x0000149c 54 invokecustom.InvokeCustom helperMethodTest9 ()V InvokeCustom.java 129 +0x000014e4 16 invokecustom.InvokeCustom run ()V InvokeCustom.java 137 +0x00001504 16 invokecustom.InvokeCustom targetMethodTest4 ()V InvokeCustom.java 68 diff --git a/test/dexdump/invoke-custom.txt b/test/dexdump/invoke-custom.txt index e92549a086..bd3250865b 100644 --- a/test/dexdump/invoke-custom.txt +++ b/test/dexdump/invoke-custom.txt @@ -2,253 +2,1424 @@ Processing 'invoke-custom.dex'... Opened 'invoke-custom.dex', DEX version '038' DEX file header: magic : 'dex\n038\0' -checksum : db57516f -signature : 57be...ffc4 -file_size : 3276 +checksum : d11a9e29 +signature : 5b54...15c3 +file_size : 8984 header_size : 112 link_size : 0 link_off : 0 (0x000000) -string_ids_size : 82 +string_ids_size : 165 string_ids_off : 112 (0x000070) -type_ids_size : 31 -type_ids_off : 440 (0x0001b8) -proto_ids_size : 16 -proto_ids_off : 564 (0x000234) +type_ids_size : 38 +type_ids_off : 772 (0x000304) +proto_ids_size : 51 +proto_ids_off : 924 (0x00039c) field_ids_size : 3 -field_ids_off : 756 (0x0002f4) -method_ids_size : 18 -method_ids_off : 780 (0x00030c) -class_defs_size : 1 -class_defs_off : 932 (0x0003a4) -data_size : 2304 -data_off : 972 (0x0003cc) +field_ids_off : 1536 (0x000600) +method_ids_size : 78 +method_ids_off : 1560 (0x000618) +class_defs_size : 2 +class_defs_off : 2184 (0x000888) +data_size : 6552 +data_off : 2432 (0x000980) Class #0 header: -class_idx : 10 -access_flags : 1 (0x0001) -superclass_idx : 15 +class_idx : 8 +access_flags : 1024 (0x0400) +superclass_idx : 13 interfaces_off : 0 (0x000000) -source_file_idx : 38 -annotations_off : 1316 (0x000524) -class_data_off : 3014 (0x000bc6) -static_fields_size : 1 +source_file_idx : 27 +annotations_off : 0 (0x000000) +class_data_off : 8589 (0x00218d) +static_fields_size : 0 instance_fields_size: 0 -direct_methods_size : 4 -virtual_methods_size: 1 - -Class #0 annotations: -Annotations on method #1 'add' - VISIBILITY_BUILD Lcom/android/jack/annotations/CalledByInvokeCustom; argumentTypes={ I I } invokeMethodHandle={ Lcom/android/jack/annotations/LinkerMethodHandle; argumentTypes={ Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Z B C S I F D Ljava/lang/String; Ljava/lang/Class; J } enclosingType=Lcom/android/jack/java7/invokecustom/test004/Tests; kind=INVOKE_STATIC name="linkerMethod" } methodHandleExtraArgs={ Lcom/android/jack/annotations/Constant; booleanValue={ true } Lcom/android/jack/annotations/Constant; byteValue={ 1 } Lcom/android/jack/annotations/Constant; charValue={ 97 } Lcom/android/jack/annotations/Constant; shortValue={ 1024 } Lcom/android/jack/annotations/Constant; intValue={ 1 } Lcom/android/jack/annotations/Constant; floatValue={ 11.1 } Lcom/android/jack/annotations/Constant; doubleValue={ 2.2 } Lcom/android/jack/annotations/Constant; stringValue={ "Hello" } Lcom/android/jack/annotations/Constant; classValue={ Lcom/android/jack/java7/invokecustom/test004/Tests; } Lcom/android/jack/annotations/Constant; longValue={ 123456789 } } name="add" returnType=I -Annotations on method #2 'linkerMethod' - VISIBILITY_SYSTEM Ldalvik/annotation/Signature; value={ "(" "Ljava/lang/invoke/MethodHandles$Lookup;" "Ljava/lang/String;" "Ljava/lang/invoke/MethodType;" "ZBCSIFD" "Ljava/lang/String;" "Ljava/lang/Class" "<*>;J)" "Ljava/lang/invoke/CallSite;" } - VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } -Annotations on method #4 'test' - VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } - VISIBILITY_RUNTIME Lorg/junit/Test; +direct_methods_size : 1 +virtual_methods_size: 2 Class #0 - - Class descriptor : 'Lcom/android/jack/java7/invokecustom/test004/Tests;' - Access flags : 0x0001 (PUBLIC) + Class descriptor : 'Linvokecustom/Super;' + Access flags : 0x0400 (ABSTRACT) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - - #0 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) - name : 'fieldCallSite' - type : 'Ljava/lang/invoke/CallSite;' - access : 0x0009 (PUBLIC STATIC) Instance fields - Direct methods - - #0 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) + #0 : (in Linvokecustom/Super;) name : '<init>' type : '()V' - access : 0x10001 (PUBLIC CONSTRUCTOR) + access : 0x10000 (CONSTRUCTOR) code - registers : 1 ins : 1 outs : 1 insns size : 4 16-bit code units -0003ec: |[0003ec] com.android.jack.java7.invokecustom.test004.Tests.<init>:()V -0003fc: 7010 0600 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0006 -000402: 0e00 |0003: return-void +000990: |[000990] invokecustom.Super.<init>:()V +0009a0: 7010 2b00 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@002b +0009a6: 0e00 |0003: return-void catches : (none) positions : - 0x0000 line=35 + 0x0000 line=29 locals : - 0x0000 - 0x0004 reg=0 this Lcom/android/jack/java7/invokecustom/test004/Tests; + 0x0000 - 0x0004 reg=0 this Linvokecustom/Super; - #1 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) - name : 'add' - type : '(II)I' - access : 0x000a (PRIVATE STATIC) + Virtual methods - + #0 : (in Linvokecustom/Super;) + name : 'helperMethodTest9' + type : '()V' + access : 0x0401 (PUBLIC ABSTRACT) + code : (none) + + #1 : (in Linvokecustom/Super;) + name : 'targetMethodTest4' + type : '()V' + access : 0x0001 (PUBLIC) code - registers : 3 - ins : 2 + ins : 1 + outs : 2 + insns size : 8 16-bit code units +0009a8: |[0009a8] invokecustom.Super.targetMethodTest4:()V +0009b8: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0009bc: 1a01 8b00 |0002: const-string v1, "targetMethodTest4 from Super" // string@008b +0009c0: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0009c6: 0e00 |0007: return-void + catches : (none) + positions : + 0x0000 line=31 + 0x0007 line=32 + locals : + 0x0000 - 0x0008 reg=2 this Linvokecustom/Super; + + source_file_idx : 27 (InvokeCustom.java) + +Class #1 header: +class_idx : 7 +access_flags : 1 (0x0001) +superclass_idx : 8 +interfaces_off : 5460 (0x001554) +source_file_idx : 27 +annotations_off : 5396 (0x001514) +class_data_off : 8607 (0x00219f) +static_fields_size : 1 +instance_fields_size: 1 +direct_methods_size : 29 +virtual_methods_size: 3 + +Class #1 annotations: +Annotations on method #3 'bsmCreateCallSite' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } +Annotations on method #4 'bsmLookupStatic' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/NoSuchMethodException; Ljava/lang/IllegalAccessException; } +Annotations on method #5 'bsmLookupStaticWithExtraArgs' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/NoSuchMethodException; Ljava/lang/IllegalAccessException; } +Annotations on method #6 'bsmLookupTest9' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } +Annotations on method #7 'checkFieldTest9' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } +Annotations on method #8 'checkStaticFieldTest9' + VISIBILITY_SYSTEM Ldalvik/annotation/Throws; value={ Ljava/lang/Throwable; } + +Class #1 - + Class descriptor : 'Linvokecustom/InvokeCustom;' + Access flags : 0x0001 (PUBLIC) + Superclass : 'Linvokecustom/Super;' + Interfaces - + #0 : 'Ljava/lang/Runnable;' + Static fields - + #0 : (in Linvokecustom/InvokeCustom;) + name : 'staticFieldTest9' + type : 'I' + access : 0x000a (PRIVATE STATIC) + Instance fields - + #0 : (in Linvokecustom/InvokeCustom;) + name : 'fieldTest9' + type : 'F' + access : 0x0002 (PRIVATE) + Direct methods - + #0 : (in Linvokecustom/InvokeCustom;) + name : '<clinit>' + type : '()V' + access : 0x10008 (STATIC CONSTRUCTOR) + code - + registers : 1 + ins : 0 outs : 0 - insns size : 3 16-bit code units -000404: |[000404] com.android.jack.java7.invokecustom.test004.Tests.add:(II)I -000414: 9000 0102 |0000: add-int v0, v1, v2 -000418: 0f00 |0002: return v0 + insns size : 4 16-bit code units +0009c8: |[0009c8] invokecustom.InvokeCustom.<clinit>:()V +0009d8: 1200 |0000: const/4 v0, #int 0 // #0 +0009da: 6700 0100 |0001: sput v0, Linvokecustom/InvokeCustom;.staticFieldTest9:I // field@0001 +0009de: 0e00 |0003: return-void + catches : (none) + positions : + 0x0000 line=102 + locals : + + #1 : (in Linvokecustom/InvokeCustom;) + name : '<init>' + type : '()V' + access : 0x10001 (PUBLIC CONSTRUCTOR) + code - + registers : 2 + ins : 1 + outs : 1 + insns size : 7 16-bit code units +0009e0: |[0009e0] invokecustom.InvokeCustom.<init>:()V +0009f0: 7010 2000 0100 |0000: invoke-direct {v1}, Linvokecustom/Super;.<init>:()V // method@0020 +0009f6: 1200 |0003: const/4 v0, #int 0 // #0 +0009f8: 5910 0000 |0004: iput v0, v1, Linvokecustom/InvokeCustom;.fieldTest9:F // field@0000 +0009fc: 0e00 |0006: return-void + catches : (none) + positions : + 0x0000 line=39 + 0x0003 line=115 + 0x0006 line=39 + locals : + 0x0000 - 0x0007 reg=1 this Linvokecustom/InvokeCustom; + + #2 : (in Linvokecustom/InvokeCustom;) + name : '<init>' + type : '(I)V' + access : 0x10001 (PUBLIC CONSTRUCTOR) + code - + registers : 5 + ins : 2 + outs : 2 + insns size : 37 16-bit code units +000a00: |[000a00] invokecustom.InvokeCustom.<init>:(I)V +000a10: 7010 2000 0300 |0000: invoke-direct {v3}, Linvokecustom/Super;.<init>:()V // method@0020 +000a16: 1200 |0003: const/4 v0, #int 0 // #0 +000a18: 5930 0000 |0004: iput v0, v3, Linvokecustom/InvokeCustom;.fieldTest9:F // field@0000 +000a1c: 6200 0200 |0006: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000a20: 2201 1000 |0008: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +000a24: 7010 3000 0100 |000a: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000a2a: 1a02 1a00 |000d: const-string v2, "InvokeCustom.<init>(" // string@001a +000a2e: 6e20 3600 2100 |000f: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000a34: 0c01 |0012: move-result-object v1 +000a36: 6e20 3300 4100 |0013: invoke-virtual {v1, v4}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +000a3c: 0c01 |0016: move-result-object v1 +000a3e: 1a02 0800 |0017: const-string v2, ")" // string@0008 +000a42: 6e20 3600 2100 |0019: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000a48: 0c01 |001c: move-result-object v1 +000a4a: 6e10 3700 0100 |001d: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000a50: 0c01 |0020: move-result-object v1 +000a52: 6e20 2900 1000 |0021: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000a58: 0e00 |0024: return-void + catches : (none) + positions : + 0x0000 line=40 + 0x0003 line=115 + 0x0006 line=41 + 0x0024 line=42 + locals : + 0x0000 - 0x0025 reg=3 this Linvokecustom/InvokeCustom; + 0x0000 - 0x0025 reg=4 (null) I + + #3 : (in Linvokecustom/InvokeCustom;) + name : 'bsmCreateCallSite' + type : '(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 7 + ins : 4 + outs : 2 + insns size : 36 16-bit code units +000a5c: |[000a5c] invokecustom.InvokeCustom.bsmCreateCallSite:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; +000a6c: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000a70: 2201 1000 |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +000a74: 7010 3000 0100 |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000a7a: 1a02 6000 |0007: const-string v2, "bsmCreateCallSite [" // string@0060 +000a7e: 6e20 3600 2100 |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000a84: 0c01 |000c: move-result-object v1 +000a86: 6e20 3500 6100 |000d: invoke-virtual {v1, v6}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000a8c: 0c01 |0010: move-result-object v1 +000a8e: 1a02 5900 |0011: const-string v2, "]" // string@0059 +000a92: 6e20 3600 2100 |0013: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000a98: 0c01 |0016: move-result-object v1 +000a9a: 6e10 3700 0100 |0017: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000aa0: 0c01 |001a: move-result-object v1 +000aa2: 6e20 2900 1000 |001b: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000aa8: 2200 1400 |001e: new-instance v0, Ljava/lang/invoke/ConstantCallSite; // type@0014 +000aac: 7020 3800 6000 |0020: invoke-direct {v0, v6}, Ljava/lang/invoke/ConstantCallSite;.<init>:(Ljava/lang/invoke/MethodHandle;)V // method@0038 +000ab2: 1100 |0023: return-object v0 + catches : (none) + positions : + 0x0000 line=160 + 0x001e line=161 + locals : + 0x0000 - 0x0024 reg=3 (null) Ljava/lang/invoke/MethodHandles$Lookup; + 0x0000 - 0x0024 reg=4 (null) Ljava/lang/String; + 0x0000 - 0x0024 reg=5 (null) Ljava/lang/invoke/MethodType; + 0x0000 - 0x0024 reg=6 (null) Ljava/lang/invoke/MethodHandle; + + #4 : (in Linvokecustom/InvokeCustom;) + name : 'bsmLookupStatic' + type : '(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 5 + ins : 3 + outs : 4 + insns size : 29 16-bit code units +000ab4: |[000ab4] invokecustom.InvokeCustom.bsmLookupStatic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; +000ac4: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000ac8: 1a01 6200 |0002: const-string v1, "bsmLookupStatic []" // string@0062 +000acc: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000ad2: 7100 4600 0000 |0007: invoke-static {}, Ljava/lang/invoke/MethodHandles;.lookup:()Ljava/lang/invoke/MethodHandles$Lookup; // method@0046 +000ad8: 0c00 |000a: move-result-object v0 +000ada: 6e10 4500 0000 |000b: invoke-virtual {v0}, Ljava/lang/invoke/MethodHandles$Lookup;.lookupClass:()Ljava/lang/Class; // method@0045 +000ae0: 0c01 |000e: move-result-object v1 +000ae2: 6e40 4400 1043 |000f: invoke-virtual {v0, v1, v3, v4}, Ljava/lang/invoke/MethodHandles$Lookup;.findStatic:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@0044 +000ae8: 0c00 |0012: move-result-object v0 +000aea: 2201 1400 |0013: new-instance v1, Ljava/lang/invoke/ConstantCallSite; // type@0014 +000aee: 6e20 3a00 4000 |0015: invoke-virtual {v0, v4}, Ljava/lang/invoke/MethodHandle;.asType:(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@003a +000af4: 0c00 |0018: move-result-object v0 +000af6: 7020 3800 0100 |0019: invoke-direct {v1, v0}, Ljava/lang/invoke/ConstantCallSite;.<init>:(Ljava/lang/invoke/MethodHandle;)V // method@0038 +000afc: 1101 |001c: return-object v1 + catches : (none) + positions : + 0x0000 line=142 + 0x0007 line=143 + 0x000b line=144 + 0x0013 line=145 + locals : + 0x0000 - 0x001d reg=2 (null) Ljava/lang/invoke/MethodHandles$Lookup; + 0x0000 - 0x001d reg=3 (null) Ljava/lang/String; + 0x0000 - 0x001d reg=4 (null) Ljava/lang/invoke/MethodType; + + #5 : (in Linvokecustom/InvokeCustom;) + name : 'bsmLookupStaticWithExtraArgs' + type : '(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IJFD)Ljava/lang/invoke/CallSite;' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 12 + ins : 9 + outs : 4 + insns size : 82 16-bit code units +000b00: |[000b00] invokecustom.InvokeCustom.bsmLookupStaticWithExtraArgs:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IJFD)Ljava/lang/invoke/CallSite; +000b10: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000b14: 2201 1000 |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +000b18: 7010 3000 0100 |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000b1e: 1a02 6400 |0007: const-string v2, "bsmLookupStaticWithExtraArgs [" // string@0064 +000b22: 6e20 3600 2100 |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000b28: 0c01 |000c: move-result-object v1 +000b2a: 6e20 3300 6100 |000d: invoke-virtual {v1, v6}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +000b30: 0c01 |0010: move-result-object v1 +000b32: 1a02 0900 |0011: const-string v2, ", " // string@0009 +000b36: 6e20 3600 2100 |0013: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000b3c: 0c01 |0016: move-result-object v1 +000b3e: 6e30 3400 7108 |0017: invoke-virtual {v1, v7, v8}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +000b44: 0c01 |001a: move-result-object v1 +000b46: 1a02 0900 |001b: const-string v2, ", " // string@0009 +000b4a: 6e20 3600 2100 |001d: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000b50: 0c01 |0020: move-result-object v1 +000b52: 6e20 3200 9100 |0021: invoke-virtual {v1, v9}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +000b58: 0c01 |0024: move-result-object v1 +000b5a: 1a02 0900 |0025: const-string v2, ", " // string@0009 +000b5e: 6e20 3600 2100 |0027: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000b64: 0c01 |002a: move-result-object v1 +000b66: 6e30 3100 a10b |002b: invoke-virtual {v1, v10, v11}, Ljava/lang/StringBuilder;.append:(D)Ljava/lang/StringBuilder; // method@0031 +000b6c: 0c01 |002e: move-result-object v1 +000b6e: 1a02 5900 |002f: const-string v2, "]" // string@0059 +000b72: 6e20 3600 2100 |0031: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000b78: 0c01 |0034: move-result-object v1 +000b7a: 6e10 3700 0100 |0035: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000b80: 0c01 |0038: move-result-object v1 +000b82: 6e20 2900 1000 |0039: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000b88: 7100 4600 0000 |003c: invoke-static {}, Ljava/lang/invoke/MethodHandles;.lookup:()Ljava/lang/invoke/MethodHandles$Lookup; // method@0046 +000b8e: 0c00 |003f: move-result-object v0 +000b90: 6e10 4500 0000 |0040: invoke-virtual {v0}, Ljava/lang/invoke/MethodHandles$Lookup;.lookupClass:()Ljava/lang/Class; // method@0045 +000b96: 0c01 |0043: move-result-object v1 +000b98: 6e40 4400 1054 |0044: invoke-virtual {v0, v1, v4, v5}, Ljava/lang/invoke/MethodHandles$Lookup;.findStatic:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@0044 +000b9e: 0c00 |0047: move-result-object v0 +000ba0: 2201 1400 |0048: new-instance v1, Ljava/lang/invoke/ConstantCallSite; // type@0014 +000ba4: 6e20 3a00 5000 |004a: invoke-virtual {v0, v5}, Ljava/lang/invoke/MethodHandle;.asType:(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@003a +000baa: 0c00 |004d: move-result-object v0 +000bac: 7020 3800 0100 |004e: invoke-direct {v1, v0}, Ljava/lang/invoke/ConstantCallSite;.<init>:(Ljava/lang/invoke/MethodHandle;)V // method@0038 +000bb2: 1101 |0051: return-object v1 + catches : (none) + positions : + 0x0000 line=151 + 0x003c line=152 + 0x0040 line=153 + 0x0048 line=154 + locals : + 0x0000 - 0x0052 reg=3 (null) Ljava/lang/invoke/MethodHandles$Lookup; + 0x0000 - 0x0052 reg=4 (null) Ljava/lang/String; + 0x0000 - 0x0052 reg=5 (null) Ljava/lang/invoke/MethodType; + 0x0000 - 0x0052 reg=6 (null) I + 0x0000 - 0x0052 reg=7 (null) J + 0x0000 - 0x0052 reg=9 (null) F + 0x0000 - 0x0052 reg=10 (null) D + + #6 : (in Linvokecustom/InvokeCustom;) + name : 'bsmLookupTest9' + type : '(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 13 + ins : 10 + outs : 4 + insns size : 135 16-bit code units +000bb4: |[000bb4] invokecustom.InvokeCustom.bsmLookupTest9:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; +000bc4: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000bc8: 2201 1000 |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +000bcc: 7010 3000 0100 |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000bd2: 1a02 6600 |0007: const-string v2, "bsmLookupTest9 [" // string@0066 +000bd6: 6e20 3600 2100 |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000bdc: 0c01 |000c: move-result-object v1 +000bde: 6e20 3500 6100 |000d: invoke-virtual {v1, v6}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000be4: 0c01 |0010: move-result-object v1 +000be6: 1a02 0900 |0011: const-string v2, ", " // string@0009 +000bea: 6e20 3600 2100 |0013: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000bf0: 0c01 |0016: move-result-object v1 +000bf2: 6e20 3500 7100 |0017: invoke-virtual {v1, v7}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000bf8: 0c01 |001a: move-result-object v1 +000bfa: 1a02 0900 |001b: const-string v2, ", " // string@0009 +000bfe: 6e20 3600 2100 |001d: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000c04: 0c01 |0020: move-result-object v1 +000c06: 6e20 3500 8100 |0021: invoke-virtual {v1, v8}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000c0c: 0c01 |0024: move-result-object v1 +000c0e: 1a02 0900 |0025: const-string v2, ", " // string@0009 +000c12: 6e20 3600 2100 |0027: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000c18: 0c01 |002a: move-result-object v1 +000c1a: 6e20 3500 9100 |002b: invoke-virtual {v1, v9}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000c20: 0c01 |002e: move-result-object v1 +000c22: 1a02 5900 |002f: const-string v2, "]" // string@0059 +000c26: 6e20 3600 2100 |0031: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000c2c: 0c01 |0034: move-result-object v1 +000c2e: 6e10 3700 0100 |0035: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000c34: 0c01 |0038: move-result-object v1 +000c36: 6e20 2900 1000 |0039: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000c3c: 6200 0200 |003c: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000c40: 2201 1000 |003e: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +000c44: 7010 3000 0100 |0040: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000c4a: 6e20 3600 4100 |0043: invoke-virtual {v1, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000c50: 0c01 |0046: move-result-object v1 +000c52: 1a02 0100 |0047: const-string v2, " " // string@0001 +000c56: 6e20 3600 2100 |0049: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000c5c: 0c01 |004c: move-result-object v1 +000c5e: 6e20 3500 5100 |004d: invoke-virtual {v1, v5}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +000c64: 0c01 |0050: move-result-object v1 +000c66: 6e10 3700 0100 |0051: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000c6c: 0c01 |0054: move-result-object v1 +000c6e: 6e20 2900 1000 |0055: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000c74: 7120 0800 7600 |0058: invoke-static {v6, v7}, Linvokecustom/InvokeCustom;.checkStaticFieldTest9:(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V // method@0008 +000c7a: 2200 0700 |005b: new-instance v0, Linvokecustom/InvokeCustom; // type@0007 +000c7e: 7010 0100 0000 |005d: invoke-direct {v0}, Linvokecustom/InvokeCustom;.<init>:()V // method@0001 +000c84: 7030 0700 8009 |0060: invoke-direct {v0, v8, v9}, Linvokecustom/InvokeCustom;.checkFieldTest9:(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V // method@0007 +000c8a: fa20 4000 0a00 2700 |0063: invoke-polymorphic {v10, v0}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (Linvokecustom/InvokeCustom;)V // method@0040, proto@0027 +000c92: 1230 |0067: const/4 v0, #int 3 // #3 +000c94: fa20 4000 0b00 0500 |0068: invoke-polymorphic {v11, v0}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (I)Linvokecustom/InvokeCustom; // method@0040, proto@0005 +000c9c: 0c00 |006c: move-result-object v0 +000c9e: fa20 3b00 0c00 2700 |006d: invoke-polymorphic {v12, v0}, Ljava/lang/invoke/MethodHandle;.invoke:([Ljava/lang/Object;)Ljava/lang/Object;, (Linvokecustom/InvokeCustom;)V // method@003b, proto@0027 +000ca6: 7100 4600 0000 |0071: invoke-static {}, Ljava/lang/invoke/MethodHandles;.lookup:()Ljava/lang/invoke/MethodHandles$Lookup; // method@0046 +000cac: 0c00 |0074: move-result-object v0 +000cae: 6e10 4500 0000 |0075: invoke-virtual {v0}, Ljava/lang/invoke/MethodHandles$Lookup;.lookupClass:()Ljava/lang/Class; // method@0045 +000cb4: 0c01 |0078: move-result-object v1 +000cb6: 6e40 4400 1054 |0079: invoke-virtual {v0, v1, v4, v5}, Ljava/lang/invoke/MethodHandles$Lookup;.findStatic:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@0044 +000cbc: 0c00 |007c: move-result-object v0 +000cbe: 2201 1400 |007d: new-instance v1, Ljava/lang/invoke/ConstantCallSite; // type@0014 +000cc2: 6e20 3a00 5000 |007f: invoke-virtual {v0, v5}, Ljava/lang/invoke/MethodHandle;.asType:(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@003a +000cc8: 0c00 |0082: move-result-object v0 +000cca: 7020 3800 0100 |0083: invoke-direct {v1, v0}, Ljava/lang/invoke/ConstantCallSite;.<init>:(Ljava/lang/invoke/MethodHandle;)V // method@0038 +000cd0: 1101 |0086: return-object v1 catches : (none) positions : - 0x0000 line=55 + 0x0000 line=170 + 0x003c line=172 + 0x0058 line=175 + 0x005b line=176 + 0x0060 line=177 + 0x0063 line=180 + 0x0067 line=182 + 0x006d line=183 + 0x0071 line=185 + 0x0075 line=186 + 0x007d line=187 locals : - 0x0000 - 0x0003 reg=1 (null) I - 0x0000 - 0x0003 reg=2 (null) I + 0x0000 - 0x0087 reg=3 (null) Ljava/lang/invoke/MethodHandles$Lookup; + 0x0000 - 0x0087 reg=4 (null) Ljava/lang/String; + 0x0000 - 0x0087 reg=5 (null) Ljava/lang/invoke/MethodType; + 0x0000 - 0x0087 reg=6 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=7 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=8 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=9 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=10 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=11 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0087 reg=12 (null) Ljava/lang/invoke/MethodHandle; - #2 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) - name : 'linkerMethod' - type : '(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;ZBCSIFDLjava/lang/String;Ljava/lang/Class;J)Ljava/lang/invoke/CallSite;' + #7 : (in Linvokecustom/InvokeCustom;) + name : 'checkFieldTest9' + type : '(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V' + access : 0x0002 (PRIVATE) + code - + registers : 9 + ins : 3 + outs : 3 + insns size : 82 16-bit code units +000cd4: |[000cd4] invokecustom.InvokeCustom.checkFieldTest9:(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V +000ce4: 1405 0ff0 6a20 |0000: const v5, #float 1.99e-19 // #206af00f +000cea: fa20 4000 6700 0100 |0003: invoke-polymorphic {v7, v6}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (Linvokecustom/InvokeCustom;)F // method@0040, proto@0001 +000cf2: 0a00 |0007: move-result v0 +000cf4: fa30 4000 6805 2800 |0008: invoke-polymorphic {v8, v6, v5}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (Linvokecustom/InvokeCustom;F)V // method@0040, proto@0028 +000cfc: fa20 4000 6700 0100 |000c: invoke-polymorphic {v7, v6}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (Linvokecustom/InvokeCustom;)F // method@0040, proto@0001 +000d04: 0a01 |0010: move-result v1 +000d06: 6202 0200 |0011: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000d0a: 2203 1000 |0013: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +000d0e: 7010 3000 0300 |0015: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000d14: 1a04 6800 |0018: const-string v4, "checkFieldTest9: old " // string@0068 +000d18: 6e20 3600 4300 |001a: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000d1e: 0c03 |001d: move-result-object v3 +000d20: 6e20 3200 0300 |001e: invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +000d26: 0c00 |0021: move-result-object v0 +000d28: 1a03 0700 |0022: const-string v3, " new " // string@0007 +000d2c: 6e20 3600 3000 |0024: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000d32: 0c00 |0027: move-result-object v0 +000d34: 6e20 3200 1000 |0028: invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +000d3a: 0c00 |002b: move-result-object v0 +000d3c: 1a03 0600 |002c: const-string v3, " expected " // string@0006 +000d40: 6e20 3600 3000 |002e: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000d46: 0c00 |0031: move-result-object v0 +000d48: 6e20 3200 5000 |0032: invoke-virtual {v0, v5}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +000d4e: 0c00 |0035: move-result-object v0 +000d50: 1a03 0100 |0036: const-string v3, " " // string@0001 +000d54: 6e20 3600 3000 |0038: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000d5a: 0c00 |003b: move-result-object v0 +000d5c: 6e10 3700 0000 |003c: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000d62: 0c00 |003f: move-result-object v0 +000d64: 6e20 2300 0200 |0040: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.print:(Ljava/lang/String;)V // method@0023 +000d6a: 6202 0200 |0043: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000d6e: 2d00 0105 |0045: cmpl-float v0, v1, v5 +000d72: 3900 0800 |0047: if-nez v0, 004f // +0008 +000d76: 1a00 4400 |0049: const-string v0, "OK" // string@0044 +000d7a: 6e20 2900 0200 |004b: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000d80: 0e00 |004e: return-void +000d82: 1a00 1100 |004f: const-string v0, "ERROR" // string@0011 +000d86: 28fa |0051: goto 004b // -0006 + catches : (none) + positions : + 0x0003 line=120 + 0x0008 line=121 + 0x000c line=122 + 0x0011 line=123 + 0x0043 line=125 + 0x004e line=126 + 0x004f line=125 + locals : + 0x0000 - 0x0052 reg=6 this Linvokecustom/InvokeCustom; + 0x0000 - 0x0052 reg=7 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0052 reg=8 (null) Ljava/lang/invoke/MethodHandle; + + #8 : (in Linvokecustom/InvokeCustom;) + name : 'checkStaticFieldTest9' + type : '(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V' access : 0x000a (PRIVATE STATIC) code - - registers : 24 - ins : 15 - outs : 6 - insns size : 83 16-bit code units -00041c: |[00041c] com.android.jack.java7.invokecustom.test004.Tests.linkerMethod:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;ZBCSIFDLjava/lang/String;Ljava/lang/Class;J)Ljava/lang/invoke/CallSite; -00042c: 7110 1100 0c00 |0000: invoke-static {v12}, Ljunit/framework/Assert;.assertTrue:(Z)V // method@0011 -000432: 1212 |0003: const/4 v2, #int 1 // #1 -000434: 7120 0d00 d200 |0004: invoke-static {v2, v13}, Ljunit/framework/Assert;.assertEquals:(II)V // method@000d -00043a: 1302 6100 |0007: const/16 v2, #int 97 // #61 -00043e: 7120 0a00 e200 |0009: invoke-static {v2, v14}, Ljunit/framework/Assert;.assertEquals:(CC)V // method@000a -000444: 1302 0004 |000c: const/16 v2, #int 1024 // #400 -000448: 7120 0d00 f200 |000e: invoke-static {v2, v15}, Ljunit/framework/Assert;.assertEquals:(II)V // method@000d -00044e: 1212 |0011: const/4 v2, #int 1 // #1 -000450: 0200 1000 |0012: move/from16 v0, v16 -000454: 7120 0d00 0200 |0014: invoke-static {v2, v0}, Ljunit/framework/Assert;.assertEquals:(II)V // method@000d -00045a: 1202 |0017: const/4 v2, #int 0 // #0 -00045c: 1403 9a99 3141 |0018: const v3, #float 11.1 // #4131999a -000462: 0200 1100 |001b: move/from16 v0, v17 -000466: 7130 0c00 0302 |001d: invoke-static {v3, v0, v2}, Ljunit/framework/Assert;.assertEquals:(FFF)V // method@000c -00046c: 1606 0000 |0020: const-wide/16 v6, #int 0 // #0 -000470: 1802 9a99 9999 9999 0140 |0022: const-wide v2, #double 2.2 // #400199999999999a -00047a: 0504 1200 |0027: move-wide/from16 v4, v18 -00047e: 7706 0b00 0200 |0029: invoke-static/range {v2, v3, v4, v5, v6, v7}, Ljunit/framework/Assert;.assertEquals:(DDD)V // method@000b -000484: 1b02 0700 0000 |002c: const-string/jumbo v2, "Hello" // string@00000007 -00048a: 0800 1400 |002f: move-object/from16 v0, v20 -00048e: 7120 1000 0200 |0031: invoke-static {v2, v0}, Ljunit/framework/Assert;.assertEquals:(Ljava/lang/String;Ljava/lang/String;)V // method@0010 -000494: 1c02 0a00 |0034: const-class v2, Lcom/android/jack/java7/invokecustom/test004/Tests; // type@000a -000498: 0800 1500 |0036: move-object/from16 v0, v21 -00049c: 7120 0f00 0200 |0038: invoke-static {v2, v0}, Ljunit/framework/Assert;.assertEquals:(Ljava/lang/Object;Ljava/lang/Object;)V // method@000f -0004a2: 1702 15cd 5b07 |003b: const-wide/32 v2, #float 1.6536e-34 // #075bcd15 -0004a8: 0500 1600 |003e: move-wide/from16 v0, v22 -0004ac: 7140 0e00 3210 |0040: invoke-static {v2, v3, v0, v1}, Ljunit/framework/Assert;.assertEquals:(JJ)V // method@000e -0004b2: 7100 0900 0000 |0043: invoke-static {}, Ljava/lang/invoke/MethodHandles;.lookup:()Ljava/lang/invoke/MethodHandles$Lookup; // method@0009 -0004b8: 0c02 |0046: move-result-object v2 -0004ba: 1c03 0a00 |0047: const-class v3, Lcom/android/jack/java7/invokecustom/test004/Tests; // type@000a -0004be: 6e40 0800 32ba |0049: invoke-virtual {v2, v3, v10, v11}, Ljava/lang/invoke/MethodHandles$Lookup;.findStatic:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle; // method@0008 -0004c4: 0c02 |004c: move-result-object v2 -0004c6: 2203 1400 |004d: new-instance v3, Ljava/lang/invoke/ConstantCallSite; // type@0014 -0004ca: 7020 0700 2300 |004f: invoke-direct {v3, v2}, Ljava/lang/invoke/ConstantCallSite;.<init>:(Ljava/lang/invoke/MethodHandle;)V // method@0007 -0004d0: 1103 |0052: return-object v3 + registers : 8 + ins : 2 + outs : 2 + insns size : 80 16-bit code units +000d88: |[000d88] invokecustom.InvokeCustom.checkStaticFieldTest9:(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V +000d98: 1405 1032 5476 |0000: const v5, #float 1.07596e+33 // #76543210 +000d9e: fa10 4000 0600 0200 |0003: invoke-polymorphic {v6}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, ()I // method@0040, proto@0002 +000da6: 0a00 |0007: move-result v0 +000da8: fa20 4000 5700 2500 |0008: invoke-polymorphic {v7, v5}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, (I)V // method@0040, proto@0025 +000db0: fa10 4000 0600 0200 |000c: invoke-polymorphic {v6}, Ljava/lang/invoke/MethodHandle;.invokeExact:([Ljava/lang/Object;)Ljava/lang/Object;, ()I // method@0040, proto@0002 +000db8: 0a01 |0010: move-result v1 +000dba: 6202 0200 |0011: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000dbe: 2203 1000 |0013: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +000dc2: 7010 3000 0300 |0015: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +000dc8: 1a04 6a00 |0018: const-string v4, "checkStaticFieldTest9: old " // string@006a +000dcc: 6e20 3600 4300 |001a: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000dd2: 0c03 |001d: move-result-object v3 +000dd4: 6e20 3300 0300 |001e: invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +000dda: 0c00 |0021: move-result-object v0 +000ddc: 1a03 0700 |0022: const-string v3, " new " // string@0007 +000de0: 6e20 3600 3000 |0024: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000de6: 0c00 |0027: move-result-object v0 +000de8: 6e20 3300 1000 |0028: invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +000dee: 0c00 |002b: move-result-object v0 +000df0: 1a03 0600 |002c: const-string v3, " expected " // string@0006 +000df4: 6e20 3600 3000 |002e: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000dfa: 0c00 |0031: move-result-object v0 +000dfc: 6e20 3300 5000 |0032: invoke-virtual {v0, v5}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +000e02: 0c00 |0035: move-result-object v0 +000e04: 1a03 0100 |0036: const-string v3, " " // string@0001 +000e08: 6e20 3600 3000 |0038: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +000e0e: 0c00 |003b: move-result-object v0 +000e10: 6e10 3700 0000 |003c: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +000e16: 0c00 |003f: move-result-object v0 +000e18: 6e20 2300 0200 |0040: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.print:(Ljava/lang/String;)V // method@0023 +000e1e: 6202 0200 |0043: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000e22: 3351 0800 |0045: if-ne v1, v5, 004d // +0008 +000e26: 1a00 4400 |0047: const-string v0, "OK" // string@0044 +000e2a: 6e20 2900 0200 |0049: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000e30: 0e00 |004c: return-void +000e32: 1a00 1100 |004d: const-string v0, "ERROR" // string@0011 +000e36: 28fa |004f: goto 0049 // -0006 catches : (none) positions : - 0x0000 line=62 - 0x0003 line=63 - 0x0007 line=64 - 0x000c line=65 - 0x0011 line=66 - 0x0017 line=67 - 0x0020 line=68 - 0x002c line=69 - 0x0034 line=70 - 0x003b line=71 - 0x0043 line=72 - 0x004d line=73 - locals : - 0x0000 - 0x0053 reg=9 (null) Ljava/lang/invoke/MethodHandles$Lookup; - 0x0000 - 0x0053 reg=10 (null) Ljava/lang/String; - 0x0000 - 0x0053 reg=11 (null) Ljava/lang/invoke/MethodType; - 0x0000 - 0x0053 reg=12 (null) Z - 0x0000 - 0x0053 reg=13 (null) B - 0x0000 - 0x0053 reg=14 (null) C - 0x0000 - 0x0053 reg=15 (null) S - 0x0000 - 0x0053 reg=16 (null) I - 0x0000 - 0x0053 reg=17 (null) F - 0x0000 - 0x0053 reg=18 (null) D - 0x0000 - 0x0053 reg=20 (null) Ljava/lang/String; - 0x0000 - 0x0053 reg=21 (null) Ljava/lang/Class; - 0x0000 - 0x0053 reg=22 (null) J - - #3 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) + 0x0003 line=107 + 0x0008 line=108 + 0x000c line=109 + 0x0011 line=110 + 0x0043 line=112 + 0x004c line=113 + 0x004d line=112 + locals : + 0x0000 - 0x0050 reg=6 (null) Ljava/lang/invoke/MethodHandle; + 0x0000 - 0x0050 reg=7 (null) Ljava/lang/invoke/MethodHandle; + + #9 : (in Linvokecustom/InvokeCustom;) + name : 'lambda$lambdaTest$0' + type : '(Ljava/lang/String;)Z' + access : 0x100a (PRIVATE STATIC SYNTHETIC) + code - + registers : 3 + ins : 1 + outs : 2 + insns size : 11 16-bit code units +000e38: |[000e38] invokecustom.InvokeCustom.lambda$lambdaTest$0:(Ljava/lang/String;)Z +000e48: 1a00 4500 |0000: const-string v0, "One" // string@0045 +000e4c: 6e10 2f00 0200 |0002: invoke-virtual {v2}, Ljava/lang/String;.trim:()Ljava/lang/String; // method@002f +000e52: 0c01 |0005: move-result-object v1 +000e54: 6e20 2e00 1000 |0006: invoke-virtual {v0, v1}, Ljava/lang/String;.equals:(Ljava/lang/Object;)Z // method@002e +000e5a: 0a00 |0009: move-result v0 +000e5c: 0f00 |000a: return v0 + catches : (none) + positions : + 0x0000 line=192 + locals : + 0x0000 - 0x000b reg=2 (null) Ljava/lang/String; + + #10 : (in Linvokecustom/InvokeCustom;) + name : 'lambdaTest' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 3 + ins : 0 + outs : 2 + insns size : 71 16-bit code units +000e60: |[000e60] invokecustom.InvokeCustom.lambdaTest:()V +000e70: 1230 |0000: const/4 v0, #int 3 // #3 +000e72: 2300 2500 |0001: new-array v0, v0, [Ljava/lang/String; // type@0025 +000e76: 1201 |0003: const/4 v1, #int 0 // #0 +000e78: 1a02 4900 |0004: const-string v2, "Three" // string@0049 +000e7c: 4d02 0001 |0006: aput-object v2, v0, v1 +000e80: 1211 |0008: const/4 v1, #int 1 // #1 +000e82: 1a02 4500 |0009: const-string v2, "One" // string@0045 +000e86: 4d02 0001 |000b: aput-object v2, v0, v1 +000e8a: 1221 |000d: const/4 v1, #int 2 // #2 +000e8c: 1a02 1600 |000e: const-string v2, "FortyTwo" // string@0016 +000e90: 4d02 0001 |0010: aput-object v2, v0, v1 +000e94: 7110 4700 0000 |0012: invoke-static {v0}, Ljava/util/Arrays;.asList:([Ljava/lang/Object;)Ljava/util/List; // method@0047 +000e9a: 0c01 |0015: move-result-object v1 +000e9c: 7210 4800 0100 |0016: invoke-interface {v1}, Ljava/util/List;.stream:()Ljava/util/stream/Stream; // method@0048 +000ea2: 0c00 |0019: move-result-object v0 +000ea4: fc00 0000 0000 |001a: invoke-custom {}, call_site@0000 +000eaa: 0c02 |001d: move-result-object v2 +000eac: 7220 4a00 2000 |001e: invoke-interface {v0, v2}, Ljava/util/stream/Stream;.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream; // method@004a +000eb2: 0c00 |0021: move-result-object v0 +000eb4: fc00 0100 0000 |0022: invoke-custom {}, call_site@0001 +000eba: 0c02 |0025: move-result-object v2 +000ebc: 7220 4d00 2000 |0026: invoke-interface {v0, v2}, Ljava/util/stream/Stream;.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream; // method@004d +000ec2: 0c00 |0029: move-result-object v0 +000ec4: 7210 4b00 0000 |002a: invoke-interface {v0}, Ljava/util/stream/Stream;.findAny:()Ljava/util/Optional; // method@004b +000eca: 0c00 |002d: move-result-object v0 +000ecc: 1a02 0000 |002e: const-string v2, "" // string@0000 +000ed0: 6e20 4900 2000 |0030: invoke-virtual {v0, v2}, Ljava/util/Optional;.orElse:(Ljava/lang/Object;)Ljava/lang/Object; // method@0049 +000ed6: 0c00 |0033: move-result-object v0 +000ed8: 1f00 0f00 |0034: check-cast v0, Ljava/lang/String; // type@000f +000edc: 7210 4800 0100 |0036: invoke-interface {v1}, Ljava/util/List;.stream:()Ljava/util/stream/Stream; // method@0048 +000ee2: 0c00 |0039: move-result-object v0 +000ee4: 6201 0200 |003a: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000ee8: 6e10 2c00 0100 |003c: invoke-virtual {v1}, Ljava/lang/Object;.getClass:()Ljava/lang/Class; // method@002c +000eee: fc10 0200 0100 |003f: invoke-custom {v1}, call_site@0002 +000ef4: 0c01 |0042: move-result-object v1 +000ef6: 7220 4c00 1000 |0043: invoke-interface {v0, v1}, Ljava/util/stream/Stream;.forEach:(Ljava/util/function/Consumer;)V // method@004c +000efc: 0e00 |0046: return-void + catches : (none) + positions : + 0x0000 line=191 + 0x0016 line=192 + 0x0026 line=193 + 0x0036 line=194 + 0x0046 line=195 + locals : + + #11 : (in Linvokecustom/InvokeCustom;) name : 'main' type : '([Ljava/lang/String;)V' access : 0x0009 (PUBLIC STATIC) code - + registers : 1 + ins : 1 + outs : 0 + insns size : 28 16-bit code units +000f00: |[000f00] invokecustom.InvokeCustom.main:([Ljava/lang/String;)V +000f10: 7100 1700 0000 |0000: invoke-static {}, Linvokecustom/InvokeCustom;.test1:()V // method@0017 +000f16: 7100 1800 0000 |0003: invoke-static {}, Linvokecustom/InvokeCustom;.test2:()V // method@0018 +000f1c: 7100 1900 0000 |0006: invoke-static {}, Linvokecustom/InvokeCustom;.test3:()V // method@0019 +000f22: 7100 1a00 0000 |0009: invoke-static {}, Linvokecustom/InvokeCustom;.test4:()V // method@001a +000f28: 7100 1b00 0000 |000c: invoke-static {}, Linvokecustom/InvokeCustom;.test5:()V // method@001b +000f2e: 7100 1c00 0000 |000f: invoke-static {}, Linvokecustom/InvokeCustom;.test6:()V // method@001c +000f34: 7100 1d00 0000 |0012: invoke-static {}, Linvokecustom/InvokeCustom;.test7:()V // method@001d +000f3a: 7100 1e00 0000 |0015: invoke-static {}, Linvokecustom/InvokeCustom;.test8:()V // method@001e +000f40: 7100 1f00 0000 |0018: invoke-static {}, Linvokecustom/InvokeCustom;.test9:()V // method@001f +000f46: 0e00 |001b: return-void + catches : (none) + positions : + locals : + + #12 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest1' + type : '()V' + access : 0x000a (PRIVATE STATIC) + code - + registers : 2 + ins : 0 + outs : 2 + insns size : 8 16-bit code units +000f48: |[000f48] invokecustom.InvokeCustom.targetMethodTest1:()V +000f58: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000f5c: 1a01 1700 |0002: const-string v1, "Hello World!" // string@0017 +000f60: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000f66: 0e00 |0007: return-void + catches : (none) + positions : + 0x0000 line=45 + 0x0007 line=46 + locals : + + #13 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest2' + type : '(ZBCSIFJDLjava/lang/String;)V' + access : 0x000a (PRIVATE STATIC) + code - + registers : 13 + ins : 11 + outs : 3 + insns size : 46 16-bit code units +000f68: |[000f68] invokecustom.InvokeCustom.targetMethodTest2:(ZBCSIFJDLjava/lang/String;)V +000f78: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000f7c: 6e20 2a00 2000 |0002: invoke-virtual {v0, v2}, Ljava/io/PrintStream;.println:(Z)V // method@002a +000f82: 6200 0200 |0005: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000f86: 6e20 2700 3000 |0007: invoke-virtual {v0, v3}, Ljava/io/PrintStream;.println:(I)V // method@0027 +000f8c: 6200 0200 |000a: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000f90: 6e20 2400 4000 |000c: invoke-virtual {v0, v4}, Ljava/io/PrintStream;.println:(C)V // method@0024 +000f96: 6200 0200 |000f: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000f9a: 6e20 2700 5000 |0011: invoke-virtual {v0, v5}, Ljava/io/PrintStream;.println:(I)V // method@0027 +000fa0: 6200 0200 |0014: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fa4: 6e20 2700 6000 |0016: invoke-virtual {v0, v6}, Ljava/io/PrintStream;.println:(I)V // method@0027 +000faa: 6200 0200 |0019: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fae: 6e20 2600 7000 |001b: invoke-virtual {v0, v7}, Ljava/io/PrintStream;.println:(F)V // method@0026 +000fb4: 6200 0200 |001e: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fb8: 6e30 2800 8009 |0020: invoke-virtual {v0, v8, v9}, Ljava/io/PrintStream;.println:(J)V // method@0028 +000fbe: 6200 0200 |0023: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fc2: 6e30 2500 a00b |0025: invoke-virtual {v0, v10, v11}, Ljava/io/PrintStream;.println:(D)V // method@0025 +000fc8: 6200 0200 |0028: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fcc: 6e20 2900 c000 |002a: invoke-virtual {v0, v12}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000fd2: 0e00 |002d: return-void + catches : (none) + positions : + 0x0000 line=50 + 0x0005 line=51 + 0x000a line=52 + 0x000f line=53 + 0x0014 line=54 + 0x0019 line=55 + 0x001e line=56 + 0x0023 line=57 + 0x0028 line=58 + 0x002d line=59 + locals : + 0x0000 - 0x002e reg=2 (null) Z + 0x0000 - 0x002e reg=3 (null) B + 0x0000 - 0x002e reg=4 (null) C + 0x0000 - 0x002e reg=5 (null) S + 0x0000 - 0x002e reg=6 (null) I + 0x0000 - 0x002e reg=7 (null) F + 0x0000 - 0x002e reg=8 (null) J + 0x0000 - 0x002e reg=10 (null) D + 0x0000 - 0x002e reg=12 (null) Ljava/lang/String; + + #14 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest3' + type : '()V' + access : 0x000a (PRIVATE STATIC) + code - + registers : 2 + ins : 0 + outs : 2 + insns size : 8 16-bit code units +000fd4: |[000fd4] invokecustom.InvokeCustom.targetMethodTest3:()V +000fe4: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +000fe8: 1a01 8800 |0002: const-string v1, "targetMethodTest3 from InvokeCustom" // string@0088 +000fec: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +000ff2: 0e00 |0007: return-void + catches : (none) + positions : + 0x0000 line=62 + 0x0007 line=63 + locals : + + #15 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest5' + type : '(III)I' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 7 + ins : 3 + outs : 2 + insns size : 83 16-bit code units +000ff4: |[000ff4] invokecustom.InvokeCustom.targetMethodTest5:(III)I +001004: 9000 0405 |0000: add-int v0, v4, v5 +001008: 6201 0200 |0002: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +00100c: 2202 1000 |0004: new-instance v2, Ljava/lang/StringBuilder; // type@0010 +001010: 7010 3000 0200 |0006: invoke-direct {v2}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +001016: 1a03 8d00 |0009: const-string v3, "targetMethodTest5 " // string@008d +00101a: 6e20 3600 3200 |000b: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001020: 0c02 |000e: move-result-object v2 +001022: 6e20 3300 4200 |000f: invoke-virtual {v2, v4}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +001028: 0c02 |0012: move-result-object v2 +00102a: 1a03 0400 |0013: const-string v3, " + " // string@0004 +00102e: 6e20 3600 3200 |0015: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001034: 0c02 |0018: move-result-object v2 +001036: 6e20 3300 5200 |0019: invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +00103c: 0c02 |001c: move-result-object v2 +00103e: 1a03 0500 |001d: const-string v3, " = " // string@0005 +001042: 6e20 3600 3200 |001f: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001048: 0c02 |0022: move-result-object v2 +00104a: 6e20 3300 0200 |0023: invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +001050: 0c02 |0026: move-result-object v2 +001052: 6e10 3700 0200 |0027: invoke-virtual {v2}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +001058: 0c02 |002a: move-result-object v2 +00105a: 6e20 2900 2100 |002b: invoke-virtual {v1, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001060: 3260 2400 |002e: if-eq v0, v6, 0052 // +0024 +001064: 6201 0200 |0030: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001068: 2202 1000 |0032: new-instance v2, Ljava/lang/StringBuilder; // type@0010 +00106c: 7010 3000 0200 |0034: invoke-direct {v2}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +001072: 1a03 1400 |0037: const-string v3, "Failed " // string@0014 +001076: 6e20 3600 3200 |0039: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +00107c: 0c02 |003c: move-result-object v2 +00107e: 6e20 3300 0200 |003d: invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +001084: 0c02 |0040: move-result-object v2 +001086: 1a03 0200 |0041: const-string v3, " != " // string@0002 +00108a: 6e20 3600 3200 |0043: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001090: 0c02 |0046: move-result-object v2 +001092: 6e20 3300 6200 |0047: invoke-virtual {v2, v6}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +001098: 0c02 |004a: move-result-object v2 +00109a: 6e10 3700 0200 |004b: invoke-virtual {v2}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +0010a0: 0c02 |004e: move-result-object v2 +0010a2: 6e20 2900 2100 |004f: invoke-virtual {v1, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0010a8: 0f00 |0052: return v0 + catches : (none) + positions : + 0x0000 line=72 + 0x0002 line=73 + 0x002e line=74 + 0x0030 line=75 + 0x0052 line=77 + locals : + 0x0000 - 0x0053 reg=4 (null) I + 0x0000 - 0x0053 reg=5 (null) I + 0x0000 - 0x0053 reg=6 (null) I + + #16 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest6' + type : '(JJJ)J' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 12 + ins : 6 + outs : 3 + insns size : 85 16-bit code units +0010ac: |[0010ac] invokecustom.InvokeCustom.targetMethodTest6:(JJJ)J +0010bc: 9b00 0608 |0000: add-long v0, v6, v8 +0010c0: 6202 0200 |0002: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0010c4: 2203 1000 |0004: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +0010c8: 7010 3000 0300 |0006: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +0010ce: 1a04 9000 |0009: const-string v4, "targetMethodTest6 " // string@0090 +0010d2: 6e20 3600 4300 |000b: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0010d8: 0c03 |000e: move-result-object v3 +0010da: 6e30 3400 6307 |000f: invoke-virtual {v3, v6, v7}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +0010e0: 0c03 |0012: move-result-object v3 +0010e2: 1a04 0400 |0013: const-string v4, " + " // string@0004 +0010e6: 6e20 3600 4300 |0015: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0010ec: 0c03 |0018: move-result-object v3 +0010ee: 6e30 3400 8309 |0019: invoke-virtual {v3, v8, v9}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +0010f4: 0c03 |001c: move-result-object v3 +0010f6: 1a04 0500 |001d: const-string v4, " = " // string@0005 +0010fa: 6e20 3600 4300 |001f: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001100: 0c03 |0022: move-result-object v3 +001102: 6e30 3400 0301 |0023: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +001108: 0c03 |0026: move-result-object v3 +00110a: 6e10 3700 0300 |0027: invoke-virtual {v3}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +001110: 0c03 |002a: move-result-object v3 +001112: 6e20 2900 3200 |002b: invoke-virtual {v2, v3}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001118: 3102 000a |002e: cmp-long v2, v0, v10 +00111c: 3802 2400 |0030: if-eqz v2, 0054 // +0024 +001120: 6202 0200 |0032: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001124: 2203 1000 |0034: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +001128: 7010 3000 0300 |0036: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +00112e: 1a04 1400 |0039: const-string v4, "Failed " // string@0014 +001132: 6e20 3600 4300 |003b: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001138: 0c03 |003e: move-result-object v3 +00113a: 6e30 3400 0301 |003f: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +001140: 0c03 |0042: move-result-object v3 +001142: 1a04 0200 |0043: const-string v4, " != " // string@0002 +001146: 6e20 3600 4300 |0045: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +00114c: 0c03 |0048: move-result-object v3 +00114e: 6e30 3400 a30b |0049: invoke-virtual {v3, v10, v11}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +001154: 0c03 |004c: move-result-object v3 +001156: 6e10 3700 0300 |004d: invoke-virtual {v3}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +00115c: 0c03 |0050: move-result-object v3 +00115e: 6e20 2900 3200 |0051: invoke-virtual {v2, v3}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001164: 1000 |0054: return-wide v0 + catches : (none) + positions : + 0x0000 line=81 + 0x0002 line=82 + 0x002e line=83 + 0x0032 line=84 + 0x0054 line=86 + locals : + 0x0000 - 0x0055 reg=6 (null) J + 0x0000 - 0x0055 reg=8 (null) J + 0x0000 - 0x0055 reg=10 (null) J + + #17 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest7' + type : '(FFD)D' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 10 + ins : 4 + outs : 3 + insns size : 86 16-bit code units +001168: |[001168] invokecustom.InvokeCustom.targetMethodTest7:(FFD)D +001178: a800 0607 |0000: mul-float v0, v6, v7 +00117c: 8900 |0002: float-to-double v0, v0 +00117e: 6202 0200 |0003: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001182: 2203 1000 |0005: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +001186: 7010 3000 0300 |0007: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +00118c: 1a04 9300 |000a: const-string v4, "targetMethodTest7 " // string@0093 +001190: 6e20 3600 4300 |000c: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001196: 0c03 |000f: move-result-object v3 +001198: 6e20 3200 6300 |0010: invoke-virtual {v3, v6}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +00119e: 0c03 |0013: move-result-object v3 +0011a0: 1a04 0300 |0014: const-string v4, " * " // string@0003 +0011a4: 6e20 3600 4300 |0016: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0011aa: 0c03 |0019: move-result-object v3 +0011ac: 6e20 3200 7300 |001a: invoke-virtual {v3, v7}, Ljava/lang/StringBuilder;.append:(F)Ljava/lang/StringBuilder; // method@0032 +0011b2: 0c03 |001d: move-result-object v3 +0011b4: 1a04 0500 |001e: const-string v4, " = " // string@0005 +0011b8: 6e20 3600 4300 |0020: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0011be: 0c03 |0023: move-result-object v3 +0011c0: 6e30 3100 0301 |0024: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(D)Ljava/lang/StringBuilder; // method@0031 +0011c6: 0c03 |0027: move-result-object v3 +0011c8: 6e10 3700 0300 |0028: invoke-virtual {v3}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +0011ce: 0c03 |002b: move-result-object v3 +0011d0: 6e20 2900 3200 |002c: invoke-virtual {v2, v3}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0011d6: 2f02 0008 |002f: cmpl-double v2, v0, v8 +0011da: 3802 2400 |0031: if-eqz v2, 0055 // +0024 +0011de: 6202 0200 |0033: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0011e2: 2203 1000 |0035: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +0011e6: 7010 3000 0300 |0037: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +0011ec: 1a04 1400 |003a: const-string v4, "Failed " // string@0014 +0011f0: 6e20 3600 4300 |003c: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0011f6: 0c03 |003f: move-result-object v3 +0011f8: 6e30 3100 0301 |0040: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(D)Ljava/lang/StringBuilder; // method@0031 +0011fe: 0c03 |0043: move-result-object v3 +001200: 1a04 0200 |0044: const-string v4, " != " // string@0002 +001204: 6e20 3600 4300 |0046: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +00120a: 0c03 |0049: move-result-object v3 +00120c: 6e30 3100 8309 |004a: invoke-virtual {v3, v8, v9}, Ljava/lang/StringBuilder;.append:(D)Ljava/lang/StringBuilder; // method@0031 +001212: 0c03 |004d: move-result-object v3 +001214: 6e10 3700 0300 |004e: invoke-virtual {v3}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +00121a: 0c03 |0051: move-result-object v3 +00121c: 6e20 2900 3200 |0052: invoke-virtual {v2, v3}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001222: 1000 |0055: return-wide v0 + catches : (none) + positions : + 0x0000 line=90 + 0x0003 line=91 + 0x002f line=92 + 0x0033 line=93 + 0x0055 line=95 + locals : + 0x0000 - 0x0056 reg=6 (null) F + 0x0000 - 0x0056 reg=7 (null) F + 0x0000 - 0x0056 reg=8 (null) D + + #18 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest8' + type : '(Ljava/lang/String;)V' + access : 0x0009 (PUBLIC STATIC) + code - registers : 4 ins : 1 outs : 2 - insns size : 12 16-bit code units -0004d4: |[0004d4] com.android.jack.java7.invokecustom.test004.Tests.main:([Ljava/lang/String;)V -0004e4: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 -0004e8: 1221 |0002: const/4 v1, #int 2 // #2 -0004ea: 1232 |0003: const/4 v2, #int 3 // #3 -0004ec: fc20 0000 2100 |0004: invoke-custom {v1, v2}, call_site@0000 -0004f2: 0a01 |0007: move-result v1 -0004f4: 6e20 0500 1000 |0008: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(I)V // method@0005 -0004fa: 0e00 |000b: return-void + insns size : 25 16-bit code units +001224: |[001224] invokecustom.InvokeCustom.targetMethodTest8:(Ljava/lang/String;)V +001234: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001238: 2201 1000 |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +00123c: 7010 3000 0100 |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +001242: 1a02 9500 |0007: const-string v2, "targetMethodTest8 " // string@0095 +001246: 6e20 3600 2100 |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +00124c: 0c01 |000c: move-result-object v1 +00124e: 6e20 3600 3100 |000d: invoke-virtual {v1, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001254: 0c01 |0010: move-result-object v1 +001256: 6e10 3700 0100 |0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +00125c: 0c01 |0014: move-result-object v1 +00125e: 6e20 2900 1000 |0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001264: 0e00 |0018: return-void + catches : (none) + positions : + 0x0000 line=99 + 0x0018 line=100 + locals : + 0x0000 - 0x0019 reg=3 (null) Ljava/lang/String; + + #19 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest9' + type : '()V' + access : 0x000a (PRIVATE STATIC) + code - + registers : 2 + ins : 0 + outs : 2 + insns size : 8 16-bit code units +001268: |[001268] invokecustom.InvokeCustom.targetMethodTest9:()V +001278: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +00127c: 1a01 9700 |0002: const-string v1, "targetMethodTest9()" // string@0097 +001280: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001286: 0e00 |0007: return-void + catches : (none) + positions : + 0x0000 line=133 + 0x0007 line=134 + locals : + + #20 : (in Linvokecustom/InvokeCustom;) + name : 'test1' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 0 + ins : 0 + outs : 0 + insns size : 4 16-bit code units +001288: |[001288] invokecustom.InvokeCustom.test1:()V +001298: fc00 0300 0000 |0000: invoke-custom {}, call_site@0003 +00129e: 0e00 |0003: return-void + catches : (none) + positions : + locals : + + #21 : (in Linvokecustom/InvokeCustom;) + name : 'test2' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 11 + ins : 0 + outs : 11 + insns size : 27 16-bit code units +0012a0: |[0012a0] invokecustom.InvokeCustom.test2:()V +0012b0: 1210 |0000: const/4 v0, #int 1 // #1 +0012b2: 1301 7f00 |0001: const/16 v1, #int 127 // #7f +0012b6: 1302 6300 |0003: const/16 v2, #int 99 // #63 +0012ba: 1303 0004 |0005: const/16 v3, #int 1024 // #400 +0012be: 1404 40e2 0100 |0007: const v4, #float 1.72999e-40 // #0001e240 +0012c4: 1405 9a99 993f |000a: const v5, #float 1.2 // #3f99999a +0012ca: 1706 15cd 5b07 |000d: const-wide/32 v6, #float 1.6536e-34 // #075bcd15 +0012d0: 1808 b6fa f8b0 4819 0c40 |0010: const-wide v8, #double 3.51235 // #400c1948b0f8fab6 +0012da: 1a0a 4800 |0015: const-string v10, "String" // string@0048 +0012de: fd0b 0400 0000 |0017: invoke-custom/range {v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10}, call_site@0004 +0012e4: 0e00 |001a: return-void + catches : (none) + positions : + locals : + + #22 : (in Linvokecustom/InvokeCustom;) + name : 'test3' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 0 + ins : 0 + outs : 0 + insns size : 4 16-bit code units +0012e8: |[0012e8] invokecustom.InvokeCustom.test3:()V +0012f8: fc00 0b00 0000 |0000: invoke-custom {}, call_site@000b +0012fe: 0e00 |0003: return-void + catches : (none) + positions : + locals : + + #23 : (in Linvokecustom/InvokeCustom;) + name : 'test4' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 1 + ins : 0 + outs : 1 + insns size : 9 16-bit code units +001300: |[001300] invokecustom.InvokeCustom.test4:()V +001310: 2200 0700 |0000: new-instance v0, Linvokecustom/InvokeCustom; // type@0007 +001314: 7010 0100 0000 |0002: invoke-direct {v0}, Linvokecustom/InvokeCustom;.<init>:()V // method@0001 +00131a: fc10 0c00 0000 |0005: invoke-custom {v0}, call_site@000c +001320: 0e00 |0008: return-void + catches : (none) + positions : + locals : + + #24 : (in Linvokecustom/InvokeCustom;) + name : 'test5' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 4 + ins : 0 + outs : 3 + insns size : 35 16-bit code units +001324: |[001324] invokecustom.InvokeCustom.test5:()V +001334: 1300 e803 |0000: const/16 v0, #int 1000 // #3e8 +001338: 1301 65fc |0002: const/16 v1, #int -923 // #fc65 +00133c: 1302 4d00 |0004: const/16 v2, #int 77 // #4d +001340: fc30 0500 1002 |0006: invoke-custom {v0, v1, v2}, call_site@0005 +001346: 0a00 |0009: move-result v0 +001348: 6201 0200 |000a: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +00134c: 2202 1000 |000c: new-instance v2, Ljava/lang/StringBuilder; // type@0010 +001350: 7010 3000 0200 |000e: invoke-direct {v2}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +001356: 1a03 8e00 |0011: const-string v3, "targetMethodTest5 returned: " // string@008e +00135a: 6e20 3600 3200 |0013: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +001360: 0c02 |0016: move-result-object v2 +001362: 6e20 3300 0200 |0017: invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@0033 +001368: 0c00 |001a: move-result-object v0 +00136a: 6e10 3700 0000 |001b: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +001370: 0c00 |001e: move-result-object v0 +001372: 6e20 2900 0100 |001f: invoke-virtual {v1, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001378: 0e00 |0022: return-void + catches : (none) + positions : + locals : + + #25 : (in Linvokecustom/InvokeCustom;) + name : 'test6' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 6 + ins : 0 + outs : 6 + insns size : 44 16-bit code units +00137c: |[00137c] invokecustom.InvokeCustom.test6:()V +00138c: 1800 7777 7777 7707 0000 |0000: const-wide v0, #double 4.05612e-311 // #0000077777777777 +001396: 1802 efee eeee eefe ffff |0005: const-wide v2, #double -nan // #fffffeeeeeeeeeef +0013a0: 1804 6666 6666 6606 0000 |000a: const-wide v4, #double 3.47668e-311 // #0000066666666666 +0013aa: fd06 0600 0000 |000f: invoke-custom/range {v0, v1, v2, v3, v4, v5}, call_site@0006 +0013b0: 0b00 |0012: move-result-wide v0 +0013b2: 6202 0200 |0013: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0013b6: 2203 1000 |0015: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +0013ba: 7010 3000 0300 |0017: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +0013c0: 1a04 9100 |001a: const-string v4, "targetMethodTest6 returned: " // string@0091 +0013c4: 6e20 3600 4300 |001c: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0013ca: 0c03 |001f: move-result-object v3 +0013cc: 6e30 3400 0301 |0020: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(J)Ljava/lang/StringBuilder; // method@0034 +0013d2: 0c00 |0023: move-result-object v0 +0013d4: 6e10 3700 0000 |0024: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +0013da: 0c00 |0027: move-result-object v0 +0013dc: 6e20 2900 0200 |0028: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0013e2: 0e00 |002b: return-void + catches : (none) + positions : + locals : + + #26 : (in Linvokecustom/InvokeCustom;) + name : 'test7' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 5 + ins : 0 + outs : 4 + insns size : 40 16-bit code units +0013e4: |[0013e4] invokecustom.InvokeCustom.test7:()V +0013f4: 1400 0040 003f |0000: const v0, #float 0.500977 // #3f004000 +0013fa: 1401 0040 00bf |0003: const v1, #float -0.500977 // #bf004000 +001400: 1802 0000 0000 0410 d0bf |0006: const-wide v2, #double -0.250978 // #bfd0100400000000 +00140a: fc40 0700 1032 |000b: invoke-custom {v0, v1, v2, v3}, call_site@0007 +001410: 0b00 |000e: move-result-wide v0 +001412: 6202 0200 |000f: sget-object v2, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001416: 2203 1000 |0011: new-instance v3, Ljava/lang/StringBuilder; // type@0010 +00141a: 7010 3000 0300 |0013: invoke-direct {v3}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +001420: 1a04 9100 |0016: const-string v4, "targetMethodTest6 returned: " // string@0091 +001424: 6e20 3600 4300 |0018: invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +00142a: 0c03 |001b: move-result-object v3 +00142c: 6e30 3100 0301 |001c: invoke-virtual {v3, v0, v1}, Ljava/lang/StringBuilder;.append:(D)Ljava/lang/StringBuilder; // method@0031 +001432: 0c00 |001f: move-result-object v0 +001434: 6e10 3700 0000 |0020: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +00143a: 0c00 |0023: move-result-object v0 +00143c: 6e20 2900 0200 |0024: invoke-virtual {v2, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001442: 0e00 |0027: return-void + catches : (none) + positions : + locals : + + #27 : (in Linvokecustom/InvokeCustom;) + name : 'test8' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 1 + ins : 0 + outs : 1 + insns size : 16 16-bit code units +001444: |[001444] invokecustom.InvokeCustom.test8:()V +001454: 1a00 1500 |0000: const-string v0, "First invokedynamic invocation" // string@0015 +001458: fc10 0800 0000 |0002: invoke-custom {v0}, call_site@0008 +00145e: 1a00 4700 |0005: const-string v0, "Second invokedynamic invocation" // string@0047 +001462: fc10 0900 0000 |0007: invoke-custom {v0}, call_site@0009 +001468: 1a00 1000 |000a: const-string v0, "Dupe first invokedynamic invocation" // string@0010 +00146c: fc10 0a00 0000 |000c: invoke-custom {v0}, call_site@000a +001472: 0e00 |000f: return-void + catches : (none) + positions : + locals : + + #28 : (in Linvokecustom/InvokeCustom;) + name : 'test9' + type : '()V' + access : 0x0009 (PUBLIC STATIC) + code - + registers : 0 + ins : 0 + outs : 0 + insns size : 4 16-bit code units +001474: |[001474] invokecustom.InvokeCustom.test9:()V +001484: fc00 0d00 0000 |0000: invoke-custom {}, call_site@000d +00148a: 0e00 |0003: return-void catches : (none) positions : - 0x0000 line=82 - 0x000b line=83 locals : - 0x0000 - 0x000c reg=3 (null) [Ljava/lang/String; Virtual methods - - #0 : (in Lcom/android/jack/java7/invokecustom/test004/Tests;) - name : 'test' + #0 : (in Linvokecustom/InvokeCustom;) + name : 'helperMethodTest9' + type : '()V' + access : 0x0001 (PUBLIC) + code - + registers : 4 + ins : 1 + outs : 2 + insns size : 27 16-bit code units +00148c: |[00148c] invokecustom.InvokeCustom.helperMethodTest9:()V +00149c: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0014a0: 2201 1000 |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0010 +0014a4: 7010 3000 0100 |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0030 +0014aa: 1a02 7300 |0007: const-string v2, "helperMethodTest9 in " // string@0073 +0014ae: 6e20 3600 2100 |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0036 +0014b4: 0c01 |000c: move-result-object v1 +0014b6: 1c02 0700 |000d: const-class v2, Linvokecustom/InvokeCustom; // type@0007 +0014ba: 6e20 3500 2100 |000f: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; // method@0035 +0014c0: 0c01 |0012: move-result-object v1 +0014c2: 6e10 3700 0100 |0013: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0037 +0014c8: 0c01 |0016: move-result-object v1 +0014ca: 6e20 2900 1000 |0017: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0014d0: 0e00 |001a: return-void + catches : (none) + positions : + 0x0000 line=129 + 0x001a line=130 + locals : + 0x0000 - 0x001b reg=3 this Linvokecustom/InvokeCustom; + + #1 : (in Linvokecustom/InvokeCustom;) + name : 'run' type : '()V' access : 0x0001 (PUBLIC) code - registers : 3 ins : 1 outs : 2 - insns size : 11 16-bit code units -0004fc: |[0004fc] com.android.jack.java7.invokecustom.test004.Tests.test:()V -00050c: 1220 |0000: const/4 v0, #int 2 // #2 -00050e: 1231 |0001: const/4 v1, #int 3 // #3 -000510: fc20 0100 1000 |0002: invoke-custom {v0, v1}, call_site@0001 -000516: 0a00 |0005: move-result v0 -000518: 1251 |0006: const/4 v1, #int 5 // #5 -00051a: 7120 0d00 0100 |0007: invoke-static {v1, v0}, Ljunit/framework/Assert;.assertEquals:(II)V // method@000d -000520: 0e00 |000a: return-void + insns size : 8 16-bit code units +0014d4: |[0014d4] invokecustom.InvokeCustom.run:()V +0014e4: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +0014e8: 1a01 8200 |0002: const-string v1, "run() for Test9" // string@0082 +0014ec: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +0014f2: 0e00 |0007: return-void catches : (none) positions : - 0x0000 line=78 - 0x000a line=79 + 0x0000 line=137 + 0x0007 line=138 locals : - 0x0000 - 0x000b reg=2 this Lcom/android/jack/java7/invokecustom/test004/Tests; + 0x0000 - 0x0008 reg=2 this Linvokecustom/InvokeCustom; - source_file_idx : 38 (Tests.java) + #2 : (in Linvokecustom/InvokeCustom;) + name : 'targetMethodTest4' + type : '()V' + access : 0x0001 (PUBLIC) + code - + registers : 3 + ins : 1 + outs : 2 + insns size : 8 16-bit code units +0014f4: |[0014f4] invokecustom.InvokeCustom.targetMethodTest4:()V +001504: 6200 0200 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0002 +001508: 1a01 8a00 |0002: const-string v1, "targetMethodTest4 from InvokeCustom (oops!)" // string@008a +00150c: 6e20 2900 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0029 +001512: 0e00 |0007: return-void + catches : (none) + positions : + 0x0000 line=68 + 0x0007 line=69 + locals : + 0x0000 - 0x0008 reg=2 this Linvokecustom/InvokeCustom; + + source_file_idx : 27 (InvokeCustom.java) Method handle #0: + type : put-static + target : Linvokecustom/InvokeCustom; staticFieldTest9 + target_type : I +Method handle #1: + type : get-static + target : Linvokecustom/InvokeCustom; staticFieldTest9 + target_type : I +Method handle #2: + type : put-instance + target : Linvokecustom/InvokeCustom; fieldTest9 + target_type : (Linvokecustom/InvokeCustom; +Method handle #3: + type : get-instance + target : Linvokecustom/InvokeCustom; fieldTest9 + target_type : (Linvokecustom/InvokeCustom; +Method handle #4: type : invoke-static - target : Lcom/android/jack/java7/invokecustom/test004/Tests; linkerMethod - target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;ZBCSIFDLjava/lang/String;Ljava/lang/Class;J)Ljava/lang/invoke/CallSite; -Call site #0: - link_argument[0] : 0 (MethodHandle) - link_argument[1] : add (String) - link_argument[2] : (II)I (MethodType) - link_argument[3] : 1 (int) - link_argument[4] : 1 (int) - link_argument[5] : 97 (int) - link_argument[6] : 1024 (int) - link_argument[7] : 1 (int) - link_argument[8] : 11.1 (float) - link_argument[9] : 2.2 (double) - link_argument[10] : Hello (String) - link_argument[11] : Tests (Class) - link_argument[12] : 123456789 (long) -Call site #1: - link_argument[0] : 0 (MethodHandle) - link_argument[1] : add (String) - link_argument[2] : (II)I (MethodType) + target : Linvokecustom/InvokeCustom; bsmCreateCallSite + target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; +Method handle #5: + type : invoke-static + target : Linvokecustom/InvokeCustom; bsmLookupStatic + target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; +Method handle #6: + type : invoke-static + target : Linvokecustom/InvokeCustom; bsmLookupStaticWithExtraArgs + target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IJFD)Ljava/lang/invoke/CallSite; +Method handle #7: + type : invoke-static + target : Linvokecustom/InvokeCustom; bsmLookupTest9 + target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; +Method handle #8: + type : invoke-static + target : Linvokecustom/InvokeCustom; lambda$lambdaTest$0 + target_type : (Ljava/lang/String;)Z +Method handle #9: + type : invoke-static + target : Ljava/lang/invoke/LambdaMetafactory; metafactory + target_type : (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; +Method handle #10: + type : invoke-instance + target : Linvokecustom/InvokeCustom; helperMethodTest9 + target_type : (Linvokecustom/InvokeCustom;)V +Method handle #11: + type : invoke-instance + target : Ljava/io/PrintStream; println + target_type : (Ljava/io/PrintStream;Ljava/lang/String;)V +Method handle #12: + type : invoke-instance + target : Ljava/lang/String; trim + target_type : (Ljava/lang/String;)Ljava/lang/String; +Method handle #13: + type : invoke-constructor + target : Linvokecustom/InvokeCustom; <init> + target_type : (Linvokecustom/InvokeCustom;I)V +Method handle #14: + type : invoke-direct + target : Linvokecustom/Super; targetMethodTest4 + target_type : (Linvokecustom/Super;)V +Method handle #15: + type : invoke-interface + target : Ljava/lang/Runnable; run + target_type : (Ljava/lang/Runnable;)V +Call site #0: // offset 8450 + link_argument[0] : 9 (MethodHandle) + link_argument[1] : test (String) + link_argument[2] : ()Ljava/util/function/Predicate; (MethodType) + link_argument[3] : (Ljava/lang/Object;)Z (MethodType) + link_argument[4] : 8 (MethodHandle) + link_argument[5] : (Ljava/lang/String;)Z (MethodType) +Call site #1: // offset 8463 + link_argument[0] : 9 (MethodHandle) + link_argument[1] : apply (String) + link_argument[2] : ()Ljava/util/function/Function; (MethodType) + link_argument[3] : (Ljava/lang/Object;)Ljava/lang/Object; (MethodType) + link_argument[4] : 12 (MethodHandle) + link_argument[5] : (Ljava/lang/String;)Ljava/lang/String; (MethodType) +Call site #2: // offset 8476 + link_argument[0] : 9 (MethodHandle) + link_argument[1] : accept (String) + link_argument[2] : (Ljava/io/PrintStream;)Ljava/util/function/Consumer; (MethodType) + link_argument[3] : (Ljava/lang/Object;)V (MethodType) + link_argument[4] : 11 (MethodHandle) + link_argument[5] : (Ljava/lang/String;)V (MethodType) +Call site #3: // offset 8489 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest1 (String) + link_argument[2] : ()V (MethodType) +Call site #4: // offset 8496 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest2 (String) + link_argument[2] : (ZBCSIFJDLjava/lang/String;)V (MethodType) +Call site #5: // offset 8503 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest5 (String) + link_argument[2] : (III)I (MethodType) +Call site #6: // offset 8510 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest6 (String) + link_argument[2] : (JJJ)J (MethodType) +Call site #7: // offset 8517 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest7 (String) + link_argument[2] : (FFD)D (MethodType) +Call site #8: // offset 8524 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest8 (String) + link_argument[2] : (Ljava/lang/String;)V (MethodType) +Call site #9: // offset 8524 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest8 (String) + link_argument[2] : (Ljava/lang/String;)V (MethodType) +Call site #10: // offset 8524 + link_argument[0] : 5 (MethodHandle) + link_argument[1] : targetMethodTest8 (String) + link_argument[2] : (Ljava/lang/String;)V (MethodType) +Call site #11: // offset 8531 + link_argument[0] : 6 (MethodHandle) + link_argument[1] : targetMethodTest3 (String) + link_argument[2] : ()V (MethodType) link_argument[3] : 1 (int) - link_argument[4] : 1 (int) - link_argument[5] : 97 (int) - link_argument[6] : 1024 (int) - link_argument[7] : 1 (int) - link_argument[8] : 11.1 (float) - link_argument[9] : 2.2 (double) - link_argument[10] : Hello (String) - link_argument[11] : Tests (Class) - link_argument[12] : 123456789 (long) + link_argument[4] : 123456789 (long) + link_argument[5] : 123.456 (float) + link_argument[6] : 123457 (double) +Call site #12: // offset 8559 + link_argument[0] : 4 (MethodHandle) + link_argument[1] : targetMethodTest4 (String) + link_argument[2] : (Linvokecustom/InvokeCustom;)V (MethodType) + link_argument[3] : 14 (MethodHandle) +Call site #13: // offset 8568 + link_argument[0] : 7 (MethodHandle) + link_argument[1] : targetMethodTest9 (String) + link_argument[2] : ()V (MethodType) + link_argument[3] : 1 (MethodHandle) + link_argument[4] : 0 (MethodHandle) + link_argument[5] : 3 (MethodHandle) + link_argument[6] : 2 (MethodHandle) + link_argument[7] : 10 (MethodHandle) + link_argument[8] : 13 (MethodHandle) + link_argument[9] : 15 (MethodHandle) diff --git a/test/dexdump/invoke-custom.xml b/test/dexdump/invoke-custom.xml index 2a2966738a..8b22a9d9ee 100644 --- a/test/dexdump/invoke-custom.xml +++ b/test/dexdump/invoke-custom.xml @@ -1,30 +1,130 @@ <api> -<package name="com.android.jack.java7.invokecustom.test004" +<package name="invokecustom" > -<class name="Tests" - extends="java.lang.Object" +<class name="InvokeCustom" + extends="invokecustom.Super" interface="false" abstract="false" static="false" final="false" visibility="public" > -<field name="fieldCallSite" - type="java.lang.invoke.CallSite" - transient="false" - volatile="false" - static="true" +<implements name="java.lang.Runnable"> +</implements> +<constructor name="InvokeCustom" + type="invokecustom.InvokeCustom" + static="false" final="false" visibility="public" > -</field> -<constructor name="Tests" - type="com.android.jack.java7.invokecustom.test004.Tests" +</constructor> +<constructor name="InvokeCustom" + type="invokecustom.InvokeCustom" static="false" final="false" visibility="public" > +<parameter name="arg0" type="int"> +</parameter> </constructor> +<method name="bsmCreateCallSite" + return="java.lang.invoke.CallSite" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="java.lang.invoke.MethodHandles.Lookup"> +</parameter> +<parameter name="arg1" type="java.lang.String"> +</parameter> +<parameter name="arg2" type="java.lang.invoke.MethodType"> +</parameter> +<parameter name="arg3" type="java.lang.invoke.MethodHandle"> +</parameter> +</method> +<method name="bsmLookupStatic" + return="java.lang.invoke.CallSite" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="java.lang.invoke.MethodHandles.Lookup"> +</parameter> +<parameter name="arg1" type="java.lang.String"> +</parameter> +<parameter name="arg2" type="java.lang.invoke.MethodType"> +</parameter> +</method> +<method name="bsmLookupStaticWithExtraArgs" + return="java.lang.invoke.CallSite" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="java.lang.invoke.MethodHandles.Lookup"> +</parameter> +<parameter name="arg1" type="java.lang.String"> +</parameter> +<parameter name="arg2" type="java.lang.invoke.MethodType"> +</parameter> +<parameter name="arg3" type="int"> +</parameter> +<parameter name="arg4" type="long"> +</parameter> +<parameter name="arg5" type="float"> +</parameter> +<parameter name="arg6" type="double"> +</parameter> +</method> +<method name="bsmLookupTest9" + return="java.lang.invoke.CallSite" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="java.lang.invoke.MethodHandles.Lookup"> +</parameter> +<parameter name="arg1" type="java.lang.String"> +</parameter> +<parameter name="arg2" type="java.lang.invoke.MethodType"> +</parameter> +<parameter name="arg3" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg4" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg5" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg6" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg7" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg8" type="java.lang.invoke.MethodHandle"> +</parameter> +<parameter name="arg9" type="java.lang.invoke.MethodHandle"> +</parameter> +</method> +<method name="lambdaTest" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> <method name="main" return="void" abstract="false" @@ -37,7 +137,177 @@ <parameter name="arg0" type="java.lang.String[]"> </parameter> </method> -<method name="test" +<method name="targetMethodTest5" + return="int" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="int"> +</parameter> +<parameter name="arg1" type="int"> +</parameter> +<parameter name="arg2" type="int"> +</parameter> +</method> +<method name="targetMethodTest6" + return="long" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="long"> +</parameter> +<parameter name="arg1" type="long"> +</parameter> +<parameter name="arg2" type="long"> +</parameter> +</method> +<method name="targetMethodTest7" + return="double" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="float"> +</parameter> +<parameter name="arg1" type="float"> +</parameter> +<parameter name="arg2" type="double"> +</parameter> +</method> +<method name="targetMethodTest8" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +<parameter name="arg0" type="java.lang.String"> +</parameter> +</method> +<method name="test1" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test2" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test3" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test4" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test5" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test6" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test7" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test8" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="test9" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + visibility="public" +> +</method> +<method name="helperMethodTest9" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + visibility="public" +> +</method> +<method name="run" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + visibility="public" +> +</method> +<method name="targetMethodTest4" return="void" abstract="false" native="false" @@ -49,41 +319,207 @@ </method> </class> <method_handle index="0" + type="put-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="staticFieldTest9" + target_member_type="I" +> +</method_handle> +<method_handle index="1" + type="get-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="staticFieldTest9" + target_member_type="I" +> +</method_handle> +<method_handle index="2" + type="put-instance" + target_class="Linvokecustom/InvokeCustom;" + target_member="fieldTest9" + target_member_type="(Linvokecustom/InvokeCustom;" +> +</method_handle> +<method_handle index="3" + type="get-instance" + target_class="Linvokecustom/InvokeCustom;" + target_member="fieldTest9" + target_member_type="(Linvokecustom/InvokeCustom;" +> +</method_handle> +<method_handle index="4" type="invoke-static" - target_class="Lcom/android/jack/java7/invokecustom/test004/Tests;" - target_member="linkerMethod" - target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;ZBCSIFDLjava/lang/String;Ljava/lang/Class;J)Ljava/lang/invoke/CallSite;" + target_class="Linvokecustom/InvokeCustom;" + target_member="bsmCreateCallSite" + target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;" > </method_handle> -<call_site index="0"> -<link_argument index="0" type="MethodHandle" value="0"/> -<link_argument index="1" type="String" values="add"/> -<link_argument index="2" type="MethodType" value="(II)I"/> -<link_argument index="3" type="int" value="1"/> -<link_argument index="4" type="int" value="1"/> -<link_argument index="5" type="int" value="97"/> -<link_argument index="6" type="int" value="1024"/> -<link_argument index="7" type="int" value="1"/> -<link_argument index="8" type="float" value="11.1"/> -<link_argument index="9" type="double" value="2.2"/> -<link_argument index="10" type="String" value="Hello"/> -<link_argument index="11" type="Class" value="Tests"/> -<link_argument index="12" type="long" value="123456789"/> +<method_handle index="5" + type="invoke-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="bsmLookupStatic" + target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" +> +</method_handle> +<method_handle index="6" + type="invoke-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="bsmLookupStaticWithExtraArgs" + target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IJFD)Ljava/lang/invoke/CallSite;" +> +</method_handle> +<method_handle index="7" + type="invoke-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="bsmLookupTest9" + target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;" +> +</method_handle> +<method_handle index="8" + type="invoke-static" + target_class="Linvokecustom/InvokeCustom;" + target_member="lambda$lambdaTest$0" + target_member_type="(Ljava/lang/String;)Z" +> +</method_handle> +<method_handle index="9" + type="invoke-static" + target_class="Ljava/lang/invoke/LambdaMetafactory;" + target_member="metafactory" + target_member_type="(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" +> +</method_handle> +<method_handle index="10" + type="invoke-instance" + target_class="Linvokecustom/InvokeCustom;" + target_member="helperMethodTest9" + target_member_type="(Linvokecustom/InvokeCustom;)V" +> +</method_handle> +<method_handle index="11" + type="invoke-instance" + target_class="Ljava/io/PrintStream;" + target_member="println" + target_member_type="(Ljava/io/PrintStream;Ljava/lang/String;)V" +> +</method_handle> +<method_handle index="12" + type="invoke-instance" + target_class="Ljava/lang/String;" + target_member="trim" + target_member_type="(Ljava/lang/String;)Ljava/lang/String;" +> +</method_handle> +<method_handle index="13" + type="invoke-constructor" + target_class="Linvokecustom/InvokeCustom;" + target_member="<init>" + target_member_type="(Linvokecustom/InvokeCustom;I)V" +> +</method_handle> +<method_handle index="14" + type="invoke-direct" + target_class="Linvokecustom/Super;" + target_member="targetMethodTest4" + target_member_type="(Linvokecustom/Super;)V" +> +</method_handle> +<method_handle index="15" + type="invoke-interface" + target_class="Ljava/lang/Runnable;" + target_member="run" + target_member_type="(Ljava/lang/Runnable;)V" +> +</method_handle> +<call_site index="0" offset="8450"> +<link_argument index="0" type="MethodHandle" value="9"/> +<link_argument index="1" type="String" values="test"/> +<link_argument index="2" type="MethodType" value="()Ljava/util/function/Predicate;"/> +<link_argument index="3" type="MethodType" value="(Ljava/lang/Object;)Z"/> +<link_argument index="4" type="MethodHandle" value="8"/> +<link_argument index="5" type="MethodType" value="(Ljava/lang/String;)Z"/> +</call_site> +<call_site index="1" offset="8463"> +<link_argument index="0" type="MethodHandle" value="9"/> +<link_argument index="1" type="String" values="apply"/> +<link_argument index="2" type="MethodType" value="()Ljava/util/function/Function;"/> +<link_argument index="3" type="MethodType" value="(Ljava/lang/Object;)Ljava/lang/Object;"/> +<link_argument index="4" type="MethodHandle" value="12"/> +<link_argument index="5" type="MethodType" value="(Ljava/lang/String;)Ljava/lang/String;"/> +</call_site> +<call_site index="2" offset="8476"> +<link_argument index="0" type="MethodHandle" value="9"/> +<link_argument index="1" type="String" values="accept"/> +<link_argument index="2" type="MethodType" value="(Ljava/io/PrintStream;)Ljava/util/function/Consumer;"/> +<link_argument index="3" type="MethodType" value="(Ljava/lang/Object;)V"/> +<link_argument index="4" type="MethodHandle" value="11"/> +<link_argument index="5" type="MethodType" value="(Ljava/lang/String;)V"/> +</call_site> +<call_site index="3" offset="8489"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest1"/> +<link_argument index="2" type="MethodType" value="()V"/> +</call_site> +<call_site index="4" offset="8496"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest2"/> +<link_argument index="2" type="MethodType" value="(ZBCSIFJDLjava/lang/String;)V"/> </call_site> -<call_site index="1"> -<link_argument index="0" type="MethodHandle" value="0"/> -<link_argument index="1" type="String" values="add"/> -<link_argument index="2" type="MethodType" value="(II)I"/> +<call_site index="5" offset="8503"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest5"/> +<link_argument index="2" type="MethodType" value="(III)I"/> +</call_site> +<call_site index="6" offset="8510"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest6"/> +<link_argument index="2" type="MethodType" value="(JJJ)J"/> +</call_site> +<call_site index="7" offset="8517"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest7"/> +<link_argument index="2" type="MethodType" value="(FFD)D"/> +</call_site> +<call_site index="8" offset="8524"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest8"/> +<link_argument index="2" type="MethodType" value="(Ljava/lang/String;)V"/> +</call_site> +<call_site index="9" offset="8524"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest8"/> +<link_argument index="2" type="MethodType" value="(Ljava/lang/String;)V"/> +</call_site> +<call_site index="10" offset="8524"> +<link_argument index="0" type="MethodHandle" value="5"/> +<link_argument index="1" type="String" values="targetMethodTest8"/> +<link_argument index="2" type="MethodType" value="(Ljava/lang/String;)V"/> +</call_site> +<call_site index="11" offset="8531"> +<link_argument index="0" type="MethodHandle" value="6"/> +<link_argument index="1" type="String" values="targetMethodTest3"/> +<link_argument index="2" type="MethodType" value="()V"/> <link_argument index="3" type="int" value="1"/> -<link_argument index="4" type="int" value="1"/> -<link_argument index="5" type="int" value="97"/> -<link_argument index="6" type="int" value="1024"/> -<link_argument index="7" type="int" value="1"/> -<link_argument index="8" type="float" value="11.1"/> -<link_argument index="9" type="double" value="2.2"/> -<link_argument index="10" type="String" value="Hello"/> -<link_argument index="11" type="Class" value="Tests"/> -<link_argument index="12" type="long" value="123456789"/> +<link_argument index="4" type="long" value="123456789"/> +<link_argument index="5" type="float" value="123.456"/> +<link_argument index="6" type="double" value="123457"/> +</call_site> +<call_site index="12" offset="8559"> +<link_argument index="0" type="MethodHandle" value="4"/> +<link_argument index="1" type="String" values="targetMethodTest4"/> +<link_argument index="2" type="MethodType" value="(Linvokecustom/InvokeCustom;)V"/> +<link_argument index="3" type="MethodHandle" value="14"/> +</call_site> +<call_site index="13" offset="8568"> +<link_argument index="0" type="MethodHandle" value="7"/> +<link_argument index="1" type="String" values="targetMethodTest9"/> +<link_argument index="2" type="MethodType" value="()V"/> +<link_argument index="3" type="MethodHandle" value="1"/> +<link_argument index="4" type="MethodHandle" value="0"/> +<link_argument index="5" type="MethodHandle" value="3"/> +<link_argument index="6" type="MethodHandle" value="2"/> +<link_argument index="7" type="MethodHandle" value="10"/> +<link_argument index="8" type="MethodHandle" value="13"/> +<link_argument index="9" type="MethodHandle" value="15"/> </call_site> </package> </api> diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index d24edccd8b..ede485a81b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n" TEST_IS_NDEBUG="n" APP_IMAGE="y" JVMTI_STRESS="n" +JVMTI_STEP_STRESS="n" JVMTI_FIELD_STRESS="n" JVMTI_TRACE_STRESS="n" JVMTI_REDEFINE_STRESS="n" @@ -163,6 +164,10 @@ while true; do JVMTI_STRESS="y" JVMTI_REDEFINE_STRESS="y" shift + elif [ "x$1" = "x--jvmti-step-stress" ]; then + JVMTI_STRESS="y" + JVMTI_STEP_STRESS="y" + shift elif [ "x$1" = "x--jvmti-field-stress" ]; then JVMTI_STRESS="y" JVMTI_FIELD_STRESS="y" @@ -426,6 +431,9 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then agent_args="${agent_args},field" fi + if [[ "$JVMTI_STEP_STRESS" = "y" ]]; then + agent_args="${agent_args},step" + fi if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then agent_args="${agent_args},trace" fi diff --git a/test/knownfailures.json b/test/knownfailures.json index db993b7f70..3939bd162f 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -502,7 +502,7 @@ "645-checker-abs-simd", "706-checker-scheduler"], "description": ["Checker tests are not compatible with jvmti."], - "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress" }, { "tests": [ @@ -510,7 +510,7 @@ "964-default-iface-init-gen" ], "description": ["Tests that just take too long with jvmti-stress"], - "variant": "jvmti-stress | redefine-stress | trace-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | step-stress" }, { "tests": [ @@ -538,10 +538,11 @@ "595-profile-saving", "900-hello-plugin", "909-attach-agent", - "981-dedup-original-dex" + "981-dedup-original-dex", + "1900-track-alloc" ], "description": ["Tests that require exact knowledge of the number of plugins and agents."], - "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress" }, { "tests": [ @@ -579,7 +580,7 @@ "004-ThreadStress" ], "description": "The thread stress test just takes too long with field-stress", - "variant": "jvmti-stress | field-stress" + "variant": "jvmti-stress | field-stress | step-stress" }, { "tests": [ @@ -594,25 +595,12 @@ }, { "tests": [ - "953-invoke-polymorphic-compiler" - ], - "description": "Test throws VerifyError when run with --build-with-javac-dx.", - "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/62722425" - }, - { - "tests": [ - "458-checker-instruct-simplification", - "536-checker-intrinsic-optimization", - "565-checker-doublenegbitwise", "567-checker-compare", - "586-checker-null-array-get", - "593-checker-boolean-2-integral-conv", - "633-checker-rtp-getclass" + "988-method-trace" ], - "description": "Checker tests failing when run with --build-with-javac-dx.", + "description": "Checker tests fail because desugar lowers Long.compare to lcmp", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/62950048" + "bug": "b/63078894" }, { "tests": [ @@ -659,6 +647,11 @@ "env_vars": {"SANITIZE_HOST": "address"} }, { + "tests": "141-class-unload", + "description": "Idk why this fails", + "env_vars": {"SANITIZE_HOST": "address"} + }, + { "tests": ["988-method-trace"], "variant": "redefine-stress | jvmti-stress", "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly." @@ -679,5 +672,12 @@ "description": [ "Flake on gcstress" ], "bug": "b/62562923", "variant": "gcstress & jit & target" + }, + { + "tests": ["004-JniTest"], + "description": [ "Tests failing with --build-with-javac-dx since the new annotation", + "lookup changes" ], + "bug": "b/63089991", + "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"} } ] diff --git a/test/run-test b/test/run-test index 9fe149697a..486b465a89 100755 --- a/test/run-test +++ b/test/run-test @@ -145,6 +145,7 @@ gc_verify="false" gc_stress="false" jvmti_trace_stress="false" jvmti_field_stress="false" +jvmti_step_stress="false" jvmti_redefine_stress="false" strace="false" always_clean="no" @@ -242,6 +243,9 @@ while true; do basic_verify="true" gc_stress="true" shift + elif [ "x$1" = "x--jvmti-step-stress" ]; then + jvmti_step_stress="true" + shift elif [ "x$1" = "x--jvmti-redefine-stress" ]; then jvmti_redefine_stress="true" shift @@ -464,6 +468,9 @@ fi if [ "$jvmti_redefine_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-redefine-stress" fi +if [ "$jvmti_step_stress" = "true" ]; then + run_args="${run_args} --no-app-image --jvmti-step-stress" +fi if [ "$jvmti_field_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-field-stress" fi @@ -679,6 +686,7 @@ if [ "$usage" = "yes" ]; then echo " --gcstress Run with gc stress testing" echo " --gcverify Run with gc verification" echo " --jvmti-trace-stress Run with jvmti method tracing stress testing" + echo " --jvmti-step-stress Run with jvmti single step stress testing" echo " --jvmti-redefine-stress" echo " Run with jvmti method redefinition stress testing" echo " --always-clean Delete the test files even if the test fails." diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index b6a5963cf4..68e1856adb 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -148,7 +148,7 @@ def gather_test_info(): VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress', - 'field-stress'} + 'field-stress', 'step-stress'} VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing', 'regalloc_gc', 'speed-profile'} @@ -445,6 +445,8 @@ def run_tests(tests): options_test += ' --jvmti-trace-stress' elif jvmti == 'redefine-stress': options_test += ' --jvmti-redefine-stress' + elif jvmti == 'step-stress': + options_test += ' --jvmti-step-stress' if address_size == '64': options_test += ' --64' @@ -965,6 +967,8 @@ def parse_option(): JVMTI_TYPES.add('redefine-stress') if options['field_stress']: JVMTI_TYPES.add('field-stress') + if options['step_stress']: + JVMTI_TYPES.add('step-stress') if options['trace_stress']: JVMTI_TYPES.add('trace-stress') if options['no_jvmti']: diff --git a/test/ti-agent/breakpoint_helper.cc b/test/ti-agent/breakpoint_helper.cc new file mode 100644 index 0000000000..78aab4376f --- /dev/null +++ b/test/ti-agent/breakpoint_helper.cc @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { + +namespace common_breakpoint { + +struct BreakpointData { + jclass test_klass; + jmethodID breakpoint_method; + bool in_callback; + bool allow_recursive; +}; + +extern "C" void breakpointCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thread, + jmethodID method, + jlocation location) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback && !data->allow_recursive) { + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->breakpoint_method, + thread, + method_arg, + static_cast<jlong>(location)); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Breakpoint_getLineNumberTableNative( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return nullptr; + } + jint nlines; + jvmtiLineNumberEntry* lines = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetLineNumberTable(method, &nlines, &lines))) { + return nullptr; + } + jintArray lines_array = env->NewIntArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jlongArray locs_array = env->NewLongArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + ScopedLocalRef<jclass> object_class(env, env->FindClass("java/lang/Object")); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jobjectArray ret = env->NewObjectArray(2, object_class.get(), nullptr); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jint* temp_lines = env->GetIntArrayElements(lines_array, /*isCopy*/nullptr); + jlong* temp_locs = env->GetLongArrayElements(locs_array, /*isCopy*/nullptr); + for (jint i = 0; i < nlines; i++) { + temp_lines[i] = lines[i].line_number; + temp_locs[i] = lines[i].start_location; + } + env->ReleaseIntArrayElements(lines_array, temp_lines, 0); + env->ReleaseLongArrayElements(locs_array, temp_locs, 0); + env->SetObjectArrayElement(ret, 0, locs_array); + env->SetObjectArrayElement(ret, 1, lines_array); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return ret; +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Breakpoint_getStartLocation(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return 0; + } + jlong start = 0; + jlong end = end; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end)); + return start; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_clearBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->ClearBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_setBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jclass method_klass, + jobject method, + jboolean allow_recursive, + jthread thr) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(BreakpointData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(BreakpointData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass)); + data->breakpoint_method = env->FromReflectedMethod(method); + data->in_callback = false; + data->allow_recursive = allow_recursive; + + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.Breakpoint = breakpointCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_stopBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jthread thr) { + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +} // namespace common_breakpoint + +} // namespace art diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 4fe58db169..e57a493fe4 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -16,76 +16,18 @@ #include "common_helper.h" -#include <dlfcn.h> -#include <map> -#include <stdio.h> #include <sstream> -#include <deque> -#include <vector> +#include <string> #include "android-base/stringprintf.h" #include "jni.h" #include "jvmti.h" -#include "jni_binder.h" #include "jvmti_helper.h" -#include "scoped_local_ref.h" -#include "test_env.h" namespace art { -static void SetupCommonRetransform(); -static void SetupCommonRedefine(); -static void SetupCommonTransform(); - -template <bool is_redefine> -static void throwCommonRedefinitionError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* target, - jvmtiError res) { - std::stringstream err; - char* error = nullptr; - jvmti->GetErrorName(res, &error); - err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class"; - if (num_targets > 1) { - err << "es"; - } - err << " <"; - for (jint i = 0; i < num_targets; i++) { - char* signature = nullptr; - char* generic = nullptr; - jvmti->GetClassSignature(target[i], &signature, &generic); - if (i != 0) { - err << ", "; - } - err << signature; - jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); - } - err << "> due to " << error; - std::string message = err.str(); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); - env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); -} - -namespace common_trace { - -// Taken from art/runtime/modifiers.h -static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic - -struct TraceData { - jclass test_klass; - jmethodID enter_method; - jmethodID exit_method; - jmethodID field_access; - jmethodID field_modify; - bool in_callback; - bool access_watch_on_load; - bool modify_watch_on_load; -}; - -static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { +jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { return nullptr; @@ -95,7 +37,7 @@ static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jf return env->ToReflectedField(field_klass, f, is_static); } -static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { +jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) { return nullptr; @@ -111,7 +53,7 @@ static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { return res; } -static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { +jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { std::string name; switch (type) { case 'V': @@ -159,10 +101,7 @@ static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { return res; } -static jobject GetJavaValue(jvmtiEnv* jvmtienv, - JNIEnv* env, - jmethodID m, - jvalue value) { +jobject GetJavaValue(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m, jvalue value) { char *fname, *fsig, *fgen; if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { return nullptr; @@ -175,745 +114,4 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return GetJavaValueByType(env, type[0], value); } -static void fieldAccessCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thr ATTRIBUTE_UNUSED, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field) { - TraceData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - if (data->in_callback) { - // Don't do callback for either of these to prevent an infinite loop. - return; - } - CHECK(data->field_access != nullptr); - data->in_callback = true; - jobject method_arg = GetJavaMethod(jvmti, jnienv, method); - jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); - jnienv->CallStaticVoidMethod(data->test_klass, - data->field_access, - method_arg, - static_cast<jlong>(location), - field_klass, - object, - field_arg); - jnienv->DeleteLocalRef(method_arg); - jnienv->DeleteLocalRef(field_arg); - data->in_callback = false; -} - -static void fieldModificationCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thr ATTRIBUTE_UNUSED, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field, - char type_char, - jvalue new_value) { - TraceData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - if (data->in_callback) { - // Don't do callback recursively to prevent an infinite loop. - return; - } - CHECK(data->field_modify != nullptr); - data->in_callback = true; - jobject method_arg = GetJavaMethod(jvmti, jnienv, method); - jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); - jobject value = GetJavaValueByType(jnienv, type_char, new_value); - if (jnienv->ExceptionCheck()) { - data->in_callback = false; - jnienv->DeleteLocalRef(method_arg); - jnienv->DeleteLocalRef(field_arg); - return; - } - jnienv->CallStaticVoidMethod(data->test_klass, - data->field_modify, - method_arg, - static_cast<jlong>(location), - field_klass, - object, - field_arg, - value); - jnienv->DeleteLocalRef(method_arg); - jnienv->DeleteLocalRef(field_arg); - data->in_callback = false; -} - -static void methodExitCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thr ATTRIBUTE_UNUSED, - jmethodID method, - jboolean was_popped_by_exception, - jvalue return_value) { - TraceData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - if (method == data->exit_method || method == data->enter_method || data->in_callback) { - // Don't do callback for either of these to prevent an infinite loop. - return; - } - CHECK(data->exit_method != nullptr); - data->in_callback = true; - jobject method_arg = GetJavaMethod(jvmti, jnienv, method); - jobject result = - was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value); - if (jnienv->ExceptionCheck()) { - data->in_callback = false; - return; - } - jnienv->CallStaticVoidMethod(data->test_klass, - data->exit_method, - method_arg, - was_popped_by_exception, - result); - jnienv->DeleteLocalRef(method_arg); - data->in_callback = false; -} - -static void methodEntryCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thr ATTRIBUTE_UNUSED, - jmethodID method) { - TraceData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - CHECK(data->enter_method != nullptr); - if (method == data->exit_method || method == data->enter_method || data->in_callback) { - // Don't do callback for either of these to prevent an infinite loop. - return; - } - data->in_callback = true; - jobject method_arg = GetJavaMethod(jvmti, jnienv, method); - if (jnienv->ExceptionCheck()) { - return; - } - jnienv->CallStaticVoidMethod(data->test_klass, data->enter_method, method_arg); - jnienv->DeleteLocalRef(method_arg); - data->in_callback = false; -} - -static void classPrepareCB(jvmtiEnv* jvmti, - JNIEnv* jnienv, - jthread thr ATTRIBUTE_UNUSED, - jclass klass) { - TraceData* data = nullptr; - if (JvmtiErrorToException(jnienv, jvmti, - jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - if (data->access_watch_on_load || data->modify_watch_on_load) { - jint nfields; - jfieldID* fields; - if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) { - return; - } - for (jint i = 0; i < nfields; i++) { - jfieldID f = fields[i]; - // Ignore errors - if (data->access_watch_on_load) { - jvmti->SetFieldAccessWatch(klass, f); - } - - if (data->modify_watch_on_load) { - jvmti->SetFieldModificationWatch(klass, f); - } - } - jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); - } -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) { - TraceData* data = nullptr; - if (JvmtiErrorToException( - env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - data->access_watch_on_load = true; - // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. - if (JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_CLASS_PREPARE, - nullptr))) { - return; - } - jint nklasses; - jclass* klasses; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { - return; - } - for (jint i = 0; i < nklasses; i++) { - jclass k = klasses[i]; - - jint nfields; - jfieldID* fields; - jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); - if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { - continue; - } else if (JvmtiErrorToException(env, jvmti_env, err)) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); - return; - } - for (jint j = 0; j < nfields; j++) { - jvmti_env->SetFieldAccessWatch(k, fields[j]); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) { - TraceData* data = nullptr; - if (JvmtiErrorToException( - env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { - return; - } - data->modify_watch_on_load = true; - // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. - if (JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_CLASS_PREPARE, - nullptr))) { - return; - } - jint nklasses; - jclass* klasses; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { - return; - } - for (jint i = 0; i < nklasses; i++) { - jclass k = klasses[i]; - - jint nfields; - jfieldID* fields; - jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); - if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { - continue; - } else if (JvmtiErrorToException(env, jvmti_env, err)) { - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); - return; - } - for (jint j = 0; j < nfields; j++) { - jvmti_env->SetFieldModificationWatch(k, fields[j]); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); - } - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); -} - -static bool GetFieldAndClass(JNIEnv* env, - jobject ref_field, - jclass* out_klass, - jfieldID* out_field) { - *out_field = env->FromReflectedField(ref_field); - if (env->ExceptionCheck()) { - return false; - } - jclass field_klass = env->FindClass("java/lang/reflect/Field"); - if (env->ExceptionCheck()) { - return false; - } - jmethodID get_declaring_class_method = - env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;"); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(field_klass); - return false; - } - *out_klass = static_cast<jclass>(env->CallObjectMethod(ref_field, get_declaring_class_method)); - if (env->ExceptionCheck()) { - *out_klass = nullptr; - env->DeleteLocalRef(field_klass); - return false; - } - env->DeleteLocalRef(field_klass); - return true; -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification( - JNIEnv* env, - jclass trace ATTRIBUTE_UNUSED, - jobject field_obj) { - jfieldID field; - jclass klass; - if (!GetFieldAndClass(env, field_obj, &klass, &field)) { - return; - } - - JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field)); - env->DeleteLocalRef(klass); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess( - JNIEnv* env, - jclass trace ATTRIBUTE_UNUSED, - jobject field_obj) { - jfieldID field; - jclass klass; - if (!GetFieldAndClass(env, field_obj, &klass, &field)) { - return; - } - JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field)); - env->DeleteLocalRef(klass); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( - JNIEnv* env, - jclass trace ATTRIBUTE_UNUSED, - jclass klass, - jobject enter, - jobject exit, - jobject field_access, - jobject field_modify, - jthread thr) { - TraceData* data = nullptr; - if (JvmtiErrorToException(env, - jvmti_env, - jvmti_env->Allocate(sizeof(TraceData), - reinterpret_cast<unsigned char**>(&data)))) { - return; - } - memset(data, 0, sizeof(TraceData)); - data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); - data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr; - data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; - data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; - data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr; - data->in_callback = false; - - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { - return; - } - - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.MethodEntry = methodEntryCB; - cb.MethodExit = methodExitCB; - cb.FieldAccess = fieldAccessCB; - cb.FieldModification = fieldModificationCB; - cb.ClassPrepare = classPrepareCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { - return; - } - if (enter != nullptr && - JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_METHOD_ENTRY, - thr))) { - return; - } - if (exit != nullptr && - JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_METHOD_EXIT, - thr))) { - return; - } - if (field_access != nullptr && - JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_FIELD_ACCESS, - thr))) { - return; - } - if (field_modify != nullptr && - JvmtiErrorToException(env, - jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_FIELD_MODIFICATION, - thr))) { - return; - } -} - -extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( - JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) { - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, - JVMTI_EVENT_FIELD_ACCESS, - thr))) { - return; - } - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, - JVMTI_EVENT_FIELD_MODIFICATION, - thr))) { - return; - } - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, - JVMTI_EVENT_METHOD_ENTRY, - thr))) { - return; - } - if (JvmtiErrorToException(env, jvmti_env, - jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, - JVMTI_EVENT_METHOD_EXIT, - thr))) { - return; - } -} - -} // namespace common_trace - -namespace common_redefine { - -static void throwRedefinitionError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* target, - jvmtiError res) { - return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); -} - -static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, - JNIEnv* env, - jint num_redefines, - jclass* targets, - jbyteArray* class_file_bytes, - jbyteArray* dex_file_bytes) { - std::vector<jvmtiClassDefinition> defs; - for (jint i = 0; i < num_redefines; i++) { - jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; - jint len = static_cast<jint>(env->GetArrayLength(desired_array)); - const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( - env->GetByteArrayElements(desired_array, nullptr)); - defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); - } - jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); - if (res != JVMTI_ERROR_NONE) { - throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); - } -} - -static void DoClassRedefine(jvmtiEnv* jvmti_env, - JNIEnv* env, - jclass target, - jbyteArray class_file_bytes, - jbyteArray dex_file_bytes) { - return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); -} - -// Magic JNI export that classes can use for redefining classes. -// To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition( - JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { - DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); -} - -// Magic JNI export that classes can use for redefining classes. -// To use classes should declare this as a native function with signature -// ([Ljava/lang/Class;[[B[[B)V -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefinition( - JNIEnv* env, - jclass, - jobjectArray targets, - jobjectArray class_file_bytes, - jobjectArray dex_file_bytes) { - std::vector<jclass> classes; - std::vector<jbyteArray> class_files; - std::vector<jbyteArray> dex_files; - jint len = env->GetArrayLength(targets); - if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) { - env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), - "the three array arguments passed to this function have different lengths!"); - return; - } - for (jint i = 0; i < len; i++) { - classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); - dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); - class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); - } - return DoMultiClassRedefine(jvmti_env, - env, - len, - classes.data(), - class_files.data(), - dex_files.data()); -} - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonRedefine(); - return 0; -} - -} // namespace common_redefine - -namespace common_retransform { - -struct CommonTransformationResult { - std::vector<unsigned char> class_bytes; - std::vector<unsigned char> dex_bytes; - - CommonTransformationResult(size_t class_size, size_t dex_size) - : class_bytes(class_size), dex_bytes(dex_size) {} - - CommonTransformationResult() = default; - CommonTransformationResult(CommonTransformationResult&&) = default; - CommonTransformationResult(CommonTransformationResult&) = default; -}; - -// Map from class name to transformation result. -std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; -bool gPopTransformations = true; - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_addCommonTransformationResult( - JNIEnv* env, jclass, jstring class_name, jbyteArray class_array, jbyteArray dex_array) { - const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); - std::string name_str(name_chrs); - env->ReleaseStringUTFChars(class_name, name_chrs); - CommonTransformationResult trans(env->GetArrayLength(class_array), - env->GetArrayLength(dex_array)); - if (env->ExceptionOccurred()) { - return; - } - env->GetByteArrayRegion(class_array, - 0, - env->GetArrayLength(class_array), - reinterpret_cast<jbyte*>(trans.class_bytes.data())); - if (env->ExceptionOccurred()) { - return; - } - env->GetByteArrayRegion(dex_array, - 0, - env->GetArrayLength(dex_array), - reinterpret_cast<jbyte*>(trans.dex_bytes.data())); - if (env->ExceptionOccurred()) { - return; - } - if (gTransformations.find(name_str) == gTransformations.end()) { - std::deque<CommonTransformationResult> list; - gTransformations[name_str] = std::move(list); - } - gTransformations[name_str].push_back(std::move(trans)); -} - -// The hook we are using. -void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, - JNIEnv* jni_env ATTRIBUTE_UNUSED, - jclass class_being_redefined ATTRIBUTE_UNUSED, - jobject loader ATTRIBUTE_UNUSED, - const char* name, - jobject protection_domain ATTRIBUTE_UNUSED, - jint class_data_len ATTRIBUTE_UNUSED, - const unsigned char* class_dat ATTRIBUTE_UNUSED, - jint* new_class_data_len, - unsigned char** new_class_data) { - std::string name_str(name); - if (gTransformations.find(name_str) != gTransformations.end() && - gTransformations[name_str].size() > 0) { - CommonTransformationResult& res = gTransformations[name_str][0]; - const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; - unsigned char* new_data; - CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); - memcpy(new_data, desired_array.data(), desired_array.size()); - *new_class_data = new_data; - *new_class_data_len = desired_array.size(); - if (gPopTransformations) { - gTransformations[name_str].pop_front(); - } - } -} - -extern "C" JNIEXPORT void Java_art_Redefinition_setPopRetransformations(JNIEnv*, - jclass, - jboolean enable) { - gPopTransformations = enable; -} - -extern "C" JNIEXPORT void Java_art_Redefinition_popTransformationFor(JNIEnv* env, - jclass, - jstring class_name) { - const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); - std::string name_str(name_chrs); - env->ReleaseStringUTFChars(class_name, name_chrs); - if (gTransformations.find(name_str) != gTransformations.end() && - gTransformations[name_str].size() > 0) { - gTransformations[name_str].pop_front(); - } else { - std::stringstream err; - err << "No transformations found for class " << name_str; - std::string message = err.str(); - env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); - } -} - -extern "C" JNIEXPORT void Java_art_Redefinition_enableCommonRetransformation(JNIEnv* env, - jclass, - jboolean enable) { - jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, - JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, - nullptr); - if (res != JVMTI_ERROR_NONE) { - JvmtiErrorToException(env, jvmti_env, res); - } -} - -static void throwRetransformationError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* targets, - jvmtiError res) { - return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res); -} - -static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) { - std::vector<jclass> classes; - jint len = env->GetArrayLength(targets); - for (jint i = 0; i < len; i++) { - classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); - } - jvmtiError res = jvmti_env->RetransformClasses(len, classes.data()); - if (res != JVMTI_ERROR_NONE) { - throwRetransformationError(jvmti_env, env, len, classes.data(), res); - } -} - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRetransformation( - JNIEnv* env, jclass, jobjectArray targets) { - jvmtiCapabilities caps; - jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); - if (caps_err != JVMTI_ERROR_NONE) { - env->ThrowNew(env->FindClass("java/lang/Exception"), - "Unable to get current jvmtiEnv capabilities"); - return; - } - - // Allocate a new environment if we don't have the can_retransform_classes capability needed to - // call the RetransformClasses function. - jvmtiEnv* real_env = nullptr; - if (caps.can_retransform_classes != 1) { - JavaVM* vm = nullptr; - if (env->GetJavaVM(&vm) != 0 || - vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { - env->ThrowNew(env->FindClass("java/lang/Exception"), - "Unable to create temporary jvmtiEnv for RetransformClasses call."); - return; - } - SetAllCapabilities(real_env); - } else { - real_env = jvmti_env; - } - DoClassRetransformation(real_env, env, targets); - if (caps.can_retransform_classes != 1) { - real_env->DisposeEnvironment(); - } -} - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonRetransform(); - return 0; -} - -} // namespace common_retransform - -namespace common_transform { - -// Get all capabilities except those related to retransformation. -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetupCommonTransform(); - return 0; -} - -} // namespace common_transform - -#define CONFIGURATION_COMMON_REDEFINE 0 -#define CONFIGURATION_COMMON_RETRANSFORM 1 -#define CONFIGURATION_COMMON_TRANSFORM 2 - -static void SetupCommonRedefine() { - jvmtiCapabilities caps; - jvmti_env->GetPotentialCapabilities(&caps); - caps.can_retransform_classes = 0; - caps.can_retransform_any_class = 0; - jvmti_env->AddCapabilities(&caps); -} - -static void SetupCommonRetransform() { - SetAllCapabilities(jvmti_env); - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); - CHECK_EQ(res, JVMTI_ERROR_NONE); - common_retransform::gTransformations.clear(); -} - -static void SetupCommonTransform() { - // Don't set the retransform caps - jvmtiCapabilities caps; - jvmti_env->GetPotentialCapabilities(&caps); - caps.can_retransform_classes = 0; - caps.can_retransform_any_class = 0; - jvmti_env->AddCapabilities(&caps); - - // Use the same callback as the retransform test. - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); - CHECK_EQ(res, JVMTI_ERROR_NONE); - common_retransform::gTransformations.clear(); -} - -extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, - jclass, - jint type) { - switch (type) { - case CONFIGURATION_COMMON_REDEFINE: { - SetupCommonRedefine(); - return; - } - case CONFIGURATION_COMMON_RETRANSFORM: { - SetupCommonRetransform(); - return; - } - case CONFIGURATION_COMMON_TRANSFORM: { - SetupCommonTransform(); - return; - } - default: { - LOG(FATAL) << "Unknown test configuration: " << type; - } - } -} } // namespace art diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index 610019e4d2..fafa1afcda 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -22,17 +22,13 @@ namespace art { -namespace common_redefine { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_redefine +// Taken from art/runtime/modifiers.h +static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic -namespace common_retransform { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_retransform - -namespace common_transform { -jint OnLoad(JavaVM* vm, char* options, void* reserved); -} // namespace common_transform +jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f); +jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m); +jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value); +jobject GetJavaValue(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m, jvalue value); } // namespace art diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index fd47f59905..0679c1bc17 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -20,7 +20,6 @@ #include "base/logging.h" #include "base/macros.h" -#include "common_helper.h" #include "jni_binder.h" #include "jvmti_helper.h" #include "test_env.h" @@ -32,6 +31,18 @@ namespace art { +namespace common_redefine { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_redefine + +namespace common_retransform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_retransform + +namespace common_transform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_transform + namespace { using OnLoad = jint (*)(JavaVM* vm, char* options, void* reserved); diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc new file mode 100644 index 0000000000..3b18879ca5 --- /dev/null +++ b/test/ti-agent/redefinition_helper.cc @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_helper.h" + +#include <deque> +#include <map> +#include <stdio.h> +#include <sstream> +#include <string> +#include <vector> + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { + +static void SetupCommonRedefine(); +static void SetupCommonRetransform(); +static void SetupCommonTransform(); +template <bool is_redefine> +static void throwCommonRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { + std::stringstream err; + char* error = nullptr; + jvmti->GetErrorName(res, &error); + err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class"; + if (num_targets > 1) { + err << "es"; + } + err << " <"; + for (jint i = 0; i < num_targets; i++) { + char* signature = nullptr; + char* generic = nullptr; + jvmti->GetClassSignature(target[i], &signature, &generic); + if (i != 0) { + err << ", "; + } + err << signature; + jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); + } + err << "> due to " << error; + std::string message = err.str(); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); +} + +#define CONFIGURATION_COMMON_REDEFINE 0 +#define CONFIGURATION_COMMON_RETRANSFORM 1 +#define CONFIGURATION_COMMON_TRANSFORM 2 + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, + jclass, + jint type) { + switch (type) { + case CONFIGURATION_COMMON_REDEFINE: { + SetupCommonRedefine(); + return; + } + case CONFIGURATION_COMMON_RETRANSFORM: { + SetupCommonRetransform(); + return; + } + case CONFIGURATION_COMMON_TRANSFORM: { + SetupCommonTransform(); + return; + } + default: { + LOG(FATAL) << "Unknown test configuration: " << type; + } + } +} + +namespace common_redefine { + +static void throwRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { + return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); +} + +static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jint num_redefines, + jclass* targets, + jbyteArray* class_file_bytes, + jbyteArray* dex_file_bytes) { + std::vector<jvmtiClassDefinition> defs; + for (jint i = 0; i < num_redefines; i++) { + jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; + jint len = static_cast<jint>(env->GetArrayLength(desired_array)); + const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( + env->GetByteArrayElements(desired_array, nullptr)); + defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); + } + jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); + if (res != JVMTI_ERROR_NONE) { + throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); + } +} + +static void DoClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jclass target, + jbyteArray class_file_bytes, + jbyteArray dex_file_bytes) { + return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition( + JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { + DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature +// ([Ljava/lang/Class;[[B[[B)V +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefinition( + JNIEnv* env, + jclass, + jobjectArray targets, + jobjectArray class_file_bytes, + jobjectArray dex_file_bytes) { + std::vector<jclass> classes; + std::vector<jbyteArray> class_files; + std::vector<jbyteArray> dex_files; + jint len = env->GetArrayLength(targets); + if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) { + env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), + "the three array arguments passed to this function have different lengths!"); + return; + } + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); + class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); + } + return DoMultiClassRedefine(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); +} + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonRedefine(); + return 0; +} + +} // namespace common_redefine + +namespace common_retransform { + +struct CommonTransformationResult { + std::vector<unsigned char> class_bytes; + std::vector<unsigned char> dex_bytes; + + CommonTransformationResult(size_t class_size, size_t dex_size) + : class_bytes(class_size), dex_bytes(dex_size) {} + + CommonTransformationResult() = default; + CommonTransformationResult(CommonTransformationResult&&) = default; + CommonTransformationResult(CommonTransformationResult&) = default; +}; + +// Map from class name to transformation result. +std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; +bool gPopTransformations = true; + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_addCommonTransformationResult( + JNIEnv* env, jclass, jstring class_name, jbyteArray class_array, jbyteArray dex_array) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + CommonTransformationResult trans(env->GetArrayLength(class_array), + env->GetArrayLength(dex_array)); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(class_array, + 0, + env->GetArrayLength(class_array), + reinterpret_cast<jbyte*>(trans.class_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(dex_array, + 0, + env->GetArrayLength(dex_array), + reinterpret_cast<jbyte*>(trans.dex_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + if (gTransformations.find(name_str) == gTransformations.end()) { + std::deque<CommonTransformationResult> list; + gTransformations[name_str] = std::move(list); + } + gTransformations[name_str].push_back(std::move(trans)); +} + +// The hook we are using. +void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jclass class_being_redefined ATTRIBUTE_UNUSED, + jobject loader ATTRIBUTE_UNUSED, + const char* name, + jobject protection_domain ATTRIBUTE_UNUSED, + jint class_data_len ATTRIBUTE_UNUSED, + const unsigned char* class_dat ATTRIBUTE_UNUSED, + jint* new_class_data_len, + unsigned char** new_class_data) { + std::string name_str(name); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { + CommonTransformationResult& res = gTransformations[name_str][0]; + const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; + unsigned char* new_data; + CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); + memcpy(new_data, desired_array.data(), desired_array.size()); + *new_class_data = new_data; + *new_class_data_len = desired_array.size(); + if (gPopTransformations) { + gTransformations[name_str].pop_front(); + } + } +} + +extern "C" JNIEXPORT void Java_art_Redefinition_setPopRetransformations(JNIEnv*, + jclass, + jboolean enable) { + gPopTransformations = enable; +} + +extern "C" JNIEXPORT void Java_art_Redefinition_popTransformationFor(JNIEnv* env, + jclass, + jstring class_name) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { + gTransformations[name_str].pop_front(); + } else { + std::stringstream err; + err << "No transformations found for class " << name_str; + std::string message = err.str(); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); + } +} + +extern "C" JNIEXPORT void Java_art_Redefinition_enableCommonRetransformation(JNIEnv* env, + jclass, + jboolean enable) { + jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + nullptr); + if (res != JVMTI_ERROR_NONE) { + JvmtiErrorToException(env, jvmti_env, res); + } +} + +static void throwRetransformationError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* targets, + jvmtiError res) { + return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res); +} + +static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) { + std::vector<jclass> classes; + jint len = env->GetArrayLength(targets); + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + } + jvmtiError res = jvmti_env->RetransformClasses(len, classes.data()); + if (res != JVMTI_ERROR_NONE) { + throwRetransformationError(jvmti_env, env, len, classes.data(), res); + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRetransformation( + JNIEnv* env, jclass, jobjectArray targets) { + jvmtiCapabilities caps; + jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); + if (caps_err != JVMTI_ERROR_NONE) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to get current jvmtiEnv capabilities"); + return; + } + + // Allocate a new environment if we don't have the can_retransform_classes capability needed to + // call the RetransformClasses function. + jvmtiEnv* real_env = nullptr; + if (caps.can_retransform_classes != 1) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0 || + vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to create temporary jvmtiEnv for RetransformClasses call."); + return; + } + SetAllCapabilities(real_env); + } else { + real_env = jvmti_env; + } + DoClassRetransformation(real_env, env, targets); + if (caps.can_retransform_classes != 1) { + real_env->DisposeEnvironment(); + } +} + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonRetransform(); + return 0; +} + +} // namespace common_retransform + +namespace common_transform { + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetupCommonTransform(); + return 0; +} + +} // namespace common_transform + +static void SetupCommonRedefine() { + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); +} + +static void SetupCommonRetransform() { + SetAllCapabilities(jvmti_env); + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + common_retransform::gTransformations.clear(); +} + +static void SetupCommonTransform() { + // Don't set the retransform caps + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); + + // Use the same callback as the retransform test. + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + common_retransform::gTransformations.clear(); +} + +} // namespace art diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc new file mode 100644 index 0000000000..7a9d1e0d92 --- /dev/null +++ b/test/ti-agent/trace_helper.cc @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { + +namespace common_trace { + +struct TraceData { + jclass test_klass; + jmethodID enter_method; + jmethodID exit_method; + jmethodID field_access; + jmethodID field_modify; + jmethodID single_step; + bool in_callback; + bool access_watch_on_load; + bool modify_watch_on_load; +}; + +static void singleStepCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thread, + jmethodID method, + jlocation location) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + return; + } + CHECK(data->single_step != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->single_step, + thread, + method_arg, + static_cast<jlong>(location)); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +static void fieldAccessCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + CHECK(data->field_access != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); + jnienv->CallStaticVoidMethod(data->test_klass, + data->field_access, + method_arg, + static_cast<jlong>(location), + field_klass, + object, + field_arg); + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + data->in_callback = false; +} + +static void fieldModificationCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char type_char, + jvalue new_value) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + // Don't do callback recursively to prevent an infinite loop. + return; + } + CHECK(data->field_modify != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); + jobject value = GetJavaValueByType(jnienv, type_char, new_value); + if (jnienv->ExceptionCheck()) { + data->in_callback = false; + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, + data->field_modify, + method_arg, + static_cast<jlong>(location), + field_klass, + object, + field_arg, + value); + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + data->in_callback = false; +} + +static void methodExitCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jboolean was_popped_by_exception, + jvalue return_value) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (method == data->exit_method || method == data->enter_method || data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + CHECK(data->exit_method != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject result = + was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value); + if (jnienv->ExceptionCheck()) { + data->in_callback = false; + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, + data->exit_method, + method_arg, + was_popped_by_exception, + result); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +static void methodEntryCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data->enter_method != nullptr); + if (method == data->exit_method || method == data->enter_method || data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + if (jnienv->ExceptionCheck()) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->enter_method, method_arg); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +static void classPrepareCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jclass klass) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->access_watch_on_load || data->modify_watch_on_load) { + jint nfields; + jfieldID* fields; + if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) { + return; + } + for (jint i = 0; i < nfields; i++) { + jfieldID f = fields[i]; + // Ignore errors + if (data->access_watch_on_load) { + jvmti->SetFieldAccessWatch(klass, f); + } + + if (data->modify_watch_on_load) { + jvmti->SetFieldModificationWatch(klass, f); + } + } + jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) { + TraceData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + data->access_watch_on_load = true; + // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr))) { + return; + } + jint nklasses; + jclass* klasses; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { + return; + } + for (jint i = 0; i < nklasses; i++) { + jclass k = klasses[i]; + + jint nfields; + jfieldID* fields; + jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); + if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { + continue; + } else if (JvmtiErrorToException(env, jvmti_env, err)) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldAccessWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) { + TraceData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + data->modify_watch_on_load = true; + // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr))) { + return; + } + jint nklasses; + jclass* klasses; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { + return; + } + for (jint i = 0; i < nklasses; i++) { + jclass k = klasses[i]; + + jint nfields; + jfieldID* fields; + jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); + if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { + continue; + } else if (JvmtiErrorToException(env, jvmti_env, err)) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldModificationWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +static bool GetFieldAndClass(JNIEnv* env, + jobject ref_field, + jclass* out_klass, + jfieldID* out_field) { + *out_field = env->FromReflectedField(ref_field); + if (env->ExceptionCheck()) { + return false; + } + jclass field_klass = env->FindClass("java/lang/reflect/Field"); + if (env->ExceptionCheck()) { + return false; + } + jmethodID get_declaring_class_method = + env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;"); + if (env->ExceptionCheck()) { + env->DeleteLocalRef(field_klass); + return false; + } + *out_klass = static_cast<jclass>(env->CallObjectMethod(ref_field, get_declaring_class_method)); + if (env->ExceptionCheck()) { + *out_klass = nullptr; + env->DeleteLocalRef(field_klass); + return false; + } + env->DeleteLocalRef(field_klass); + return true; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jobject field_obj) { + jfieldID field; + jclass klass; + if (!GetFieldAndClass(env, field_obj, &klass, &field)) { + return; + } + + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field)); + env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jobject field_obj) { + jfieldID field; + jclass klass; + if (!GetFieldAndClass(env, field_obj, &klass, &field)) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field)); + env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jclass klass, + jobject enter, + jobject exit, + jobject field_access, + jobject field_modify, + jobject single_step, + jthread thr) { + TraceData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(TraceData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(TraceData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); + data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr; + data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; + data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; + data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr; + data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr; + data->in_callback = false; + + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.MethodEntry = methodEntryCB; + cb.MethodExit = methodExitCB; + cb.FieldAccess = fieldAccessCB; + cb.FieldModification = fieldModificationCB; + cb.ClassPrepare = classPrepareCB; + cb.SingleStep = singleStepCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (enter != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_ENTRY, + thr))) { + return; + } + if (exit != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_EXIT, + thr))) { + return; + } + if (field_access != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FIELD_ACCESS, + thr))) { + return; + } + if (field_modify != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } + if (single_step != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) { + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_ACCESS, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_ENTRY, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_EXIT, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } +} + +} // namespace common_trace + + +} // namespace art diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index 40fcc4f11d..d197acd216 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -17,6 +17,7 @@ #include <jni.h> #include <stdio.h> #include <iostream> +#include <iomanip> #include <fstream> #include <memory> #include <stdio.h> @@ -40,6 +41,7 @@ struct StressData { bool trace_stress; bool redefine_stress; bool field_stress; + bool step_stress; }; static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { @@ -586,6 +588,21 @@ void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv, } } +void JNICALL SingleStepHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID method, + jlocation location) { + ScopedThreadInfo info(jvmtienv, env, thread); + ScopedMethodInfo method_info(jvmtienv, env, method); + if (!method_info.Init()) { + LOG(ERROR) << "Unable to get method info!"; + return; + } + LOG(INFO) << "Single step at location: 0x" << std::setw(8) << std::setfill('0') << std::hex + << location << " in method " << method_info << " thread: " << info.GetName(); +} + // The hook we are using. void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, JNIEnv* jni_env ATTRIBUTE_UNUSED, @@ -645,6 +662,8 @@ static void ReadOptions(StressData* data, char* options) { std::string cur = GetOption(ops); if (cur == "trace") { data->trace_stress = true; + } else if (cur == "step") { + data->step_stress = true; } else if (cur == "field") { data->field_stress = true; } else if (cur == "redefine") { @@ -776,6 +795,7 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, cb.FieldAccess = FieldAccessHook; cb.FieldModification = FieldModificationHook; cb.ClassPrepare = ClassPrepareHook; + cb.SingleStep = SingleStepHook; if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set class file load hook cb!"; return 1; @@ -837,6 +857,13 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, return 1; } } + if (data->step_stress) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + nullptr) != JVMTI_ERROR_NONE) { + return 1; + } + } return 0; } diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index bf7692ab15..75694c340c 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -68,7 +68,7 @@ if $using_jack; then fi if [[ $mode == "host" ]]; then - make_command="make $j_arg $showcommands build-art-host-tests $common_targets" + make_command="make $j_arg $showcommands build-art-host-tests $common_targets dx-tests" make_command+=" ${out_dir}/host/linux-x86/lib/libjavacoretests.so " make_command+=" ${out_dir}/host/linux-x86/lib64/libjavacoretests.so" elif [[ $mode == "target" ]]; then diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README index 78f73f5c11..58f6226ce7 100644 --- a/tools/dexfuzz/README +++ b/tools/dexfuzz/README @@ -137,9 +137,12 @@ FieldFlagChanger 40 InstructionDeleter 40 InstructionDuplicator 80 InstructionSwapper 80 +InvokeChanger 30 NewMethodCaller 10 NonsenseStringPrinter 10 +OppositeBranchChanger 40 PoolIndexChanger 30 +RandomBranchChanger 30 RandomInstructionGenerator 30 SwitchBranchShifter 30 TryBlockShifter 40 diff --git a/tools/dexfuzz/src/dexfuzz/DexFuzz.java b/tools/dexfuzz/src/dexfuzz/DexFuzz.java index 3b2875482e..97cdfee77e 100644 --- a/tools/dexfuzz/src/dexfuzz/DexFuzz.java +++ b/tools/dexfuzz/src/dexfuzz/DexFuzz.java @@ -34,7 +34,7 @@ import dexfuzz.listeners.UpdatingConsoleListener; */ public class DexFuzz { private static int majorVersion = 1; - private static int minorVersion = 1; + private static int minorVersion = 3; private static int seedChangeVersion = 0; /** diff --git a/tools/dexfuzz/src/dexfuzz/program/Program.java b/tools/dexfuzz/src/dexfuzz/program/Program.java index 286fe5221e..1d0c678414 100644 --- a/tools/dexfuzz/src/dexfuzz/program/Program.java +++ b/tools/dexfuzz/src/dexfuzz/program/Program.java @@ -30,9 +30,12 @@ import dexfuzz.program.mutators.FieldFlagChanger; import dexfuzz.program.mutators.InstructionDeleter; import dexfuzz.program.mutators.InstructionDuplicator; import dexfuzz.program.mutators.InstructionSwapper; +import dexfuzz.program.mutators.InvokeChanger; import dexfuzz.program.mutators.NewMethodCaller; import dexfuzz.program.mutators.NonsenseStringPrinter; +import dexfuzz.program.mutators.OppositeBranchChanger; import dexfuzz.program.mutators.PoolIndexChanger; +import dexfuzz.program.mutators.RandomBranchChanger; import dexfuzz.program.mutators.RandomInstructionGenerator; import dexfuzz.program.mutators.SwitchBranchShifter; import dexfuzz.program.mutators.TryBlockShifter; @@ -197,9 +200,12 @@ public class Program { registerMutator(new InstructionDeleter(rng, mutationStats, mutations)); registerMutator(new InstructionDuplicator(rng, mutationStats, mutations)); registerMutator(new InstructionSwapper(rng, mutationStats, mutations)); + registerMutator(new InvokeChanger(rng, mutationStats, mutations)); registerMutator(new NewMethodCaller(rng, mutationStats, mutations)); registerMutator(new NonsenseStringPrinter(rng, mutationStats, mutations)); + registerMutator(new OppositeBranchChanger(rng, mutationStats, mutations)); registerMutator(new PoolIndexChanger(rng, mutationStats, mutations)); + registerMutator(new RandomBranchChanger(rng, mutationStats, mutations)); registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations)); registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations)); registerMutator(new TryBlockShifter(rng, mutationStats, mutations)); diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/IfBranchChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/IfBranchChanger.java new file mode 100644 index 0000000000..872b29730c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/IfBranchChanger.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2017 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 dexfuzz.program.mutators; + +import dexfuzz.Log; +import dexfuzz.MutationStats; +import dexfuzz.program.MInsn; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * This class mutates the comparison operator of the if + * statements by taking in a random instruction, checking whether + * it is an if statement and, if so, changing the comparison + * operator. The inheriting classes implement the way comparison + * operator changes. For example, by choosing the opposite + * comparison operator or by choosing a random comparison operator. + */ +public abstract class IfBranchChanger extends CodeMutator { + /** + * Every CodeMutator has an AssociatedMutation, representing the + * mutation that this CodeMutator can perform, to allow separate + * generateMutation() and applyMutation() phases, allowing serialization. + */ + public static class AssociatedMutation extends Mutation { + public int ifBranchInsnIdx; + + @Override + public String getString() { + return Integer.toString(ifBranchInsnIdx); + } + + @Override + public void parseString(String[] elements) { + ifBranchInsnIdx = Integer.parseInt(elements[2]); + } + } + + // The following two methods are here for the benefit of MutationSerializer, + // so it can create a CodeMutator and get the correct associated Mutation, as it + // reads in mutations from a dump of mutations. + @Override + public Mutation getNewMutation() { + return new AssociatedMutation(); + } + + public IfBranchChanger() { } + + public IfBranchChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> ifBranchInsns = null; + + private void generateCachedifBranchInsns(MutatableCode mutatableCode) { + if (ifBranchInsns != null) { + return; + } + + ifBranchInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isIfBranchOperation(mInsn)) { + ifBranchInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isIfBranchOperation(mInsn)) { + return true; + } + } + + Log.debug("No if branch operation, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedifBranchInsns(mutatableCode); + + int ifBranchInsnIdx = rng.nextInt(ifBranchInsns.size()); + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.ifBranchInsnIdx = ifBranchInsnIdx; + return mutation; + } + + @Override + protected void applyMutation(Mutation uncastMutation) { + AssociatedMutation mutation = (AssociatedMutation) uncastMutation; + MutatableCode mutatableCode = mutation.mutatableCode; + + generateCachedifBranchInsns(mutatableCode); + + MInsn ifBranchInsn = ifBranchInsns.get(mutation.ifBranchInsnIdx); + + String oldInsnString = ifBranchInsn.toString(); + + Opcode newOpcode = getModifiedOpcode(ifBranchInsn); + + ifBranchInsn.insn.info = Instruction.getOpcodeInfo(newOpcode); + + Log.info("Changed " + oldInsnString + " to " + ifBranchInsn); + + stats.incrementStat("Changed if branch operator to " + getMutationTag() + " operator"); + + // Clear cache. + ifBranchInsns = null; + } + + /** + * Get a different if branch instruction. + * @return opcode of the new comparison operator. + */ + protected abstract Opcode getModifiedOpcode(MInsn mInsn); + + /** + * Get the tag of the mutation that fired. + * @return string tag of the type of mutation used + */ + protected abstract String getMutationTag(); + + private boolean isIfBranchOperation(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.IF_EQ, Opcode.IF_LEZ)) { + return true; + } + return false; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java new file mode 100644 index 0000000000..348850321c --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2017 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 dexfuzz.program.mutators; + +import dexfuzz.Log; +import dexfuzz.MutationStats; +import dexfuzz.program.MInsn; +import dexfuzz.program.MutatableCode; +import dexfuzz.program.Mutation; +import dexfuzz.rawdex.Instruction; +import dexfuzz.rawdex.Opcode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class InvokeChanger extends CodeMutator { + + private static final Opcode[] INVOKE_LIST = { + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_SUPER, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_STATIC, + Opcode.INVOKE_INTERFACE, + }; + + private static final Opcode[] INVOKE_RANGE_LIST = { + Opcode.INVOKE_VIRTUAL_RANGE, + Opcode.INVOKE_SUPER_RANGE, + Opcode.INVOKE_DIRECT_RANGE, + Opcode.INVOKE_STATIC_RANGE, + Opcode.INVOKE_INTERFACE_RANGE, + }; + + /** + * Every CodeMutator has an AssociatedMutation, representing the + * mutation that this CodeMutator can perform, to allow separate + * generateMutation() and applyMutation() phases, allowing serialization. + */ + public static class AssociatedMutation extends Mutation { + + public int invokeCallInsnIdx; + + @Override + public String getString() { + return Integer.toString(invokeCallInsnIdx); + } + + @Override + public void parseString(String[] elements) { + invokeCallInsnIdx = Integer.parseInt(elements[2]); + } + } + + // The following two methods are here for the benefit of MutationSerializer, + // so it can create a CodeMutator and get the correct associated Mutation, as it + // reads in mutations from a dump of mutations. + @Override + public Mutation getNewMutation() { + return new AssociatedMutation(); + } + + public InvokeChanger() { } + + public InvokeChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + // A cache that should only exist between generateMutation() and applyMutation(), + // or be created at the start of applyMutation(), if we're reading in mutations from + // a file. + private List<MInsn> invokeCallInsns = null; + + private void generateCachedinvokeCallInsns(MutatableCode mutatableCode) { + if (invokeCallInsns != null) { + return; + } + + invokeCallInsns = new ArrayList<MInsn>(); + + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isInvokeCallInst(mInsn)) { + invokeCallInsns.add(mInsn); + } + } + } + + @Override + protected boolean canMutate(MutatableCode mutatableCode) { + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isInvokeCallInst(mInsn)) { + return true; + } + } + + Log.debug("No invoke instruction in method, skipping..."); + return false; + } + + @Override + protected Mutation generateMutation(MutatableCode mutatableCode) { + generateCachedinvokeCallInsns(mutatableCode); + + int invokeCallInsnIdx = rng.nextInt(invokeCallInsns.size()); + + AssociatedMutation mutation = new AssociatedMutation(); + mutation.setup(this.getClass(), mutatableCode); + mutation.invokeCallInsnIdx = invokeCallInsnIdx; + return mutation; + } + + @Override + protected void applyMutation(Mutation uncastMutation) { + // Cast the Mutation to our AssociatedMutation, so we can access its fields. + AssociatedMutation mutation = (AssociatedMutation) uncastMutation; + MutatableCode mutatableCode = mutation.mutatableCode; + + generateCachedinvokeCallInsns(mutatableCode); + + MInsn invokeInsn = invokeCallInsns.get(mutation.invokeCallInsnIdx); + + String oldInsnString = invokeInsn.toString(); + + Opcode newOpcode = isInvokeCalIInst(invokeInsn); + + invokeInsn.insn.info = Instruction.getOpcodeInfo(newOpcode); + + Log.info("Changed " + oldInsnString + " to " + invokeInsn); + + stats.incrementStat("Changed invoke call instruction"); + + // Clear cache. + invokeCallInsns = null; + } + + private Opcode isInvokeCalIInst(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (isSimpleInvokeInst(opcode)) { + int index = opcode.ordinal() - Opcode.INVOKE_VIRTUAL.ordinal(); + int length = INVOKE_LIST.length; + return INVOKE_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; + } else if (isRangeInvokeInst(opcode)) { + int index = opcode.ordinal() - Opcode.INVOKE_VIRTUAL_RANGE.ordinal(); + int length = INVOKE_RANGE_LIST.length; + return INVOKE_RANGE_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; + } + return opcode; + } + + private boolean isSimpleInvokeInst(Opcode opcode){ + return Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE); + } + + private boolean isRangeInvokeInst(Opcode opcode){ + return Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE); + + } + + private boolean isInvokeCallInst(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + return isSimpleInvokeInst(opcode) || isRangeInvokeInst(opcode); + } +} diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/OppositeBranchChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/OppositeBranchChanger.java new file mode 100644 index 0000000000..cb25b641cc --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/OppositeBranchChanger.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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 dexfuzz.program.mutators; + +import dexfuzz.Log; +import dexfuzz.MutationStats; +import dexfuzz.program.MInsn; +import dexfuzz.program.Mutation; +import dexfuzz.rawdex.Opcode; +import java.util.List; +import java.util.Random; + +public class OppositeBranchChanger extends IfBranchChanger { + + public OppositeBranchChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 40; + } + + @Override + protected Opcode getModifiedOpcode(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + switch (opcode) { + case IF_EQ: + return Opcode.IF_NE; + case IF_NE: + return Opcode.IF_EQ; + case IF_LT: + return Opcode.IF_GE; + case IF_GT: + return Opcode.IF_LE; + case IF_GE: + return Opcode.IF_LT; + case IF_LE: + return Opcode.IF_GT; + case IF_EQZ: + return Opcode.IF_NEZ; + case IF_NEZ: + return Opcode.IF_EQZ; + case IF_LTZ: + return Opcode.IF_GEZ; + case IF_GTZ: + return Opcode.IF_LEZ; + case IF_GEZ: + return Opcode.IF_LTZ; + case IF_LEZ: + return Opcode.IF_GTZ; + default: + Log.errorAndQuit("Could not find if branch."); + return opcode; + } + } + + @Override + protected String getMutationTag() { + return "opposite"; + } +}
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/RandomBranchChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomBranchChanger.java new file mode 100644 index 0000000000..fc42c2ed6f --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/RandomBranchChanger.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 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 dexfuzz.program.mutators; + +import dexfuzz.MutationStats; +import dexfuzz.program.MInsn; +import dexfuzz.program.Mutation; +import dexfuzz.rawdex.Opcode; +import java.util.List; +import java.util.Random; + +public class RandomBranchChanger extends IfBranchChanger { + + private static final Opcode[] EQUALITY_CMP_OP_LIST = { + Opcode.IF_EQ, + Opcode.IF_NE, + Opcode.IF_LT, + Opcode.IF_GE, + Opcode.IF_GT, + Opcode.IF_LE + }; + + private static final Opcode[] ZERO_CMP_OP_LIST = { + Opcode.IF_EQZ, + Opcode.IF_NEZ, + Opcode.IF_LTZ, + Opcode.IF_GEZ, + Opcode.IF_GTZ, + Opcode.IF_LEZ + }; + + public RandomBranchChanger(Random rng, MutationStats stats, List<Mutation> mutations) { + super(rng, stats, mutations); + likelihood = 30; + } + + @Override + protected Opcode getModifiedOpcode(MInsn mInsn) { + Opcode opcode = mInsn.insn.info.opcode; + if (Opcode.isBetween(opcode, Opcode.IF_EQ, Opcode.IF_LE)) { + int index = opcode.ordinal() - Opcode.IF_EQ.ordinal(); + int length = EQUALITY_CMP_OP_LIST.length; + return EQUALITY_CMP_OP_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; + } else if (Opcode.isBetween(opcode, Opcode.IF_EQZ, Opcode.IF_LEZ)) { + int index = opcode.ordinal() - Opcode.IF_EQZ.ordinal(); + int length = ZERO_CMP_OP_LIST.length; + return ZERO_CMP_OP_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; + } + return opcode; + } + + @Override + protected String getMutationTag() { + return "random"; + } +}
\ No newline at end of file diff --git a/tools/libcore_gcstress_failures.txt b/tools/libcore_gcstress_failures.txt new file mode 100644 index 0000000000..e049cb3ee9 --- /dev/null +++ b/tools/libcore_gcstress_failures.txt @@ -0,0 +1,13 @@ +/* + * This file contains expectations for ART's buildbot when running gcstress. + * The script that uses this file is art/tools/run-libcore-tests.sh. + */ + +[ +{ + description: "Timeouts on target with gcstress.", + result: EXEC_FAILED, + modes: [device], + names: ["libcore.javax.crypto.CipherBasicsTest#testGcmEncryption"] +} +] diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 6dcc23a9fc..eef74d27ab 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -158,9 +158,12 @@ if $use_jit; then fi vogar_args="$vogar_args --vm-arg -Xusejit:$use_jit" -# gcstress and debug may lead to timeouts, so we need a dedicated expectations file for it. -if [[ $gcstress && $debug ]]; then - expectations="$expectations --expectations art/tools/libcore_gcstress_debug_failures.txt" +# gcstress may lead to timeouts, so we need dedicated expectations files for it. +if [[ $gcstress ]]; then + expectations="$expectations --expectations art/tools/libcore_gcstress_failures.txt" + if [[ $debug ]]; then + expectations="$expectations --expectations art/tools/libcore_gcstress_debug_failures.txt" + fi fi # Run the tests using vogar. |