diff options
158 files changed, 7241 insertions, 1270 deletions
diff --git a/build/art.go b/build/art.go index f52c63525a..db626fd19c 100644 --- a/build/art.go +++ b/build/art.go @@ -83,20 +83,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 +168,10 @@ func globalDefaults(ctx android.LoadHookContext) { Cflags []string } } - Cflags []string - Asflags []string + Cflags []string + Asflags []string Sanitize struct { - Recover []string + Recover []string } } @@ -182,7 +182,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", } } 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/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 66135414f7..715d97379e 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -90,36 +90,16 @@ 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()); + const InstructionSet instruction_set = kRuntimeISA; for (const StringPiece option : Runtime::Current()->GetCompilerOptions()) { VLOG(compiler) << "JIT compiler option " << option; diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 0b3ac204ff..6b9f232e8f 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -2875,21 +2875,28 @@ void InstructionCodeGeneratorARM::GenerateCompareTestAndBranch(HCondition* condi if (CanGenerateTest(condition, codegen_->GetAssembler())) { Label* non_fallthrough_target; bool invert; + bool emit_both_branches; if (true_target_in == nullptr) { + // The true target is fallthrough. DCHECK(false_target_in != nullptr); non_fallthrough_target = false_target_in; invert = true; + emit_both_branches = false; } else { + // Either the false target is fallthrough, or there is no fallthrough + // and both branches must be emitted. non_fallthrough_target = true_target_in; invert = false; + emit_both_branches = (false_target_in != nullptr); } const auto cond = GenerateTest(condition, invert, codegen_); __ b(non_fallthrough_target, cond.first); - if (false_target_in != nullptr && false_target_in != non_fallthrough_target) { + if (emit_both_branches) { + // No target falls through, we need to branch. __ b(false_target_in); } diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 34397e66bc..2561ed0762 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -3608,9 +3608,6 @@ void InstructionCodeGeneratorARM64::GenerateTestAndBranch(HInstruction* instruct size_t condition_input_index, vixl::aarch64::Label* true_target, vixl::aarch64::Label* false_target) { - // FP branching requires both targets to be explicit. If either of the targets - // is nullptr (fallthrough) use and bind `fallthrough_target` instead. - vixl::aarch64::Label fallthrough_target; HInstruction* cond = instruction->InputAt(condition_input_index); if (true_target == nullptr && false_target == nullptr) { @@ -3711,10 +3708,6 @@ void InstructionCodeGeneratorARM64::GenerateTestAndBranch(HInstruction* instruct if (true_target != nullptr && false_target != nullptr) { __ B(false_target); } - - if (fallthrough_target.IsLinked()) { - __ Bind(&fallthrough_target); - } } void LocationsBuilderARM64::VisitIf(HIf* if_instr) { diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index a8b00c358b..9a2402be04 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -2964,21 +2964,28 @@ void InstructionCodeGeneratorARMVIXL::GenerateCompareTestAndBranch(HCondition* c if (CanGenerateTest(condition, codegen_->GetAssembler())) { vixl32::Label* non_fallthrough_target; bool invert; + bool emit_both_branches; if (true_target_in == nullptr) { + // The true target is fallthrough. DCHECK(false_target_in != nullptr); non_fallthrough_target = false_target_in; invert = true; + emit_both_branches = false; } else { non_fallthrough_target = true_target_in; invert = false; + // Either the false target is fallthrough, or there is no fallthrough + // and both branches must be emitted. + emit_both_branches = (false_target_in != nullptr); } const auto cond = GenerateTest(condition, invert, codegen_); __ B(cond.first, non_fallthrough_target, is_far_target); - if (false_target_in != nullptr && false_target_in != non_fallthrough_target) { + if (emit_both_branches) { + // No target falls through, we need to branch. __ B(false_target_in); } diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 7dcf2440b2..a20ec3c0db 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -451,8 +451,16 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { void VisitInvoke(HInvoke* invoke) OVERRIDE { StartAttributeStream("dex_file_index") << invoke->GetDexMethodIndex(); - StartAttributeStream("method_name") << GetGraph()->GetDexFile().PrettyMethod( - invoke->GetDexMethodIndex(), /* with_signature */ false); + ArtMethod* method = invoke->GetResolvedMethod(); + // We don't print signatures, which conflict with c1visualizer format. + static constexpr bool kWithSignature = false; + // Note that we can only use the graph's dex file for the unresolved case. The + // other invokes might be coming from inlined methods. + ScopedObjectAccess soa(Thread::Current()); + std::string method_name = (method == nullptr) + ? GetGraph()->GetDexFile().PrettyMethod(invoke->GetDexMethodIndex(), kWithSignature) + : method->PrettyMethod(kWithSignature); + StartAttributeStream("method_name") << method_name; } void VisitInvokeUnresolved(HInvokeUnresolved* invoke) OVERRIDE { 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/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index d2493137fe..b61d7b80d1 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)); } @@ -1164,8 +1218,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 +1233,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 +1509,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 +1688,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 +1726,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.cc b/compiler/optimizing/nodes.cc index d0047c54f2..4ca833707b 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -967,6 +967,7 @@ void HInstructionList::AddInstruction(HInstruction* instruction) { DCHECK(last_instruction_ == nullptr); first_instruction_ = last_instruction_ = instruction; } else { + DCHECK(last_instruction_ != nullptr); last_instruction_->next_ = instruction; instruction->previous_ = last_instruction_; last_instruction_ = instruction; 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/scheduler.cc b/compiler/optimizing/scheduler.cc index 320f01a727..147fa1c05a 100644 --- a/compiler/optimizing/scheduler.cc +++ b/compiler/optimizing/scheduler.cc @@ -109,6 +109,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); @@ -377,6 +381,10 @@ void HScheduler::Schedule(HBasicBlock* block) { scheduling_graph_.Clear(); 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); 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/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/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/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/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 ac433dd403..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())) { @@ -405,15 +418,19 @@ bool ArtMethod::IsOverridableByDefaultMethod() { bool ArtMethod::IsAnnotatedWithFastNative() { return IsAnnotatedWith(WellKnownClasses::dalvik_annotation_optimization_FastNative, - DexFile::kDexVisibilityBuild); + DexFile::kDexVisibilityBuild, + /* lookup_in_resolved_boot_classes */ true); } bool ArtMethod::IsAnnotatedWithCriticalNative() { return IsAnnotatedWith(WellKnownClasses::dalvik_annotation_optimization_CriticalNative, - DexFile::kDexVisibilityBuild); + DexFile::kDexVisibilityBuild, + /* lookup_in_resolved_boot_classes */ true); } -bool ArtMethod::IsAnnotatedWith(jclass klass, uint32_t visibility) { +bool ArtMethod::IsAnnotatedWith(jclass klass, + uint32_t visibility, + bool lookup_in_resolved_boot_classes) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); StackHandleScope<1> shs(self); @@ -422,10 +439,8 @@ bool ArtMethod::IsAnnotatedWith(jclass klass, uint32_t visibility) { DCHECK(annotation->IsAnnotation()); Handle<mirror::Class> annotation_handle(shs.NewHandle(annotation)); - // Note: Resolves any method annotations' classes as a side-effect. - // -- This seems allowed by the spec since it says we can preload any classes - // referenced by another classes's constant pool table. - return annotations::IsMethodAnnotationPresent(this, annotation_handle, visibility); + return annotations::IsMethodAnnotationPresent( + this, annotation_handle, visibility, lookup_in_resolved_boot_classes); } static uint32_t GetOatMethodIndexFromMethodIndex(const DexFile& dex_file, diff --git a/runtime/art_method.h b/runtime/art_method.h index 396c8784a3..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_); @@ -723,7 +729,10 @@ class ArtMethod FINAL { private: uint16_t FindObsoleteDexClassDefIndex() REQUIRES_SHARED(Locks::mutator_lock_); - bool IsAnnotatedWith(jclass klass, uint32_t visibility); + // If `lookup_in_resolved_boot_classes` is true, look up any of the + // method's annotations' classes in the bootstrap class loader's + // resolved types; otherwise, resolve them as a side effect. + bool IsAnnotatedWith(jclass klass, uint32_t visibility, bool lookup_in_resolved_boot_classes); static constexpr size_t PtrSizedFieldsOffset(PointerSize pointer_size) { // Round up to pointer size for padding field. Tested in art_method.cc. diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 141df1ec1a..10e0bd28c3 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -4064,7 +4064,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(); @@ -4107,6 +4110,10 @@ verifier::FailureKind ClassLinker::VerifyClass( } } + VLOG(class_linker) << "Beginning verification for class: " + << klass->PrettyDescriptor() + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); + // Verify super class. StackHandleScope<2> hs(self); MutableHandle<mirror::Class> supertype(hs.NewHandle(klass->GetSuperClass())); @@ -4161,6 +4168,13 @@ verifier::FailureKind ClassLinker::VerifyClass( const DexFile& dex_file = *klass->GetDexCache()->GetDexFile(); mirror::Class::Status oat_file_class_status(mirror::Class::kStatusNotReady); bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status); + + VLOG(class_linker) << "Class preverified status for class " + << klass->PrettyDescriptor() + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8() + << ": " + << preverified; + // If the oat file says the class had an error, re-run the verifier. That way we will get a // precise error message. To ensure a rerun, test: // mirror::Class::IsErroneous(oat_file_class_status) => !preverified diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 12bdb32fec..cc12439074 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -77,25 +77,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 +110,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 +1352,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) @@ -2887,7 +2873,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; } } diff --git a/runtime/dex_file_annotations.cc b/runtime/dex_file_annotations.cc index f21f1a2704..2b81f0a99a 100644 --- a/runtime/dex_file_annotations.cc +++ b/runtime/dex_file_annotations.cc @@ -751,7 +751,8 @@ const DexFile::AnnotationItem* GetAnnotationItemFromAnnotationSet( const ClassData& klass, const DexFile::AnnotationSetItem* annotation_set, uint32_t visibility, - Handle<mirror::Class> annotation_class) + Handle<mirror::Class> annotation_class, + bool lookup_in_resolved_boot_classes = false) REQUIRES_SHARED(Locks::mutator_lock_) { const DexFile& dex_file = klass.GetDexFile(); for (uint32_t i = 0; i < annotation_set->size_; ++i) { @@ -761,19 +762,37 @@ const DexFile::AnnotationItem* GetAnnotationItemFromAnnotationSet( } const uint8_t* annotation = annotation_item->annotation_; uint32_t type_index = DecodeUnsignedLeb128(&annotation); - StackHandleScope<2> hs(Thread::Current()); - mirror::Class* resolved_class = Runtime::Current()->GetClassLinker()->ResolveType( - klass.GetDexFile(), - dex::TypeIndex(type_index), - hs.NewHandle(klass.GetDexCache()), - hs.NewHandle(klass.GetClassLoader())); - if (resolved_class == nullptr) { - std::string temp; - LOG(WARNING) << StringPrintf("Unable to resolve %s annotation class %d", - klass.GetRealClass()->GetDescriptor(&temp), type_index); - CHECK(Thread::Current()->IsExceptionPending()); - Thread::Current()->ClearException(); - continue; + mirror::Class* resolved_class; + if (lookup_in_resolved_boot_classes) { + ObjPtr<mirror::Class> looked_up_class = + Runtime::Current()->GetClassLinker()->LookupResolvedType( + klass.GetDexFile(), + dex::TypeIndex(type_index), + klass.GetDexCache(), + // Force the use of the bootstrap class loader. + static_cast<mirror::ClassLoader*>(nullptr)); + resolved_class = looked_up_class.Ptr(); + if (resolved_class == nullptr) { + // If `resolved_class` is null, this is fine: just ignore that + // annotation item. We expect this to happen, as we do not + // attempt to resolve the annotation's class in this code path. + continue; + } + } else { + StackHandleScope<2> hs(Thread::Current()); + resolved_class = Runtime::Current()->GetClassLinker()->ResolveType( + klass.GetDexFile(), + dex::TypeIndex(type_index), + hs.NewHandle(klass.GetDexCache()), + hs.NewHandle(klass.GetClassLoader())); + if (resolved_class == nullptr) { + std::string temp; + LOG(WARNING) << StringPrintf("Unable to resolve %s annotation class %d", + klass.GetRealClass()->GetDescriptor(&temp), type_index); + CHECK(Thread::Current()->IsExceptionPending()); + Thread::Current()->ClearException(); + continue; + } } if (resolved_class == annotation_class.Get()) { return annotation_item; @@ -1200,15 +1219,20 @@ mirror::ObjectArray<mirror::String>* GetSignatureAnnotationForMethod(ArtMethod* return GetSignatureValue(ClassData(method), annotation_set); } -bool IsMethodAnnotationPresent(ArtMethod* method, Handle<mirror::Class> annotation_class, - uint32_t visibility /* = DexFile::kDexVisibilityRuntime */) { +bool IsMethodAnnotationPresent(ArtMethod* method, + Handle<mirror::Class> annotation_class, + uint32_t visibility /* = DexFile::kDexVisibilityRuntime */, + bool lookup_in_resolved_boot_classes /* = false */) { const DexFile::AnnotationSetItem* annotation_set = FindAnnotationSetForMethod(method); if (annotation_set == nullptr) { return false; } const DexFile::AnnotationItem* annotation_item = GetAnnotationItemFromAnnotationSet(ClassData(method), - annotation_set, visibility, annotation_class); + annotation_set, + visibility, + annotation_class, + lookup_in_resolved_boot_classes); return annotation_item != nullptr; } diff --git a/runtime/dex_file_annotations.h b/runtime/dex_file_annotations.h index 651c9844eb..e1088823c3 100644 --- a/runtime/dex_file_annotations.h +++ b/runtime/dex_file_annotations.h @@ -65,8 +65,15 @@ bool GetParametersMetadataForMethod(ArtMethod* method, REQUIRES_SHARED(Locks::mutator_lock_); mirror::ObjectArray<mirror::String>* GetSignatureAnnotationForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); -bool IsMethodAnnotationPresent(ArtMethod* method, Handle<mirror::Class> annotation_class, - uint32_t visibility = DexFile::kDexVisibilityRuntime) +// Check whether `method` is annotated with `annotation_class`. +// If `lookup_in_resolved_boot_classes` is true, look up any of the +// method's annotations' classes in the bootstrap class loader's +// resolved types; if it is false (default value), resolve them as a +// side effect. +bool IsMethodAnnotationPresent(ArtMethod* method, + Handle<mirror::Class> annotation_class, + uint32_t visibility = DexFile::kDexVisibilityRuntime, + bool lookup_in_resolved_boot_classes = false) REQUIRES_SHARED(Locks::mutator_lock_); // Class annotations. diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index b7cd39f107..2c99aeba88 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -2181,11 +2181,39 @@ extern "C" TwoWordReturn artQuickGenericJniTrampoline(Thread* self, ArtMethod** REQUIRES_SHARED(Locks::mutator_lock_) { ArtMethod* called = *sp; DCHECK(called->IsNative()) << called->PrettyMethod(true); + // Fix up a callee-save frame at the bottom of the stack (at `*sp`, + // above the alloca region) while we check for optimization + // annotations, thus allowing stack walking until the completion of + // the JNI frame creation. + // + // Note however that the Generic JNI trampoline does not expect + // exception being thrown at that stage. + *sp = Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs); + self->SetTopOfStack(sp); uint32_t shorty_len = 0; const char* shorty = called->GetShorty(&shorty_len); + // Optimization annotations lookup does not try to resolve classes, + // as this may throw an exception, which is not supported by the + // Generic JNI trampoline at this stage; instead, method's + // annotations' classes are looked up in the bootstrap class + // loader's resolved types (which won't trigger an exception). bool critical_native = called->IsAnnotatedWithCriticalNative(); + // ArtMethod::IsAnnotatedWithCriticalNative should not throw + // an exception; clear it if it happened anyway. + // TODO: Revisit this code path and turn this into a CHECK(!self->IsExceptionPending()). + if (self->IsExceptionPending()) { + self->ClearException(); + } bool fast_native = called->IsAnnotatedWithFastNative(); + // ArtMethod::IsAnnotatedWithFastNative should not throw + // an exception; clear it if it happened anyway. + // TODO: Revisit this code path and turn this into a CHECK(!self->IsExceptionPending()). + if (self->IsExceptionPending()) { + self->ClearException(); + } bool normal_native = !critical_native && !fast_native; + // Restore the initial ArtMethod pointer at `*sp`. + *sp = called; // Run the visitor and update sp. BuildGenericJniFrameVisitor visitor(self, diff --git a/runtime/gc/gc_cause.cc b/runtime/gc/gc_cause.cc index 39b5e3952d..a3a2051934 100644 --- a/runtime/gc/gc_cause.cc +++ b/runtime/gc/gc_cause.cc @@ -25,6 +25,7 @@ namespace gc { const char* PrettyCause(GcCause cause) { switch (cause) { + case kGcCauseNone: return "None"; case kGcCauseForAlloc: return "Alloc"; case kGcCauseBackground: return "Background"; case kGcCauseExplicit: return "Explicit"; diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h index b2b3a91645..78496f3ead 100644 --- a/runtime/gc/gc_cause.h +++ b/runtime/gc/gc_cause.h @@ -24,6 +24,8 @@ namespace gc { // What caused the GC? enum GcCause { + // Invalid GC cause used as a placeholder. + kGcCauseNone, // GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before // retrying allocation. kGcCauseForAlloc, diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index d944ce4904..880b2d40bd 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -214,6 +214,7 @@ Heap::Heap(size_t initial_size, disable_thread_flip_count_(0), thread_flip_running_(false), collector_type_running_(kCollectorTypeNone), + last_gc_cause_(kGcCauseNone), thread_running_gc_(nullptr), last_gc_type_(collector::kGcTypeNone), next_gc_type_(collector::kGcTypePartial), @@ -1458,6 +1459,7 @@ void Heap::StartGC(Thread* self, GcCause cause, CollectorType collector_type) { // Ensure there is only one GC at a time. WaitForGcToCompleteLocked(cause, self); collector_type_running_ = collector_type; + last_gc_cause_ = cause; thread_running_gc_ = self; } @@ -3537,6 +3539,7 @@ collector::GcType Heap::WaitForGcToComplete(GcCause cause, Thread* self) { collector::GcType Heap::WaitForGcToCompleteLocked(GcCause cause, Thread* self) { collector::GcType last_gc_type = collector::kGcTypeNone; + GcCause last_gc_cause = kGcCauseNone; uint64_t wait_start = NanoTime(); while (collector_type_running_ != kCollectorTypeNone) { if (self != task_processor_->GetRunningThread()) { @@ -3551,12 +3554,13 @@ collector::GcType Heap::WaitForGcToCompleteLocked(GcCause cause, Thread* self) { // We must wait, change thread state then sleep on gc_complete_cond_; gc_complete_cond_->Wait(self); last_gc_type = last_gc_type_; + last_gc_cause = last_gc_cause_; } uint64_t wait_time = NanoTime() - wait_start; total_wait_time_ += wait_time; if (wait_time > long_pause_log_threshold_) { - LOG(INFO) << "WaitForGcToComplete blocked for " << PrettyDuration(wait_time) - << " for cause " << cause; + LOG(INFO) << "WaitForGcToComplete blocked " << cause << " on " << last_gc_cause << " for " + << PrettyDuration(wait_time); } if (self != task_processor_->GetRunningThread()) { // The current thread is about to run a collection. If the thread diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 0289250966..3484e0297d 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -1189,9 +1189,12 @@ class Heap { // Task processor, proxies heap trim requests to the daemon threads. std::unique_ptr<TaskProcessor> task_processor_; - // True while the garbage collector is running. + // Collector type of the running GC. volatile CollectorType collector_type_running_ GUARDED_BY(gc_complete_lock_); + // Cause of the last running GC. + volatile GcCause last_gc_cause_ GUARDED_BY(gc_complete_lock_); + // The thread currently running the GC. volatile Thread* thread_running_gc_ GUARDED_BY(gc_complete_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..82e8f20154 100644 --- a/runtime/gc/space/region_space-inl.h +++ b/runtime/gc/space/region_space-inl.h @@ -48,58 +48,32 @@ 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)) { + if (kForEvac) { + evac_region_ = r; + } else { + current_region_ = r; } + obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); + CHECK(obj != nullptr); + return obj; } } else { // Large object. diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc index 8d8c4885ef..1f7bd09b3f 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); @@ -449,21 +453,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 +540,68 @@ 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) { + ZeroAndReleasePages(begin_, end_ - begin_); + } + if (kProtectClearedRegions) { + mprotect(begin_, end_ - begin_, PROT_NONE); + } + 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_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/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index e38f265c5a..619a49aa71 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -27,6 +27,7 @@ cc_defaults { "fixed_up_dex_file.cc", "object_tagging.cc", "OpenjdkJvmTi.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..e3768b358f 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -48,6 +48,7 @@ #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_breakpoint.h" #include "ti_class.h" #include "ti_dump.h" #include "ti_field.h" @@ -619,20 +620,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) { diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index b5f12191e6..2d5d527e68 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) { @@ -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/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_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/thread.cc b/runtime/thread.cc index be1614b3cc..5edd071675 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -549,27 +549,40 @@ 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. + volatile char space[kPageSize - 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 " << diff --git a/test/442-checker-constant-folding/build b/test/442-checker-constant-folding/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/442-checker-constant-folding/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/442-checker-constant-folding/src/Main.java b/test/442-checker-constant-folding/src/Main.java index 64180d5273..eba5137f6f 100644 --- a/test/442-checker-constant-folding/src/Main.java +++ b/test/442-checker-constant-folding/src/Main.java @@ -27,12 +27,6 @@ public class Main { } } - public static void assertTrue(boolean condition) { - if (!condition) { - throw new Error(); - } - } - public static void assertIntEquals(int expected, int result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); @@ -57,21 +51,6 @@ public class Main { } } - private static int $inline$int(int x) { - return x; - } - - private static long $inline$long(long x) { - return x; - } - - private static float $inline$float(float x) { - return x; - } - - private static double $inline$double(double x) { - return x; - } // Wrappers around methods located in file TestCmp.smali. @@ -215,119 +194,121 @@ public class Main { return y; } + /** * Exercise constant folding on addition. */ - /// CHECK-START: int Main.IntAddition1() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntAddition1() constant_folding (before) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Add:i\d+>> Add [<<Const1>>,<<Const2>>] /// CHECK-DAG: Return [<<Add>>] - /// CHECK-START: int Main.IntAddition1() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntAddition1() constant_folding (after) /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: Return [<<Const3>>] - /// CHECK-START: int Main.IntAddition1() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntAddition1() constant_folding (after) /// CHECK-NOT: Add public static int IntAddition1() { int a, b, c; - a = $inline$int(1); - b = $inline$int(2); + a = 1; + b = 2; c = a + b; return c; } - /// CHECK-START: int Main.IntAddition2() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntAddition2() constant_folding (before) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Const5:i\d+>> IntConstant 5 /// CHECK-DAG: <<Const6:i\d+>> IntConstant 6 + /// CHECK-DAG: <<Const11:i\d+>> IntConstant 11 /// CHECK-DAG: <<Add1:i\d+>> Add [<<Const1>>,<<Const2>>] - /// CHECK-DAG: <<Add2:i\d+>> Add [<<Const5>>,<<Const6>>] - /// CHECK-DAG: <<Add3:i\d+>> Add [<<Add1>>,<<Add2>>] + /// CHECK-DAG: Add [<<Const5>>,<<Const6>>] + /// CHECK-DAG: <<Add3:i\d+>> Add [<<Add1>>,<<Const11>>] /// CHECK-DAG: Return [<<Add3>>] - /// CHECK-START: int Main.IntAddition2() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntAddition2() constant_folding (after) /// CHECK-DAG: <<Const14:i\d+>> IntConstant 14 /// CHECK-DAG: Return [<<Const14>>] - /// CHECK-START: int Main.IntAddition2() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntAddition2() constant_folding (after) /// CHECK-NOT: Add public static int IntAddition2() { int a, b, c; - a = $inline$int(1); - b = $inline$int(2); + a = 1; + b = 2; a += b; - b = $inline$int(5); - c = $inline$int(6); + b = 5; + c = 6; b += c; c = a + b; return c; } - /// CHECK-START: long Main.LongAddition() constant_folding$after_inlining (before) + /// CHECK-START: long Main.LongAddition() constant_folding (before) /// CHECK-DAG: <<Const1:j\d+>> LongConstant 1 /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: <<Add:j\d+>> Add [<<Const1>>,<<Const2>>] /// CHECK-DAG: Return [<<Add>>] - /// CHECK-START: long Main.LongAddition() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongAddition() constant_folding (after) /// CHECK-DAG: <<Const3:j\d+>> LongConstant 3 /// CHECK-DAG: Return [<<Const3>>] - /// CHECK-START: long Main.LongAddition() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongAddition() constant_folding (after) /// CHECK-NOT: Add public static long LongAddition() { long a, b, c; - a = $inline$long(1L); - b = $inline$long(2L); + a = 1L; + b = 2L; c = a + b; return c; } - /// CHECK-START: float Main.FloatAddition() constant_folding$after_inlining (before) + /// CHECK-START: float Main.FloatAddition() constant_folding (before) /// CHECK-DAG: <<Const1:f\d+>> FloatConstant 1 /// CHECK-DAG: <<Const2:f\d+>> FloatConstant 2 /// CHECK-DAG: <<Add:f\d+>> Add [<<Const1>>,<<Const2>>] /// CHECK-DAG: Return [<<Add>>] - /// CHECK-START: float Main.FloatAddition() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatAddition() constant_folding (after) /// CHECK-DAG: <<Const3:f\d+>> FloatConstant 3 /// CHECK-DAG: Return [<<Const3>>] - /// CHECK-START: float Main.FloatAddition() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatAddition() constant_folding (after) /// CHECK-NOT: Add public static float FloatAddition() { float a, b, c; - a = $inline$float(1F); - b = $inline$float(2F); + a = 1F; + b = 2F; c = a + b; return c; } - /// CHECK-START: double Main.DoubleAddition() constant_folding$after_inlining (before) + /// CHECK-START: double Main.DoubleAddition() constant_folding (before) /// CHECK-DAG: <<Const1:d\d+>> DoubleConstant 1 /// CHECK-DAG: <<Const2:d\d+>> DoubleConstant 2 /// CHECK-DAG: <<Add:d\d+>> Add [<<Const1>>,<<Const2>>] /// CHECK-DAG: Return [<<Add>>] - /// CHECK-START: double Main.DoubleAddition() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleAddition() constant_folding (after) /// CHECK-DAG: <<Const3:d\d+>> DoubleConstant 3 /// CHECK-DAG: Return [<<Const3>>] - /// CHECK-START: double Main.DoubleAddition() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleAddition() constant_folding (after) /// CHECK-NOT: Add public static double DoubleAddition() { double a, b, c; - a = $inline$double(1D); - b = $inline$double(2D); + a = 1D; + b = 2D; c = a + b; return c; } @@ -337,86 +318,86 @@ public class Main { * Exercise constant folding on subtraction. */ - /// CHECK-START: int Main.IntSubtraction() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntSubtraction() constant_folding (before) /// CHECK-DAG: <<Const6:i\d+>> IntConstant 6 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Sub:i\d+>> Sub [<<Const6>>,<<Const2>>] /// CHECK-DAG: Return [<<Sub>>] - /// CHECK-START: int Main.IntSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntSubtraction() constant_folding (after) /// CHECK-DAG: <<Const4:i\d+>> IntConstant 4 /// CHECK-DAG: Return [<<Const4>>] - /// CHECK-START: int Main.IntSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntSubtraction() constant_folding (after) /// CHECK-NOT: Sub public static int IntSubtraction() { int a, b, c; - a = $inline$int(6); - b = $inline$int(2); + a = 6; + b = 2; c = a - b; return c; } - /// CHECK-START: long Main.LongSubtraction() constant_folding$after_inlining (before) + /// CHECK-START: long Main.LongSubtraction() constant_folding (before) /// CHECK-DAG: <<Const6:j\d+>> LongConstant 6 /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: <<Sub:j\d+>> Sub [<<Const6>>,<<Const2>>] /// CHECK-DAG: Return [<<Sub>>] - /// CHECK-START: long Main.LongSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongSubtraction() constant_folding (after) /// CHECK-DAG: <<Const4:j\d+>> LongConstant 4 /// CHECK-DAG: Return [<<Const4>>] - /// CHECK-START: long Main.LongSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongSubtraction() constant_folding (after) /// CHECK-NOT: Sub public static long LongSubtraction() { long a, b, c; - a = $inline$long(6L); - b = $inline$long(2L); + a = 6L; + b = 2L; c = a - b; return c; } - /// CHECK-START: float Main.FloatSubtraction() constant_folding$after_inlining (before) + /// CHECK-START: float Main.FloatSubtraction() constant_folding (before) /// CHECK-DAG: <<Const6:f\d+>> FloatConstant 6 /// CHECK-DAG: <<Const2:f\d+>> FloatConstant 2 /// CHECK-DAG: <<Sub:f\d+>> Sub [<<Const6>>,<<Const2>>] /// CHECK-DAG: Return [<<Sub>>] - /// CHECK-START: float Main.FloatSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatSubtraction() constant_folding (after) /// CHECK-DAG: <<Const4:f\d+>> FloatConstant 4 /// CHECK-DAG: Return [<<Const4>>] - /// CHECK-START: float Main.FloatSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatSubtraction() constant_folding (after) /// CHECK-NOT: Sub public static float FloatSubtraction() { float a, b, c; - a = $inline$float(6F); - b = $inline$float(2F); + a = 6F; + b = 2F; c = a - b; return c; } - /// CHECK-START: double Main.DoubleSubtraction() constant_folding$after_inlining (before) + /// CHECK-START: double Main.DoubleSubtraction() constant_folding (before) /// CHECK-DAG: <<Const6:d\d+>> DoubleConstant 6 /// CHECK-DAG: <<Const2:d\d+>> DoubleConstant 2 /// CHECK-DAG: <<Sub:d\d+>> Sub [<<Const6>>,<<Const2>>] /// CHECK-DAG: Return [<<Sub>>] - /// CHECK-START: double Main.DoubleSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleSubtraction() constant_folding (after) /// CHECK-DAG: <<Const4:d\d+>> DoubleConstant 4 /// CHECK-DAG: Return [<<Const4>>] - /// CHECK-START: double Main.DoubleSubtraction() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleSubtraction() constant_folding (after) /// CHECK-NOT: Sub public static double DoubleSubtraction() { double a, b, c; - a = $inline$double(6D); - b = $inline$double(2D); + a = 6D; + b = 2D; c = a - b; return c; } @@ -426,86 +407,86 @@ public class Main { * Exercise constant folding on multiplication. */ - /// CHECK-START: int Main.IntMultiplication() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntMultiplication() constant_folding (before) /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<Mul:i\d+>> Mul [<<Const7>>,<<Const3>>] /// CHECK-DAG: Return [<<Mul>>] - /// CHECK-START: int Main.IntMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntMultiplication() constant_folding (after) /// CHECK-DAG: <<Const21:i\d+>> IntConstant 21 /// CHECK-DAG: Return [<<Const21>>] - /// CHECK-START: int Main.IntMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntMultiplication() constant_folding (after) /// CHECK-NOT: Mul public static int IntMultiplication() { int a, b, c; - a = $inline$int(7); - b = $inline$int(3); + a = 7; + b = 3; c = a * b; return c; } - /// CHECK-START: long Main.LongMultiplication() constant_folding$after_inlining (before) + /// CHECK-START: long Main.LongMultiplication() constant_folding (before) /// CHECK-DAG: <<Const7:j\d+>> LongConstant 7 /// CHECK-DAG: <<Const3:j\d+>> LongConstant 3 /// CHECK-DAG: <<Mul:j\d+>> Mul [<<Const7>>,<<Const3>>] /// CHECK-DAG: Return [<<Mul>>] - /// CHECK-START: long Main.LongMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongMultiplication() constant_folding (after) /// CHECK-DAG: <<Const21:j\d+>> LongConstant 21 /// CHECK-DAG: Return [<<Const21>>] - /// CHECK-START: long Main.LongMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongMultiplication() constant_folding (after) /// CHECK-NOT: Mul public static long LongMultiplication() { long a, b, c; - a = $inline$long(7L); - b = $inline$long(3L); + a = 7L; + b = 3L; c = a * b; return c; } - /// CHECK-START: float Main.FloatMultiplication() constant_folding$after_inlining (before) + /// CHECK-START: float Main.FloatMultiplication() constant_folding (before) /// CHECK-DAG: <<Const7:f\d+>> FloatConstant 7 /// CHECK-DAG: <<Const3:f\d+>> FloatConstant 3 /// CHECK-DAG: <<Mul:f\d+>> Mul [<<Const7>>,<<Const3>>] /// CHECK-DAG: Return [<<Mul>>] - /// CHECK-START: float Main.FloatMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatMultiplication() constant_folding (after) /// CHECK-DAG: <<Const21:f\d+>> FloatConstant 21 /// CHECK-DAG: Return [<<Const21>>] - /// CHECK-START: float Main.FloatMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatMultiplication() constant_folding (after) /// CHECK-NOT: Mul public static float FloatMultiplication() { float a, b, c; - a = $inline$float(7F); - b = $inline$float(3F); + a = 7F; + b = 3F; c = a * b; return c; } - /// CHECK-START: double Main.DoubleMultiplication() constant_folding$after_inlining (before) + /// CHECK-START: double Main.DoubleMultiplication() constant_folding (before) /// CHECK-DAG: <<Const7:d\d+>> DoubleConstant 7 /// CHECK-DAG: <<Const3:d\d+>> DoubleConstant 3 /// CHECK-DAG: <<Mul:d\d+>> Mul [<<Const7>>,<<Const3>>] /// CHECK-DAG: Return [<<Mul>>] - /// CHECK-START: double Main.DoubleMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleMultiplication() constant_folding (after) /// CHECK-DAG: <<Const21:d\d+>> DoubleConstant 21 /// CHECK-DAG: Return [<<Const21>>] - /// CHECK-START: double Main.DoubleMultiplication() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleMultiplication() constant_folding (after) /// CHECK-NOT: Mul public static double DoubleMultiplication() { double a, b, c; - a = $inline$double(7D); - b = $inline$double(3D); + a = 7D; + b = 3D; c = a * b; return c; } @@ -515,90 +496,90 @@ public class Main { * Exercise constant folding on division. */ - /// CHECK-START: int Main.IntDivision() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntDivision() constant_folding (before) /// CHECK-DAG: <<Const8:i\d+>> IntConstant 8 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<Div0Chk:i\d+>> DivZeroCheck [<<Const3>>] /// CHECK-DAG: <<Div:i\d+>> Div [<<Const8>>,<<Div0Chk>>] /// CHECK-DAG: Return [<<Div>>] - /// CHECK-START: int Main.IntDivision() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntDivision() constant_folding (after) /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: int Main.IntDivision() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntDivision() constant_folding (after) /// CHECK-NOT: DivZeroCheck /// CHECK-NOT: Div public static int IntDivision() { int a, b, c; - a = $inline$int(8); - b = $inline$int(3); + a = 8; + b = 3; c = a / b; return c; } - /// CHECK-START: long Main.LongDivision() constant_folding$after_inlining (before) + /// CHECK-START: long Main.LongDivision() constant_folding (before) /// CHECK-DAG: <<Const8:j\d+>> LongConstant 8 /// CHECK-DAG: <<Const3:j\d+>> LongConstant 3 /// CHECK-DAG: <<Div0Chk:j\d+>> DivZeroCheck [<<Const3>>] /// CHECK-DAG: <<Div:j\d+>> Div [<<Const8>>,<<Div0Chk>>] /// CHECK-DAG: Return [<<Div>>] - /// CHECK-START: long Main.LongDivision() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongDivision() constant_folding (after) /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: long Main.LongDivision() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongDivision() constant_folding (after) /// CHECK-NOT: DivZeroCheck /// CHECK-NOT: Div public static long LongDivision() { long a, b, c; - a = $inline$long(8L); - b = $inline$long(3L); + a = 8L; + b = 3L; c = a / b; return c; } - /// CHECK-START: float Main.FloatDivision() constant_folding$after_inlining (before) + /// CHECK-START: float Main.FloatDivision() constant_folding (before) /// CHECK-DAG: <<Const8:f\d+>> FloatConstant 8 /// CHECK-DAG: <<Const2P5:f\d+>> FloatConstant 2.5 /// CHECK-DAG: <<Div:f\d+>> Div [<<Const8>>,<<Const2P5>>] /// CHECK-DAG: Return [<<Div>>] - /// CHECK-START: float Main.FloatDivision() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatDivision() constant_folding (after) /// CHECK-DAG: <<Const3P2:f\d+>> FloatConstant 3.2 /// CHECK-DAG: Return [<<Const3P2>>] - /// CHECK-START: float Main.FloatDivision() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatDivision() constant_folding (after) /// CHECK-NOT: Div public static float FloatDivision() { float a, b, c; - a = $inline$float(8F); - b = $inline$float(2.5F); + a = 8F; + b = 2.5F; c = a / b; return c; } - /// CHECK-START: double Main.DoubleDivision() constant_folding$after_inlining (before) + /// CHECK-START: double Main.DoubleDivision() constant_folding (before) /// CHECK-DAG: <<Const8:d\d+>> DoubleConstant 8 /// CHECK-DAG: <<Const2P5:d\d+>> DoubleConstant 2.5 /// CHECK-DAG: <<Div:d\d+>> Div [<<Const8>>,<<Const2P5>>] /// CHECK-DAG: Return [<<Div>>] - /// CHECK-START: double Main.DoubleDivision() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleDivision() constant_folding (after) /// CHECK-DAG: <<Const3P2:d\d+>> DoubleConstant 3.2 /// CHECK-DAG: Return [<<Const3P2>>] - /// CHECK-START: double Main.DoubleDivision() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleDivision() constant_folding (after) /// CHECK-NOT: Div public static double DoubleDivision() { double a, b, c; - a = $inline$double(8D); - b = $inline$double(2.5D); + a = 8D; + b = 2.5D; c = a / b; return c; } @@ -608,90 +589,90 @@ public class Main { * Exercise constant folding on remainder. */ - /// CHECK-START: int Main.IntRemainder() constant_folding$after_inlining (before) + /// CHECK-START: int Main.IntRemainder() constant_folding (before) /// CHECK-DAG: <<Const8:i\d+>> IntConstant 8 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<Div0Chk:i\d+>> DivZeroCheck [<<Const3>>] /// CHECK-DAG: <<Rem:i\d+>> Rem [<<Const8>>,<<Div0Chk>>] /// CHECK-DAG: Return [<<Rem>>] - /// CHECK-START: int Main.IntRemainder() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntRemainder() constant_folding (after) /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: int Main.IntRemainder() constant_folding$after_inlining (after) + /// CHECK-START: int Main.IntRemainder() constant_folding (after) /// CHECK-NOT: DivZeroCheck /// CHECK-NOT: Rem public static int IntRemainder() { int a, b, c; - a = $inline$int(8); - b = $inline$int(3); + a = 8; + b = 3; c = a % b; return c; } - /// CHECK-START: long Main.LongRemainder() constant_folding$after_inlining (before) + /// CHECK-START: long Main.LongRemainder() constant_folding (before) /// CHECK-DAG: <<Const8:j\d+>> LongConstant 8 /// CHECK-DAG: <<Const3:j\d+>> LongConstant 3 /// CHECK-DAG: <<Div0Chk:j\d+>> DivZeroCheck [<<Const3>>] /// CHECK-DAG: <<Rem:j\d+>> Rem [<<Const8>>,<<Div0Chk>>] /// CHECK-DAG: Return [<<Rem>>] - /// CHECK-START: long Main.LongRemainder() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongRemainder() constant_folding (after) /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: long Main.LongRemainder() constant_folding$after_inlining (after) + /// CHECK-START: long Main.LongRemainder() constant_folding (after) /// CHECK-NOT: DivZeroCheck /// CHECK-NOT: Rem public static long LongRemainder() { long a, b, c; - a = $inline$long(8L); - b = $inline$long(3L); + a = 8L; + b = 3L; c = a % b; return c; } - /// CHECK-START: float Main.FloatRemainder() constant_folding$after_inlining (before) + /// CHECK-START: float Main.FloatRemainder() constant_folding (before) /// CHECK-DAG: <<Const8:f\d+>> FloatConstant 8 /// CHECK-DAG: <<Const2P5:f\d+>> FloatConstant 2.5 /// CHECK-DAG: <<Rem:f\d+>> Rem [<<Const8>>,<<Const2P5>>] /// CHECK-DAG: Return [<<Rem>>] - /// CHECK-START: float Main.FloatRemainder() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatRemainder() constant_folding (after) /// CHECK-DAG: <<Const0P5:f\d+>> FloatConstant 0.5 /// CHECK-DAG: Return [<<Const0P5>>] - /// CHECK-START: float Main.FloatRemainder() constant_folding$after_inlining (after) + /// CHECK-START: float Main.FloatRemainder() constant_folding (after) /// CHECK-NOT: Rem public static float FloatRemainder() { float a, b, c; - a = $inline$float(8F); - b = $inline$float(2.5F); + a = 8F; + b = 2.5F; c = a % b; return c; } - /// CHECK-START: double Main.DoubleRemainder() constant_folding$after_inlining (before) + /// CHECK-START: double Main.DoubleRemainder() constant_folding (before) /// CHECK-DAG: <<Const8:d\d+>> DoubleConstant 8 /// CHECK-DAG: <<Const2P5:d\d+>> DoubleConstant 2.5 /// CHECK-DAG: <<Rem:d\d+>> Rem [<<Const8>>,<<Const2P5>>] /// CHECK-DAG: Return [<<Rem>>] - /// CHECK-START: double Main.DoubleRemainder() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleRemainder() constant_folding (after) /// CHECK-DAG: <<Const0P5:d\d+>> DoubleConstant 0.5 /// CHECK-DAG: Return [<<Const0P5>>] - /// CHECK-START: double Main.DoubleRemainder() constant_folding$after_inlining (after) + /// CHECK-START: double Main.DoubleRemainder() constant_folding (after) /// CHECK-NOT: Rem public static double DoubleRemainder() { double a, b, c; - a = $inline$double(8D); - b = $inline$double(2.5D); + a = 8D; + b = 2.5D; c = a % b; return c; } @@ -701,42 +682,42 @@ public class Main { * Exercise constant folding on left shift. */ - /// CHECK-START: int Main.ShlIntLong() constant_folding$after_inlining (before) + /// CHECK-START: int Main.ShlIntLong() constant_folding (before) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 /// CHECK-DAG: <<Const2L:j\d+>> LongConstant 2 /// CHECK-DAG: <<TypeConv:i\d+>> TypeConversion [<<Const2L>>] /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Const1>>,<<TypeConv>>] /// CHECK-DAG: Return [<<Shl>>] - /// CHECK-START: int Main.ShlIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ShlIntLong() constant_folding (after) /// CHECK-DAG: <<Const4:i\d+>> IntConstant 4 /// CHECK-DAG: Return [<<Const4>>] - /// CHECK-START: int Main.ShlIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ShlIntLong() constant_folding (after) /// CHECK-NOT: Shl public static int ShlIntLong() { - int lhs = $inline$int(1); - long rhs = $inline$long(2L); + int lhs = 1; + long rhs = 2; return lhs << rhs; } - /// CHECK-START: long Main.ShlLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.ShlLongInt() constant_folding (before) /// CHECK-DAG: <<Const3L:j\d+>> LongConstant 3 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Shl:j\d+>> Shl [<<Const3L>>,<<Const2>>] /// CHECK-DAG: Return [<<Shl>>] - /// CHECK-START: long Main.ShlLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ShlLongInt() constant_folding (after) /// CHECK-DAG: <<Const12L:j\d+>> LongConstant 12 /// CHECK-DAG: Return [<<Const12L>>] - /// CHECK-START: long Main.ShlLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ShlLongInt() constant_folding (after) /// CHECK-NOT: Shl public static long ShlLongInt() { - long lhs = $inline$long(3L); - int rhs = $inline$int(2); + long lhs = 3; + int rhs = 2; return lhs << rhs; } @@ -745,42 +726,42 @@ public class Main { * Exercise constant folding on right shift. */ - /// CHECK-START: int Main.ShrIntLong() constant_folding$after_inlining (before) + /// CHECK-START: int Main.ShrIntLong() constant_folding (before) /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<Const2L:j\d+>> LongConstant 2 /// CHECK-DAG: <<TypeConv:i\d+>> TypeConversion [<<Const2L>>] /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Const7>>,<<TypeConv>>] /// CHECK-DAG: Return [<<Shr>>] - /// CHECK-START: int Main.ShrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ShrIntLong() constant_folding (after) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 /// CHECK-DAG: Return [<<Const1>>] - /// CHECK-START: int Main.ShrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ShrIntLong() constant_folding (after) /// CHECK-NOT: Shr public static int ShrIntLong() { - int lhs = $inline$int(7); - long rhs = $inline$long(2L); + int lhs = 7; + long rhs = 2; return lhs >> rhs; } - /// CHECK-START: long Main.ShrLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.ShrLongInt() constant_folding (before) /// CHECK-DAG: <<Const9L:j\d+>> LongConstant 9 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Shr:j\d+>> Shr [<<Const9L>>,<<Const2>>] /// CHECK-DAG: Return [<<Shr>>] - /// CHECK-START: long Main.ShrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ShrLongInt() constant_folding (after) /// CHECK-DAG: <<Const2L:j\d+>> LongConstant 2 /// CHECK-DAG: Return [<<Const2L>>] - /// CHECK-START: long Main.ShrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ShrLongInt() constant_folding (after) /// CHECK-NOT: Shr public static long ShrLongInt() { - long lhs = $inline$long(9); - int rhs = $inline$int(2); + long lhs = 9; + int rhs = 2; return lhs >> rhs; } @@ -789,42 +770,42 @@ public class Main { * Exercise constant folding on unsigned right shift. */ - /// CHECK-START: int Main.UShrIntLong() constant_folding$after_inlining (before) + /// CHECK-START: int Main.UShrIntLong() constant_folding (before) /// CHECK-DAG: <<ConstM7:i\d+>> IntConstant -7 /// CHECK-DAG: <<Const2L:j\d+>> LongConstant 2 /// CHECK-DAG: <<TypeConv:i\d+>> TypeConversion [<<Const2L>>] /// CHECK-DAG: <<UShr:i\d+>> UShr [<<ConstM7>>,<<TypeConv>>] /// CHECK-DAG: Return [<<UShr>>] - /// CHECK-START: int Main.UShrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.UShrIntLong() constant_folding (after) /// CHECK-DAG: <<ConstRes:i\d+>> IntConstant 1073741822 /// CHECK-DAG: Return [<<ConstRes>>] - /// CHECK-START: int Main.UShrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: int Main.UShrIntLong() constant_folding (after) /// CHECK-NOT: UShr public static int UShrIntLong() { - int lhs = $inline$int(-7); - long rhs = $inline$long(2L); + int lhs = -7; + long rhs = 2; return lhs >>> rhs; } - /// CHECK-START: long Main.UShrLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.UShrLongInt() constant_folding (before) /// CHECK-DAG: <<ConstM9L:j\d+>> LongConstant -9 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<UShr:j\d+>> UShr [<<ConstM9L>>,<<Const2>>] /// CHECK-DAG: Return [<<UShr>>] - /// CHECK-START: long Main.UShrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.UShrLongInt() constant_folding (after) /// CHECK-DAG: <<ConstRes:j\d+>> LongConstant 4611686018427387901 /// CHECK-DAG: Return [<<ConstRes>>] - /// CHECK-START: long Main.UShrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.UShrLongInt() constant_folding (after) /// CHECK-NOT: UShr public static long UShrLongInt() { - long lhs = $inline$long(-9); - int rhs = $inline$int(2); + long lhs = -9; + int rhs = 2; return lhs >>> rhs; } @@ -833,43 +814,43 @@ public class Main { * Exercise constant folding on logical and. */ - /// CHECK-START: long Main.AndIntLong() constant_folding$after_inlining (before) + /// CHECK-START: long Main.AndIntLong() constant_folding (before) /// CHECK-DAG: <<Const10:i\d+>> IntConstant 10 /// CHECK-DAG: <<Const3L:j\d+>> LongConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const10>>] /// CHECK-DAG: <<And:j\d+>> And [<<TypeConv>>,<<Const3L>>] /// CHECK-DAG: Return [<<And>>] - /// CHECK-START: long Main.AndIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.AndIntLong() constant_folding (after) /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: long Main.AndIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.AndIntLong() constant_folding (after) /// CHECK-NOT: And public static long AndIntLong() { - int lhs = $inline$int(10); - long rhs = $inline$long(3L); + int lhs = 10; + long rhs = 3; return lhs & rhs; } - /// CHECK-START: long Main.AndLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.AndLongInt() constant_folding (before) /// CHECK-DAG: <<Const10L:j\d+>> LongConstant 10 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const3>>] /// CHECK-DAG: <<And:j\d+>> And [<<TypeConv>>,<<Const10L>>] /// CHECK-DAG: Return [<<And>>] - /// CHECK-START: long Main.AndLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.AndLongInt() constant_folding (after) /// CHECK-DAG: <<Const2:j\d+>> LongConstant 2 /// CHECK-DAG: Return [<<Const2>>] - /// CHECK-START: long Main.AndLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.AndLongInt() constant_folding (after) /// CHECK-NOT: And public static long AndLongInt() { - long lhs = $inline$long(10L); - int rhs = $inline$int(3); + long lhs = 10; + int rhs = 3; return lhs & rhs; } @@ -878,43 +859,43 @@ public class Main { * Exercise constant folding on logical or. */ - /// CHECK-START: long Main.OrIntLong() constant_folding$after_inlining (before) + /// CHECK-START: long Main.OrIntLong() constant_folding (before) /// CHECK-DAG: <<Const10:i\d+>> IntConstant 10 /// CHECK-DAG: <<Const3L:j\d+>> LongConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const10>>] /// CHECK-DAG: <<Or:j\d+>> Or [<<TypeConv>>,<<Const3L>>] /// CHECK-DAG: Return [<<Or>>] - /// CHECK-START: long Main.OrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.OrIntLong() constant_folding (after) /// CHECK-DAG: <<Const11:j\d+>> LongConstant 11 /// CHECK-DAG: Return [<<Const11>>] - /// CHECK-START: long Main.OrIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.OrIntLong() constant_folding (after) /// CHECK-NOT: Or public static long OrIntLong() { - int lhs = $inline$int(10); - long rhs = $inline$long(3L); + int lhs = 10; + long rhs = 3; return lhs | rhs; } - /// CHECK-START: long Main.OrLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.OrLongInt() constant_folding (before) /// CHECK-DAG: <<Const10L:j\d+>> LongConstant 10 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const3>>] /// CHECK-DAG: <<Or:j\d+>> Or [<<TypeConv>>,<<Const10L>>] /// CHECK-DAG: Return [<<Or>>] - /// CHECK-START: long Main.OrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.OrLongInt() constant_folding (after) /// CHECK-DAG: <<Const11:j\d+>> LongConstant 11 /// CHECK-DAG: Return [<<Const11>>] - /// CHECK-START: long Main.OrLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.OrLongInt() constant_folding (after) /// CHECK-NOT: Or public static long OrLongInt() { - long lhs = $inline$long(10L); - int rhs = $inline$int(3); + long lhs = 10; + int rhs = 3; return lhs | rhs; } @@ -923,43 +904,43 @@ public class Main { * Exercise constant folding on logical exclusive or. */ - /// CHECK-START: long Main.XorIntLong() constant_folding$after_inlining (before) + /// CHECK-START: long Main.XorIntLong() constant_folding (before) /// CHECK-DAG: <<Const10:i\d+>> IntConstant 10 /// CHECK-DAG: <<Const3L:j\d+>> LongConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const10>>] /// CHECK-DAG: <<Xor:j\d+>> Xor [<<TypeConv>>,<<Const3L>>] /// CHECK-DAG: Return [<<Xor>>] - /// CHECK-START: long Main.XorIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.XorIntLong() constant_folding (after) /// CHECK-DAG: <<Const9:j\d+>> LongConstant 9 /// CHECK-DAG: Return [<<Const9>>] - /// CHECK-START: long Main.XorIntLong() constant_folding$after_inlining (after) + /// CHECK-START: long Main.XorIntLong() constant_folding (after) /// CHECK-NOT: Xor public static long XorIntLong() { - int lhs = $inline$int(10); - long rhs = $inline$long(3L); + int lhs = 10; + long rhs = 3; return lhs ^ rhs; } - /// CHECK-START: long Main.XorLongInt() constant_folding$after_inlining (before) + /// CHECK-START: long Main.XorLongInt() constant_folding (before) /// CHECK-DAG: <<Const10L:j\d+>> LongConstant 10 /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<TypeConv:j\d+>> TypeConversion [<<Const3>>] /// CHECK-DAG: <<Xor:j\d+>> Xor [<<TypeConv>>,<<Const10L>>] /// CHECK-DAG: Return [<<Xor>>] - /// CHECK-START: long Main.XorLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.XorLongInt() constant_folding (after) /// CHECK-DAG: <<Const9:j\d+>> LongConstant 9 /// CHECK-DAG: Return [<<Const9>>] - /// CHECK-START: long Main.XorLongInt() constant_folding$after_inlining (after) + /// CHECK-START: long Main.XorLongInt() constant_folding (after) /// CHECK-NOT: Xor public static long XorLongInt() { - long lhs = $inline$long(10L); - int rhs = $inline$int(3); + long lhs = 10; + int rhs = 3; return lhs ^ rhs; } @@ -968,23 +949,23 @@ public class Main { * Exercise constant folding on constant (static) condition. */ - /// CHECK-START: int Main.StaticCondition() constant_folding$after_inlining (before) + /// CHECK-START: int Main.StaticCondition() constant_folding (before) /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Cond:z\d+>> GreaterThanOrEqual [<<Const7>>,<<Const2>>] - /// CHECK-DAG: Select [{{i\d+}},{{i\d+}},<<Cond>>] + /// CHECK-DAG: If [<<Cond>>] - /// CHECK-START: int Main.StaticCondition() constant_folding$after_inlining (after) + /// CHECK-START: int Main.StaticCondition() constant_folding (after) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 - /// CHECK-DAG: Select [{{i\d+}},{{i\d+}},<<Const1>>] + /// CHECK-DAG: If [<<Const1>>] - /// CHECK-START: int Main.StaticCondition() constant_folding$after_inlining (after) + /// CHECK-START: int Main.StaticCondition() constant_folding (after) /// CHECK-NOT: GreaterThanOrEqual public static int StaticCondition() { int a, b, c; - a = $inline$int(7); - b = $inline$int(2); + a = 7; + b = 2; if (a < b) c = a + b; else @@ -1029,30 +1010,28 @@ public class Main { * (forward) post-order traversal of the the dominator tree. */ - /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding$after_inlining (before) - /// CHECK-DAG: <<Cond:z\d+>> ParameterValue + /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding (before) /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 /// CHECK-DAG: <<Const5:i\d+>> IntConstant 5 /// CHECK-DAG: <<Add:i\d+>> Add [<<Const5>>,<<Const2>>] /// CHECK-DAG: <<Sub:i\d+>> Sub [<<Const5>>,<<Const2>>] - /// CHECK-DAG: <<Phi:i\d+>> Select [<<Sub>>,<<Add>>,<<Cond>>] + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Add>>,<<Sub>>] /// CHECK-DAG: Return [<<Phi>>] - /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding$after_inlining (after) - /// CHECK-DAG: <<Cond:z\d+>> ParameterValue + /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding (after) /// CHECK-DAG: <<Const3:i\d+>> IntConstant 3 /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 - /// CHECK-DAG: <<Phi:i\d+>> Select [<<Const3>>,<<Const7>>,<<Cond>>] + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Const7>>,<<Const3>>] /// CHECK-DAG: Return [<<Phi>>] - /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding$after_inlining (after) + /// CHECK-START: int Main.JumpsAndConditionals(boolean) constant_folding (after) /// CHECK-NOT: Add /// CHECK-NOT: Sub public static int JumpsAndConditionals(boolean cond) { int a, b, c; - a = $inline$int(5); - b = $inline$int(2); + a = 5; + b = 2; if (cond) c = a + b; else @@ -1328,259 +1307,207 @@ public class Main { /** - * Test optimizations of comparisons with null yielding a constant result. - */ - - /// CHECK-START: boolean Main.ConstStringEqualsNull() constant_folding$after_inlining (before) - /// CHECK-DAG: <<ConstStr:l\d+>> LoadString - /// CHECK-DAG: <<Null:l\d+>> NullConstant - /// CHECK-DAG: <<Eq:z\d+>> Equal [<<ConstStr>>,<<Null>>] - /// CHECK-DAG: If [<<Eq>>] - - /// CHECK-START: boolean Main.ConstStringEqualsNull() constant_folding$after_inlining (after) - /// CHECK-DAG: <<False:i\d+>> IntConstant 0 - /// CHECK-DAG: If [<<False>>] - - /// CHECK-START: boolean Main.ConstStringEqualsNull() constant_folding$after_inlining (after) - /// CHECK-NOT: Equal - - public static boolean ConstStringEqualsNull() { - // Due to Jack emitting code using the opposite condition, use != to generate Equal. - if ($inline$ConstString() != null) { - return false; - } else { - return true; - } - } - - /// CHECK-START: boolean Main.ConstStringNotEqualsNull() constant_folding$after_inlining (before) - /// CHECK-DAG: <<ConstStr:l\d+>> LoadString - /// CHECK-DAG: <<Null:l\d+>> NullConstant - /// CHECK-DAG: <<Ne:z\d+>> NotEqual [<<ConstStr>>,<<Null>>] - /// CHECK-DAG: If [<<Ne>>] - - /// CHECK-START: boolean Main.ConstStringNotEqualsNull() constant_folding$after_inlining (after) - /// CHECK-DAG: <<True:i\d+>> IntConstant 1 - /// CHECK-DAG: If [<<True>>] - - /// CHECK-START: boolean Main.ConstStringNotEqualsNull() constant_folding$after_inlining (after) - /// CHECK-NOT: NotEqual - - public static boolean ConstStringNotEqualsNull() { - // Due to Jack emitting code using the opposite condition, use == to generate NotEqual. - if ($inline$ConstString() == null) { - return false; - } else { - return true; - } - } - - public static String $inline$ConstString() { - return ""; - } - - /** * Exercise constant folding on type conversions. */ - /// CHECK-START: int Main.ReturnInt33() constant_folding$after_inlining (before) + /// CHECK-START: int Main.ReturnInt33() constant_folding (before) /// CHECK-DAG: <<Const33:j\d+>> LongConstant 33 /// CHECK-DAG: <<Convert:i\d+>> TypeConversion [<<Const33>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: int Main.ReturnInt33() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnInt33() constant_folding (after) /// CHECK-DAG: <<Const33:i\d+>> IntConstant 33 /// CHECK-DAG: Return [<<Const33>>] - /// CHECK-START: int Main.ReturnInt33() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnInt33() constant_folding (after) /// CHECK-NOT: TypeConversion public static int ReturnInt33() { - long imm = $inline$long(33L); + long imm = 33L; return (int) imm; } - /// CHECK-START: int Main.ReturnIntMax() constant_folding$after_inlining (before) + /// CHECK-START: int Main.ReturnIntMax() constant_folding (before) /// CHECK-DAG: <<ConstMax:f\d+>> FloatConstant 1e+34 /// CHECK-DAG: <<Convert:i\d+>> TypeConversion [<<ConstMax>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: int Main.ReturnIntMax() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnIntMax() constant_folding (after) /// CHECK-DAG: <<ConstMax:i\d+>> IntConstant 2147483647 /// CHECK-DAG: Return [<<ConstMax>>] - /// CHECK-START: int Main.ReturnIntMax() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnIntMax() constant_folding (after) /// CHECK-NOT: TypeConversion public static int ReturnIntMax() { - float imm = $inline$float(1.0e34f); + float imm = 1.0e34f; return (int) imm; } - /// CHECK-START: int Main.ReturnInt0() constant_folding$after_inlining (before) + /// CHECK-START: int Main.ReturnInt0() constant_folding (before) /// CHECK-DAG: <<ConstNaN:d\d+>> DoubleConstant nan /// CHECK-DAG: <<Convert:i\d+>> TypeConversion [<<ConstNaN>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: int Main.ReturnInt0() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnInt0() constant_folding (after) /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 /// CHECK-DAG: Return [<<Const0>>] - /// CHECK-START: int Main.ReturnInt0() constant_folding$after_inlining (after) + /// CHECK-START: int Main.ReturnInt0() constant_folding (after) /// CHECK-NOT: TypeConversion public static int ReturnInt0() { - double imm = $inline$double(Double.NaN); + double imm = Double.NaN; return (int) imm; } - /// CHECK-START: long Main.ReturnLong33() constant_folding$after_inlining (before) + /// CHECK-START: long Main.ReturnLong33() constant_folding (before) /// CHECK-DAG: <<Const33:i\d+>> IntConstant 33 /// CHECK-DAG: <<Convert:j\d+>> TypeConversion [<<Const33>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: long Main.ReturnLong33() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong33() constant_folding (after) /// CHECK-DAG: <<Const33:j\d+>> LongConstant 33 /// CHECK-DAG: Return [<<Const33>>] - /// CHECK-START: long Main.ReturnLong33() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong33() constant_folding (after) /// CHECK-NOT: TypeConversion public static long ReturnLong33() { - int imm = $inline$int(33); + int imm = 33; return (long) imm; } - /// CHECK-START: long Main.ReturnLong34() constant_folding$after_inlining (before) + /// CHECK-START: long Main.ReturnLong34() constant_folding (before) /// CHECK-DAG: <<Const34:f\d+>> FloatConstant 34 /// CHECK-DAG: <<Convert:j\d+>> TypeConversion [<<Const34>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: long Main.ReturnLong34() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong34() constant_folding (after) /// CHECK-DAG: <<Const34:j\d+>> LongConstant 34 /// CHECK-DAG: Return [<<Const34>>] - /// CHECK-START: long Main.ReturnLong34() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong34() constant_folding (after) /// CHECK-NOT: TypeConversion public static long ReturnLong34() { - float imm = $inline$float(34.0f); + float imm = 34.0f; return (long) imm; } - /// CHECK-START: long Main.ReturnLong0() constant_folding$after_inlining (before) + /// CHECK-START: long Main.ReturnLong0() constant_folding (before) /// CHECK-DAG: <<ConstNaN:d\d+>> DoubleConstant nan /// CHECK-DAG: <<Convert:j\d+>> TypeConversion [<<ConstNaN>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: long Main.ReturnLong0() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong0() constant_folding (after) /// CHECK-DAG: <<Const0:j\d+>> LongConstant 0 /// CHECK-DAG: Return [<<Const0>>] - /// CHECK-START: long Main.ReturnLong0() constant_folding$after_inlining (after) + /// CHECK-START: long Main.ReturnLong0() constant_folding (after) /// CHECK-NOT: TypeConversion public static long ReturnLong0() { - double imm = $inline$double(-Double.NaN); + double imm = -Double.NaN; return (long) imm; } - /// CHECK-START: float Main.ReturnFloat33() constant_folding$after_inlining (before) + /// CHECK-START: float Main.ReturnFloat33() constant_folding (before) /// CHECK-DAG: <<Const33:i\d+>> IntConstant 33 /// CHECK-DAG: <<Convert:f\d+>> TypeConversion [<<Const33>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: float Main.ReturnFloat33() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat33() constant_folding (after) /// CHECK-DAG: <<Const33:f\d+>> FloatConstant 33 /// CHECK-DAG: Return [<<Const33>>] - /// CHECK-START: float Main.ReturnFloat33() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat33() constant_folding (after) /// CHECK-NOT: TypeConversion public static float ReturnFloat33() { - int imm = $inline$int(33); + int imm = 33; return (float) imm; } - /// CHECK-START: float Main.ReturnFloat34() constant_folding$after_inlining (before) + /// CHECK-START: float Main.ReturnFloat34() constant_folding (before) /// CHECK-DAG: <<Const34:j\d+>> LongConstant 34 /// CHECK-DAG: <<Convert:f\d+>> TypeConversion [<<Const34>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: float Main.ReturnFloat34() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat34() constant_folding (after) /// CHECK-DAG: <<Const34:f\d+>> FloatConstant 34 /// CHECK-DAG: Return [<<Const34>>] - /// CHECK-START: float Main.ReturnFloat34() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat34() constant_folding (after) /// CHECK-NOT: TypeConversion public static float ReturnFloat34() { - long imm = $inline$long(34L); + long imm = 34L; return (float) imm; } - /// CHECK-START: float Main.ReturnFloat99P25() constant_folding$after_inlining (before) + /// CHECK-START: float Main.ReturnFloat99P25() constant_folding (before) /// CHECK-DAG: <<Const:d\d+>> DoubleConstant 99.25 /// CHECK-DAG: <<Convert:f\d+>> TypeConversion [<<Const>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: float Main.ReturnFloat99P25() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat99P25() constant_folding (after) /// CHECK-DAG: <<Const:f\d+>> FloatConstant 99.25 /// CHECK-DAG: Return [<<Const>>] - /// CHECK-START: float Main.ReturnFloat99P25() constant_folding$after_inlining (after) + /// CHECK-START: float Main.ReturnFloat99P25() constant_folding (after) /// CHECK-NOT: TypeConversion public static float ReturnFloat99P25() { - double imm = $inline$double(99.25); + double imm = 99.25; return (float) imm; } - /// CHECK-START: double Main.ReturnDouble33() constant_folding$after_inlining (before) + /// CHECK-START: double Main.ReturnDouble33() constant_folding (before) /// CHECK-DAG: <<Const33:i\d+>> IntConstant 33 /// CHECK-DAG: <<Convert:d\d+>> TypeConversion [<<Const33>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: double Main.ReturnDouble33() constant_folding$after_inlining (after) + /// CHECK-START: double Main.ReturnDouble33() constant_folding (after) /// CHECK-DAG: <<Const33:d\d+>> DoubleConstant 33 /// CHECK-DAG: Return [<<Const33>>] public static double ReturnDouble33() { - int imm = $inline$int(33); + int imm = 33; return (double) imm; } - /// CHECK-START: double Main.ReturnDouble34() constant_folding$after_inlining (before) + /// CHECK-START: double Main.ReturnDouble34() constant_folding (before) /// CHECK-DAG: <<Const34:j\d+>> LongConstant 34 /// CHECK-DAG: <<Convert:d\d+>> TypeConversion [<<Const34>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: double Main.ReturnDouble34() constant_folding$after_inlining (after) + /// CHECK-START: double Main.ReturnDouble34() constant_folding (after) /// CHECK-DAG: <<Const34:d\d+>> DoubleConstant 34 /// CHECK-DAG: Return [<<Const34>>] - /// CHECK-START: double Main.ReturnDouble34() constant_folding$after_inlining (after) + /// CHECK-START: double Main.ReturnDouble34() constant_folding (after) /// CHECK-NOT: TypeConversion public static double ReturnDouble34() { - long imm = $inline$long(34L); + long imm = 34L; return (double) imm; } - /// CHECK-START: double Main.ReturnDouble99P25() constant_folding$after_inlining (before) + /// CHECK-START: double Main.ReturnDouble99P25() constant_folding (before) /// CHECK-DAG: <<Const:f\d+>> FloatConstant 99.25 /// CHECK-DAG: <<Convert:d\d+>> TypeConversion [<<Const>>] /// CHECK-DAG: Return [<<Convert>>] - /// CHECK-START: double Main.ReturnDouble99P25() constant_folding$after_inlining (after) + /// CHECK-START: double Main.ReturnDouble99P25() constant_folding (after) /// CHECK-DAG: <<Const:d\d+>> DoubleConstant 99.25 /// CHECK-DAG: Return [<<Const>>] - /// CHECK-START: double Main.ReturnDouble99P25() constant_folding$after_inlining (after) + /// CHECK-START: double Main.ReturnDouble99P25() constant_folding (after) /// CHECK-NOT: TypeConversion public static double ReturnDouble99P25() { - float imm = $inline$float(99.25f); + float imm = 99.25f; return (double) imm; } @@ -1659,9 +1586,6 @@ public class Main { assertFalse(CmpFloatGreaterThanNaN(arbitrary)); assertFalse(CmpDoubleLessThanNaN(arbitrary)); - assertFalse(ConstStringEqualsNull()); - assertTrue(ConstStringNotEqualsNull()); - Main main = new Main(); assertIntEquals(1, main.smaliCmpLongConstants()); assertIntEquals(-1, main.smaliCmpGtFloatConstants()); diff --git a/test/450-checker-types/build b/test/450-checker-types/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/450-checker-types/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/450-checker-types/smali/SmaliTests.smali b/test/450-checker-types/smali/SmaliTests.smali deleted file mode 100644 index 6a3122e41b..0000000000 --- a/test/450-checker-types/smali/SmaliTests.smali +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -.class public LSmaliTests; -.super Ljava/lang/Object; - -## CHECK-START: void SmaliTests.testInstanceOf_EQ0_NotInlined(java.lang.Object) builder (after) -## CHECK-DAG: <<Cst0:i\d+>> IntConstant 0 -## CHECK-DAG: <<IOf:z\d+>> InstanceOf -## CHECK-DAG: Equal [<<IOf>>,<<Cst0>>] - -## CHECK-START: void SmaliTests.testInstanceOf_EQ0_NotInlined(java.lang.Object) instruction_simplifier (before) -## CHECK: CheckCast - -## CHECK-START: void SmaliTests.testInstanceOf_EQ0_NotInlined(java.lang.Object) instruction_simplifier (after) -## CHECK-NOT: CheckCast - -.method public static testInstanceOf_EQ0_NotInlined(Ljava/lang/Object;)V - .registers 3 - - const v0, 0x0 - instance-of v1, p0, LSubclassC; - if-eq v1, v0, :return - - check-cast p0, LSubclassC; - invoke-virtual {p0}, LSubclassC;->$noinline$g()V - - :return - return-void - -.end method - -## CHECK-START: void SmaliTests.testInstanceOf_EQ1_NotInlined(java.lang.Object) builder (after) -## CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 -## CHECK-DAG: <<IOf:z\d+>> InstanceOf -## CHECK-DAG: Equal [<<IOf>>,<<Cst1>>] - -## CHECK-START: void SmaliTests.testInstanceOf_EQ1_NotInlined(java.lang.Object) instruction_simplifier (before) -## CHECK: CheckCast - -## CHECK-START: void SmaliTests.testInstanceOf_EQ1_NotInlined(java.lang.Object) instruction_simplifier (after) -## CHECK-NOT: CheckCast - -.method public static testInstanceOf_EQ1_NotInlined(Ljava/lang/Object;)V - .registers 3 - - const v0, 0x1 - instance-of v1, p0, LSubclassC; - if-eq v1, v0, :invoke - return-void - - :invoke - check-cast p0, LSubclassC; - invoke-virtual {p0}, LSubclassC;->$noinline$g()V - return-void - -.end method - -## CHECK-START: void SmaliTests.testInstanceOf_NE0_NotInlined(java.lang.Object) builder (after) -## CHECK-DAG: <<Cst0:i\d+>> IntConstant 0 -## CHECK-DAG: <<IOf:z\d+>> InstanceOf -## CHECK-DAG: NotEqual [<<IOf>>,<<Cst0>>] - -## CHECK-START: void SmaliTests.testInstanceOf_NE0_NotInlined(java.lang.Object) instruction_simplifier (before) -## CHECK: CheckCast - -## CHECK-START: void SmaliTests.testInstanceOf_NE0_NotInlined(java.lang.Object) instruction_simplifier (after) -## CHECK-NOT: CheckCast - -.method public static testInstanceOf_NE0_NotInlined(Ljava/lang/Object;)V - .registers 3 - - const v0, 0x0 - instance-of v1, p0, LSubclassC; - if-ne v1, v0, :invoke - return-void - - :invoke - check-cast p0, LSubclassC; - invoke-virtual {p0}, LSubclassC;->$noinline$g()V - return-void - -.end method - -## CHECK-START: void SmaliTests.testInstanceOf_NE1_NotInlined(java.lang.Object) builder (after) -## CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 -## CHECK-DAG: <<IOf:z\d+>> InstanceOf -## CHECK-DAG: NotEqual [<<IOf>>,<<Cst1>>] - -## CHECK-START: void SmaliTests.testInstanceOf_NE1_NotInlined(java.lang.Object) instruction_simplifier (before) -## CHECK: CheckCast - -## CHECK-START: void SmaliTests.testInstanceOf_NE1_NotInlined(java.lang.Object) instruction_simplifier (after) -## CHECK-NOT: CheckCast - -.method public static testInstanceOf_NE1_NotInlined(Ljava/lang/Object;)V - .registers 3 - - const v0, 0x1 - instance-of v1, p0, LSubclassC; - if-ne v1, v0, :return - - check-cast p0, LSubclassC; - invoke-virtual {p0}, LSubclassC;->$noinline$g()V - - :return - return-void - -.end method diff --git a/test/450-checker-types/src/Main.java b/test/450-checker-types/src/Main.java index ea8609ee5e..ae0fdbe576 100644 --- a/test/450-checker-types/src/Main.java +++ b/test/450-checker-types/src/Main.java @@ -210,6 +210,58 @@ public class Main { public static boolean $inline$InstanceofSubclassB(Object o) { return o instanceof SubclassB; } public static boolean $inline$InstanceofSubclassC(Object o) { return o instanceof SubclassC; } + /// CHECK-START: void Main.testInstanceOf_NotInlined(java.lang.Object) builder (after) + /// CHECK-DAG: <<Cst0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<IOf1:z\d+>> InstanceOf + /// CHECK-DAG: NotEqual [<<IOf1>>,<<Cst1>>] + /// CHECK-DAG: <<IOf2:z\d+>> InstanceOf + /// CHECK-DAG: Equal [<<IOf2>>,<<Cst0>>] + + /// CHECK-START: void Main.testInstanceOf_NotInlined(java.lang.Object) instruction_simplifier (before) + /// CHECK: CheckCast + /// CHECK: CheckCast + /// CHECK-NOT: CheckCast + + /// CHECK-START: void Main.testInstanceOf_NotInlined(java.lang.Object) instruction_simplifier (after) + /// CHECK-NOT: CheckCast + public void testInstanceOf_NotInlined(Object o) { + if ((o instanceof SubclassC) == true) { + ((SubclassC)o).$noinline$g(); + } + if ((o instanceof SubclassB) != false) { + ((SubclassB)o).$noinline$g(); + } + } + + /// CHECK-START: void Main.testNotInstanceOf_NotInlined(java.lang.Object) builder (after) + /// CHECK-DAG: <<Cst0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Cst1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<IOf1:z\d+>> InstanceOf + /// CHECK-DAG: Equal [<<IOf1>>,<<Cst1>>] + /// CHECK-DAG: <<IOf2:z\d+>> InstanceOf + /// CHECK-DAG: NotEqual [<<IOf2>>,<<Cst0>>] + + /// CHECK-START: void Main.testNotInstanceOf_NotInlined(java.lang.Object) instruction_simplifier (before) + /// CHECK: CheckCast + /// CHECK: CheckCast + /// CHECK-NOT: CheckCast + + /// CHECK-START: void Main.testNotInstanceOf_NotInlined(java.lang.Object) instruction_simplifier (after) + /// CHECK-NOT: CheckCast + public void testNotInstanceOf_NotInlined(Object o) { + if ((o instanceof SubclassC) != true) { + // Empty branch to flip the condition. + } else { + ((SubclassC)o).$noinline$g(); + } + if ((o instanceof SubclassB) == false) { + // Empty branch to flip the condition. + } else { + ((SubclassB)o).$noinline$g(); + } + } + /// CHECK-START: void Main.testInstanceOf_Inlined(java.lang.Object) inliner (after) /// CHECK-DAG: <<IOf:z\d+>> InstanceOf /// CHECK-DAG: If [<<IOf>>] @@ -634,7 +686,7 @@ public class Main { /// CHECK-DAG: <<Null:l\d+>> NullConstant /// CHECK-DAG: <<Phi:l\d+>> Phi [<<Arg>>,<<Null>>] klass:SubclassA /// CHECK-DAG: <<NCPhi:l\d+>> NullCheck [<<Phi>>] - /// CHECK-DAG: InvokeVirtual [<<NCPhi>>] method_name:Super.hashCode + /// CHECK-DAG: InvokeVirtual [<<NCPhi>>] method_name:java.lang.Object.hashCode public void testThisArgumentMoreSpecific(boolean cond) { // Inlining method from Super will build it with `this` typed as Super. @@ -655,7 +707,7 @@ public class Main { /// CHECK-START: void Main.testExplicitArgumentMoreSpecific(SubclassA) inliner (after) /// CHECK-DAG: <<Arg:l\d+>> ParameterValue klass:SubclassA /// CHECK-DAG: <<NCArg:l\d+>> NullCheck [<<Arg>>] klass:SubclassA - /// CHECK-DAG: InvokeVirtual [<<NCArg>>] method_name:Super.hashCode + /// CHECK-DAG: InvokeVirtual [<<NCArg>>] method_name:java.lang.Object.hashCode public void testExplicitArgumentMoreSpecific(SubclassA obj) { // Inlining a method will build it with reference types from its signature, 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/463-checker-boolean-simplifier/build b/test/463-checker-boolean-simplifier/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/463-checker-boolean-simplifier/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/463-checker-boolean-simplifier/smali/BooleanNotDx.smali b/test/463-checker-boolean-simplifier/smali/BooleanNotDx.smali deleted file mode 100644 index 765d0eb663..0000000000 --- a/test/463-checker-boolean-simplifier/smali/BooleanNotDx.smali +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -.class public LBooleanNotSmali; -.super Ljava/lang/Object; - -# -# Elementary test negating a boolean. Verifies that blocks are merged and -# empty branches removed. -# - -## CHECK-START: boolean BooleanNotSmali.BooleanNot(boolean) select_generator (before) -## CHECK-DAG: <<Param:z\d+>> ParameterValue -## CHECK-DAG: <<Const0:i\d+>> IntConstant 0 -## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 -## CHECK-DAG: If [<<Param>>] -## CHECK-DAG: <<Phi:i\d+>> Phi [<<Const0>>,<<Const1>>] -## CHECK-DAG: Return [<<Phi>>] - -## CHECK-START: boolean BooleanNotSmali.BooleanNot(boolean) select_generator (before) -## CHECK: Goto -## CHECK: Goto -## CHECK: Goto -## CHECK-NOT: Goto - -## CHECK-START: boolean BooleanNotSmali.BooleanNot(boolean) select_generator (after) -## CHECK-DAG: <<Param:z\d+>> ParameterValue -## CHECK-DAG: <<Const0:i\d+>> IntConstant 0 -## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 -## CHECK-DAG: <<NotParam:i\d+>> Select [<<Const1>>,<<Const0>>,<<Param>>] -## CHECK-DAG: Return [<<NotParam>>] - -## CHECK-START: boolean BooleanNotSmali.BooleanNot(boolean) select_generator (after) -## CHECK-NOT: If -## CHECK-NOT: Phi - -## CHECK-START: boolean BooleanNotSmali.BooleanNot(boolean) select_generator (after) -## CHECK: Goto -## CHECK-NOT: Goto - -.method public static BooleanNot(Z)Z - .registers 2 - - if-eqz v1, :true_start - const/4 v0, 0x0 - -:return_start - return v0 - -:true_start - const/4 v0, 0x1 - goto :return_start - -.end method diff --git a/test/463-checker-boolean-simplifier/src/Main.java b/test/463-checker-boolean-simplifier/src/Main.java index 9368488056..d1d02cdfee 100644 --- a/test/463-checker-boolean-simplifier/src/Main.java +++ b/test/463-checker-boolean-simplifier/src/Main.java @@ -32,14 +32,42 @@ public class Main { } } - // Invoke a method written in smali that implements the boolean ! operator. This method - // uses the if/else pattern generated by dx (while Jack generates a different pattern). - // Since this method is in a smali-generated class, we invoke it through reflection. - public static boolean SmaliBooleanNot(boolean x) throws Exception { - Class<?> c = Class.forName("BooleanNotSmali"); - java.lang.reflect.Method method = c.getMethod("BooleanNot", boolean.class); - Object retValue = method.invoke(null, new Object[] { Boolean.valueOf(x) }); - return ((Boolean) retValue).booleanValue(); + /* + * Elementary test negating a boolean. Verifies that blocks are merged and + * empty branches removed. + */ + + /// CHECK-START: boolean Main.BooleanNot(boolean) select_generator (before) + /// CHECK-DAG: <<Param:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: If [<<Param>>] + /// CHECK-DAG: <<Phi:i\d+>> Phi [<<Const1>>,<<Const0>>] + /// CHECK-DAG: Return [<<Phi>>] + + /// CHECK-START: boolean Main.BooleanNot(boolean) select_generator (before) + /// CHECK: Goto + /// CHECK: Goto + /// CHECK: Goto + /// CHECK-NOT: Goto + + /// CHECK-START: boolean Main.BooleanNot(boolean) select_generator (after) + /// CHECK-DAG: <<Param:z\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<NotParam:i\d+>> Select [<<Const1>>,<<Const0>>,<<Param>>] + /// CHECK-DAG: Return [<<NotParam>>] + + /// CHECK-START: boolean Main.BooleanNot(boolean) select_generator (after) + /// CHECK-NOT: If + /// CHECK-NOT: Phi + + /// CHECK-START: boolean Main.BooleanNot(boolean) select_generator (after) + /// CHECK: Goto + /// CHECK-NOT: Goto + + public static boolean BooleanNot(boolean x) { + return !x; } /* @@ -157,7 +185,11 @@ public class Main { /// CHECK-NOT: BooleanNot public static int NegatedCondition(boolean x) { - return (x != false) ? 42 : 43; + if (x != false) { + return 42; + } else { + return 43; + } } /// CHECK-START: int Main.SimpleTrueBlock(boolean, int) select_generator (after) @@ -221,7 +253,13 @@ public class Main { /// CHECK-DAG: Return [<<Select123>>] public static int ThreeBlocks(boolean x, boolean y) { - return x ? 1 : (y ? 2 : 3); + if (x) { + return 1; + } else if (y) { + return 2; + } else { + return 3; + } } /// CHECK-START: int Main.MultiplePhis() select_generator (before) @@ -254,10 +292,8 @@ public class Main { while (y++ < 10) { if (y > 1) { x = 13; - continue; } else { x = 42; - continue; } } return x; @@ -330,8 +366,8 @@ public class Main { } public static void main(String[] args) throws Exception { - assertBoolEquals(false, SmaliBooleanNot(true)); - assertBoolEquals(true, SmaliBooleanNot(false)); + assertBoolEquals(false, BooleanNot(true)); + assertBoolEquals(true, BooleanNot(false)); assertBoolEquals(true, GreaterThan(10, 5)); assertBoolEquals(false, GreaterThan(10, 10)); assertBoolEquals(false, GreaterThan(5, 10)); 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/537-checker-inline-and-unverified/build b/test/537-checker-inline-and-unverified/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/537-checker-inline-and-unverified/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/537-checker-inline-and-unverified/src/Main.java b/test/537-checker-inline-and-unverified/src/Main.java index b9d5fc98cc..bdc14b027c 100644 --- a/test/537-checker-inline-and-unverified/src/Main.java +++ b/test/537-checker-inline-and-unverified/src/Main.java @@ -45,14 +45,12 @@ public class Main { } public static boolean $opt$noinline$testNoInline() { - boolean result = true; try { - result = (null instanceof InaccessibleClass); - throw new Error("Unreachable"); + return null instanceof InaccessibleClass; } catch (IllegalAccessError e) { // expected } - return result; + return false; } public static boolean $opt$inline$testInline() { 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/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/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-annotation-lookup-generic-jni/check b/test/656-annotation-lookup-generic-jni/check new file mode 100755 index 0000000000..39a52d5297 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/check @@ -0,0 +1,21 @@ +#!/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. + +# On gcstress configurations, an extra "JNI_OnUnload called" line may +# be emitted. If so, remove it. +sed -e '${/^JNI_OnUnload called$/d;}' "$2" > "$2.tmp" + +./default-check "$1" "$2.tmp" diff --git a/test/656-annotation-lookup-generic-jni/expected.txt b/test/656-annotation-lookup-generic-jni/expected.txt new file mode 100644 index 0000000000..4519c7e442 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/expected.txt @@ -0,0 +1,3 @@ +JNI_OnLoad called +Java_Test_nativeMethodWithAnnotation +passed diff --git a/test/656-annotation-lookup-generic-jni/info.txt b/test/656-annotation-lookup-generic-jni/info.txt new file mode 100644 index 0000000000..9049bfcf80 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/info.txt @@ -0,0 +1,7 @@ +Non-regression test for b/38454151, where the invocation of a native +method with an annotation (to be found in a custom class loader) +through Generic JNI would crash the Generic JNI trampoline because it +would throw an exception (that should eventually be caught) and walk a +stack with an unexpected layout when trying to resolve the method's +annotation classes (during the CriticalNative/FastNative optimization +annotation lookup). diff --git a/test/656-annotation-lookup-generic-jni/src-art/Main.java b/test/656-annotation-lookup-generic-jni/src-art/Main.java new file mode 100644 index 0000000000..01b288a900 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/src-art/Main.java @@ -0,0 +1,76 @@ +/* + * 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 dalvik.system.InMemoryDexClassLoader; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Main { + + public static void main(String[] args) throws Exception { + // Extract Dex file contents from the secondary Jar file. + String jarFilename = + System.getenv("DEX_LOCATION") + "/656-annotation-lookup-generic-jni-ex.jar"; + ZipFile zipFile = new ZipFile(jarFilename); + ZipEntry zipEntry = zipFile.getEntry("classes.dex"); + InputStream inputStream = zipFile.getInputStream(zipEntry); + int dexFileSize = (int) zipEntry.getSize(); + byte[] dexFileContents = new byte[dexFileSize]; + inputStream.read(dexFileContents, 0, dexFileSize); + + // Create class loader from secondary Dex file. + ByteBuffer dexBuffer = ByteBuffer.wrap(dexFileContents); + ClassLoader classLoader = createUnquickenedDexClassLoader(dexBuffer); + + // Load and initialize the Test class. + Class<?> testClass = classLoader.loadClass("Test"); + Method initialize = testClass.getMethod("initialize", String.class); + initialize.invoke(null, args[0]); + + // Invoke Test.nativeMethodWithAnnotation(). + Method nativeMethodWithAnnotation = testClass.getMethod("nativeMethodWithAnnotation"); + // Invoking the native method Test.nativeMethodWithAnnotation used + // to crash the Generic JNI trampoline during the resolution of + // the method's annotations (DummyAnnotation) (see b/38454151). + nativeMethodWithAnnotation.invoke(null); + + zipFile.close(); + System.out.println("passed"); + } + + // Create a class loader loading a Dex file in memory + // *without creating an Oat file*. This way, the Dex file won't be + // quickened and JNI stubs won't be compiled, thus forcing the use + // of Generic JNI when invoking the native method + // Test.nativeMethodWithAnnotation. + static ClassLoader createUnquickenedDexClassLoader(ByteBuffer dexBuffer) { + InMemoryDexClassLoader cl = new InMemoryDexClassLoader(dexBuffer, getBootClassLoader()); + return cl; + } + + static ClassLoader getBootClassLoader() { + ClassLoader cl = Main.class.getClassLoader(); + while (cl.getParent() != null) { + cl = cl.getParent(); + } + return cl; + } + +} diff --git a/test/656-annotation-lookup-generic-jni/src-ex/DummyAnnotation.java b/test/656-annotation-lookup-generic-jni/src-ex/DummyAnnotation.java new file mode 100644 index 0000000000..6caac6685e --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/src-ex/DummyAnnotation.java @@ -0,0 +1,17 @@ +/* + * 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 @interface DummyAnnotation {} diff --git a/test/656-annotation-lookup-generic-jni/src-ex/Test.java b/test/656-annotation-lookup-generic-jni/src-ex/Test.java new file mode 100644 index 0000000000..838b4fe0d6 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/src-ex/Test.java @@ -0,0 +1,28 @@ +/* + * 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 Test { + + public static void initialize(String libname) { + // Load test native library to get access to the implementation of + // Test.nativeMethodWithAnnotation. + System.loadLibrary(libname); + } + + @DummyAnnotation + public static native void nativeMethodWithAnnotation(); + +} diff --git a/test/656-annotation-lookup-generic-jni/test.cc b/test/656-annotation-lookup-generic-jni/test.cc new file mode 100644 index 0000000000..c8aa2af921 --- /dev/null +++ b/test/656-annotation-lookup-generic-jni/test.cc @@ -0,0 +1,28 @@ +/* + * 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 "jni.h" + +#include <iostream> + +namespace art { + +// Native method annotated with `DummyAnnotation` in Java source. +extern "C" JNIEXPORT void JNICALL Java_Test_nativeMethodWithAnnotation(JNIEnv*, jclass) { + std::cout << "Java_Test_nativeMethodWithAnnotation" << std::endl; +} + +} // namespace art 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/656-loop-deopt/src/Main.java b/test/656-loop-deopt/src/Main.java index c99cccf4f1..20e6d723d1 100644 --- a/test/656-loop-deopt/src/Main.java +++ b/test/656-loop-deopt/src/Main.java @@ -32,6 +32,15 @@ public class Main { $noinline$loopIncrement(new Main()); ensureJitCompiled(Main.class, "$noinline$loopIncrement"); $noinline$loopIncrement(new SubMain()); + + $noinline$objectReturned(new Main()); + ensureJitCompiled(Main.class, "$noinline$objectReturned"); + Object o = $noinline$objectReturned(new SubMain()); + // We used to get 0xebadde09 in 'o' here and therefore crash + // both interpreter and compiled code. + if (o instanceof Cloneable) { + System.out.println("Unexpected object type " + o.getClass()); + } } public boolean doCheck() { @@ -59,7 +68,7 @@ public class Main { public static void $noinline$objectUpdate(Main m) { Object o = new Object(); // We used to kill 'o' when the inline cache of 'doCheck' only - // contains 'Main' (which makes the only branch using 'a' dead). + // contains 'Main' (which makes the only branch using 'o' dead). // So the deoptimization at the inline cache was incorrectly assuming // 'o' was dead. // This lead to a NPE on the 'toString' call just after deoptimizing. @@ -82,8 +91,8 @@ public class Main { // 'k' was 5000. for (int i = 0; i < 5000; i++, k++) { if (m.doCheck()) { - // We make this branch the only true user of the 'a' phi. All other uses - // of 'a' are phi updates. + // We make this branch the only true user of the 'k' phi. All other uses + // of 'k' are phi updates. myIntStatic = k; } } @@ -92,6 +101,28 @@ public class Main { } } + public static Object $noinline$objectReturned(Main m) { + Object o = new Object(); + // We used to kill 'o' when the inline cache of 'doCheck' only + // contains 'Main' (which makes the only branch using 'o' dead). + // So the deoptimization at the inline cache was incorrectly assuming + // 'o' was dead. + // We also need to make 'o' escape through a return instruction, as mterp + // executes the same code for return and return-object, and the 0xebadde09 + // sentinel for dead value is only pushed to non-object dex registers. + Object myReturnValue = null; + for (int i = 0; i < 5000; i++) { + if (m.doCheck()) { + // We make this branch the only true user of the 'o' phi. All other uses + // of 'o' are phi updates. + myReturnValue = o; + } else if (myIntStatic == 42) { + o = m; + } + } + return myReturnValue; + } + public static int myIntStatic = 0; public static native void ensureJitCompiled(Class<?> itf, String name); diff --git a/test/657-branches/expected.txt b/test/657-branches/expected.txt new file mode 100644 index 0000000000..d9fd864709 --- /dev/null +++ b/test/657-branches/expected.txt @@ -0,0 +1 @@ +Hello World: 4.0 diff --git a/test/657-branches/info.txt b/test/657-branches/info.txt new file mode 100644 index 0000000000..84a2bb908c --- /dev/null +++ b/test/657-branches/info.txt @@ -0,0 +1,2 @@ +Regression test for the ARM backend, which used to have a bug +handling branches fallthrough. diff --git a/test/657-branches/src/Main.java b/test/657-branches/src/Main.java new file mode 100644 index 0000000000..2b62c5faa1 --- /dev/null +++ b/test/657-branches/src/Main.java @@ -0,0 +1,47 @@ +/* + * 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 foo(float f) { + // The reason this used to break: + // 1) We inline the 'foo' call, so blocks now only contain HLoadClass instructions. + // 2) We then run the select_generator pass, which cannot change the + // if/else because blocks contain instructions. + // 3) We run GVN which will remove the HLoadClass instructions in the blocks. + // 4) At code generation, we are in the unlikely situation that a diamond shape + // contains no instruction (usually removed by select_generator). This used + // to trip the ARM code generators. + if (f < 1.2f) { + foo(Main.class, Object.class); + if (f < 0.2f) { + foo(Main.class, Object.class); + } else { + foo(Main.class, Object.class); + } + } else { + System.out.println("Hello World: " + f); + } + } + + public static void foo(Object a, Object b) {} + + public static void main(String[] args) { + foo(0f); + foo(4f); + foo(0.1f); + } +} diff --git a/test/961-default-iface-resolution-gen/build b/test/961-default-iface-resolution-gen/build index f2c222524e..d719a9ffe9 100755 --- a/test/961-default-iface-resolution-gen/build +++ b/test/961-default-iface-resolution-gen/build @@ -23,4 +23,4 @@ mkdir -p ./src ./util-src/generate_java.py ./src ./expected.txt # dx runs out of memory with default 256M, give it more memory. -./default-build "$@" --experimental default-methods --dx-vm-option -JXmx512M +./default-build "$@" --experimental default-methods --dx-vm-option -JXmx1024M diff --git a/test/964-default-iface-init-gen/build b/test/964-default-iface-init-gen/build index a800151670..e504690043 100755 --- a/test/964-default-iface-init-gen/build +++ b/test/964-default-iface-init-gen/build @@ -23,4 +23,4 @@ mkdir -p ./src ./util-src/generate_java.py ./src ./expected.txt # dx runs out of memory with just 256m, so increase it. -./default-build "$@" --experimental default-methods --dx-vm-option -JXmx512M +./default-build "$@" --experimental default-methods --dx-vm-option -JXmx1024M diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index 30ad532f6c..8e42a48865 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 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.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() @@ -143,11 +143,11 @@ fibonacci(5)=5 ......=> 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>> .....<= 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:228) + art.Test988$IterOp.applyAsInt(Test988.java:223) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:286) + <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:228) + art.Test988$IterOp.applyAsInt(Test988.java:223) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:286) + <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> @@ -244,11 +244,11 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ......=> 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>> .....<= 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:250) + art.Test988$RecurOp.applyAsInt(Test988.java:245) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:287) + <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> ...<= public java.lang.Error(java.lang.String) -> <null: null> @@ -264,14 +264,14 @@ 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:250) + art.Test988$RecurOp.applyAsInt(Test988.java:245) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:287) + <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 java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> => public static native void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index 6a45c0eaa2..e40c612851 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) {} } @@ -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(); @@ -134,8 +145,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) + "<= " + m + " -> <" + klass_print + ": " + print + ">"); } } 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/996-breakpoint-obsolete/src/art/Redefinition.java b/test/996-breakpoint-obsolete/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/996-breakpoint-obsolete/src/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/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/Android.bp b/test/Android.bp index 9e6ecffe79..0dff01b6cf 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -281,6 +281,8 @@ 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", ], shared_libs: [ "libbase", @@ -396,6 +398,7 @@ cc_defaults { "626-const-class-linking/clear_dex_cache_types.cc", "642-fp-callees/fp_callees.cc", "647-jni-get-field-id/get_field_id.cc", + "656-annotation-lookup-generic-jni/test.cc", "708-jit-cache-churn/jit.cc" ], shared_libs: [ diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk index 60ce6c7003..753fe9a330 100644 --- a/test/Android.run-test-jvmti-java-library.mk +++ b/test/Android.run-test-jvmti-java-library.mk @@ -22,6 +22,7 @@ include $(CLEAR_VARS) LOCAL_SHIM_CLASSES := \ 902-hello-transformation/src/art/Redefinition.java \ 903-hello-tagging/src/art/Main.java \ + 989-method-trace-throw/src/art/Trace.java \ LOCAL_SRC_FILES := $(LOCAL_SHIM_CLASSES) @@ -77,6 +78,12 @@ LOCAL_SRC_FILES += \ 984-obsolete-invoke/src/art/Test984.java \ 985-re-obsolete/src/art/Test985.java \ 986-native-method-bind/src/art/Test986.java \ + 988-method-trace/src/art/Test988.java \ + 989-method-trace-throw/src/art/Test989.java \ + 990-field-trace/src/art/Test990.java \ + 991-field-trace-2/src/art/Test991.java \ + 992-source-data/src/art/Test992.java \ + 992-source-data/src/art/Target2.java \ JVMTI_RUN_TEST_GENERATED_NUMBERS := \ 901 \ @@ -119,6 +126,11 @@ JVMTI_RUN_TEST_GENERATED_NUMBERS := \ 984 \ 985 \ 986 \ + 988 \ + 989 \ + 990 \ + 991 \ + 992 \ # 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), \ 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 7bfa417691..d3d92c651e 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": [ @@ -541,7 +541,7 @@ "981-dedup-original-dex" ], "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 +579,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,11 +594,11 @@ }, { "tests": [ - "953-invoke-polymorphic-compiler" + "567-checker-compare" ], - "description": "Test throws VerifyError when run with --build-with-javac-dx.", + "description": "Checker tests failing when run with --build-with-javac-dx.", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/62722425" + "bug": "b/62950048" }, { "tests": [ @@ -665,5 +665,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 e6c2480ed0..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." @@ -749,7 +757,7 @@ export TEST_NAME=`basename ${test_dir}` # Tests named '<number>-checker-*' will also have their CFGs verified with # Checker when compiled with Optimizing on host. if [[ "$TEST_NAME" =~ ^[0-9]+-checker- ]]; then - if [ "$runtime" = "art" -a "$image_suffix" = "" -a "$USE_JACK" = "true" ]; then + if [ "$runtime" = "art" -a "$image_suffix" = "" ]; then # In no-prebuild or no-image mode, the compiler only quickens so disable the checker. if [ "$prebuild_mode" = "yes" -a "$have_image" = "yes" ]; then run_checker="yes" 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/common_helper.cc b/test/ti-agent/common_helper.cc index 4fe58db169..0eb71f8371 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -38,6 +38,9 @@ static void SetupCommonRetransform(); static void SetupCommonRedefine(); static void SetupCommonTransform(); +// Taken from art/runtime/modifiers.h +static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic + template <bool is_redefine> static void throwCommonRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, @@ -69,22 +72,6 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, 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) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { @@ -175,6 +162,221 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return GetJavaValueByType(env, type[0], value); } +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 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, @@ -481,6 +683,7 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( jobject exit, jobject field_access, jobject field_modify, + jobject single_step, jthread thr) { TraceData* data = nullptr; if (JvmtiErrorToException(env, @@ -495,8 +698,17 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( 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; } @@ -508,6 +720,7 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( cb.FieldAccess = fieldAccessCB; cb.FieldModification = fieldModificationCB; cb.ClassPrepare = classPrepareCB; + cb.SingleStep = singleStepCB; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { return; } @@ -543,6 +756,14 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( 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( @@ -571,6 +792,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( thr))) { return; } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } } } // namespace common_trace 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..3c0c65e7e2 100644 --- a/tools/dexfuzz/README +++ b/tools/dexfuzz/README @@ -139,7 +139,9 @@ InstructionDuplicator 80 InstructionSwapper 80 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..41ce7b2706 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 = 2; 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..e550d30308 100644 --- a/tools/dexfuzz/src/dexfuzz/program/Program.java +++ b/tools/dexfuzz/src/dexfuzz/program/Program.java @@ -32,7 +32,9 @@ import dexfuzz.program.mutators.InstructionDuplicator; import dexfuzz.program.mutators.InstructionSwapper; 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; @@ -199,7 +201,9 @@ public class Program { registerMutator(new InstructionSwapper(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/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 |