diff options
25 files changed, 1277 insertions, 355 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/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index d2493137fe..32f40024d3 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 @@ -645,6 +632,9 @@ bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t } } + // Consider dynamic loop peeling for alignment. + SetPeelingCandidate(trip_count); + // Success! return true; } @@ -657,28 +647,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 +705,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 +740,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 +752,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 +764,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. @@ -795,7 +846,7 @@ bool HLoopOptimization::VectorizeDef(LoopNode* node, 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)); } @@ -852,7 +903,7 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node, induction_range_.IsUnitStride(instruction, index, &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,7 +1215,7 @@ 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_; + HInstruction* subscript = vector_index_; if (offset != nullptr) { subscript = new (global_allocator_) HAdd(Primitive::kPrimInt, subscript, offset); if (org->IsPhi()) { @@ -1178,17 +1229,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 +1505,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 +1684,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 +1722,21 @@ void HLoopOptimization::RemoveDeadInstructions(const HInstructionList& list) { } } +bool HLoopOptimization::CanRemoveCycle() { + for (HInstruction* i : *iset_) { + // We can never remove instructions that have environment + // uses when we compile 'debuggable'. + if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { + return false; + } + // A deoptimization should never have an environment input removed. + for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { + if (use.GetUser()->GetHolder()->IsDeoptimize()) { + return false; + } + } + } + return true; +} + } // namespace art diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index cc6343aeb5..de4bd85fc8 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -116,14 +116,15 @@ class HLoopOptimization : public HOptimization { void OptimizeInnerLoop(LoopNode* node); // Vectorization analysis and synthesis. - bool CanVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); + bool ShouldVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); void Vectorize(LoopNode* node, HBasicBlock* block, HBasicBlock* exit, int64_t trip_count); void GenerateNewLoop(LoopNode* node, HBasicBlock* block, HBasicBlock* new_preheader, HInstruction* lo, HInstruction* hi, - HInstruction* step); + HInstruction* step, + uint32_t unroll); bool VectorizeDef(LoopNode* node, HInstruction* instruction, bool generate_code); bool VectorizeUse(LoopNode* node, HInstruction* instruction, @@ -133,10 +134,11 @@ class HLoopOptimization : public HOptimization { bool TrySetVectorType(Primitive::Type type, /*out*/ uint64_t* restrictions); bool TrySetVectorLength(uint32_t length); void GenerateVecInv(HInstruction* org, Primitive::Type type); - void GenerateVecSub(HInstruction* org, HInstruction* off); + void GenerateVecSub(HInstruction* org, HInstruction* offset); void GenerateVecMem(HInstruction* org, HInstruction* opa, HInstruction* opb, + HInstruction* offset, Primitive::Type type); void GenerateVecOp(HInstruction* org, HInstruction* opa, @@ -151,6 +153,11 @@ class HLoopOptimization : public HOptimization { Primitive::Type type, uint64_t restrictions); + // Vectorization heuristics. + bool IsVectorizationProfitable(int64_t trip_count); + void SetPeelingCandidate(int64_t trip_count); + uint32_t GetUnrollingFactor(HBasicBlock* block, int64_t trip_count); + // Helpers. bool TrySetPhiInduction(HPhi* phi, bool restrict_uses); bool TrySetSimpleLoopHeader(HBasicBlock* block); @@ -208,20 +215,25 @@ class HLoopOptimization : public HOptimization { // Contents reside in phase-local heap memory. ArenaSet<ArrayReference>* vector_refs_; + // Dynamic loop peeling candidate for alignment. + const ArrayReference* vector_peeling_candidate_; + + // Dynamic data dependence test of the form a != b. + HInstruction* vector_runtime_test_a_; + HInstruction* vector_runtime_test_b_; + // Mapping used during vectorization synthesis for both the scalar peeling/cleanup - // loop (simd_ is false) and the actual vector loop (simd_ is true). The data + // loop (mode is kSequential) and the actual vector loop (mode is kVector). The data // structure maps original instructions into the new instructions. // Contents reside in phase-local heap memory. ArenaSafeMap<HInstruction*, HInstruction*>* vector_map_; // Temporary vectorization bookkeeping. + VectorMode vector_mode_; // synthesis mode HBasicBlock* vector_preheader_; // preheader of the new loop HBasicBlock* vector_header_; // header of the new loop HBasicBlock* vector_body_; // body of the new loop - HInstruction* vector_runtime_test_a_; - HInstruction* vector_runtime_test_b_; // defines a != b runtime test - HPhi* vector_phi_; // the Phi representing the normalized loop index - VectorMode vector_mode_; // selects synthesis mode + HInstruction* vector_index_; // normalized index of the new loop friend class LoopOptimizationTest; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index ffa16dd787..b21c4a5aa7 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -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/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/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..26b7282a0a 100644 --- a/runtime/gc/space/region_space.cc +++ b/runtime/gc/space/region_space.cc @@ -29,6 +29,9 @@ namespace space { // value of the region size, evaculate the region. static constexpr uint kEvaculateLivePercentThreshold = 75U; +// If we protect the cleared regions. +static constexpr bool kProtectClearedRegions = true; + MemMap* RegionSpace::CreateMemMap(const std::string& name, size_t capacity, uint8_t* requested_begin) { CHECK_ALIGNED(capacity, kRegionSize); @@ -449,21 +452,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 +539,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/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/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/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..0d0885c85a --- /dev/null +++ b/test/656-checker-simd-opt/src/Main.java @@ -0,0 +1,69 @@ +/* + * 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; + } + } + + 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]); + } + 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/knownfailures.json b/test/knownfailures.json index 2b1232bdd3..a8d492bf4d 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -602,10 +602,7 @@ }, { "tests": [ - "565-checker-doublenegbitwise", - "567-checker-compare", - "586-checker-null-array-get", - "633-checker-rtp-getclass" + "567-checker-compare" ], "description": "Checker tests failing when run with --build-with-javac-dx.", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, @@ -676,5 +673,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/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 |