diff options
123 files changed, 2513 insertions, 1368 deletions
diff --git a/compiler/debug/elf_debug_line_writer.h b/compiler/debug/elf_debug_line_writer.h index ac0f4ca21d..d3859ca752 100644 --- a/compiler/debug/elf_debug_line_writer.h +++ b/compiler/debug/elf_debug_line_writer.h @@ -24,7 +24,6 @@ #include "debug/dwarf/headers.h" #include "debug/elf_compilation_unit.h" #include "dex_file-inl.h" -#include "dex_file.h" #include "elf_builder.h" #include "stack_map.h" @@ -90,8 +89,9 @@ class ElfDebugLineWriter { continue; } - ArrayRef<const SrcMapElem> src_mapping_table; - std::vector<SrcMapElem> src_mapping_table_from_stack_maps; + uint32_t prologue_end = std::numeric_limits<uint32_t>::max(); + ArrayRef<const SrcMapElem> pc2dex_map; + std::vector<SrcMapElem> pc2dex_map_from_stack_maps; if (mi->IsFromOptimizingCompiler()) { // Use stack maps to create mapping table from pc to dex. const CodeInfo code_info(mi->compiled_method->GetVmapTable().data()); @@ -99,35 +99,36 @@ class ElfDebugLineWriter { for (uint32_t s = 0; s < code_info.GetNumberOfStackMaps(); s++) { StackMap stack_map = code_info.GetStackMapAt(s, encoding); DCHECK(stack_map.IsValid()); - // Emit only locations where we have local-variable information. - // In particular, skip mappings inside the prologue. + const uint32_t pc = stack_map.GetNativePcOffset(encoding); + const int32_t dex = stack_map.GetDexPc(encoding); + pc2dex_map_from_stack_maps.push_back({pc, dex}); if (stack_map.HasDexRegisterMap(encoding)) { - const uint32_t pc = stack_map.GetNativePcOffset(encoding); - const int32_t dex = stack_map.GetDexPc(encoding); - src_mapping_table_from_stack_maps.push_back({pc, dex}); + // Guess that the first map with local variables is the end of prologue. + prologue_end = std::min(prologue_end, pc); } } - std::sort(src_mapping_table_from_stack_maps.begin(), - src_mapping_table_from_stack_maps.end()); - src_mapping_table = ArrayRef<const SrcMapElem>(src_mapping_table_from_stack_maps); + std::sort(pc2dex_map_from_stack_maps.begin(), + pc2dex_map_from_stack_maps.end()); + pc2dex_map = ArrayRef<const SrcMapElem>(pc2dex_map_from_stack_maps); } else { // Use the mapping table provided by the quick compiler. - src_mapping_table = mi->compiled_method->GetSrcMappingTable(); + pc2dex_map = mi->compiled_method->GetSrcMappingTable(); + prologue_end = 0; } - if (src_mapping_table.empty()) { + if (pc2dex_map.empty()) { continue; } Elf_Addr method_address = text_address + mi->low_pc; - PositionInfos position_infos; + PositionInfos dex2line_map; const DexFile* dex = mi->dex_file; - if (!dex->DecodeDebugPositionInfo(mi->code_item, PositionInfoCallback, &position_infos)) { + if (!dex->DecodeDebugPositionInfo(mi->code_item, PositionInfoCallback, &dex2line_map)) { continue; } - if (position_infos.empty()) { + if (dex2line_map.empty()) { continue; } @@ -184,21 +185,25 @@ class ElfDebugLineWriter { // Generate mapping opcodes from PC to Java lines. if (file_index != 0) { bool first = true; - for (SrcMapElem pc2dex : src_mapping_table) { + for (SrcMapElem pc2dex : pc2dex_map) { uint32_t pc = pc2dex.from_; int dex_pc = pc2dex.to_; // Find mapping with address with is greater than our dex pc; then go back one step. - auto ub = std::upper_bound(position_infos.begin(), position_infos.end(), dex_pc, + auto dex2line = std::upper_bound( + dex2line_map.begin(), + dex2line_map.end(), + dex_pc, [](uint32_t address, const DexFile::PositionInfo& entry) { return address < entry.address_; }); - if (ub != position_infos.begin()) { - int line = (--ub)->line_; + // Look for first valid mapping after the prologue. + if (dex2line != dex2line_map.begin() && pc >= prologue_end) { + int line = (--dex2line)->line_; if (first) { first = false; if (pc > 0) { // Assume that any preceding code is prologue. - int first_line = position_infos.front().line_; + int first_line = dex2line_map.front().line_; // Prologue is not a sensible place for a breakpoint. opcodes.NegateStmt(); opcodes.AddRow(method_address, first_line); diff --git a/compiler/debug/elf_debug_loc_writer.h b/compiler/debug/elf_debug_loc_writer.h index fd7f949c5d..a19b36f9cc 100644 --- a/compiler/debug/elf_debug_loc_writer.h +++ b/compiler/debug/elf_debug_loc_writer.h @@ -82,6 +82,8 @@ struct VariableLocation { // Get the location of given dex register (e.g. stack or machine register). // Note that the location might be different based on the current pc. // The result will cover all ranges where the variable is in scope. +// PCs corresponding to stackmap with dex register map are accurate, +// all other PCs are best-effort only. std::vector<VariableLocation> GetVariableLocations(const MethodDebugInfo* method_info, uint16_t vreg, bool is64bitValue, @@ -141,6 +143,9 @@ std::vector<VariableLocation> GetVariableLocations(const MethodDebugInfo* method variable_locations.back().high_pc == low_pc) { // Merge with the previous entry (extend its range). variable_locations.back().high_pc = high_pc; + } else if (!variable_locations.empty() && reg_lo == DexRegisterLocation::None()) { + // Unknown location - use the last known location as best-effort guess. + variable_locations.back().high_pc = high_pc; } else { variable_locations.push_back({low_pc, high_pc, reg_lo, reg_hi}); } diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index c694ea8118..ba1b1683d7 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -1166,8 +1166,8 @@ class BCEVisitor : public HGraphVisitor { // Attempt dominator-based dynamic elimination on remaining candidates. void AddComparesWithDeoptimization(HBasicBlock* block) { - for (const auto& it : first_index_bounds_check_map_) { - HBoundsCheck* bounds_check = it.second; + for (const auto& entry : first_index_bounds_check_map_) { + HBoundsCheck* bounds_check = entry.second; HInstruction* index = bounds_check->InputAt(0); HInstruction* array_length = bounds_check->InputAt(1); if (!array_length->IsArrayLength()) { @@ -1215,8 +1215,7 @@ class BCEVisitor : public HGraphVisitor { } } // Add standby candidates that fall in selected range. - for (size_t i = 0; i < standby.size(); ++i) { - HBoundsCheck* other_bounds_check = standby[i]->AsBoundsCheck(); + for (HBoundsCheck* other_bounds_check : standby) { HInstruction* other_index = other_bounds_check->InputAt(0); int32_t other_c = ValueBound::AsValueBound(other_index).GetConstant(); if (min_c <= other_c && other_c <= max_c) { @@ -1233,8 +1232,7 @@ class BCEVisitor : public HGraphVisitor { (base != nullptr || min_c >= 0) && // reject certain OOB distance <= kMaxLengthForAddingDeoptimize) { // reject likely/certain deopt AddCompareWithDeoptimization(block, array_length, base, min_c, max_c); - for (size_t i = 0; i < candidates.size(); ++i) { - HInstruction* other_bounds_check = candidates[i]; + for (HInstruction* other_bounds_check : candidates) { ReplaceInstruction(other_bounds_check, other_bounds_check->InputAt(0)); } } diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index a1d62760a1..05e1356ed8 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -32,6 +32,7 @@ #include "nodes.h" #include "primitive.h" #include "scoped_thread_state_change.h" +#include "ssa_builder.h" #include "thread.h" #include "utils/dex_cache_arrays_layout-inl.h" @@ -248,7 +249,7 @@ void HGraphBuilder::InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item) // loop for synchronized blocks. if (block->HasThrowingInstructions()) { // Try to find a TryItem covering the block. - DCHECK_NE(block->GetDexPc(), kNoDexPc) << "Block must have a dec_pc to find its TryItem."; + DCHECK_NE(block->GetDexPc(), kNoDexPc) << "Block must have a dex_pc to find its TryItem."; const int32_t try_item_idx = DexFile::FindTryItem(code_item, block->GetDexPc()); if (try_item_idx != -1) { // Block throwing and in a TryItem. Store the try block information. @@ -322,7 +323,8 @@ void HGraphBuilder::InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item) } } -bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { +GraphAnalysisResult HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item, + StackHandleScopeCollection* handles) { DCHECK(graph_->GetBlocks().empty()); const uint16_t* code_ptr = code_item.insns_; @@ -349,12 +351,12 @@ bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { // start a new block, and create these blocks. if (!ComputeBranchTargets(code_ptr, code_end, &number_of_branches)) { MaybeRecordStat(MethodCompilationStat::kNotCompiledBranchOutsideMethodCode); - return false; + return kAnalysisInvalidBytecode; } // Note that the compiler driver is null when unit testing. if ((compiler_driver_ != nullptr) && SkipCompilation(code_item, number_of_branches)) { - return false; + return kAnalysisInvalidBytecode; } // Find locations where we want to generate extra stackmaps for native debugging. @@ -385,7 +387,7 @@ bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { } } if (!AnalyzeDexInstruction(instruction, dex_pc)) { - return false; + return kAnalysisInvalidBytecode; } dex_pc += instruction.SizeInCodeUnits(); code_ptr += instruction.SizeInCodeUnits(); @@ -404,7 +406,13 @@ bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) { // non-exceptional edges to have been created. InsertTryBoundaryBlocks(code_item); - return true; + GraphAnalysisResult result = graph_->BuildDominatorTree(); + if (result != kAnalysisSuccess) { + return result; + } + + graph_->InitializeInexactObjectRTI(handles); + return SsaBuilder(graph_, handles).BuildSsa(); } void HGraphBuilder::MaybeUpdateCurrentBlock(size_t dex_pc) { diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 93e17d6422..e3dd0e8216 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -80,7 +80,8 @@ class HGraphBuilder : public ValueObject { null_dex_cache_(), dex_cache_(null_dex_cache_) {} - bool BuildGraph(const DexFile::CodeItem& code); + GraphAnalysisResult BuildGraph(const DexFile::CodeItem& code, + StackHandleScopeCollection* handles); static constexpr const char* kBuilderPassName = "builder"; diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 005b6c189e..87f52c6f21 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -2079,6 +2079,8 @@ void LocationsBuilderARM::VisitTypeConversion(HTypeConversion* conversion) { switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to byte is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2097,6 +2099,8 @@ void LocationsBuilderARM::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2181,6 +2185,8 @@ void LocationsBuilderARM::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to char is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2280,6 +2286,10 @@ void InstructionCodeGeneratorARM::VisitTypeConversion(HTypeConversion* conversio switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to byte is a result of code transformations. + __ sbfx(out.AsRegister<Register>(), in.AsRegisterPairLow<Register>(), 0, 8); + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2297,6 +2307,10 @@ void InstructionCodeGeneratorARM::VisitTypeConversion(HTypeConversion* conversio case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. + __ sbfx(out.AsRegister<Register>(), in.AsRegisterPairLow<Register>(), 0, 16); + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2398,6 +2412,10 @@ void InstructionCodeGeneratorARM::VisitTypeConversion(HTypeConversion* conversio case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to char is a result of code transformations. + __ ubfx(out.AsRegister<Register>(), in.AsRegisterPairLow<Register>(), 0, 16); + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index de23fe8eef..435ae5e954 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -2928,30 +2928,128 @@ void InstructionCodeGeneratorARM64::VisitDeoptimize(HDeoptimize* deoptimize) { /* false_target */ nullptr); } -void LocationsBuilderARM64::VisitSelect(HSelect* select) { - LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(select); +enum SelectVariant { + kCsel, + kCselFalseConst, + kCselTrueConst, + kFcsel, +}; + +static inline bool IsConditionOnFloatingPointValues(HInstruction* condition) { + return condition->IsCondition() && + Primitive::IsFloatingPointType(condition->InputAt(0)->GetType()); +} + +static inline bool IsRecognizedCselConstant(HInstruction* constant) { + if (constant->IsConstant()) { + int64_t value = Int64FromConstant(constant->AsConstant()); + if ((value == -1) || (value == 0) || (value == 1)) { + return true; + } + } + return false; +} + +static inline SelectVariant GetSelectVariant(HSelect* select) { if (Primitive::IsFloatingPointType(select->GetType())) { - locations->SetInAt(0, Location::RequiresFpuRegister()); - locations->SetInAt(1, Location::RequiresFpuRegister()); + return kFcsel; + } else if (IsRecognizedCselConstant(select->GetFalseValue())) { + return kCselFalseConst; + } else if (IsRecognizedCselConstant(select->GetTrueValue())) { + return kCselTrueConst; } else { - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RequiresRegister()); + return kCsel; + } +} + +static inline bool HasSwappedInputs(SelectVariant variant) { + return variant == kCselTrueConst; +} + +static inline Condition GetConditionForSelect(HCondition* condition, SelectVariant variant) { + IfCondition cond = HasSwappedInputs(variant) ? condition->GetOppositeCondition() + : condition->GetCondition(); + return IsConditionOnFloatingPointValues(condition) ? ARM64FPCondition(cond, condition->IsGtBias()) + : ARM64Condition(cond); +} + +void LocationsBuilderARM64::VisitSelect(HSelect* select) { + LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(select); + switch (GetSelectVariant(select)) { + case kCsel: + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister()); + break; + case kCselFalseConst: + locations->SetInAt(0, Location::ConstantLocation(select->InputAt(0)->AsConstant())); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister()); + break; + case kCselTrueConst: + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::ConstantLocation(select->InputAt(1)->AsConstant())); + locations->SetOut(Location::RequiresRegister()); + break; + case kFcsel: + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresFpuRegister()); + break; } if (IsBooleanValueOrMaterializedCondition(select->GetCondition())) { locations->SetInAt(2, Location::RequiresRegister()); } - locations->SetOut(Location::SameAsFirstInput()); } void InstructionCodeGeneratorARM64::VisitSelect(HSelect* select) { - LocationSummary* locations = select->GetLocations(); - vixl::Label false_target; - GenerateTestAndBranch(select, - /* condition_input_index */ 2, - /* true_target */ nullptr, - &false_target); - codegen_->MoveLocation(locations->Out(), locations->InAt(1), select->GetType()); - __ Bind(&false_target); + HInstruction* cond = select->GetCondition(); + SelectVariant variant = GetSelectVariant(select); + Condition csel_cond; + + if (IsBooleanValueOrMaterializedCondition(cond)) { + if (cond->IsCondition() && cond->GetNext() == select) { + // Condition codes set from previous instruction. + csel_cond = GetConditionForSelect(cond->AsCondition(), variant); + } else { + __ Cmp(InputRegisterAt(select, 2), 0); + csel_cond = HasSwappedInputs(variant) ? eq : ne; + } + } else if (IsConditionOnFloatingPointValues(cond)) { + Location rhs = cond->GetLocations()->InAt(1); + if (rhs.IsConstant()) { + DCHECK(IsFloatingPointZeroConstant(rhs.GetConstant())); + __ Fcmp(InputFPRegisterAt(cond, 0), 0.0); + } else { + __ Fcmp(InputFPRegisterAt(cond, 0), InputFPRegisterAt(cond, 1)); + } + csel_cond = GetConditionForSelect(cond->AsCondition(), variant); + } else { + __ Cmp(InputRegisterAt(cond, 0), InputOperandAt(cond, 1)); + csel_cond = GetConditionForSelect(cond->AsCondition(), variant); + } + + switch (variant) { + case kCsel: + case kCselFalseConst: + __ Csel(OutputRegister(select), + InputRegisterAt(select, 1), + InputOperandAt(select, 0), + csel_cond); + break; + case kCselTrueConst: + __ Csel(OutputRegister(select), + InputRegisterAt(select, 0), + InputOperandAt(select, 1), + csel_cond); + break; + case kFcsel: + __ Fcsel(OutputFPRegister(select), + InputFPRegisterAt(select, 1), + InputFPRegisterAt(select, 0), + csel_cond); + break; + } } void LocationsBuilderARM64::VisitNativeDebugInfo(HNativeDebugInfo* info) { diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 71d65e84a6..119084e026 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -3943,18 +3943,26 @@ void InstructionCodeGeneratorMIPS64::VisitTypeConversion(HTypeConversion* conver __ Andi(dst, src, 0xFFFF); break; case Primitive::kPrimByte: - // long is never converted into types narrower than int directly, - // so SEB and SEH can be used without ever causing unpredictable results - // on 64-bit inputs - DCHECK(input_type != Primitive::kPrimLong); - __ Seb(dst, src); + if (input_type == Primitive::kPrimLong) { + // Type conversion from long to types narrower than int is a result of code + // transformations. To avoid unpredictable results for SEB and SEH, we first + // need to sign-extend the low 32-bit value into bits 32 through 63. + __ Sll(dst, src, 0); + __ Seb(dst, dst); + } else { + __ Seb(dst, src); + } break; case Primitive::kPrimShort: - // long is never converted into types narrower than int directly, - // so SEB and SEH can be used without ever causing unpredictable results - // on 64-bit inputs - DCHECK(input_type != Primitive::kPrimLong); - __ Seh(dst, src); + if (input_type == Primitive::kPrimLong) { + // Type conversion from long to types narrower than int is a result of code + // transformations. To avoid unpredictable results for SEB and SEH, we first + // need to sign-extend the low 32-bit value into bits 32 through 63. + __ Sll(dst, src, 0); + __ Seh(dst, dst); + } else { + __ Seh(dst, src); + } break; case Primitive::kPrimInt: case Primitive::kPrimLong: diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 3713690335..3c880c2aaa 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -1276,7 +1276,7 @@ void InstructionCodeGeneratorX86::GenerateLongComparesAndJumps(HCondition* cond, } // Must be equal high, so compare the lows. codegen_->Compare32BitValue(left_low, val_low); - } else { + } else if (right.IsRegisterPair()) { Register right_high = right.AsRegisterPairHigh<Register>(); Register right_low = right.AsRegisterPairLow<Register>(); @@ -1291,6 +1291,19 @@ void InstructionCodeGeneratorX86::GenerateLongComparesAndJumps(HCondition* cond, } // Must be equal high, so compare the lows. __ cmpl(left_low, right_low); + } else { + DCHECK(right.IsDoubleStackSlot()); + __ cmpl(left_high, Address(ESP, right.GetHighStackIndex(kX86WordSize))); + if (if_cond == kCondNE) { + __ j(X86Condition(true_high_cond), true_label); + } else if (if_cond == kCondEQ) { + __ j(X86Condition(false_high_cond), false_label); + } else { + __ j(X86Condition(true_high_cond), true_label); + __ j(X86Condition(false_high_cond), false_label); + } + // Must be equal high, so compare the lows. + __ cmpl(left_low, Address(ESP, right.GetStackIndex())); } // The last comparison might be unsigned. __ j(final_condition, true_label); @@ -1593,7 +1606,7 @@ void LocationsBuilderX86::HandleCondition(HCondition* cond) { switch (cond->InputAt(0)->GetType()) { case Primitive::kPrimLong: { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrConstant(cond->InputAt(1))); + locations->SetInAt(1, Location::Any()); if (!cond->IsEmittedAtUseSite()) { locations->SetOut(Location::RequiresRegister()); } @@ -2145,6 +2158,18 @@ void LocationsBuilderX86::VisitTypeConversion(HTypeConversion* conversion) { switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: { + // Type conversion from long to byte is a result of code transformations. + HInstruction* input = conversion->InputAt(0); + Location input_location = input->IsConstant() + ? Location::ConstantLocation(input->AsConstant()) + : Location::RegisterPairLocation(EAX, EDX); + locations->SetInAt(0, input_location); + // Make the output overlap to please the register allocator. This greatly simplifies + // the validation of the linear scan implementation + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + break; + } case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2165,6 +2190,8 @@ void LocationsBuilderX86::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2242,6 +2269,8 @@ void LocationsBuilderX86::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to char is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2336,6 +2365,16 @@ void InstructionCodeGeneratorX86::VisitTypeConversion(HTypeConversion* conversio switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to byte is a result of code transformations. + if (in.IsRegisterPair()) { + __ movsxb(out.AsRegister<Register>(), in.AsRegisterPairLow<ByteRegister>()); + } else { + DCHECK(in.GetConstant()->IsLongConstant()); + int64_t value = in.GetConstant()->AsLongConstant()->GetValue(); + __ movl(out.AsRegister<Register>(), Immediate(static_cast<int8_t>(value))); + } + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2359,6 +2398,18 @@ void InstructionCodeGeneratorX86::VisitTypeConversion(HTypeConversion* conversio case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. + if (in.IsRegisterPair()) { + __ movsxw(out.AsRegister<Register>(), in.AsRegisterPairLow<Register>()); + } else if (in.IsDoubleStackSlot()) { + __ movsxw(out.AsRegister<Register>(), Address(ESP, in.GetStackIndex())); + } else { + DCHECK(in.GetConstant()->IsLongConstant()); + int64_t value = in.GetConstant()->AsLongConstant()->GetValue(); + __ movl(out.AsRegister<Register>(), Immediate(static_cast<int16_t>(value))); + } + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2495,6 +2546,18 @@ void InstructionCodeGeneratorX86::VisitTypeConversion(HTypeConversion* conversio case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. + if (in.IsRegisterPair()) { + __ movzxw(out.AsRegister<Register>(), in.AsRegisterPairLow<Register>()); + } else if (in.IsDoubleStackSlot()) { + __ movzxw(out.AsRegister<Register>(), Address(ESP, in.GetStackIndex())); + } else { + DCHECK(in.GetConstant()->IsLongConstant()); + int64_t value = in.GetConstant()->AsLongConstant()->GetValue(); + __ movl(out.AsRegister<Register>(), Immediate(static_cast<uint16_t>(value))); + } + break; case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 35603aa030..a53a6be3de 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1551,14 +1551,16 @@ void LocationsBuilderX86_64::VisitSelect(HSelect* select) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(select); if (Primitive::IsFloatingPointType(select->GetType())) { locations->SetInAt(0, Location::RequiresFpuRegister()); - // Since we can't use CMOV, there is no need to force 'true' into a register. locations->SetInAt(1, Location::Any()); } else { locations->SetInAt(0, Location::RequiresRegister()); if (SelectCanUseCMOV(select)) { - locations->SetInAt(1, Location::RequiresRegister()); + if (select->InputAt(1)->IsConstant()) { + locations->SetInAt(1, Location::RequiresRegister()); + } else { + locations->SetInAt(1, Location::Any()); + } } else { - // Since we can't use CMOV, there is no need to force 'true' into a register. locations->SetInAt(1, Location::Any()); } } @@ -1574,7 +1576,7 @@ void InstructionCodeGeneratorX86_64::VisitSelect(HSelect* select) { // If both the condition and the source types are integer, we can generate // a CMOV to implement Select. CpuRegister value_false = locations->InAt(0).AsRegister<CpuRegister>(); - CpuRegister value_true = locations->InAt(1).AsRegister<CpuRegister>(); + Location value_true_loc = locations->InAt(1); DCHECK(locations->InAt(0).Equals(locations->Out())); HInstruction* select_condition = select->GetCondition(); @@ -1606,7 +1608,14 @@ void InstructionCodeGeneratorX86_64::VisitSelect(HSelect* select) { // If the condition is true, overwrite the output, which already contains false. // Generate the correct sized CMOV. - __ cmov(cond, value_false, value_true, select->GetType() == Primitive::kPrimLong); + bool is_64_bit = Primitive::Is64BitType(select->GetType()); + if (value_true_loc.IsRegister()) { + __ cmov(cond, value_false, value_true_loc.AsRegister<CpuRegister>(), is_64_bit); + } else { + __ cmov(cond, + value_false, + Address(CpuRegister(RSP), value_true_loc.GetStackIndex()), is_64_bit); + } } else { NearLabel false_target; GenerateTestAndBranch<NearLabel>(select, @@ -2363,6 +2372,8 @@ void LocationsBuilderX86_64::VisitTypeConversion(HTypeConversion* conversion) { switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to byte is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2381,6 +2392,8 @@ void LocationsBuilderX86_64::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2458,6 +2471,8 @@ void LocationsBuilderX86_64::VisitTypeConversion(HTypeConversion* conversion) { case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to char is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2552,6 +2567,8 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver switch (result_type) { case Primitive::kPrimByte: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to byte is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimShort: @@ -2560,13 +2577,12 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver // Processing a Dex `int-to-byte' instruction. if (in.IsRegister()) { __ movsxb(out.AsRegister<CpuRegister>(), in.AsRegister<CpuRegister>()); - } else if (in.IsStackSlot()) { + } else if (in.IsStackSlot() || in.IsDoubleStackSlot()) { __ movsxb(out.AsRegister<CpuRegister>(), Address(CpuRegister(RSP), in.GetStackIndex())); } else { - DCHECK(in.GetConstant()->IsIntConstant()); __ movl(out.AsRegister<CpuRegister>(), - Immediate(static_cast<int8_t>(in.GetConstant()->AsIntConstant()->GetValue()))); + Immediate(static_cast<int8_t>(Int64FromConstant(in.GetConstant())))); } break; @@ -2578,6 +2594,8 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver case Primitive::kPrimShort: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to short is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2586,13 +2604,12 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver // Processing a Dex `int-to-short' instruction. if (in.IsRegister()) { __ movsxw(out.AsRegister<CpuRegister>(), in.AsRegister<CpuRegister>()); - } else if (in.IsStackSlot()) { + } else if (in.IsStackSlot() || in.IsDoubleStackSlot()) { __ movsxw(out.AsRegister<CpuRegister>(), Address(CpuRegister(RSP), in.GetStackIndex())); } else { - DCHECK(in.GetConstant()->IsIntConstant()); __ movl(out.AsRegister<CpuRegister>(), - Immediate(static_cast<int16_t>(in.GetConstant()->AsIntConstant()->GetValue()))); + Immediate(static_cast<int16_t>(Int64FromConstant(in.GetConstant())))); } break; @@ -2735,6 +2752,8 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver case Primitive::kPrimChar: switch (input_type) { + case Primitive::kPrimLong: + // Type conversion from long to char is a result of code transformations. case Primitive::kPrimBoolean: // Boolean input is a result of code transformations. case Primitive::kPrimByte: @@ -2743,14 +2762,12 @@ void InstructionCodeGeneratorX86_64::VisitTypeConversion(HTypeConversion* conver // Processing a Dex `int-to-char' instruction. if (in.IsRegister()) { __ movzxw(out.AsRegister<CpuRegister>(), in.AsRegister<CpuRegister>()); - } else if (in.IsStackSlot()) { + } else if (in.IsStackSlot() || in.IsDoubleStackSlot()) { __ movzxw(out.AsRegister<CpuRegister>(), Address(CpuRegister(RSP), in.GetStackIndex())); } else { - DCHECK(in.GetConstant()->IsIntConstant()); __ movl(out.AsRegister<CpuRegister>(), - Immediate(static_cast<uint16_t>( - in.GetConstant()->AsIntConstant()->GetValue()))); + Immediate(static_cast<uint16_t>(Int64FromConstant(in.GetConstant())))); } break; diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc index 4f37c37e39..6be79fa75c 100644 --- a/compiler/optimizing/codegen_test.cc +++ b/compiler/optimizing/codegen_test.cc @@ -206,10 +206,13 @@ static void RunCode(CodeGenerator* codegen, std::function<void(HGraph*)> hook_before_codegen, bool has_result, Expected expected) { - ASSERT_TRUE(graph->IsInSsaForm()); - - SSAChecker graph_checker(graph); + GraphChecker graph_checker(graph); graph_checker.Run(); + if (!graph_checker.IsValid()) { + for (auto error : graph_checker.GetErrors()) { + std::cout << error << std::endl; + } + } ASSERT_TRUE(graph_checker.IsValid()); SsaLivenessAnalysis liveness(graph, codegen); @@ -292,14 +295,9 @@ static void TestCode(const uint16_t* data, for (InstructionSet target_isa : GetTargetISAs()) { ArenaPool pool; ArenaAllocator arena(&pool); - HGraph* graph = CreateGraph(&arena); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); + HGraph* graph = CreateCFG(&arena, data); // Remove suspend checks, they cannot be executed in this context. RemoveSuspendChecks(graph); - TransformToSsa(graph); RunCode(target_isa, graph, [](HGraph*) {}, has_result, expected); } } @@ -310,14 +308,9 @@ static void TestCodeLong(const uint16_t* data, for (InstructionSet target_isa : GetTargetISAs()) { ArenaPool pool; ArenaAllocator arena(&pool); - HGraph* graph = CreateGraph(&arena); - HGraphBuilder builder(graph, Primitive::kPrimLong); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); + HGraph* graph = CreateCFG(&arena, data, Primitive::kPrimLong); // Remove suspend checks, they cannot be executed in this context. RemoveSuspendChecks(graph); - TransformToSsa(graph); RunCode(target_isa, graph, [](HGraph*) {}, has_result, expected); } } @@ -640,6 +633,7 @@ TEST_F(CodegenTest, NonMaterializedCondition) { ArenaAllocator allocator(&pool); HGraph* graph = CreateGraph(&allocator); + HBasicBlock* entry = new (&allocator) HBasicBlock(graph); graph->AddBlock(entry); graph->SetEntryBlock(entry); @@ -672,7 +666,7 @@ TEST_F(CodegenTest, NonMaterializedCondition) { else_block->AddInstruction(new (&allocator) HReturn(constant1)); ASSERT_FALSE(equal->IsEmittedAtUseSite()); - TransformToSsa(graph); + graph->BuildDominatorTree(); PrepareForRegisterAllocation(graph).Run(); ASSERT_TRUE(equal->IsEmittedAtUseSite()); @@ -723,7 +717,7 @@ TEST_F(CodegenTest, MaterializedCondition1) { HReturn ret(&cmp_lt); code_block->AddInstruction(&ret); - TransformToSsa(graph); + graph->BuildDominatorTree(); auto hook_before_codegen = [](HGraph* graph_in) { HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); @@ -791,7 +785,7 @@ TEST_F(CodegenTest, MaterializedCondition2) { HReturn ret_ge(cst_ge); if_false_block->AddInstruction(&ret_ge); - TransformToSsa(graph); + graph->BuildDominatorTree(); auto hook_before_codegen = [](HGraph* graph_in) { HBasicBlock* block = graph_in->GetEntryBlock()->GetSuccessors()[0]; HParallelMove* move = new (graph_in->GetArena()) HParallelMove(graph_in->GetArena()); @@ -907,7 +901,7 @@ static void TestComparison(IfCondition condition, block->AddInstruction(comparison); block->AddInstruction(new (&allocator) HReturn(comparison)); - TransformToSsa(graph); + graph->BuildDominatorTree(); RunCode(target_isa, graph, [](HGraph*) {}, true, expected_result); } diff --git a/compiler/optimizing/constant_folding_test.cc b/compiler/optimizing/constant_folding_test.cc index a8f65bf516..9c69f8c75b 100644 --- a/compiler/optimizing/constant_folding_test.cc +++ b/compiler/optimizing/constant_folding_test.cc @@ -56,7 +56,6 @@ class ConstantFoldingTest : public CommonCompilerTest { const std::string& expected_after_dce, std::function<void(HGraph*)> check_after_cf) { ASSERT_NE(graph_, nullptr); - TransformToSsa(graph_); StringPrettyPrinter printer_before(graph_); printer_before.VisitInsertionOrder(); @@ -67,9 +66,9 @@ class ConstantFoldingTest : public CommonCompilerTest { X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegenX86(graph_, *features_x86.get(), CompilerOptions()); HConstantFolding(graph_).Run(); - SSAChecker ssa_checker_cf(graph_); - ssa_checker_cf.Run(); - ASSERT_TRUE(ssa_checker_cf.IsValid()); + GraphChecker graph_checker_cf(graph_); + graph_checker_cf.Run(); + ASSERT_TRUE(graph_checker_cf.IsValid()); StringPrettyPrinter printer_after_cf(graph_); printer_after_cf.VisitInsertionOrder(); @@ -79,9 +78,9 @@ class ConstantFoldingTest : public CommonCompilerTest { check_after_cf(graph_); HDeadCodeElimination(graph_).Run(); - SSAChecker ssa_checker_dce(graph_); - ssa_checker_dce.Run(); - ASSERT_TRUE(ssa_checker_dce.IsValid()); + GraphChecker graph_checker_dce(graph_); + graph_checker_dce.Run(); + ASSERT_TRUE(graph_checker_dce.IsValid()); StringPrettyPrinter printer_after_dce(graph_); printer_after_dce.VisitInsertionOrder(); @@ -775,76 +774,87 @@ TEST_F(ConstantFoldingTest, UnsignedComparisonsWithZero) { HInstruction* zero = graph_->GetIntConstant(0); HInstruction* last; block->AddInstruction(last = new (&allocator_) HAbove(zero, parameter)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HAbove(parameter, zero)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HAboveOrEqual(zero, parameter)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HAboveOrEqual(parameter, zero)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HBelow(zero, parameter)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HBelow(parameter, zero)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HBelowOrEqual(zero, parameter)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); block->AddInstruction(last = new (&allocator_) HBelowOrEqual(parameter, zero)); - block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(new (&allocator_) HSelect(last, parameter, parameter, 0)); entry_block->AddInstruction(new (&allocator_) HGoto()); block->AddInstruction(new (&allocator_) HReturn(zero)); exit_block->AddInstruction(new (&allocator_) HExit()); + graph_->BuildDominatorTree(); + const std::string expected_before = "BasicBlock 0, succ: 1\n" - " 0: ParameterValue [16, 14, 12, 10, 8, 6, 4, 2]\n" + " 0: ParameterValue [17, 17, 16, 15, 15, 14, 13, 13, 12, 11, 11, 10, 9, 9, " + "8, 7, 7, 6, 5, 5, 4, 3, 3, 2]\n" " 1: IntConstant [19, 16, 14, 12, 10, 8, 6, 4, 2]\n" " 18: Goto 1\n" "BasicBlock 1, pred: 0, succ: 2\n" " 2: Above(1, 0) [3]\n" - " 3: Deoptimize(2)\n" + " 3: Select(0, 0, 2)\n" " 4: Above(0, 1) [5]\n" - " 5: Deoptimize(4)\n" + " 5: Select(0, 0, 4)\n" " 6: AboveOrEqual(1, 0) [7]\n" - " 7: Deoptimize(6)\n" + " 7: Select(0, 0, 6)\n" " 8: AboveOrEqual(0, 1) [9]\n" - " 9: Deoptimize(8)\n" + " 9: Select(0, 0, 8)\n" " 10: Below(1, 0) [11]\n" - " 11: Deoptimize(10)\n" + " 11: Select(0, 0, 10)\n" " 12: Below(0, 1) [13]\n" - " 13: Deoptimize(12)\n" + " 13: Select(0, 0, 12)\n" " 14: BelowOrEqual(1, 0) [15]\n" - " 15: Deoptimize(14)\n" + " 15: Select(0, 0, 14)\n" " 16: BelowOrEqual(0, 1) [17]\n" - " 17: Deoptimize(16)\n" + " 17: Select(0, 0, 16)\n" " 19: Return(1)\n" "BasicBlock 2, pred: 1\n" " 20: Exit\n"; const std::string expected_after_cf = "BasicBlock 0, succ: 1\n" - " 0: ParameterValue [16, 10, 6, 4]\n" + " 0: ParameterValue [17, 17, 16, 15, 15, 13, 13, 11, 11, 10, 9, 9, 7, 7, 6, 5, 5, 4, 3, 3]\n" " 1: IntConstant [13, 3, 19, 16, 10, 6, 4]\n" " 21: IntConstant [15, 9]\n" " 18: Goto 1\n" "BasicBlock 1, pred: 0, succ: 2\n" - " 3: Deoptimize(1)\n" + " 3: Select(0, 0, 1)\n" " 4: Above(0, 1) [5]\n" - " 5: Deoptimize(4)\n" + " 5: Select(0, 0, 4)\n" " 6: AboveOrEqual(1, 0) [7]\n" - " 7: Deoptimize(6)\n" - " 9: Deoptimize(21)\n" + " 7: Select(0, 0, 6)\n" + " 9: Select(0, 0, 21)\n" " 10: Below(1, 0) [11]\n" - " 11: Deoptimize(10)\n" - " 13: Deoptimize(1)\n" - " 15: Deoptimize(21)\n" + " 11: Select(0, 0, 10)\n" + " 13: Select(0, 0, 1)\n" + " 15: Select(0, 0, 21)\n" " 16: BelowOrEqual(0, 1) [17]\n" - " 17: Deoptimize(16)\n" + " 17: Select(0, 0, 16)\n" " 19: Return(1)\n" "BasicBlock 2, pred: 1\n" " 20: Exit\n"; - const std::string expected_after_dce = expected_after_cf; + const std::string expected_after_dce = + "BasicBlock 0, succ: 1\n" + " 0: ParameterValue\n" + " 1: IntConstant [19]\n" + " 18: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 2\n" + " 19: Return(1)\n" + "BasicBlock 2, pred: 1\n" + " 20: Exit\n"; auto check_after_cf = [](HGraph* graph) { CHECK(graph != nullptr); diff --git a/compiler/optimizing/dead_code_elimination_test.cc b/compiler/optimizing/dead_code_elimination_test.cc index f0f98efadb..930795b4f6 100644 --- a/compiler/optimizing/dead_code_elimination_test.cc +++ b/compiler/optimizing/dead_code_elimination_test.cc @@ -36,8 +36,6 @@ static void TestCode(const uint16_t* data, HGraph* graph = CreateCFG(&allocator, data); ASSERT_NE(graph, nullptr); - TransformToSsa(graph); - StringPrettyPrinter printer_before(graph); printer_before.VisitInsertionOrder(); std::string actual_before = printer_before.str(); @@ -47,9 +45,9 @@ static void TestCode(const uint16_t* data, X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegenX86(graph, *features_x86.get(), CompilerOptions()); HDeadCodeElimination(graph).Run(); - SSAChecker ssa_checker(graph); - ssa_checker.Run(); - ASSERT_TRUE(ssa_checker.IsValid()); + GraphChecker graph_checker(graph); + graph_checker.Run(); + ASSERT_TRUE(graph_checker.IsValid()); StringPrettyPrinter printer_after(graph); printer_after.VisitInsertionOrder(); diff --git a/compiler/optimizing/dominator_test.cc b/compiler/optimizing/dominator_test.cc index feb8b2092a..50c677adf5 100644 --- a/compiler/optimizing/dominator_test.cc +++ b/compiler/optimizing/dominator_test.cc @@ -24,15 +24,12 @@ namespace art { +class OptimizerTest : public CommonCompilerTest {}; + static void TestCode(const uint16_t* data, const uint32_t* blocks, size_t blocks_length) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - graph->BuildDominatorTree(); + HGraph* graph = CreateCFG(&allocator, data); ASSERT_EQ(graph->GetBlocks().size(), blocks_length); for (size_t i = 0, e = blocks_length; i < e; ++i) { if (blocks[i] == kInvalidBlockId) { @@ -50,7 +47,7 @@ static void TestCode(const uint16_t* data, const uint32_t* blocks, size_t blocks } } -TEST(OptimizerTest, ReturnVoid) { +TEST_F(OptimizerTest, ReturnVoid) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::RETURN_VOID); // Block number 1 @@ -63,7 +60,7 @@ TEST(OptimizerTest, ReturnVoid) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG1) { +TEST_F(OptimizerTest, CFG1) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x100, // Block number 1 Instruction::RETURN_VOID); // Block number 2 @@ -78,7 +75,7 @@ TEST(OptimizerTest, CFG1) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG2) { +TEST_F(OptimizerTest, CFG2) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x100, // Block number 1 Instruction::GOTO | 0x100, // Block number 2 @@ -95,7 +92,7 @@ TEST(OptimizerTest, CFG2) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG3) { +TEST_F(OptimizerTest, CFG3) { const uint16_t data1[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x200, // Block number 1 Instruction::RETURN_VOID, // Block number 2 @@ -126,7 +123,7 @@ TEST(OptimizerTest, CFG3) { TestCode(data3, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG4) { +TEST_F(OptimizerTest, CFG4) { const uint16_t data1[] = ZERO_REGISTER_CODE_ITEM( Instruction::NOP, Instruction::GOTO | 0xFF00); @@ -146,7 +143,7 @@ TEST(OptimizerTest, CFG4) { TestCode(data2, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG5) { +TEST_F(OptimizerTest, CFG5) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::RETURN_VOID, // Block number 1 Instruction::GOTO | 0x100, // Dead block @@ -163,7 +160,7 @@ TEST(OptimizerTest, CFG5) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG6) { +TEST_F(OptimizerTest, CFG6) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -182,7 +179,7 @@ TEST(OptimizerTest, CFG6) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG7) { +TEST_F(OptimizerTest, CFG7) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, // Block number 1 @@ -202,7 +199,7 @@ TEST(OptimizerTest, CFG7) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG8) { +TEST_F(OptimizerTest, CFG8) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, // Block number 1 @@ -223,7 +220,7 @@ TEST(OptimizerTest, CFG8) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG9) { +TEST_F(OptimizerTest, CFG9) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, // Block number 1 @@ -244,7 +241,7 @@ TEST(OptimizerTest, CFG9) { TestCode(data, dominators, sizeof(dominators) / sizeof(int)); } -TEST(OptimizerTest, CFG10) { +TEST_F(OptimizerTest, CFG10) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 6, // Block number 1 diff --git a/compiler/optimizing/find_loops_test.cc b/compiler/optimizing/find_loops_test.cc index 4770fa2eca..04789d9a2d 100644 --- a/compiler/optimizing/find_loops_test.cc +++ b/compiler/optimizing/find_loops_test.cc @@ -27,16 +27,9 @@ namespace art { -static HGraph* TestCode(const uint16_t* data, ArenaAllocator* allocator) { - HGraph* graph = CreateGraph(allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - builder.BuildGraph(*item); - graph->BuildDominatorTree(); - return graph; -} +class FindLoopsTest : public CommonCompilerTest {}; -TEST(FindLoopsTest, CFG1) { +TEST_F(FindLoopsTest, CFG1) { // Constant is not used. const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -44,26 +37,26 @@ TEST(FindLoopsTest, CFG1) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); for (HBasicBlock* block : graph->GetBlocks()) { ASSERT_EQ(block->GetLoopInformation(), nullptr); } } -TEST(FindLoopsTest, CFG2) { +TEST_F(FindLoopsTest, CFG2) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::RETURN); ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); for (HBasicBlock* block : graph->GetBlocks()) { ASSERT_EQ(block->GetLoopInformation(), nullptr); } } -TEST(FindLoopsTest, CFG3) { +TEST_F(FindLoopsTest, CFG3) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, @@ -73,13 +66,13 @@ TEST(FindLoopsTest, CFG3) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); for (HBasicBlock* block : graph->GetBlocks()) { ASSERT_EQ(block->GetLoopInformation(), nullptr); } } -TEST(FindLoopsTest, CFG4) { +TEST_F(FindLoopsTest, CFG4) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 4, @@ -90,13 +83,13 @@ TEST(FindLoopsTest, CFG4) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); for (HBasicBlock* block : graph->GetBlocks()) { ASSERT_EQ(block->GetLoopInformation(), nullptr); } } -TEST(FindLoopsTest, CFG5) { +TEST_F(FindLoopsTest, CFG5) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -105,7 +98,7 @@ TEST(FindLoopsTest, CFG5) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); for (HBasicBlock* block : graph->GetBlocks()) { ASSERT_EQ(block->GetLoopInformation(), nullptr); } @@ -137,7 +130,7 @@ static void TestBlock(HGraph* graph, } } -TEST(FindLoopsTest, Loop1) { +TEST_F(FindLoopsTest, Loop1) { // Simple loop with one preheader and one back edge. // var a = 0; // while (a == a) { @@ -151,7 +144,7 @@ TEST(FindLoopsTest, Loop1) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header @@ -162,7 +155,7 @@ TEST(FindLoopsTest, Loop1) { TestBlock(graph, 5, false, kInvalidBlockId); // exit block } -TEST(FindLoopsTest, Loop2) { +TEST_F(FindLoopsTest, Loop2) { // Make sure we support a preheader of a loop not being the first predecessor // in the predecessor list of the header. // var a = 0; @@ -179,7 +172,7 @@ TEST(FindLoopsTest, Loop2) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // goto block @@ -191,7 +184,7 @@ TEST(FindLoopsTest, Loop2) { TestBlock(graph, 6, false, kInvalidBlockId); // exit block } -TEST(FindLoopsTest, Loop3) { +TEST_F(FindLoopsTest, Loop3) { // Make sure we create a preheader of a loop when a header originally has two // incoming blocks and one back edge. const uint16_t data[] = ONE_REGISTER_CODE_ITEM( @@ -204,7 +197,7 @@ TEST(FindLoopsTest, Loop3) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // goto block @@ -218,7 +211,7 @@ TEST(FindLoopsTest, Loop3) { TestBlock(graph, 8, false, kInvalidBlockId); // synthesized pre header } -TEST(FindLoopsTest, Loop4) { +TEST_F(FindLoopsTest, Loop4) { // Test loop with originally two back edges. const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -230,7 +223,7 @@ TEST(FindLoopsTest, Loop4) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header @@ -244,7 +237,7 @@ TEST(FindLoopsTest, Loop4) { } -TEST(FindLoopsTest, Loop5) { +TEST_F(FindLoopsTest, Loop5) { // Test loop with two exit edges. const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -256,7 +249,7 @@ TEST(FindLoopsTest, Loop5) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header @@ -270,7 +263,7 @@ TEST(FindLoopsTest, Loop5) { TestBlock(graph, 8, false, kInvalidBlockId); // synthesized block at the loop exit } -TEST(FindLoopsTest, InnerLoop) { +TEST_F(FindLoopsTest, InnerLoop) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 6, @@ -281,7 +274,7 @@ TEST(FindLoopsTest, InnerLoop) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header of outer loop @@ -301,7 +294,7 @@ TEST(FindLoopsTest, InnerLoop) { *graph->GetBlocks()[3]->GetLoopInformation())); } -TEST(FindLoopsTest, TwoLoops) { +TEST_F(FindLoopsTest, TwoLoops) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -312,7 +305,7 @@ TEST(FindLoopsTest, TwoLoops) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header of first loop @@ -331,7 +324,7 @@ TEST(FindLoopsTest, TwoLoops) { *graph->GetBlocks()[4]->GetLoopInformation())); } -TEST(FindLoopsTest, NonNaturalLoop) { +TEST_F(FindLoopsTest, NonNaturalLoop) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -342,14 +335,14 @@ TEST(FindLoopsTest, NonNaturalLoop) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); ASSERT_TRUE(graph->GetBlocks()[3]->IsLoopHeader()); HLoopInformation* info = graph->GetBlocks()[3]->GetLoopInformation(); ASSERT_EQ(1u, info->NumberOfBackEdges()); ASSERT_FALSE(info->GetHeader()->Dominates(info->GetBackEdges()[0])); } -TEST(FindLoopsTest, DoWhileLoop) { +TEST_F(FindLoopsTest, DoWhileLoop) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::GOTO | 0x0100, @@ -358,7 +351,7 @@ TEST(FindLoopsTest, DoWhileLoop) { ArenaPool arena; ArenaAllocator allocator(&arena); - HGraph* graph = TestCode(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); TestBlock(graph, 0, false, kInvalidBlockId); // entry block TestBlock(graph, 1, false, kInvalidBlockId); // pre header of first loop diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 962e77dfc9..e6e9177841 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -149,6 +149,103 @@ void GraphChecker::VisitBasicBlock(HBasicBlock* block) { } current->Accept(this); } + + // Ensure that catch blocks are not normal successors, and normal blocks are + // never exceptional successors. + for (HBasicBlock* successor : block->GetNormalSuccessors()) { + if (successor->IsCatchBlock()) { + AddError(StringPrintf("Catch block %d is a normal successor of block %d.", + successor->GetBlockId(), + block->GetBlockId())); + } + } + for (HBasicBlock* successor : block->GetExceptionalSuccessors()) { + if (!successor->IsCatchBlock()) { + AddError(StringPrintf("Normal block %d is an exceptional successor of block %d.", + successor->GetBlockId(), + block->GetBlockId())); + } + } + + // Ensure dominated blocks have `block` as the dominator. + for (HBasicBlock* dominated : block->GetDominatedBlocks()) { + if (dominated->GetDominator() != block) { + AddError(StringPrintf("Block %d should be the dominator of %d.", + block->GetBlockId(), + dominated->GetBlockId())); + } + } + + // Ensure there is no critical edge (i.e., an edge connecting a + // block with multiple successors to a block with multiple + // predecessors). Exceptional edges are synthesized and hence + // not accounted for. + if (block->GetSuccessors().size() > 1) { + for (HBasicBlock* successor : block->GetNormalSuccessors()) { + if (successor->IsExitBlock() && + block->IsSingleTryBoundary() && + block->GetPredecessors().size() == 1u && + block->GetSinglePredecessor()->GetLastInstruction()->IsThrow()) { + // Allowed critical edge Throw->TryBoundary->Exit. + } else if (successor->GetPredecessors().size() > 1) { + AddError(StringPrintf("Critical edge between blocks %d and %d.", + block->GetBlockId(), + successor->GetBlockId())); + } + } + } + + // Ensure try membership information is consistent. + if (block->IsCatchBlock()) { + if (block->IsTryBlock()) { + const HTryBoundary& try_entry = block->GetTryCatchInformation()->GetTryEntry(); + AddError(StringPrintf("Catch blocks should not be try blocks but catch block %d " + "has try entry %s:%d.", + block->GetBlockId(), + try_entry.DebugName(), + try_entry.GetId())); + } + + if (block->IsLoopHeader()) { + AddError(StringPrintf("Catch blocks should not be loop headers but catch block %d is.", + block->GetBlockId())); + } + } else { + for (HBasicBlock* predecessor : block->GetPredecessors()) { + const HTryBoundary* incoming_try_entry = predecessor->ComputeTryEntryOfSuccessors(); + if (block->IsTryBlock()) { + const HTryBoundary& stored_try_entry = block->GetTryCatchInformation()->GetTryEntry(); + if (incoming_try_entry == nullptr) { + AddError(StringPrintf("Block %d has try entry %s:%d but no try entry follows " + "from predecessor %d.", + block->GetBlockId(), + stored_try_entry.DebugName(), + stored_try_entry.GetId(), + predecessor->GetBlockId())); + } else if (!incoming_try_entry->HasSameExceptionHandlersAs(stored_try_entry)) { + AddError(StringPrintf("Block %d has try entry %s:%d which is not consistent " + "with %s:%d that follows from predecessor %d.", + block->GetBlockId(), + stored_try_entry.DebugName(), + stored_try_entry.GetId(), + incoming_try_entry->DebugName(), + incoming_try_entry->GetId(), + predecessor->GetBlockId())); + } + } else if (incoming_try_entry != nullptr) { + AddError(StringPrintf("Block %d is not a try block but try entry %s:%d follows " + "from predecessor %d.", + block->GetBlockId(), + incoming_try_entry->DebugName(), + incoming_try_entry->GetId(), + predecessor->GetBlockId())); + } + } + } + + if (block->IsLoopHeader()) { + HandleLoop(block); + } } void GraphChecker::VisitBoundsCheck(HBoundsCheck* check) { @@ -168,7 +265,7 @@ void GraphChecker::VisitTryBoundary(HTryBoundary* try_boundary) { // Ensure that all exception handlers are catch blocks. // Note that a normal-flow successor may be a catch block before CFG - // simplification. We only test normal-flow successors in SsaChecker. + // simplification. We only test normal-flow successors in GraphChecker. for (HBasicBlock* handler : handlers) { if (!handler->IsCatchBlock()) { AddError(StringPrintf("Block %d with %s:%d has exceptional successor %d which " @@ -303,6 +400,88 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { input->GetId())); } } + + // Ensure an instruction dominates all its uses. + for (HUseIterator<HInstruction*> use_it(instruction->GetUses()); + !use_it.Done(); use_it.Advance()) { + HInstruction* use = use_it.Current()->GetUser(); + if (!use->IsPhi() && !instruction->StrictlyDominates(use)) { + AddError(StringPrintf("Instruction %s:%d in block %d does not dominate " + "use %s:%d in block %d.", + instruction->DebugName(), + instruction->GetId(), + current_block_->GetBlockId(), + use->DebugName(), + use->GetId(), + use->GetBlock()->GetBlockId())); + } + } + + if (instruction->NeedsEnvironment() && !instruction->HasEnvironment()) { + AddError(StringPrintf("Instruction %s:%d in block %d requires an environment " + "but does not have one.", + instruction->DebugName(), + instruction->GetId(), + current_block_->GetBlockId())); + } + + // Ensure an instruction having an environment is dominated by the + // instructions contained in the environment. + for (HEnvironment* environment = instruction->GetEnvironment(); + environment != nullptr; + environment = environment->GetParent()) { + for (size_t i = 0, e = environment->Size(); i < e; ++i) { + HInstruction* env_instruction = environment->GetInstructionAt(i); + if (env_instruction != nullptr + && !env_instruction->StrictlyDominates(instruction)) { + AddError(StringPrintf("Instruction %d in environment of instruction %d " + "from block %d does not dominate instruction %d.", + env_instruction->GetId(), + instruction->GetId(), + current_block_->GetBlockId(), + instruction->GetId())); + } + } + } + + // Ensure that reference type instructions have reference type info. + if (instruction->GetType() == Primitive::kPrimNot) { + ScopedObjectAccess soa(Thread::Current()); + if (!instruction->GetReferenceTypeInfo().IsValid()) { + AddError(StringPrintf("Reference type instruction %s:%d does not have " + "valid reference type information.", + instruction->DebugName(), + instruction->GetId())); + } + } + + if (instruction->CanThrowIntoCatchBlock()) { + // Find the top-level environment. This corresponds to the environment of + // the catch block since we do not inline methods with try/catch. + HEnvironment* environment = instruction->GetEnvironment(); + while (environment->GetParent() != nullptr) { + environment = environment->GetParent(); + } + + // Find all catch blocks and test that `instruction` has an environment + // value for each one. + const HTryBoundary& entry = instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry(); + for (HBasicBlock* catch_block : entry.GetExceptionHandlers()) { + for (HInstructionIterator phi_it(catch_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + HPhi* catch_phi = phi_it.Current()->AsPhi(); + if (environment->GetInstructionAt(catch_phi->GetRegNumber()) == nullptr) { + AddError(StringPrintf("Instruction %s:%d throws into catch block %d " + "with catch phi %d for vreg %d but its " + "corresponding environment slot is empty.", + instruction->DebugName(), + instruction->GetId(), + catch_block->GetBlockId(), + catch_phi->GetId(), + catch_phi->GetRegNumber())); + } + } + } + } } void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { @@ -371,108 +550,7 @@ void GraphChecker::VisitInstanceOf(HInstanceOf* instruction) { } } -void SSAChecker::VisitBasicBlock(HBasicBlock* block) { - super_type::VisitBasicBlock(block); - - // Ensure that catch blocks are not normal successors, and normal blocks are - // never exceptional successors. - for (HBasicBlock* successor : block->GetNormalSuccessors()) { - if (successor->IsCatchBlock()) { - AddError(StringPrintf("Catch block %d is a normal successor of block %d.", - successor->GetBlockId(), - block->GetBlockId())); - } - } - for (HBasicBlock* successor : block->GetExceptionalSuccessors()) { - if (!successor->IsCatchBlock()) { - AddError(StringPrintf("Normal block %d is an exceptional successor of block %d.", - successor->GetBlockId(), - block->GetBlockId())); - } - } - - // Ensure dominated blocks have `block` as the dominator. - for (HBasicBlock* dominated : block->GetDominatedBlocks()) { - if (dominated->GetDominator() != block) { - AddError(StringPrintf("Block %d should be the dominator of %d.", - block->GetBlockId(), - dominated->GetBlockId())); - } - } - - // Ensure there is no critical edge (i.e., an edge connecting a - // block with multiple successors to a block with multiple - // predecessors). Exceptional edges are synthesized and hence - // not accounted for. - if (block->GetSuccessors().size() > 1) { - for (HBasicBlock* successor : block->GetNormalSuccessors()) { - if (successor->IsExitBlock() && - block->IsSingleTryBoundary() && - block->GetPredecessors().size() == 1u && - block->GetSinglePredecessor()->GetLastInstruction()->IsThrow()) { - // Allowed critical edge Throw->TryBoundary->Exit. - } else if (successor->GetPredecessors().size() > 1) { - AddError(StringPrintf("Critical edge between blocks %d and %d.", - block->GetBlockId(), - successor->GetBlockId())); - } - } - } - - // Ensure try membership information is consistent. - if (block->IsCatchBlock()) { - if (block->IsTryBlock()) { - const HTryBoundary& try_entry = block->GetTryCatchInformation()->GetTryEntry(); - AddError(StringPrintf("Catch blocks should not be try blocks but catch block %d " - "has try entry %s:%d.", - block->GetBlockId(), - try_entry.DebugName(), - try_entry.GetId())); - } - - if (block->IsLoopHeader()) { - AddError(StringPrintf("Catch blocks should not be loop headers but catch block %d is.", - block->GetBlockId())); - } - } else { - for (HBasicBlock* predecessor : block->GetPredecessors()) { - const HTryBoundary* incoming_try_entry = predecessor->ComputeTryEntryOfSuccessors(); - if (block->IsTryBlock()) { - const HTryBoundary& stored_try_entry = block->GetTryCatchInformation()->GetTryEntry(); - if (incoming_try_entry == nullptr) { - AddError(StringPrintf("Block %d has try entry %s:%d but no try entry follows " - "from predecessor %d.", - block->GetBlockId(), - stored_try_entry.DebugName(), - stored_try_entry.GetId(), - predecessor->GetBlockId())); - } else if (!incoming_try_entry->HasSameExceptionHandlersAs(stored_try_entry)) { - AddError(StringPrintf("Block %d has try entry %s:%d which is not consistent " - "with %s:%d that follows from predecessor %d.", - block->GetBlockId(), - stored_try_entry.DebugName(), - stored_try_entry.GetId(), - incoming_try_entry->DebugName(), - incoming_try_entry->GetId(), - predecessor->GetBlockId())); - } - } else if (incoming_try_entry != nullptr) { - AddError(StringPrintf("Block %d is not a try block but try entry %s:%d follows " - "from predecessor %d.", - block->GetBlockId(), - incoming_try_entry->DebugName(), - incoming_try_entry->GetId(), - predecessor->GetBlockId())); - } - } - } - - if (block->IsLoopHeader()) { - CheckLoop(block); - } -} - -void SSAChecker::CheckLoop(HBasicBlock* loop_header) { +void GraphChecker::HandleLoop(HBasicBlock* loop_header) { int id = loop_header->GetBlockId(); HLoopInformation* loop_information = loop_header->GetLoopInformation(); @@ -582,92 +660,6 @@ void SSAChecker::CheckLoop(HBasicBlock* loop_header) { } } -void SSAChecker::VisitInstruction(HInstruction* instruction) { - super_type::VisitInstruction(instruction); - - // Ensure an instruction dominates all its uses. - for (HUseIterator<HInstruction*> use_it(instruction->GetUses()); - !use_it.Done(); use_it.Advance()) { - HInstruction* use = use_it.Current()->GetUser(); - if (!use->IsPhi() && !instruction->StrictlyDominates(use)) { - AddError(StringPrintf("Instruction %s:%d in block %d does not dominate " - "use %s:%d in block %d.", - instruction->DebugName(), - instruction->GetId(), - current_block_->GetBlockId(), - use->DebugName(), - use->GetId(), - use->GetBlock()->GetBlockId())); - } - } - - if (instruction->NeedsEnvironment() && !instruction->HasEnvironment()) { - AddError(StringPrintf("Instruction %s:%d in block %d requires an environment " - "but does not have one.", - instruction->DebugName(), - instruction->GetId(), - current_block_->GetBlockId())); - } - - // Ensure an instruction having an environment is dominated by the - // instructions contained in the environment. - for (HEnvironment* environment = instruction->GetEnvironment(); - environment != nullptr; - environment = environment->GetParent()) { - for (size_t i = 0, e = environment->Size(); i < e; ++i) { - HInstruction* env_instruction = environment->GetInstructionAt(i); - if (env_instruction != nullptr - && !env_instruction->StrictlyDominates(instruction)) { - AddError(StringPrintf("Instruction %d in environment of instruction %d " - "from block %d does not dominate instruction %d.", - env_instruction->GetId(), - instruction->GetId(), - current_block_->GetBlockId(), - instruction->GetId())); - } - } - } - - // Ensure that reference type instructions have reference type info. - if (instruction->GetType() == Primitive::kPrimNot) { - ScopedObjectAccess soa(Thread::Current()); - if (!instruction->GetReferenceTypeInfo().IsValid()) { - AddError(StringPrintf("Reference type instruction %s:%d does not have " - "valid reference type information.", - instruction->DebugName(), - instruction->GetId())); - } - } - - if (instruction->CanThrowIntoCatchBlock()) { - // Find the top-level environment. This corresponds to the environment of - // the catch block since we do not inline methods with try/catch. - HEnvironment* environment = instruction->GetEnvironment(); - while (environment->GetParent() != nullptr) { - environment = environment->GetParent(); - } - - // Find all catch blocks and test that `instruction` has an environment - // value for each one. - const HTryBoundary& entry = instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry(); - for (HBasicBlock* catch_block : entry.GetExceptionHandlers()) { - for (HInstructionIterator phi_it(catch_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) { - HPhi* catch_phi = phi_it.Current()->AsPhi(); - if (environment->GetInstructionAt(catch_phi->GetRegNumber()) == nullptr) { - AddError(StringPrintf("Instruction %s:%d throws into catch block %d " - "with catch phi %d for vreg %d but its " - "corresponding environment slot is empty.", - instruction->DebugName(), - instruction->GetId(), - catch_block->GetBlockId(), - catch_phi->GetId(), - catch_phi->GetRegNumber())); - } - } - } - } -} - static Primitive::Type PrimitiveKind(Primitive::Type type) { switch (type) { case Primitive::kPrimBoolean: @@ -710,7 +702,7 @@ static bool IsConstantEquivalent(HInstruction* insn1, HInstruction* insn2, BitVe } } -void SSAChecker::VisitPhi(HPhi* phi) { +void GraphChecker::VisitPhi(HPhi* phi) { VisitInstruction(phi); // Ensure the first input of a phi is not itself. @@ -846,7 +838,7 @@ void SSAChecker::VisitPhi(HPhi* phi) { } } -void SSAChecker::HandleBooleanInput(HInstruction* instruction, size_t input_index) { +void GraphChecker::HandleBooleanInput(HInstruction* instruction, size_t input_index) { HInstruction* input = instruction->InputAt(input_index); if (input->IsIntConstant()) { int32_t value = input->AsIntConstant()->GetValue(); @@ -876,7 +868,7 @@ void SSAChecker::HandleBooleanInput(HInstruction* instruction, size_t input_inde } } -void SSAChecker::VisitPackedSwitch(HPackedSwitch* instruction) { +void GraphChecker::VisitPackedSwitch(HPackedSwitch* instruction) { VisitInstruction(instruction); // Check that the number of block successors matches the switch count plus // one for the default block. @@ -892,22 +884,22 @@ void SSAChecker::VisitPackedSwitch(HPackedSwitch* instruction) { } } -void SSAChecker::VisitIf(HIf* instruction) { +void GraphChecker::VisitIf(HIf* instruction) { VisitInstruction(instruction); HandleBooleanInput(instruction, 0); } -void SSAChecker::VisitSelect(HSelect* instruction) { +void GraphChecker::VisitSelect(HSelect* instruction) { VisitInstruction(instruction); HandleBooleanInput(instruction, 2); } -void SSAChecker::VisitBooleanNot(HBooleanNot* instruction) { +void GraphChecker::VisitBooleanNot(HBooleanNot* instruction) { VisitInstruction(instruction); HandleBooleanInput(instruction, 0); } -void SSAChecker::VisitCondition(HCondition* op) { +void GraphChecker::VisitCondition(HCondition* op) { VisitInstruction(op); if (op->GetType() != Primitive::kPrimBoolean) { AddError(StringPrintf( @@ -937,7 +929,7 @@ void SSAChecker::VisitCondition(HCondition* op) { } } -void SSAChecker::VisitBinaryOperation(HBinaryOperation* op) { +void GraphChecker::VisitBinaryOperation(HBinaryOperation* op) { VisitInstruction(op); if (op->IsUShr() || op->IsShr() || op->IsShl() || op->IsRor()) { if (PrimitiveKind(op->InputAt(1)->GetType()) != Primitive::kPrimInt) { @@ -979,7 +971,7 @@ void SSAChecker::VisitBinaryOperation(HBinaryOperation* op) { } } -void SSAChecker::VisitConstant(HConstant* instruction) { +void GraphChecker::VisitConstant(HConstant* instruction) { HBasicBlock* block = instruction->GetBlock(); if (!block->IsEntryBlock()) { AddError(StringPrintf( @@ -990,7 +982,7 @@ void SSAChecker::VisitConstant(HConstant* instruction) { } } -void SSAChecker::VisitBoundType(HBoundType* instruction) { +void GraphChecker::VisitBoundType(HBoundType* instruction) { VisitInstruction(instruction); ScopedObjectAccess soa(Thread::Current()); diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h index 8724cde5dd..52252cd3d4 100644 --- a/compiler/optimizing/graph_checker.h +++ b/compiler/optimizing/graph_checker.h @@ -32,34 +32,38 @@ class GraphChecker : public HGraphDelegateVisitor { dump_prefix_(dump_prefix), seen_ids_(graph->GetArena(), graph->GetCurrentInstructionId(), false) {} - // Check the whole graph (in insertion order). - virtual void Run() { VisitInsertionOrder(); } + // Check the whole graph (in reverse post-order). + void Run() { + // VisitReversePostOrder is used instead of VisitInsertionOrder, + // as the latter might visit dead blocks removed by the dominator + // computation. + VisitReversePostOrder(); + } - // Check `block`. void VisitBasicBlock(HBasicBlock* block) OVERRIDE; - // Check `instruction`. void VisitInstruction(HInstruction* instruction) OVERRIDE; + void VisitPhi(HPhi* phi) OVERRIDE; - // Perform control-flow graph checks on instruction. - void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; - - // Check that the HasBoundsChecks() flag is set for bounds checks. + void VisitBinaryOperation(HBinaryOperation* op) OVERRIDE; + void VisitBooleanNot(HBooleanNot* instruction) OVERRIDE; + void VisitBoundType(HBoundType* instruction) OVERRIDE; void VisitBoundsCheck(HBoundsCheck* check) OVERRIDE; - - // Check successors of blocks ending in TryBoundary. - void VisitTryBoundary(HTryBoundary* try_boundary) OVERRIDE; - - // Check that LoadException is the first instruction in a catch block. - void VisitLoadException(HLoadException* load) OVERRIDE; - - // Check that HCheckCast and HInstanceOf have HLoadClass as second input. void VisitCheckCast(HCheckCast* check) OVERRIDE; + void VisitCondition(HCondition* op) OVERRIDE; + void VisitConstant(HConstant* instruction) OVERRIDE; + void VisitIf(HIf* instruction) OVERRIDE; void VisitInstanceOf(HInstanceOf* check) OVERRIDE; - - // Check that the Return and ReturnVoid jump to the exit block. + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; + void VisitLoadException(HLoadException* load) OVERRIDE; + void VisitPackedSwitch(HPackedSwitch* instruction) OVERRIDE; void VisitReturn(HReturn* ret) OVERRIDE; void VisitReturnVoid(HReturnVoid* ret) OVERRIDE; + void VisitSelect(HSelect* instruction) OVERRIDE; + void VisitTryBoundary(HTryBoundary* try_boundary) OVERRIDE; + + void HandleLoop(HBasicBlock* loop_header); + void HandleBooleanInput(HInstruction* instruction, size_t input_index); // Was the last visit of the graph valid? bool IsValid() const { @@ -97,46 +101,6 @@ class GraphChecker : public HGraphDelegateVisitor { DISALLOW_COPY_AND_ASSIGN(GraphChecker); }; - -// An SSA graph visitor performing various checks. -class SSAChecker : public GraphChecker { - public: - typedef GraphChecker super_type; - - explicit SSAChecker(HGraph* graph) - : GraphChecker(graph, "art::SSAChecker: ") {} - - // Check the whole graph (in reverse post-order). - void Run() OVERRIDE { - // VisitReversePostOrder is used instead of VisitInsertionOrder, - // as the latter might visit dead blocks removed by the dominator - // computation. - VisitReversePostOrder(); - } - - // Perform SSA form checks on `block`. - void VisitBasicBlock(HBasicBlock* block) OVERRIDE; - // Loop-related checks from block `loop_header`. - void CheckLoop(HBasicBlock* loop_header); - - // Perform SSA form checks on instructions. - void VisitInstruction(HInstruction* instruction) OVERRIDE; - void VisitPhi(HPhi* phi) OVERRIDE; - void VisitBinaryOperation(HBinaryOperation* op) OVERRIDE; - void VisitCondition(HCondition* op) OVERRIDE; - void VisitIf(HIf* instruction) OVERRIDE; - void VisitPackedSwitch(HPackedSwitch* instruction) OVERRIDE; - void VisitSelect(HSelect* instruction) OVERRIDE; - void VisitBooleanNot(HBooleanNot* instruction) OVERRIDE; - void VisitConstant(HConstant* instruction) OVERRIDE; - void VisitBoundType(HBoundType* instruction) OVERRIDE; - - void HandleBooleanInput(HInstruction* instruction, size_t input_index); - - private: - DISALLOW_COPY_AND_ASSIGN(SSAChecker); -}; - } // namespace art #endif // ART_COMPILER_OPTIMIZING_GRAPH_CHECKER_H_ diff --git a/compiler/optimizing/graph_checker_test.cc b/compiler/optimizing/graph_checker_test.cc index d10df4ce3f..2b8231942b 100644 --- a/compiler/optimizing/graph_checker_test.cc +++ b/compiler/optimizing/graph_checker_test.cc @@ -38,6 +38,7 @@ HGraph* CreateSimpleCFG(ArenaAllocator* allocator) { graph->AddBlock(exit_block); graph->SetExitBlock(exit_block); entry_block->AddSuccessor(exit_block); + graph->BuildDominatorTree(); return graph; } @@ -52,28 +53,16 @@ static void TestCode(const uint16_t* data) { ASSERT_TRUE(graph_checker.IsValid()); } -static void TestCodeSSA(const uint16_t* data) { - ArenaPool pool; - ArenaAllocator allocator(&pool); - HGraph* graph = CreateCFG(&allocator, data); - ASSERT_NE(graph, nullptr); - - TransformToSsa(graph); +class GraphCheckerTest : public CommonCompilerTest {}; - SSAChecker ssa_checker(graph); - ssa_checker.Run(); - ASSERT_TRUE(ssa_checker.IsValid()); -} - - -TEST(GraphChecker, ReturnVoid) { +TEST_F(GraphCheckerTest, ReturnVoid) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::RETURN_VOID); TestCode(data); } -TEST(GraphChecker, CFG1) { +TEST_F(GraphCheckerTest, CFG1) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO | 0x100, Instruction::RETURN_VOID); @@ -81,7 +70,7 @@ TEST(GraphChecker, CFG1) { TestCode(data); } -TEST(GraphChecker, CFG2) { +TEST_F(GraphCheckerTest, CFG2) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -91,7 +80,7 @@ TEST(GraphChecker, CFG2) { TestCode(data); } -TEST(GraphChecker, CFG3) { +TEST_F(GraphCheckerTest, CFG3) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 3, @@ -103,7 +92,7 @@ TEST(GraphChecker, CFG3) { // Test case with an invalid graph containing inconsistent // predecessor/successor arcs in CFG. -TEST(GraphChecker, InconsistentPredecessorsAndSuccessors) { +TEST_F(GraphCheckerTest, InconsistentPredecessorsAndSuccessors) { ArenaPool pool; ArenaAllocator allocator(&pool); @@ -121,7 +110,7 @@ TEST(GraphChecker, InconsistentPredecessorsAndSuccessors) { // Test case with an invalid graph containing a non-branch last // instruction in a block. -TEST(GraphChecker, BlockEndingWithNonBranchInstruction) { +TEST_F(GraphCheckerTest, BlockEndingWithNonBranchInstruction) { ArenaPool pool; ArenaAllocator allocator(&pool); @@ -141,9 +130,7 @@ TEST(GraphChecker, BlockEndingWithNonBranchInstruction) { ASSERT_FALSE(graph_checker.IsValid()); } -class SSACheckerTest : public CommonCompilerTest {}; - -TEST_F(SSACheckerTest, SSAPhi) { +TEST_F(GraphCheckerTest, SSAPhi) { // This code creates one Phi function during the conversion to SSA form. const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -151,7 +138,7 @@ TEST_F(SSACheckerTest, SSAPhi) { Instruction::CONST_4 | 4 << 12 | 0, Instruction::RETURN | 0 << 8); - TestCodeSSA(data); + TestCode(data); } } // namespace art diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 63b8fd05c0..4cf0eb1565 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -22,6 +22,7 @@ #include <sstream> #include "bounds_check_elimination.h" +#include "builder.h" #include "code_generator.h" #include "dead_code_elimination.h" #include "disassembler.h" @@ -31,7 +32,6 @@ #include "optimization.h" #include "reference_type_propagation.h" #include "register_allocator.h" -#include "ssa_builder.h" #include "ssa_liveness_analysis.h" #include "utils/assembler.h" @@ -510,7 +510,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { || IsPass(HDeadCodeElimination::kInitialDeadCodeEliminationPassName) || IsPass(BoundsCheckElimination::kBoundsCheckEliminationPassName) || IsPass(RegisterAllocator::kRegisterAllocatorPassName) - || IsPass(SsaBuilder::kSsaBuilderPassName)) { + || IsPass(HGraphBuilder::kBuilderPassName)) { HLoopInformation* info = instruction->GetBlock()->GetLoopInformation(); if (info == nullptr) { StartAttributeStream("loop") << "none"; @@ -527,7 +527,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { } } - if ((IsPass(SsaBuilder::kSsaBuilderPassName) + if ((IsPass(HGraphBuilder::kBuilderPassName) || IsPass(HInliner::kInlinerPassName)) && (instruction->GetType() == Primitive::kPrimNot)) { ReferenceTypeInfo info = instruction->IsLoadClass() @@ -547,7 +547,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { // doesn't run or doesn't inline anything, the NullConstant remains untyped. // So we should check NullConstants for validity only after reference type propagation. DCHECK(graph_in_bad_state_ || - (!is_after_pass_ && IsPass(SsaBuilder::kSsaBuilderPassName))) + (!is_after_pass_ && IsPass(HGraphBuilder::kBuilderPassName))) << instruction->DebugName() << instruction->GetId() << " has invalid rti " << (is_after_pass_ ? "after" : "before") << " pass " << pass_name_; } diff --git a/compiler/optimizing/gvn_test.cc b/compiler/optimizing/gvn_test.cc index 1f4eaf3cfd..56dc08826b 100644 --- a/compiler/optimizing/gvn_test.cc +++ b/compiler/optimizing/gvn_test.cc @@ -100,7 +100,7 @@ TEST_F(GVNTest, LocalFieldElimination) { ASSERT_EQ(different_offset->GetBlock(), block); ASSERT_EQ(use_after_kill->GetBlock(), block); - TransformToSsa(graph); + graph->BuildDominatorTree(); SideEffectsAnalysis side_effects(graph); side_effects.Run(); GVNOptimization(graph, side_effects).Run(); @@ -182,7 +182,7 @@ TEST_F(GVNTest, GlobalFieldElimination) { 0)); join->AddInstruction(new (&allocator) HExit()); - TransformToSsa(graph); + graph->BuildDominatorTree(); SideEffectsAnalysis side_effects(graph); side_effects.Run(); GVNOptimization(graph, side_effects).Run(); @@ -288,7 +288,7 @@ TEST_F(GVNTest, LoopFieldElimination) { ASSERT_EQ(field_get_in_loop_body->GetBlock(), loop_body); ASSERT_EQ(field_get_in_exit->GetBlock(), exit); - TransformToSsa(graph); + graph->BuildDominatorTree(); { SideEffectsAnalysis side_effects(graph); side_effects.Run(); @@ -364,7 +364,7 @@ TEST_F(GVNTest, LoopSideEffects) { inner_loop_exit->AddInstruction(new (&allocator) HGoto()); outer_loop_exit->AddInstruction(new (&allocator) HExit()); - TransformToSsa(graph); + graph->BuildDominatorTree(); ASSERT_TRUE(inner_loop_header->GetLoopInformation()->IsIn( *outer_loop_header->GetLoopInformation())); diff --git a/compiler/optimizing/induction_var_analysis_test.cc b/compiler/optimizing/induction_var_analysis_test.cc index 29a1845658..89e4690de2 100644 --- a/compiler/optimizing/induction_var_analysis_test.cc +++ b/compiler/optimizing/induction_var_analysis_test.cc @@ -86,39 +86,28 @@ class InductionVarAnalysisTest : public CommonCompilerTest { constant1_ = graph_->GetIntConstant(1); constant100_ = graph_->GetIntConstant(100); float_constant0_ = graph_->GetFloatConstant(0.0f); - induc_ = new (&allocator_) HLocal(n); - entry_->AddInstruction(induc_); - entry_->AddInstruction(new (&allocator_) HStoreLocal(induc_, constant0_)); - tmp_ = new (&allocator_) HLocal(n + 1); - entry_->AddInstruction(tmp_); - entry_->AddInstruction(new (&allocator_) HStoreLocal(tmp_, constant100_)); - dum_ = new (&allocator_) HLocal(n + 2); - entry_->AddInstruction(dum_); return_->AddInstruction(new (&allocator_) HReturnVoid()); exit_->AddInstruction(new (&allocator_) HExit()); // Provide loop instructions. for (int d = 0; d < n; d++) { - basic_[d] = new (&allocator_) HLocal(d); - entry_->AddInstruction(basic_[d]); - loop_preheader_[d]->AddInstruction(new (&allocator_) HStoreLocal(basic_[d], constant0_)); + basic_[d] = new (&allocator_) HPhi(&allocator_, d, 0, Primitive::kPrimInt); loop_preheader_[d]->AddInstruction(new (&allocator_) HGoto()); - HInstruction* load = new (&allocator_) HLoadLocal(basic_[d], Primitive::kPrimInt); - loop_header_[d]->AddInstruction(load); - HInstruction* compare = new (&allocator_) HLessThan(load, constant100_); + loop_header_[d]->AddPhi(basic_[d]); + HInstruction* compare = new (&allocator_) HLessThan(basic_[d], constant100_); loop_header_[d]->AddInstruction(compare); loop_header_[d]->AddInstruction(new (&allocator_) HIf(compare)); - load = new (&allocator_) HLoadLocal(basic_[d], Primitive::kPrimInt); - loop_body_[d]->AddInstruction(load); - increment_[d] = new (&allocator_) HAdd(Primitive::kPrimInt, load, constant1_); + increment_[d] = new (&allocator_) HAdd(Primitive::kPrimInt, basic_[d], constant1_); loop_body_[d]->AddInstruction(increment_[d]); - loop_body_[d]->AddInstruction(new (&allocator_) HStoreLocal(basic_[d], increment_[d])); loop_body_[d]->AddInstruction(new (&allocator_) HGoto()); + + basic_[d]->AddInput(constant0_); + basic_[d]->AddInput(increment_[d]); } } // Builds if-statement at depth d. - void BuildIf(int d, HBasicBlock** ifT, HBasicBlock **ifF) { + HPhi* BuildIf(int d, HBasicBlock** ifT, HBasicBlock **ifF) { HBasicBlock* cond = new (&allocator_) HBasicBlock(graph_); HBasicBlock* ifTrue = new (&allocator_) HBasicBlock(graph_); HBasicBlock* ifFalse = new (&allocator_) HBasicBlock(graph_); @@ -134,6 +123,10 @@ class InductionVarAnalysisTest : public CommonCompilerTest { cond->AddInstruction(new (&allocator_) HIf(parameter_)); *ifT = ifTrue; *ifF = ifFalse; + + HPhi* select_phi = new (&allocator_) HPhi(&allocator_, -1, 0, Primitive::kPrimInt); + loop_body_[d]->AddPhi(select_phi); + return select_phi; } // Inserts instruction right before increment at depth d. @@ -142,25 +135,20 @@ class InductionVarAnalysisTest : public CommonCompilerTest { return instruction; } - // Inserts local load at depth d. - HInstruction* InsertLocalLoad(HLocal* local, int d) { - return InsertInstruction(new (&allocator_) HLoadLocal(local, Primitive::kPrimInt), d); - } - - // Inserts local store at depth d. - HInstruction* InsertLocalStore(HLocal* local, HInstruction* rhs, int d) { - return InsertInstruction(new (&allocator_) HStoreLocal(local, rhs), d); + // Inserts a phi to loop header at depth d and returns it. + HPhi* InsertLoopPhi(int vreg, int d) { + HPhi* phi = new (&allocator_) HPhi(&allocator_, vreg, 0, Primitive::kPrimInt); + loop_header_[d]->AddPhi(phi); + return phi; } - // Inserts an array store with given local as subscript at depth d to + // Inserts an array store with given `subscript` at depth d to // enable tests to inspect the computed induction at that point easily. - HInstruction* InsertArrayStore(HLocal* subscript, int d) { - HInstruction* load = InsertInstruction( - new (&allocator_) HLoadLocal(subscript, Primitive::kPrimInt), d); + HInstruction* InsertArrayStore(HInstruction* subscript, int d) { // ArraySet is given a float value in order to avoid SsaBuilder typing // it from the array's non-existent reference type info. return InsertInstruction(new (&allocator_) HArraySet( - parameter_, load, float_constant0_, Primitive::kPrimFloat, 0), d); + parameter_, subscript, float_constant0_, Primitive::kPrimFloat, 0), d); } // Returns induction information of instruction in loop at depth d. @@ -171,7 +159,7 @@ class InductionVarAnalysisTest : public CommonCompilerTest { // Performs InductionVarAnalysis (after proper set up). void PerformInductionVarAnalysis() { - TransformToSsa(graph_); + graph_->BuildDominatorTree(); iva_ = new (&allocator_) HInductionVarAnalysis(graph_); iva_->Run(); } @@ -191,16 +179,13 @@ class InductionVarAnalysisTest : public CommonCompilerTest { HInstruction* constant1_; HInstruction* constant100_; HInstruction* float_constant0_; - HLocal* induc_; // "vreg_n", the "k" - HLocal* tmp_; // "vreg_n+1" - HLocal* dum_; // "vreg_n+2" // Loop specifics. HBasicBlock* loop_preheader_[10]; HBasicBlock* loop_header_[10]; HBasicBlock* loop_body_[10]; HInstruction* increment_[10]; - HLocal* basic_[10]; // "vreg_d", the "i_d" + HPhi* basic_[10]; // "vreg_d", the "i_d" }; // @@ -216,7 +201,7 @@ TEST_F(InductionVarAnalysisTest, ProperLoopSetup) { // .. // } BuildLoopNest(10); - TransformToSsa(graph_); + graph_->BuildDominatorTree(); ASSERT_EQ(entry_->GetLoopInformation(), nullptr); for (int d = 0; d < 1; d++) { ASSERT_EQ(loop_preheader_[d]->GetLoopInformation(), @@ -258,20 +243,15 @@ TEST_F(InductionVarAnalysisTest, FindDerivedInduction) { // } BuildLoopNest(1); HInstruction *add = InsertInstruction( - new (&allocator_) HAdd(Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(induc_, add, 0); + new (&allocator_) HAdd(Primitive::kPrimInt, constant100_, basic_[0]), 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(induc_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, constant100_, basic_[0]), 0); HInstruction *mul = InsertInstruction( - new (&allocator_) HMul(Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(induc_, mul, 0); + new (&allocator_) HMul(Primitive::kPrimInt, constant100_, basic_[0]), 0); HInstruction *shl = InsertInstruction( - new (&allocator_) HShl(Primitive::kPrimInt, InsertLocalLoad(basic_[0], 0), constant1_), 0); - InsertLocalStore(induc_, shl, 0); + new (&allocator_) HShl(Primitive::kPrimInt, basic_[0], constant1_), 0); HInstruction *neg = InsertInstruction( - new (&allocator_) HNeg(Primitive::kPrimInt, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(induc_, neg, 0); + new (&allocator_) HNeg(Primitive::kPrimInt, basic_[0]), 0); PerformInductionVarAnalysis(); EXPECT_STREQ("((1) * i + (100))", GetInductionInfo(add, 0).c_str()); @@ -291,14 +271,16 @@ TEST_F(InductionVarAnalysisTest, FindChainInduction) { // a[k] = 0; // } BuildLoopNest(1); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + HInstruction *add = InsertInstruction( - new (&allocator_) HAdd(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(induc_, add, 0); - HInstruction* store1 = InsertArrayStore(induc_, 0); + new (&allocator_) HAdd(Primitive::kPrimInt, k, constant100_), 0); + HInstruction* store1 = InsertArrayStore(add, 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant1_), 0); - InsertLocalStore(induc_, sub, 0); - HInstruction* store2 = InsertArrayStore(induc_, 0); + new (&allocator_) HSub(Primitive::kPrimInt, add, constant1_), 0); + HInstruction* store2 = InsertArrayStore(sub, 0); + k->AddInput(sub); PerformInductionVarAnalysis(); EXPECT_STREQ("(((100) - (1)) * i + (100))", @@ -316,23 +298,24 @@ TEST_F(InductionVarAnalysisTest, FindTwoWayBasicInduction) { // a[k] = 0; // } BuildLoopNest(1); + HPhi* k_header = InsertLoopPhi(0, 0); + k_header->AddInput(constant0_); + HBasicBlock* ifTrue; HBasicBlock* ifFalse; - BuildIf(0, &ifTrue, &ifFalse); + HPhi* k_body = BuildIf(0, &ifTrue, &ifFalse); + // True-branch. - HInstruction* load1 = new (&allocator_) HLoadLocal(induc_, Primitive::kPrimInt); - ifTrue->AddInstruction(load1); - HInstruction* inc1 = new (&allocator_) HAdd(Primitive::kPrimInt, load1, constant1_); + HInstruction* inc1 = new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant1_); ifTrue->AddInstruction(inc1); - ifTrue->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc1)); + k_body->AddInput(inc1); // False-branch. - HInstruction* load2 = new (&allocator_) HLoadLocal(induc_, Primitive::kPrimInt); - ifFalse->AddInstruction(load2); - HInstruction* inc2 = new (&allocator_) HAdd(Primitive::kPrimInt, load2, constant1_); + HInstruction* inc2 = new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant1_); ifFalse->AddInstruction(inc2); - ifFalse->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc2)); + k_body->AddInput(inc2); // Merge over a phi. - HInstruction* store = InsertArrayStore(induc_, 0); + HInstruction* store = InsertArrayStore(k_body, 0); + k_header->AddInput(k_body); PerformInductionVarAnalysis(); EXPECT_STREQ("((1) * i + (1))", GetInductionInfo(store->InputAt(1), 0).c_str()); @@ -348,21 +331,18 @@ TEST_F(InductionVarAnalysisTest, FindTwoWayDerivedInduction) { BuildLoopNest(1); HBasicBlock* ifTrue; HBasicBlock* ifFalse; - BuildIf(0, &ifTrue, &ifFalse); + HPhi* k = BuildIf(0, &ifTrue, &ifFalse); + // True-branch. - HInstruction* load1 = new (&allocator_) HLoadLocal(basic_[0], Primitive::kPrimInt); - ifTrue->AddInstruction(load1); - HInstruction* inc1 = new (&allocator_) HAdd(Primitive::kPrimInt, load1, constant1_); + HInstruction* inc1 = new (&allocator_) HAdd(Primitive::kPrimInt, basic_[0], constant1_); ifTrue->AddInstruction(inc1); - ifTrue->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc1)); + k->AddInput(inc1); // False-branch. - HInstruction* load2 = new (&allocator_) HLoadLocal(basic_[0], Primitive::kPrimInt); - ifFalse->AddInstruction(load2); - HInstruction* inc2 = new (&allocator_) HAdd(Primitive::kPrimInt, load2, constant1_); + HInstruction* inc2 = new (&allocator_) HAdd(Primitive::kPrimInt, basic_[0], constant1_); ifFalse->AddInstruction(inc2); - ifFalse->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc2)); + k->AddInput(inc2); // Merge over a phi. - HInstruction* store = InsertArrayStore(induc_, 0); + HInstruction* store = InsertArrayStore(k, 0); PerformInductionVarAnalysis(); EXPECT_STREQ("((1) * i + (1))", GetInductionInfo(store->InputAt(1), 0).c_str()); @@ -376,10 +356,13 @@ TEST_F(InductionVarAnalysisTest, FindFirstOrderWrapAroundInduction) { // k = 100 - i; // } BuildLoopNest(1); - HInstruction* store = InsertArrayStore(induc_, 0); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + + HInstruction* store = InsertArrayStore(k, 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(induc_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, constant100_, basic_[0]), 0); + k->AddInput(sub); PerformInductionVarAnalysis(); EXPECT_STREQ("wrap((0), (( - (1)) * i + (100)))", @@ -396,11 +379,16 @@ TEST_F(InductionVarAnalysisTest, FindSecondOrderWrapAroundInduction) { // t = 100 - i; // } BuildLoopNest(1); - HInstruction* store = InsertArrayStore(induc_, 0); - InsertLocalStore(induc_, InsertLocalLoad(tmp_, 0), 0); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + HPhi* t = InsertLoopPhi(1, 0); + t->AddInput(constant100_); + + HInstruction* store = InsertArrayStore(k, 0); + k->AddInput(t); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0); - InsertLocalStore(tmp_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, constant100_, basic_[0], 0), 0); + t->AddInput(sub); PerformInductionVarAnalysis(); EXPECT_STREQ("wrap((0), wrap((100), (( - (1)) * i + (100))))", @@ -419,26 +407,21 @@ TEST_F(InductionVarAnalysisTest, FindWrapAroundDerivedInduction) { // k = i << 1; // } BuildLoopNest(1); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + HInstruction *add = InsertInstruction( - new (&allocator_) HAdd(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, add, 0); + new (&allocator_) HAdd(Primitive::kPrimInt, k, constant100_), 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, k, constant100_), 0); HInstruction *mul = InsertInstruction( - new (&allocator_) HMul(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, mul, 0); + new (&allocator_) HMul(Primitive::kPrimInt, k, constant100_), 0); HInstruction *shl = InsertInstruction( - new (&allocator_) HShl(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant1_), 0); - InsertLocalStore(tmp_, shl, 0); + new (&allocator_) HShl(Primitive::kPrimInt, k, constant1_), 0); HInstruction *neg = InsertInstruction( - new (&allocator_) HNeg(Primitive::kPrimInt, InsertLocalLoad(induc_, 0)), 0); - InsertLocalStore(tmp_, neg, 0); - InsertLocalStore( - induc_, - InsertInstruction( - new (&allocator_) - HShl(Primitive::kPrimInt, InsertLocalLoad(basic_[0], 0), constant1_), 0), 0); + new (&allocator_) HNeg(Primitive::kPrimInt, k), 0); + k->AddInput( + InsertInstruction(new (&allocator_) HShl(Primitive::kPrimInt, basic_[0], constant1_), 0)); PerformInductionVarAnalysis(); EXPECT_STREQ("wrap((100), ((2) * i + (100)))", GetInductionInfo(add, 0).c_str()); @@ -461,11 +444,15 @@ TEST_F(InductionVarAnalysisTest, FindPeriodicInduction) { // k = d; // } BuildLoopNest(1); - HInstruction* store1 = InsertArrayStore(induc_, 0); - HInstruction* store2 = InsertArrayStore(tmp_, 0); - InsertLocalStore(dum_, InsertLocalLoad(tmp_, 0), 0); - InsertLocalStore(tmp_, InsertLocalLoad(induc_, 0), 0); - InsertLocalStore(induc_, InsertLocalLoad(dum_, 0), 0); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + HPhi* t = InsertLoopPhi(1, 0); + t->AddInput(constant100_); + + HInstruction* store1 = InsertArrayStore(k, 0); + HInstruction* store2 = InsertArrayStore(t, 0); + k->AddInput(t); + t->AddInput(k); PerformInductionVarAnalysis(); EXPECT_STREQ("periodic((0), (100))", GetInductionInfo(store1->InputAt(1), 0).c_str()); @@ -480,10 +467,13 @@ TEST_F(InductionVarAnalysisTest, FindIdiomaticPeriodicInduction) { // k = 1 - k; // } BuildLoopNest(1); - HInstruction* store = InsertArrayStore(induc_, 0); + HPhi* k = InsertLoopPhi(0, 0); + k->AddInput(constant0_); + + HInstruction* store = InsertArrayStore(k, 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, constant1_, InsertLocalLoad(induc_, 0)), 0); - InsertLocalStore(induc_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, constant1_, k), 0); + k->AddInput(sub); PerformInductionVarAnalysis(); EXPECT_STREQ("periodic((0), (1))", GetInductionInfo(store->InputAt(1), 0).c_str()); @@ -502,26 +492,24 @@ TEST_F(InductionVarAnalysisTest, FindDerivedPeriodicInduction) { // t = - k; // } BuildLoopNest(1); - InsertLocalStore( - induc_, - InsertInstruction(new (&allocator_) - HSub(Primitive::kPrimInt, constant1_, InsertLocalLoad(induc_, 0)), 0), 0); + HPhi* k_header = InsertLoopPhi(0, 0); + k_header->AddInput(constant0_); + + HInstruction* k_body = InsertInstruction( + new (&allocator_) HSub(Primitive::kPrimInt, constant1_, k_header), 0); + k_header->AddInput(k_body); + // Derived expressions. HInstruction *add = InsertInstruction( - new (&allocator_) HAdd(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, add, 0); + new (&allocator_) HAdd(Primitive::kPrimInt, k_body, constant100_), 0); HInstruction *sub = InsertInstruction( - new (&allocator_) HSub(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, sub, 0); + new (&allocator_) HSub(Primitive::kPrimInt, k_body, constant100_), 0); HInstruction *mul = InsertInstruction( - new (&allocator_) HMul(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0); - InsertLocalStore(tmp_, mul, 0); + new (&allocator_) HMul(Primitive::kPrimInt, k_body, constant100_), 0); HInstruction *shl = InsertInstruction( - new (&allocator_) HShl(Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant1_), 0); - InsertLocalStore(tmp_, shl, 0); + new (&allocator_) HShl(Primitive::kPrimInt, k_body, constant1_), 0); HInstruction *neg = InsertInstruction( - new (&allocator_) HNeg(Primitive::kPrimInt, InsertLocalLoad(induc_, 0)), 0); - InsertLocalStore(tmp_, neg, 0); + new (&allocator_) HNeg(Primitive::kPrimInt, k_body), 0); PerformInductionVarAnalysis(); EXPECT_STREQ("periodic(((1) + (100)), (100))", GetInductionInfo(add, 0).c_str()); @@ -543,10 +531,20 @@ TEST_F(InductionVarAnalysisTest, FindDeepLoopInduction) { // .. // } BuildLoopNest(10); + + HPhi* k[10]; + for (int d = 0; d < 10; d++) { + k[d] = InsertLoopPhi(0, d); + } + HInstruction *inc = InsertInstruction( - new (&allocator_) HAdd(Primitive::kPrimInt, constant1_, InsertLocalLoad(induc_, 9)), 9); - InsertLocalStore(induc_, inc, 9); - HInstruction* store = InsertArrayStore(induc_, 9); + new (&allocator_) HAdd(Primitive::kPrimInt, constant1_, k[9]), 9); + HInstruction* store = InsertArrayStore(inc, 9); + + for (int d = 0; d < 10; d++) { + k[d]->AddInput((d != 0) ? k[d - 1] : constant0_); + k[d]->AddInput((d != 9) ? k[d + 1] : inc); + } PerformInductionVarAnalysis(); // Avoid exact phi number, since that depends on the SSA building phase. diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc index eda9c01a01..55a654e301 100644 --- a/compiler/optimizing/induction_var_range_test.cc +++ b/compiler/optimizing/induction_var_range_test.cc @@ -86,25 +86,20 @@ class InductionVarRangeTest : public CommonCompilerTest { loop_body->AddSuccessor(loop_header); return_block->AddSuccessor(exit_block_); // Instructions. - HLocal* induc = new (&allocator_) HLocal(0); - entry_block_->AddInstruction(induc); - loop_preheader_->AddInstruction( - new (&allocator_) HStoreLocal(induc, graph_->GetIntConstant(lower))); // i = l loop_preheader_->AddInstruction(new (&allocator_) HGoto()); - HInstruction* load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt); - loop_header->AddInstruction(load); + HPhi* phi = new (&allocator_) HPhi(&allocator_, 0, 0, Primitive::kPrimInt); + loop_header->AddPhi(phi); + phi->AddInput(graph_->GetIntConstant(lower)); // i = l if (stride > 0) { - condition_ = new (&allocator_) HLessThan(load, upper); // i < u + condition_ = new (&allocator_) HLessThan(phi, upper); // i < u } else { - condition_ = new (&allocator_) HGreaterThan(load, upper); // i > u + condition_ = new (&allocator_) HGreaterThan(phi, upper); // i > u } loop_header->AddInstruction(condition_); loop_header->AddInstruction(new (&allocator_) HIf(condition_)); - load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt); - loop_body->AddInstruction(load); - increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, load, graph_->GetIntConstant(stride)); - loop_body->AddInstruction(increment_); - loop_body->AddInstruction(new (&allocator_) HStoreLocal(induc, increment_)); // i += s + increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, phi, graph_->GetIntConstant(stride)); + loop_body->AddInstruction(increment_); // i += s + phi->AddInput(increment_); loop_body->AddInstruction(new (&allocator_) HGoto()); return_block->AddInstruction(new (&allocator_) HReturnVoid()); exit_block_->AddInstruction(new (&allocator_) HExit()); @@ -112,7 +107,7 @@ class InductionVarRangeTest : public CommonCompilerTest { /** Constructs SSA and performs induction variable analysis. */ void PerformInductionVarAnalysis() { - TransformToSsa(graph_); + graph_->BuildDominatorTree(); iva_->Run(); } diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 3740c405c1..a5acab81ab 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -258,8 +258,9 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { } if (actual_method != nullptr) { - return TryInline(invoke_instruction, actual_method); + return TryInlineAndReplace(invoke_instruction, actual_method, /* do_rtp */ true); } + DCHECK(!invoke_instruction->IsInvokeStaticOrDirect()); // Check if we can use an inline cache. @@ -344,7 +345,7 @@ bool HInliner::TryInlineMonomorphicCall(HInvoke* invoke_instruction, HInstruction* cursor = invoke_instruction->GetPrevious(); HBasicBlock* bb_cursor = invoke_instruction->GetBlock(); - if (!TryInline(invoke_instruction, resolved_method, /* do_rtp */ false)) { + if (!TryInlineAndReplace(invoke_instruction, resolved_method, /* do_rtp */ false)) { return false; } @@ -431,7 +432,7 @@ bool HInliner::TryInlinePolymorphicCall(HInvoke* invoke_instruction, HInstruction* cursor = invoke_instruction->GetPrevious(); HBasicBlock* bb_cursor = invoke_instruction->GetBlock(); - if (!TryInline(invoke_instruction, actual_method, /* do_rtp */ false)) { + if (!TryInlineAndReplace(invoke_instruction, actual_method, /* do_rtp */ false)) { return false; } @@ -485,14 +486,29 @@ bool HInliner::TryInlinePolymorphicCall(HInvoke* invoke_instruction, return true; } -bool HInliner::TryInline(HInvoke* invoke_instruction, ArtMethod* method, bool do_rtp) { +bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction, ArtMethod* method, bool do_rtp) { + HInstruction* return_replacement = nullptr; + if (!TryBuildAndInline(invoke_instruction, method, &return_replacement)) { + return false; + } + if (return_replacement != nullptr) { + invoke_instruction->ReplaceWith(return_replacement); + } + invoke_instruction->GetBlock()->RemoveInstruction(invoke_instruction); + FixUpReturnReferenceType(invoke_instruction, method, return_replacement, do_rtp); + return true; +} + +bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction, + ArtMethod* method, + HInstruction** return_replacement) { const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); // Check whether we're allowed to inline. The outermost compilation unit is the relevant // dex file here (though the transitivity of an inline chain would allow checking the calller). if (!compiler_driver_->MayInline(method->GetDexFile(), outer_compilation_unit_.GetDexFile())) { - if (TryPatternSubstitution(invoke_instruction, method, do_rtp)) { + if (TryPatternSubstitution(invoke_instruction, method, return_replacement)) { VLOG(compiler) << "Successfully replaced pattern of invoke " << PrettyMethod(method); MaybeRecordStat(kReplacedInvokeWithSimplePattern); return true; @@ -559,7 +575,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, ArtMethod* method, bool do return false; } - if (!TryBuildAndInline(method, invoke_instruction, same_dex_file, do_rtp)) { + if (!TryBuildAndInlineHelper(invoke_instruction, method, same_dex_file, return_replacement)) { return false; } @@ -586,27 +602,27 @@ static HInstruction* GetInvokeInputForArgVRegIndex(HInvoke* invoke_instruction, // Try to recognize known simple patterns and replace invoke call with appropriate instructions. bool HInliner::TryPatternSubstitution(HInvoke* invoke_instruction, ArtMethod* resolved_method, - bool do_rtp) { + HInstruction** return_replacement) { InlineMethod inline_method; if (!InlineMethodAnalyser::AnalyseMethodCode(resolved_method, &inline_method)) { return false; } - HInstruction* return_replacement = nullptr; switch (inline_method.opcode) { case kInlineOpNop: DCHECK_EQ(invoke_instruction->GetType(), Primitive::kPrimVoid); + *return_replacement = nullptr; break; case kInlineOpReturnArg: - return_replacement = GetInvokeInputForArgVRegIndex(invoke_instruction, - inline_method.d.return_data.arg); + *return_replacement = GetInvokeInputForArgVRegIndex(invoke_instruction, + inline_method.d.return_data.arg); break; case kInlineOpNonWideConst: if (resolved_method->GetShorty()[0] == 'L') { DCHECK_EQ(inline_method.d.data, 0u); - return_replacement = graph_->GetNullConstant(); + *return_replacement = graph_->GetNullConstant(); } else { - return_replacement = graph_->GetIntConstant(static_cast<int32_t>(inline_method.d.data)); + *return_replacement = graph_->GetIntConstant(static_cast<int32_t>(inline_method.d.data)); } break; case kInlineOpIGet: { @@ -621,7 +637,7 @@ bool HInliner::TryPatternSubstitution(HInvoke* invoke_instruction, DCHECK_EQ(iget->GetFieldOffset().Uint32Value(), data.field_offset); DCHECK_EQ(iget->IsVolatile() ? 1u : 0u, data.is_volatile); invoke_instruction->GetBlock()->InsertInstructionBefore(iget, invoke_instruction); - return_replacement = iget; + *return_replacement = iget; break; } case kInlineOpIPut: { @@ -639,7 +655,7 @@ bool HInliner::TryPatternSubstitution(HInvoke* invoke_instruction, invoke_instruction->GetBlock()->InsertInstructionBefore(iput, invoke_instruction); if (data.return_arg_plus1 != 0u) { size_t return_arg = data.return_arg_plus1 - 1u; - return_replacement = GetInvokeInputForArgVRegIndex(invoke_instruction, return_arg); + *return_replacement = GetInvokeInputForArgVRegIndex(invoke_instruction, return_arg); } break; } @@ -694,19 +710,13 @@ bool HInliner::TryPatternSubstitution(HInvoke* invoke_instruction, HMemoryBarrier* barrier = new (graph_->GetArena()) HMemoryBarrier(kStoreStore, kNoDexPc); invoke_instruction->GetBlock()->InsertInstructionBefore(barrier, invoke_instruction); } + *return_replacement = nullptr; break; } default: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } - - if (return_replacement != nullptr) { - invoke_instruction->ReplaceWith(return_replacement); - } - invoke_instruction->GetBlock()->RemoveInstruction(invoke_instruction); - - FixUpReturnReferenceType(resolved_method, invoke_instruction, return_replacement, do_rtp); return true; } @@ -760,10 +770,10 @@ HInstanceFieldSet* HInliner::CreateInstanceFieldSet(Handle<mirror::DexCache> dex return iput; } -bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, - HInvoke* invoke_instruction, - bool same_dex_file, - bool do_rtp) { +bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, + ArtMethod* resolved_method, + bool same_dex_file, + HInstruction** return_replacement) { ScopedObjectAccess soa(Thread::Current()); const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); const DexFile& callee_dex_file = *resolved_method->GetDexFile(); @@ -829,7 +839,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, resolved_method->GetQuickenedInfo(), dex_cache); - if (!builder.BuildGraph(*code_item)) { + if (builder.BuildGraph(*code_item, handles_) != kAnalysisSuccess) { VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be built, so cannot be inlined"; return false; @@ -842,12 +852,6 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, return false; } - if (callee_graph->TryBuildingSsa(handles_) != kAnalysisSuccess) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) - << " could not be transformed to SSA"; - return false; - } - size_t parameter_index = 0; for (HInstructionIterator instructions(callee_graph->GetEntryBlock()->GetInstructions()); !instructions.Done(); @@ -1022,16 +1026,12 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, } number_of_inlined_instructions_ += number_of_instructions; - HInstruction* return_replacement = callee_graph->InlineInto(graph_, invoke_instruction); - if (return_replacement != nullptr) { - DCHECK_EQ(graph_, return_replacement->GetBlock()->GetGraph()); - } - FixUpReturnReferenceType(resolved_method, invoke_instruction, return_replacement, do_rtp); + *return_replacement = callee_graph->InlineInto(graph_, invoke_instruction); return true; } -void HInliner::FixUpReturnReferenceType(ArtMethod* resolved_method, - HInvoke* invoke_instruction, +void HInliner::FixUpReturnReferenceType(HInvoke* invoke_instruction, + ArtMethod* resolved_method, HInstruction* return_replacement, bool do_rtp) { // Check the integrity of reference types and run another type propagation if needed. diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 7d343c62eb..9dd9bf5ad8 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -61,12 +61,25 @@ class HInliner : public HOptimization { bool TryInline(HInvoke* invoke_instruction); // Try to inline `resolved_method` in place of `invoke_instruction`. `do_rtp` is whether - // reference type propagation can run after the inlining. - bool TryInline(HInvoke* invoke_instruction, ArtMethod* resolved_method, bool do_rtp = true) + // reference type propagation can run after the inlining. If the inlining is successful, this + // method will replace and remove the `invoke_instruction`. + bool TryInlineAndReplace(HInvoke* invoke_instruction, ArtMethod* resolved_method, bool do_rtp) SHARED_REQUIRES(Locks::mutator_lock_); + bool TryBuildAndInline(HInvoke* invoke_instruction, + ArtMethod* resolved_method, + HInstruction** return_replacement) + SHARED_REQUIRES(Locks::mutator_lock_); + + bool TryBuildAndInlineHelper(HInvoke* invoke_instruction, + ArtMethod* resolved_method, + bool same_dex_file, + HInstruction** return_replacement); + // Try to recognize known simple patterns and replace invoke call with appropriate instructions. - bool TryPatternSubstitution(HInvoke* invoke_instruction, ArtMethod* resolved_method, bool do_rtp) + bool TryPatternSubstitution(HInvoke* invoke_instruction, + ArtMethod* resolved_method, + HInstruction** return_replacement) SHARED_REQUIRES(Locks::mutator_lock_); // Create a new HInstanceFieldGet. @@ -94,18 +107,13 @@ class HInliner : public HOptimization { const InlineCache& ic) SHARED_REQUIRES(Locks::mutator_lock_); - bool TryBuildAndInline(ArtMethod* resolved_method, - HInvoke* invoke_instruction, - bool same_dex_file, - bool do_rtp = true); - HInstanceFieldGet* BuildGetReceiverClass(ClassLinker* class_linker, HInstruction* receiver, uint32_t dex_pc) const SHARED_REQUIRES(Locks::mutator_lock_); - void FixUpReturnReferenceType(ArtMethod* resolved_method, - HInvoke* invoke_instruction, + void FixUpReturnReferenceType(HInvoke* invoke_instruction, + ArtMethod* resolved_method, HInstruction* return_replacement, bool do_rtp) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 0029cc3650..a48d06f3d0 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -757,11 +757,97 @@ void InstructionSimplifierVisitor::VisitArraySet(HArraySet* instruction) { } } +static bool IsTypeConversionImplicit(Primitive::Type input_type, Primitive::Type result_type) { + // Besides conversion to the same type, widening integral conversions are implicit, + // excluding conversions to long and the byte->char conversion where we need to + // clear the high 16 bits of the 32-bit sign-extended representation of byte. + return result_type == input_type || + (result_type == Primitive::kPrimInt && input_type == Primitive::kPrimByte) || + (result_type == Primitive::kPrimInt && input_type == Primitive::kPrimShort) || + (result_type == Primitive::kPrimInt && input_type == Primitive::kPrimChar) || + (result_type == Primitive::kPrimShort && input_type == Primitive::kPrimByte); +} + +static bool IsTypeConversionLossless(Primitive::Type input_type, Primitive::Type result_type) { + // The conversion to a larger type is loss-less with the exception of two cases, + // - conversion to char, the only unsigned type, where we may lose some bits, and + // - conversion from float to long, the only FP to integral conversion with smaller FP type. + // For integral to FP conversions this holds because the FP mantissa is large enough. + DCHECK_NE(input_type, result_type); + return Primitive::ComponentSize(result_type) > Primitive::ComponentSize(input_type) && + result_type != Primitive::kPrimChar && + !(result_type == Primitive::kPrimLong && input_type == Primitive::kPrimFloat); +} + void InstructionSimplifierVisitor::VisitTypeConversion(HTypeConversion* instruction) { - if (instruction->GetResultType() == instruction->GetInputType()) { - // Remove the instruction if it's converting to the same type. - instruction->ReplaceWith(instruction->GetInput()); + HInstruction* input = instruction->GetInput(); + Primitive::Type input_type = input->GetType(); + Primitive::Type result_type = instruction->GetResultType(); + if (IsTypeConversionImplicit(input_type, result_type)) { + // Remove the implicit conversion; this includes conversion to the same type. + instruction->ReplaceWith(input); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); + return; + } + + if (input->IsTypeConversion()) { + HTypeConversion* input_conversion = input->AsTypeConversion(); + HInstruction* original_input = input_conversion->GetInput(); + Primitive::Type original_type = original_input->GetType(); + + // When the first conversion is lossless, a direct conversion from the original type + // to the final type yields the same result, even for a lossy second conversion, for + // example float->double->int or int->double->float. + bool is_first_conversion_lossless = IsTypeConversionLossless(original_type, input_type); + + // For integral conversions, see if the first conversion loses only bits that the second + // doesn't need, i.e. the final type is no wider than the intermediate. If so, direct + // conversion yields the same result, for example long->int->short or int->char->short. + bool integral_conversions_with_non_widening_second = + Primitive::IsIntegralType(input_type) && + Primitive::IsIntegralType(original_type) && + Primitive::IsIntegralType(result_type) && + Primitive::ComponentSize(result_type) <= Primitive::ComponentSize(input_type); + + if (is_first_conversion_lossless || integral_conversions_with_non_widening_second) { + // If the merged conversion is implicit, do the simplification unconditionally. + if (IsTypeConversionImplicit(original_type, result_type)) { + instruction->ReplaceWith(original_input); + instruction->GetBlock()->RemoveInstruction(instruction); + if (!input_conversion->HasUses()) { + // Don't wait for DCE. + input_conversion->GetBlock()->RemoveInstruction(input_conversion); + } + RecordSimplification(); + return; + } + // Otherwise simplify only if the first conversion has no other use. + if (input_conversion->HasOnlyOneNonEnvironmentUse()) { + input_conversion->ReplaceWith(original_input); + input_conversion->GetBlock()->RemoveInstruction(input_conversion); + RecordSimplification(); + return; + } + } + } else if (input->IsAnd() && + Primitive::IsIntegralType(result_type) && + input->HasOnlyOneNonEnvironmentUse()) { + DCHECK(Primitive::IsIntegralType(input_type)); + HAnd* input_and = input->AsAnd(); + HConstant* constant = input_and->GetConstantRight(); + if (constant != nullptr) { + int64_t value = Int64FromConstant(constant); + DCHECK_NE(value, -1); // "& -1" would have been optimized away in VisitAnd(). + size_t trailing_ones = CTZ(~static_cast<uint64_t>(value)); + if (trailing_ones >= kBitsPerByte * Primitive::ComponentSize(result_type)) { + // The `HAnd` is useless, for example in `(byte) (x & 0xff)`, get rid of it. + input_and->ReplaceWith(input_and->GetLeastConstantLeft()); + input_and->GetBlock()->RemoveInstruction(input_and); + RecordSimplification(); + return; + } + } } } diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index ab4f6f9d28..22cefe8aa5 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -261,7 +261,8 @@ static void CreateFloatToFloat(ArenaAllocator* arena, HInvoke* invoke) { locations->SetOut(Location::SameAsFirstInput()); HInvokeStaticOrDirect* static_or_direct = invoke->AsInvokeStaticOrDirect(); DCHECK(static_or_direct != nullptr); - if (invoke->InputAt(static_or_direct->GetSpecialInputIndex())->IsX86ComputeBaseMethodAddress()) { + if (static_or_direct->HasSpecialInput() && + invoke->InputAt(static_or_direct->GetSpecialInputIndex())->IsX86ComputeBaseMethodAddress()) { // We need addressibility for the constant area. locations->SetInAt(1, Location::RequiresRegister()); // We need a temporary to hold the constant. @@ -276,7 +277,7 @@ static void MathAbsFP(LocationSummary* locations, Location output = locations->Out(); DCHECK(output.IsFpuRegister()); - if (locations->InAt(1).IsValid()) { + if (locations->GetInputCount() == 2 && locations->InAt(1).IsValid()) { DCHECK(locations->InAt(1).IsRegister()); // We also have a constant area pointer. Register constant_area = locations->InAt(1).AsRegister<Register>(); @@ -465,7 +466,7 @@ static void GenMinMaxFP(LocationSummary* locations, // NaN handling. __ Bind(&nan); // Do we have a constant area pointer? - if (locations->InAt(2).IsValid()) { + if (locations->GetInputCount() == 3 && locations->InAt(2).IsValid()) { DCHECK(locations->InAt(2).IsRegister()); Register constant_area = locations->InAt(2).AsRegister<Register>(); if (is_double) { @@ -510,7 +511,8 @@ static void CreateFPFPToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { locations->SetOut(Location::SameAsFirstInput()); HInvokeStaticOrDirect* static_or_direct = invoke->AsInvokeStaticOrDirect(); DCHECK(static_or_direct != nullptr); - if (invoke->InputAt(static_or_direct->GetSpecialInputIndex())->IsX86ComputeBaseMethodAddress()) { + if (static_or_direct->HasSpecialInput() && + invoke->InputAt(static_or_direct->GetSpecialInputIndex())->IsX86ComputeBaseMethodAddress()) { locations->SetInAt(2, Location::RequiresRegister()); } } diff --git a/compiler/optimizing/licm_test.cc b/compiler/optimizing/licm_test.cc index 2b63ec8971..9fb32f4001 100644 --- a/compiler/optimizing/licm_test.cc +++ b/compiler/optimizing/licm_test.cc @@ -76,7 +76,7 @@ class LICMTest : public CommonCompilerTest { // Performs LICM optimizations (after proper set up). void PerformLICM() { - TransformToSsa(graph_); + graph_->BuildDominatorTree(); SideEffectsAnalysis side_effects(graph_); side_effects.Run(); LICM(graph_, side_effects).Run(); diff --git a/compiler/optimizing/linearize_test.cc b/compiler/optimizing/linearize_test.cc index ed275b1544..13e14c53b5 100644 --- a/compiler/optimizing/linearize_test.cc +++ b/compiler/optimizing/linearize_test.cc @@ -39,14 +39,7 @@ template <size_t number_of_blocks> static void TestCode(const uint16_t* data, const uint32_t (&expected_order)[number_of_blocks]) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - - TransformToSsa(graph); - + HGraph* graph = CreateCFG(&allocator, data); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); diff --git a/compiler/optimizing/live_ranges_test.cc b/compiler/optimizing/live_ranges_test.cc index 991f8f70ea..3202493c3a 100644 --- a/compiler/optimizing/live_ranges_test.cc +++ b/compiler/optimizing/live_ranges_test.cc @@ -32,14 +32,10 @@ namespace art { class LiveRangesTest : public CommonCompilerTest {}; static HGraph* BuildGraph(const uint16_t* data, ArenaAllocator* allocator) { - HGraph* graph = CreateGraph(allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - builder.BuildGraph(*item); + HGraph* graph = CreateCFG(allocator, data); // Suspend checks implementation may change in the future, and this test relies // on how instructions are ordered. RemoveSuspendChecks(graph); - TransformToSsa(graph); // `Inline` conditions into ifs. PrepareForRegisterAllocation(graph).Run(); return graph; @@ -303,13 +299,12 @@ TEST_F(LiveRangesTest, Loop2) { * 12: equal * 14: if +++++ * | \ + - * | 18: suspend - * | 20: add - * | 22: goto + * | 18: add + * | 20: goto * | - * 26: return + * 24: return * | - * 30: exit + * 28: exit * * We want to make sure the phi at 10 has a lifetime hole after the add at 20. */ @@ -345,18 +340,18 @@ TEST_F(LiveRangesTest, Loop2) { interval = phi->GetLiveInterval(); range = interval->GetFirstRange(); ASSERT_EQ(10u, range->GetStart()); - ASSERT_EQ(21u, range->GetEnd()); + ASSERT_EQ(19u, range->GetEnd()); range = range->GetNext(); ASSERT_TRUE(range != nullptr); - ASSERT_EQ(24u, range->GetStart()); - ASSERT_EQ(26u, range->GetEnd()); + ASSERT_EQ(22u, range->GetStart()); + ASSERT_EQ(24u, range->GetEnd()); // Test for the add instruction. HAdd* add = liveness.GetInstructionFromSsaIndex(2)->AsAdd(); interval = add->GetLiveInterval(); range = interval->GetFirstRange(); - ASSERT_EQ(20u, range->GetStart()); - ASSERT_EQ(24u, range->GetEnd()); + ASSERT_EQ(18u, range->GetStart()); + ASSERT_EQ(22u, range->GetEnd()); ASSERT_TRUE(range->GetNext() == nullptr); } diff --git a/compiler/optimizing/liveness_test.cc b/compiler/optimizing/liveness_test.cc index 7736eedae1..92a987cb1d 100644 --- a/compiler/optimizing/liveness_test.cc +++ b/compiler/optimizing/liveness_test.cc @@ -46,12 +46,7 @@ static void DumpBitVector(BitVector* vector, static void TestCode(const uint16_t* data, const char* expected) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - TransformToSsa(graph); + HGraph* graph = CreateCFG(&allocator, data); // `Inline` conditions into ifs. PrepareForRegisterAllocation(graph).Run(); std::unique_ptr<const X86InstructionSetFeatures> features_x86( diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 453571451c..27015c0ac6 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -27,6 +27,15 @@ namespace art { +void HGraph::InitializeInexactObjectRTI(StackHandleScopeCollection* handles) { + ScopedObjectAccess soa(Thread::Current()); + // Create the inexact Object reference type and store it in the HGraph. + ClassLinker* linker = Runtime::Current()->GetClassLinker(); + inexact_object_rti_ = ReferenceTypeInfo::Create( + handles->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangObject)), + /* is_exact */ false); +} + void HGraph::AddBlock(HBasicBlock* block) { block->SetBlockId(blocks_.size()); blocks_.push_back(block); @@ -236,29 +245,6 @@ void HGraph::ComputeDominanceInformation() { } } -GraphAnalysisResult HGraph::TryBuildingSsa(StackHandleScopeCollection* handles) { - GraphAnalysisResult result = BuildDominatorTree(); - if (result != kAnalysisSuccess) { - return result; - } - - // Create the inexact Object reference type and store it in the HGraph. - ScopedObjectAccess soa(Thread::Current()); - ClassLinker* linker = Runtime::Current()->GetClassLinker(); - inexact_object_rti_ = ReferenceTypeInfo::Create( - handles->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangObject)), - /* is_exact */ false); - - // Tranforms graph to SSA form. - result = SsaBuilder(this, handles).BuildSsa(); - if (result != kAnalysisSuccess) { - return result; - } - - in_ssa_form_ = true; - return kAnalysisSuccess; -} - HBasicBlock* HGraph::SplitEdge(HBasicBlock* block, HBasicBlock* successor) { HBasicBlock* new_block = new (arena_) HBasicBlock(this, successor->GetDexPc()); AddBlock(new_block); @@ -1592,7 +1578,7 @@ void HBasicBlock::DisconnectAndDelete() { loop_info->Remove(this); if (loop_info->IsBackEdge(*this)) { // If this was the last back edge of the loop, we deliberately leave the - // loop in an inconsistent state and will fail SSAChecker unless the + // loop in an inconsistent state and will fail GraphChecker unless the // entire loop is removed during the pass. loop_info->RemoveBackEdge(this); } @@ -1631,7 +1617,7 @@ void HBasicBlock::DisconnectAndDelete() { } else if (num_pred_successors == 0u) { // The predecessor has no remaining successors and therefore must be dead. // We deliberately leave it without a control-flow instruction so that the - // SSAChecker fails unless it is not removed during the pass too. + // GraphChecker fails unless it is not removed during the pass too. predecessor->RemoveInstruction(last_instruction); } else { // There are multiple successors left. The removed block might be a successor @@ -2044,13 +2030,6 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { } } - if (return_value != nullptr) { - invoke->ReplaceWith(return_value); - } - - // Finally remove the invoke from the caller. - invoke->GetBlock()->RemoveInstruction(invoke); - return return_value; } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 2697af33eb..854854f238 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -98,6 +98,7 @@ enum IfCondition { }; enum GraphAnalysisResult { + kAnalysisInvalidBytecode, kAnalysisFailThrowCatchLoop, kAnalysisFailAmbiguousArrayOp, kAnalysisSuccess, @@ -308,10 +309,14 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { blocks_.reserve(kDefaultNumberOfBlocks); } + // Acquires and stores RTI of inexact Object to be used when creating HNullConstant. + void InitializeInexactObjectRTI(StackHandleScopeCollection* handles); + ArenaAllocator* GetArena() const { return arena_; } const ArenaVector<HBasicBlock*>& GetBlocks() const { return blocks_; } bool IsInSsaForm() const { return in_ssa_form_; } + void SetInSsaForm() { in_ssa_form_ = true; } HBasicBlock* GetEntryBlock() const { return entry_block_; } HBasicBlock* GetExitBlock() const { return exit_block_; } @@ -322,11 +327,6 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { void AddBlock(HBasicBlock* block); - // Try building the SSA form of this graph, with dominance computation and - // loop recognition. Returns a code specifying that it was successful or the - // reason for failure. - GraphAnalysisResult TryBuildingSsa(StackHandleScopeCollection* handles); - void ComputeDominanceInformation(); void ClearDominanceInformation(); void ClearLoopInformation(); @@ -345,8 +345,9 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { void ComputeTryBlockInformation(); // Inline this graph in `outer_graph`, replacing the given `invoke` instruction. - // Returns the instruction used to replace the invoke expression or null if the - // invoke is for a void method. + // Returns the instruction to replace the invoke expression or null if the + // invoke is for a void method. Note that the caller is responsible for replacing + // and removing the invoke instruction. HInstruction* InlineInto(HGraph* outer_graph, HInvoke* invoke); // Need to add a couple of blocks to test if the loop body is entered and @@ -3671,6 +3672,7 @@ class HInvokeStaticOrDirect : public HInvoke { // method pointer; otherwise there may be one platform-specific special input, // such as PC-relative addressing base. uint32_t GetSpecialInputIndex() const { return GetNumberOfArguments(); } + bool HasSpecialInput() const { return GetNumberOfArguments() != InputCount(); } InvokeType GetOptimizedInvokeType() const { return optimized_invoke_type_; } void SetOptimizedInvokeType(InvokeType invoke_type) { @@ -5997,9 +5999,14 @@ class HBlocksInLoopReversePostOrderIterator : public ValueObject { }; inline int64_t Int64FromConstant(HConstant* constant) { - DCHECK(constant->IsIntConstant() || constant->IsLongConstant()); - return constant->IsIntConstant() ? constant->AsIntConstant()->GetValue() - : constant->AsLongConstant()->GetValue(); + if (constant->IsIntConstant()) { + return constant->AsIntConstant()->GetValue(); + } else if (constant->IsLongConstant()) { + return constant->AsLongConstant()->GetValue(); + } else { + DCHECK(constant->IsNullConstant()); + return 0; + } } inline bool IsSameDexFile(const DexFile& lhs, const DexFile& rhs) { diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index dcd8e7d216..12b748b7b6 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -186,18 +186,10 @@ class PassObserver : public ValueObject { // Validate the HGraph if running in debug mode. if (kIsDebugBuild) { if (!graph_in_bad_state_) { - if (graph_->IsInSsaForm()) { - SSAChecker checker(graph_); - checker.Run(); - if (!checker.IsValid()) { - LOG(FATAL) << "Error after " << pass_name << ": " << Dumpable<SSAChecker>(checker); - } - } else { - GraphChecker checker(graph_); - checker.Run(); - if (!checker.IsValid()) { - LOG(FATAL) << "Error after " << pass_name << ": " << Dumpable<GraphChecker>(checker); - } + GraphChecker checker(graph_); + checker.Run(); + if (!checker.IsValid()) { + LOG(FATAL) << "Error after " << pass_name << ": " << Dumpable<GraphChecker>(checker); } } } @@ -665,6 +657,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, && compiler_driver->RequiresConstructorBarrier(Thread::Current(), dex_compilation_unit.GetDexFile(), dex_compilation_unit.GetClassDefIndex()); + HGraph* graph = new (arena) HGraph( arena, dex_file, @@ -675,6 +668,21 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, compiler_driver->GetCompilerOptions().GetDebuggable(), osr); + const uint8_t* interpreter_metadata = nullptr; + { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> loader(hs.NewHandle( + soa.Decode<mirror::ClassLoader*>(class_loader))); + ArtMethod* art_method = compiler_driver->ResolveMethod( + soa, dex_cache, loader, &dex_compilation_unit, method_idx, invoke_type); + // We may not get a method, for example if its class is erroneous. + if (art_method != nullptr) { + graph->SetArtMethod(art_method); + interpreter_metadata = art_method->GetQuickenedInfo(); + } + } + std::unique_ptr<CodeGenerator> codegen( CodeGenerator::Create(graph, instruction_set, @@ -692,74 +700,55 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, visualizer_output_.get(), compiler_driver); - const uint8_t* interpreter_metadata = nullptr; - { - ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> loader(hs.NewHandle( - soa.Decode<mirror::ClassLoader*>(class_loader))); - ArtMethod* art_method = compiler_driver->ResolveMethod( - soa, dex_cache, loader, &dex_compilation_unit, method_idx, invoke_type); - // We may not get a method, for example if its class is erroneous. - if (art_method != nullptr) { - graph->SetArtMethod(art_method); - interpreter_metadata = art_method->GetQuickenedInfo(); - } - } - HGraphBuilder builder(graph, - &dex_compilation_unit, - &dex_compilation_unit, - &dex_file, - compiler_driver, - compilation_stats_.get(), - interpreter_metadata, - dex_cache); - VLOG(compiler) << "Building " << pass_observer.GetMethodName(); { - PassScope scope(HGraphBuilder::kBuilderPassName, &pass_observer); - if (!builder.BuildGraph(*code_item)) { - pass_observer.SetGraphInBadState(); - return nullptr; + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + // Do not hold `mutator_lock_` between optimizations. + ScopedThreadSuspension sts(soa.Self(), kNative); + + { + PassScope scope(HGraphBuilder::kBuilderPassName, &pass_observer); + HGraphBuilder builder(graph, + &dex_compilation_unit, + &dex_compilation_unit, + &dex_file, + compiler_driver, + compilation_stats_.get(), + interpreter_metadata, + dex_cache); + GraphAnalysisResult result = builder.BuildGraph(*code_item, &handles); + if (result != kAnalysisSuccess) { + switch (result) { + case kAnalysisInvalidBytecode: + break; + case kAnalysisFailThrowCatchLoop: + MaybeRecordStat(MethodCompilationStat::kNotCompiledThrowCatchLoop); + break; + case kAnalysisFailAmbiguousArrayOp: + MaybeRecordStat(MethodCompilationStat::kNotCompiledAmbiguousArrayOp); + break; + case kAnalysisSuccess: + UNREACHABLE(); + } + pass_observer.SetGraphInBadState(); + return nullptr; + } } - } - VLOG(compiler) << "Optimizing " << pass_observer.GetMethodName(); + RunOptimizations(graph, + codegen.get(), + compiler_driver, + compilation_stats_.get(), + dex_compilation_unit, + &pass_observer, + &handles); - ScopedObjectAccess soa(Thread::Current()); - StackHandleScopeCollection handles(soa.Self()); - ScopedThreadSuspension sts(soa.Self(), kNative); - - { - PassScope scope(SsaBuilder::kSsaBuilderPassName, &pass_observer); - GraphAnalysisResult result = graph->TryBuildingSsa(&handles); - if (result != kAnalysisSuccess) { - switch (result) { - case kAnalysisFailThrowCatchLoop: - MaybeRecordStat(MethodCompilationStat::kNotCompiledThrowCatchLoop); - break; - case kAnalysisFailAmbiguousArrayOp: - MaybeRecordStat(MethodCompilationStat::kNotCompiledAmbiguousArrayOp); - break; - case kAnalysisSuccess: - UNREACHABLE(); - } - pass_observer.SetGraphInBadState(); - return nullptr; - } + codegen->Compile(code_allocator); + pass_observer.DumpDisassembly(); } - RunOptimizations(graph, - codegen.get(), - compiler_driver, - compilation_stats_.get(), - dex_compilation_unit, - &pass_observer, - &handles); - codegen->Compile(code_allocator); - pass_observer.DumpDisassembly(); - if (kArenaAllocatorCountAllocations) { if (arena->BytesAllocated() > 4 * MB) { MemStats mem_stats(arena->GetMemStats()); diff --git a/compiler/optimizing/optimizing_unit_test.h b/compiler/optimizing/optimizing_unit_test.h index 5a910433b4..0c7648edc2 100644 --- a/compiler/optimizing/optimizing_unit_test.h +++ b/compiler/optimizing/optimizing_unit_test.h @@ -64,10 +64,12 @@ LiveInterval* BuildInterval(const size_t ranges[][2], void RemoveSuspendChecks(HGraph* graph) { for (HBasicBlock* block : graph->GetBlocks()) { - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - HInstruction* current = it.Current(); - if (current->IsSuspendCheck()) { - current->GetBlock()->RemoveInstruction(current); + if (block != nullptr) { + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + HInstruction* current = it.Current(); + if (current->IsSuspendCheck()) { + current->GetBlock()->RemoveInstruction(current); + } } } } @@ -83,12 +85,17 @@ inline HGraph* CreateGraph(ArenaAllocator* allocator) { inline HGraph* CreateCFG(ArenaAllocator* allocator, const uint16_t* data, Primitive::Type return_type = Primitive::kPrimInt) { - HGraph* graph = CreateGraph(allocator); - HGraphBuilder builder(graph, return_type); const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - return graph_built ? graph : nullptr; + HGraph* graph = CreateGraph(allocator); + + { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + HGraphBuilder builder(graph, return_type); + bool graph_built = (builder.BuildGraph(*item, &handles) == kAnalysisSuccess); + return graph_built ? graph : nullptr; + } } // Naive string diff data type. @@ -114,12 +121,6 @@ inline bool IsRemoved(HInstruction* instruction) { return instruction->GetBlock() == nullptr; } -inline void TransformToSsa(HGraph* graph) { - ScopedObjectAccess soa(Thread::Current()); - StackHandleScopeCollection handles(soa.Self()); - EXPECT_EQ(graph->TryBuildingSsa(&handles), kAnalysisSuccess); -} - } // namespace art #endif // ART_COMPILER_OPTIMIZING_OPTIMIZING_UNIT_TEST_H_ diff --git a/compiler/optimizing/pretty_printer_test.cc b/compiler/optimizing/pretty_printer_test.cc index c56100dfa1..2de0c1be72 100644 --- a/compiler/optimizing/pretty_printer_test.cc +++ b/compiler/optimizing/pretty_printer_test.cc @@ -30,17 +30,15 @@ namespace art { static void TestCode(const uint16_t* data, const char* expected) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); + HGraph* graph = CreateCFG(&allocator, data); StringPrettyPrinter printer(graph); printer.VisitInsertionOrder(); ASSERT_STREQ(expected, printer.str().c_str()); } -TEST(PrettyPrinterTest, ReturnVoid) { +class PrettyPrinterTest : public CommonCompilerTest {}; + +TEST_F(PrettyPrinterTest, ReturnVoid) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::RETURN_VOID); @@ -56,7 +54,7 @@ TEST(PrettyPrinterTest, ReturnVoid) { TestCode(data, expected); } -TEST(PrettyPrinterTest, CFG1) { +TEST_F(PrettyPrinterTest, CFG1) { const char* expected = "BasicBlock 0, succ: 1\n" " 3: SuspendCheck\n" @@ -76,7 +74,7 @@ TEST(PrettyPrinterTest, CFG1) { TestCode(data, expected); } -TEST(PrettyPrinterTest, CFG2) { +TEST_F(PrettyPrinterTest, CFG2) { const char* expected = "BasicBlock 0, succ: 1\n" " 4: SuspendCheck\n" @@ -98,7 +96,7 @@ TEST(PrettyPrinterTest, CFG2) { TestCode(data, expected); } -TEST(PrettyPrinterTest, CFG3) { +TEST_F(PrettyPrinterTest, CFG3) { const char* expected = "BasicBlock 0, succ: 1\n" " 4: SuspendCheck\n" @@ -134,16 +132,16 @@ TEST(PrettyPrinterTest, CFG3) { TestCode(data3, expected); } -TEST(PrettyPrinterTest, CFG4) { +TEST_F(PrettyPrinterTest, CFG4) { const char* expected = - "BasicBlock 0, succ: 1\n" + "BasicBlock 0, succ: 3\n" " 3: SuspendCheck\n" - " 4: Goto 1\n" - "BasicBlock 1, pred: 0, 1, succ: 1\n" + " 4: Goto 3\n" + "BasicBlock 1, pred: 3, 1, succ: 1\n" " 0: SuspendCheck\n" " 1: Goto 1\n" - "BasicBlock 2\n" - " 2: Exit\n"; + "BasicBlock 3, pred: 0, succ: 1\n" + " 5: Goto 1\n"; const uint16_t data1[] = ZERO_REGISTER_CODE_ITEM( Instruction::NOP, @@ -157,15 +155,13 @@ TEST(PrettyPrinterTest, CFG4) { TestCode(data2, expected); } -TEST(PrettyPrinterTest, CFG5) { +TEST_F(PrettyPrinterTest, CFG5) { const char* expected = "BasicBlock 0, succ: 1\n" " 3: SuspendCheck\n" " 4: Goto 1\n" - "BasicBlock 1, pred: 0, 2, succ: 3\n" + "BasicBlock 1, pred: 0, succ: 3\n" " 0: ReturnVoid\n" - "BasicBlock 2, succ: 1\n" - " 1: Goto 1\n" "BasicBlock 3, pred: 1\n" " 2: Exit\n"; @@ -177,25 +173,23 @@ TEST(PrettyPrinterTest, CFG5) { TestCode(data, expected); } -TEST(PrettyPrinterTest, CFG6) { +TEST_F(PrettyPrinterTest, CFG6) { const char* expected = "BasicBlock 0, succ: 1\n" - " 0: Local [4, 3, 2]\n" - " 1: IntConstant [2]\n" + " 1: IntConstant [5, 5]\n" " 10: SuspendCheck\n" " 11: Goto 1\n" - "BasicBlock 1, pred: 0, succ: 3, 2\n" - " 2: StoreLocal(0, 1)\n" - " 3: LoadLocal(0) [5]\n" - " 4: LoadLocal(0) [5]\n" - " 5: Equal(3, 4) [6]\n" + "BasicBlock 1, pred: 0, succ: 5, 2\n" + " 5: Equal(1, 1) [6]\n" " 6: If(5)\n" "BasicBlock 2, pred: 1, succ: 3\n" " 7: Goto 3\n" - "BasicBlock 3, pred: 1, 2, succ: 4\n" + "BasicBlock 3, pred: 5, 2, succ: 4\n" " 8: ReturnVoid\n" "BasicBlock 4, pred: 3\n" - " 9: Exit\n"; + " 9: Exit\n" + "BasicBlock 5, pred: 1, succ: 3\n" + " 12: Goto 3\n"; const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -206,26 +200,24 @@ TEST(PrettyPrinterTest, CFG6) { TestCode(data, expected); } -TEST(PrettyPrinterTest, CFG7) { +TEST_F(PrettyPrinterTest, CFG7) { const char* expected = "BasicBlock 0, succ: 1\n" - " 0: Local [4, 3, 2]\n" - " 1: IntConstant [2]\n" + " 1: IntConstant [5, 5]\n" " 11: SuspendCheck\n" " 12: Goto 1\n" - "BasicBlock 1, pred: 0, succ: 3, 2\n" - " 2: StoreLocal(0, 1)\n" - " 3: LoadLocal(0) [5]\n" - " 4: LoadLocal(0) [5]\n" - " 5: Equal(3, 4) [6]\n" + "BasicBlock 1, pred: 0, succ: 5, 6\n" + " 5: Equal(1, 1) [6]\n" " 6: If(5)\n" - "BasicBlock 2, pred: 1, 3, succ: 3\n" + "BasicBlock 2, pred: 6, 3, succ: 3\n" " 7: Goto 3\n" - "BasicBlock 3, pred: 1, 2, succ: 2\n" + "BasicBlock 3, pred: 5, 2, succ: 2\n" " 8: SuspendCheck\n" " 9: Goto 2\n" - "BasicBlock 4\n" - " 10: Exit\n"; + "BasicBlock 5, pred: 1, succ: 3\n" + " 13: Goto 3\n" + "BasicBlock 6, pred: 1, succ: 2\n" + " 14: Goto 2\n"; const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, @@ -236,15 +228,13 @@ TEST(PrettyPrinterTest, CFG7) { TestCode(data, expected); } -TEST(PrettyPrinterTest, IntConstant) { +TEST_F(PrettyPrinterTest, IntConstant) { const char* expected = "BasicBlock 0, succ: 1\n" - " 0: Local [2]\n" - " 1: IntConstant [2]\n" + " 1: IntConstant\n" " 5: SuspendCheck\n" " 6: Goto 1\n" "BasicBlock 1, pred: 0, succ: 2\n" - " 2: StoreLocal(0, 1)\n" " 3: ReturnVoid\n" "BasicBlock 2, pred: 1\n" " 4: Exit\n"; diff --git a/compiler/optimizing/register_allocator_test.cc b/compiler/optimizing/register_allocator_test.cc index 572faa841e..a9de7c3e59 100644 --- a/compiler/optimizing/register_allocator_test.cc +++ b/compiler/optimizing/register_allocator_test.cc @@ -38,11 +38,7 @@ class RegisterAllocatorTest : public CommonCompilerTest {}; static bool Check(const uint16_t* data) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - builder.BuildGraph(*item); - TransformToSsa(graph); + HGraph* graph = CreateCFG(&allocator, data); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); @@ -254,15 +250,6 @@ TEST_F(RegisterAllocatorTest, Loop2) { ASSERT_TRUE(Check(data)); } -static HGraph* BuildSSAGraph(const uint16_t* data, ArenaAllocator* allocator) { - HGraph* graph = CreateGraph(allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - builder.BuildGraph(*item); - TransformToSsa(graph); - return graph; -} - TEST_F(RegisterAllocatorTest, Loop3) { /* * Test the following snippet: @@ -302,7 +289,7 @@ TEST_F(RegisterAllocatorTest, Loop3) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = BuildSSAGraph(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); @@ -336,7 +323,7 @@ TEST_F(RegisterAllocatorTest, FirstRegisterUse) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = BuildSSAGraph(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); @@ -390,7 +377,7 @@ TEST_F(RegisterAllocatorTest, DeadPhi) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = BuildSSAGraph(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); SsaDeadPhiElimination(graph).Run(); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); @@ -414,7 +401,7 @@ TEST_F(RegisterAllocatorTest, FreeUntil) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = BuildSSAGraph(data, &allocator); + HGraph* graph = CreateCFG(&allocator, data); SsaDeadPhiElimination(graph).Run(); std::unique_ptr<const X86InstructionSetFeatures> features_x86( X86InstructionSetFeatures::FromCppDefines()); diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index 2d0a399290..43f2499b24 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -430,8 +430,6 @@ void SsaBuilder::RemoveRedundantUninitializedStrings() { } for (HNewInstance* new_instance : uninitialized_strings_) { - DCHECK(new_instance->IsStringAlloc()); - // Replace NewInstance of String with NullConstant if not used prior to // calling StringFactory. In case of deoptimization, the interpreter is // expected to skip null check on the `this` argument of the StringFactory call. @@ -440,10 +438,26 @@ void SsaBuilder::RemoveRedundantUninitializedStrings() { new_instance->GetBlock()->RemoveInstruction(new_instance); // Remove LoadClass if not needed any more. - HLoadClass* load_class = new_instance->InputAt(0)->AsLoadClass(); + HInstruction* input = new_instance->InputAt(0); + HLoadClass* load_class = nullptr; + + // If the class was not present in the dex cache at the point of building + // the graph, the builder inserted a HClinitCheck in between. Since the String + // class is always initialized at the point of running Java code, we can remove + // that check. + if (input->IsClinitCheck()) { + load_class = input->InputAt(0)->AsLoadClass(); + input->ReplaceWith(load_class); + input->GetBlock()->RemoveInstruction(input); + } else { + load_class = input->AsLoadClass(); + DCHECK(new_instance->IsStringAlloc()); + DCHECK(!load_class->NeedsAccessCheck()) << "String class is always accessible"; + } DCHECK(load_class != nullptr); - DCHECK(!load_class->NeedsAccessCheck()) << "String class is always accessible"; if (!load_class->HasUses()) { + // Even if the HLoadClass needs access check, we can remove it, as we know the + // String class does not need it. load_class->GetBlock()->RemoveInstruction(load_class); } } @@ -451,6 +465,8 @@ void SsaBuilder::RemoveRedundantUninitializedStrings() { } GraphAnalysisResult SsaBuilder::BuildSsa() { + DCHECK(!GetGraph()->IsInSsaForm()); + // 1) Visit in reverse post order. We need to have all predecessors of a block // visited (with the exception of loops) in order to create the right environment // for that block. For loops, we create phis whose inputs will be set in 2). @@ -533,6 +549,7 @@ GraphAnalysisResult SsaBuilder::BuildSsa() { } } + GetGraph()->SetInSsaForm(); return kAnalysisSuccess; } diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h index 4cba41f389..2dae9c2de0 100644 --- a/compiler/optimizing/ssa_builder.h +++ b/compiler/optimizing/ssa_builder.h @@ -79,8 +79,6 @@ class SsaBuilder : public HGraphVisitor { void VisitArraySet(HArraySet* aset) OVERRIDE; void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; - static constexpr const char* kSsaBuilderPassName = "ssa_builder"; - private: void SetLoopHeaderPhiInputs(); void FixEnvironmentPhis(); diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index 1dd35080a6..83e9dacb1a 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -226,7 +226,7 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { // The only instructions which may not be recorded in the environments // are constants created by the SSA builder as typed equivalents of // untyped constants from the bytecode, or phis with only such constants - // as inputs (verified by SSAChecker). Their raw binary value must + // as inputs (verified by GraphChecker). Their raw binary value must // therefore be the same and we only need to keep alive one. } else { size_t phi_input_index = successor->GetPredecessorIndexOf(block); diff --git a/compiler/optimizing/ssa_test.cc b/compiler/optimizing/ssa_test.cc index d2885a8fd7..a6880921c5 100644 --- a/compiler/optimizing/ssa_test.cc +++ b/compiler/optimizing/ssa_test.cc @@ -79,13 +79,7 @@ static void ReNumberInstructions(HGraph* graph) { static void TestCode(const uint16_t* data, const char* expected) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - - TransformToSsa(graph); + HGraph* graph = CreateCFG(&allocator, data); // Suspend checks implementation may change in the future, and this test relies // on how instructions are ordered. RemoveSuspendChecks(graph); diff --git a/compiler/optimizing/suspend_check_test.cc b/compiler/optimizing/suspend_check_test.cc index b6c704c1b1..15cd4e8a08 100644 --- a/compiler/optimizing/suspend_check_test.cc +++ b/compiler/optimizing/suspend_check_test.cc @@ -18,6 +18,7 @@ #include "dex_instruction.h" #include "nodes.h" #include "optimizing_unit_test.h" +#include "pretty_printer.h" #include "gtest/gtest.h" @@ -30,20 +31,17 @@ namespace art { static void TestCode(const uint16_t* data) { ArenaPool pool; ArenaAllocator allocator(&pool); - HGraph* graph = CreateGraph(&allocator); - HGraphBuilder builder(graph); - const DexFile::CodeItem* item = reinterpret_cast<const DexFile::CodeItem*>(data); - bool graph_built = builder.BuildGraph(*item); - ASSERT_TRUE(graph_built); - - HBasicBlock* first_block = graph->GetEntryBlock()->GetSuccessors()[0]; - HInstruction* first_instruction = first_block->GetFirstInstruction(); - // Account for some tests having a store local as first instruction. - ASSERT_TRUE(first_instruction->IsSuspendCheck() - || first_instruction->GetNext()->IsSuspendCheck()); + HGraph* graph = CreateCFG(&allocator, data); + HBasicBlock* first_block = graph->GetEntryBlock()->GetSingleSuccessor(); + HBasicBlock* loop_header = first_block->GetSingleSuccessor(); + ASSERT_TRUE(loop_header->IsLoopHeader()); + ASSERT_EQ(loop_header->GetLoopInformation()->GetPreHeader(), first_block); + ASSERT_TRUE(loop_header->GetFirstInstruction()->IsSuspendCheck()); } -TEST(CodegenTest, CFG1) { +class SuspendCheckTest : public CommonCompilerTest {}; + +TEST_F(SuspendCheckTest, CFG1) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::NOP, Instruction::GOTO | 0xFF00); @@ -51,14 +49,14 @@ TEST(CodegenTest, CFG1) { TestCode(data); } -TEST(CodegenTest, CFG2) { +TEST_F(SuspendCheckTest, CFG2) { const uint16_t data[] = ZERO_REGISTER_CODE_ITEM( Instruction::GOTO_32, 0, 0); TestCode(data); } -TEST(CodegenTest, CFG3) { +TEST_F(SuspendCheckTest, CFG3) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 0xFFFF, @@ -67,7 +65,7 @@ TEST(CodegenTest, CFG3) { TestCode(data); } -TEST(CodegenTest, CFG4) { +TEST_F(SuspendCheckTest, CFG4) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_NE, 0xFFFF, @@ -76,7 +74,7 @@ TEST(CodegenTest, CFG4) { TestCode(data); } -TEST(CodegenTest, CFG5) { +TEST_F(SuspendCheckTest, CFG5) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQZ, 0xFFFF, @@ -85,7 +83,7 @@ TEST(CodegenTest, CFG5) { TestCode(data); } -TEST(CodegenTest, CFG6) { +TEST_F(SuspendCheckTest, CFG6) { const uint16_t data[] = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_NEZ, 0xFFFF, diff --git a/runtime/base/scoped_arena_allocator.cc b/runtime/base/scoped_arena_allocator.cc index 90c6ee34ec..7d04fa0223 100644 --- a/runtime/base/scoped_arena_allocator.cc +++ b/runtime/base/scoped_arena_allocator.cc @@ -99,7 +99,7 @@ void* ArenaStack::AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind) { if (UNLIKELY(static_cast<size_t>(top_end_ - ptr) < rounded_bytes)) { ptr = AllocateFromNextArena(rounded_bytes); CHECK(ptr != nullptr) << "Failed to allocate memory"; - MEMORY_TOOL_MAKE_NOACCESS(ptr, top_end_); + MEMORY_TOOL_MAKE_NOACCESS(ptr, top_end_ - ptr); } CurrentStats()->RecordAlloc(bytes, kind); top_ptr_ = ptr + rounded_bytes; diff --git a/runtime/base/scoped_arena_containers.h b/runtime/base/scoped_arena_containers.h index 1236585afd..9b56856f11 100644 --- a/runtime/base/scoped_arena_containers.h +++ b/runtime/base/scoped_arena_containers.h @@ -201,22 +201,28 @@ inline ScopedArenaAllocatorAdapter<void> ScopedArenaAllocator::Adapter(ArenaAllo template <typename T> class ArenaDelete { static constexpr uint8_t kMagicFill = 0xCE; - public: - void operator()(T* ptr) const { - ptr->~T(); + protected: + // Used for variable sized objects such as RegisterLine. + ALWAYS_INLINE void ProtectMemory(T* ptr, size_t size) const { if (RUNNING_ON_MEMORY_TOOL > 0) { // Writing to the memory will fail if it we already destroyed the pointer with // DestroyOnlyDelete since we make it no access. - memset(ptr, kMagicFill, sizeof(T)); - MEMORY_TOOL_MAKE_NOACCESS(ptr, sizeof(T)); + memset(ptr, kMagicFill, size); + MEMORY_TOOL_MAKE_NOACCESS(ptr, size); } else if (kIsDebugBuild) { CHECK(ArenaStack::ArenaTagForAllocation(reinterpret_cast<void*>(ptr)) == ArenaFreeTag::kUsed) << "Freeing invalid object " << ptr; ArenaStack::ArenaTagForAllocation(reinterpret_cast<void*>(ptr)) = ArenaFreeTag::kFree; // Write a magic value to try and catch use after free error. - memset(ptr, kMagicFill, sizeof(T)); + memset(ptr, kMagicFill, size); } } + + public: + void operator()(T* ptr) const { + ptr->~T(); + ProtectMemory(ptr, sizeof(T)); + } }; // In general we lack support for arrays. We would need to call the destructor on each element, diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 0631ebe374..5278d1bb05 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2620,19 +2620,37 @@ const void* ClassLinker::GetQuickOatCodeFor(const DexFile& dex_file, return oat_class.GetOatMethod(oat_method_idx).GetQuickCode(); } -// Returns true if the method must run with interpreter, false otherwise. -static bool NeedsInterpreter(ArtMethod* method, const void* quick_code) - SHARED_REQUIRES(Locks::mutator_lock_) { +bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* quick_code) { + if (UNLIKELY(method->IsNative() || method->IsProxyMethod())) { + return false; + } + if (quick_code == nullptr) { - // No code: need interpreter. - // May return true for native code, in the case of generic JNI - // DCHECK(!method->IsNative()); return true; } - // If interpreter mode is enabled, every method (except native and proxy) must - // be run with interpreter. - return Runtime::Current()->GetInstrumentation()->InterpretOnly() && - !method->IsNative() && !method->IsProxyMethod(); + + Runtime* runtime = Runtime::Current(); + instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); + if (instr->InterpretOnly()) { + return true; + } + + if (runtime->GetClassLinker()->IsQuickToInterpreterBridge(quick_code)) { + // Doing this check avoids doing compiled/interpreter transitions. + return true; + } + + if (Dbg::IsForcedInterpreterNeededForCalling(Thread::Current(), method)) { + // Force the use of interpreter when it is required by the debugger. + return true; + } + + if (runtime->UseJit() && runtime->GetJit()->JitAtFirstUse()) { + // The force JIT uses the interpreter entry point to execute the JIT. + return true; + } + + return false; } void ClassLinker::FixupStaticTrampolines(mirror::Class* klass) { @@ -2677,15 +2695,12 @@ void ClassLinker::FixupStaticTrampolines(mirror::Class* klass) { OatFile::OatMethod oat_method = oat_class.GetOatMethod(method_index); quick_code = oat_method.GetQuickCode(); } - const bool enter_interpreter = NeedsInterpreter(method, quick_code); - if (enter_interpreter) { + // Check whether the method is native, in which case it's generic JNI. + if (quick_code == nullptr && method->IsNative()) { + quick_code = GetQuickGenericJniStub(); + } else if (ShouldUseInterpreterEntrypoint(method, quick_code)) { // Use interpreter entry point. - // Check whether the method is native, in which case it's generic JNI. - if (quick_code == nullptr && method->IsNative()) { - quick_code = GetQuickGenericJniStub(); - } else { - quick_code = GetQuickToInterpreterBridge(); - } + quick_code = GetQuickToInterpreterBridge(); } runtime->GetInstrumentation()->UpdateMethodsCode(method, quick_code); } @@ -2716,7 +2731,8 @@ void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class } // Install entry point from interpreter. - bool enter_interpreter = NeedsInterpreter(method, method->GetEntryPointFromQuickCompiledCode()); + const void* quick_code = method->GetEntryPointFromQuickCompiledCode(); + bool enter_interpreter = ShouldUseInterpreterEntrypoint(method, quick_code); if (!method->IsInvokable()) { EnsureThrowsInvocationError(method); @@ -2728,20 +2744,18 @@ void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines // after initializing class (see ClassLinker::InitializeClass method). method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub()); + } else if (quick_code == nullptr && method->IsNative()) { + method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub()); } else if (enter_interpreter) { - if (!method->IsNative()) { - // Set entry point from compiled code if there's no code or in interpreter only mode. - method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); - } else { - method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub()); - } + // Set entry point from compiled code if there's no code or in interpreter only mode. + method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); } if (method->IsNative()) { // Unregistering restores the dlsym lookup stub. method->UnregisterNative(); - if (enter_interpreter) { + if (enter_interpreter || quick_code == nullptr) { // We have a native method here without code. Then it should have either the generic JNI // trampoline as entrypoint (non-static), or the resolution trampoline (static). // TODO: this doesn't handle all the cases where trampolines may be installed. diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 56a868a4d0..a9448f732c 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -592,6 +592,9 @@ class ClassLinker { REQUIRES(!Locks::classlinker_classes_lock_) SHARED_REQUIRES(Locks::mutator_lock_); + static bool ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* quick_code) + SHARED_REQUIRES(Locks::mutator_lock_); + struct DexCacheData { // Weak root to the DexCache. Note: Do not decode this unnecessarily or else class unloading may // not work properly. diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 0b2471b4c0..4fd3c78f44 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -27,6 +27,7 @@ #include "unstarted_runtime.h" #include "mterp/mterp.h" #include "jit/jit.h" +#include "jit/jit_code_cache.h" namespace art { namespace interpreter { @@ -293,9 +294,10 @@ static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, method, 0); } - if (UNLIKELY(Runtime::Current()->GetJit() != nullptr && - Runtime::Current()->GetJit()->JitAtFirstUse() && - method->HasAnyCompiledCode())) { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (UNLIKELY(jit != nullptr && + jit->JitAtFirstUse() && + jit->GetCodeCache()->ContainsMethod(method))) { JValue result; // Pop the shadow frame before calling into compiled code. diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 09d860140f..cbaa8173d2 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -20,6 +20,7 @@ #include "debugger.h" #include "entrypoints/runtime_asm_entrypoints.h" +#include "jit/jit.h" #include "mirror/array-inl.h" #include "stack.h" #include "unstarted_runtime.h" @@ -501,23 +502,6 @@ static inline bool DoCallCommon(ArtMethod* called_method, uint32_t (&arg)[kVarArgMax], uint32_t vregC) ALWAYS_INLINE; -SHARED_REQUIRES(Locks::mutator_lock_) -static inline bool NeedsInterpreter(Thread* self, ShadowFrame* new_shadow_frame) ALWAYS_INLINE; - -static inline bool NeedsInterpreter(Thread* self, ShadowFrame* new_shadow_frame) { - ArtMethod* target = new_shadow_frame->GetMethod(); - if (UNLIKELY(target->IsNative() || target->IsProxyMethod())) { - return false; - } - Runtime* runtime = Runtime::Current(); - ClassLinker* class_linker = runtime->GetClassLinker(); - return runtime->GetInstrumentation()->IsForcedInterpretOnly() || - // Doing this check avoids doing compiled/interpreter transitions. - class_linker->IsQuickToInterpreterBridge(target->GetEntryPointFromQuickCompiledCode()) || - // Force the use of interpreter when it is required by the debugger. - Dbg::IsForcedInterpreterNeededForCalling(self, target); -} - void ArtInterpreterToCompiledCodeBridge(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame* shadow_frame, @@ -736,7 +720,10 @@ static inline bool DoCallCommon(ArtMethod* called_method, // Do the call now. if (LIKELY(Runtime::Current()->IsStarted())) { - if (NeedsInterpreter(self, new_shadow_frame)) { + ArtMethod* target = new_shadow_frame->GetMethod(); + if (ClassLinker::ShouldUseInterpreterEntrypoint( + target, + target->GetEntryPointFromQuickCompiledCode())) { ArtInterpreterToInterpreterBridge(self, code_item, new_shadow_frame, result); } else { ArtInterpreterToCompiledCodeBridge(self, code_item, new_shadow_frame, result); diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 31c278e748..188deb07a5 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -36,7 +36,7 @@ namespace art { namespace jit { -static constexpr bool kEnableOnStackReplacement = false; +static constexpr bool kEnableOnStackReplacement = true; JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& options) { auto* jit_options = new JitOptions; @@ -290,7 +290,7 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, } if (kRuntimeISA == kMips || kRuntimeISA == kMips64) { - VLOG(jit) << "OSR not supported on this platform"; + VLOG(jit) << "OSR not supported on this platform: " << kRuntimeISA; return false; } @@ -304,79 +304,93 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, return false; } - const OatQuickMethodHeader* osr_method = jit->GetCodeCache()->LookupOsrMethodHeader(method); - if (osr_method == nullptr) { - // No osr method yet, just return to the interpreter. - return false; - } - + // Fetch some data before looking up for an OSR method. We don't want thread + // suspension once we hold an OSR method, as the JIT code cache could delete the OSR + // method while we are being suspended. const size_t number_of_vregs = method->GetCodeItem()->registers_size_; - CodeInfo code_info = osr_method->GetOptimizedCodeInfo(); - StackMapEncoding encoding = code_info.ExtractEncoding(); - - // Find stack map starting at the target dex_pc. - StackMap stack_map = code_info.GetOsrStackMapForDexPc(dex_pc + dex_pc_offset, encoding); - if (!stack_map.IsValid()) { - // There is no OSR stack map for this dex pc offset. Just return to the interpreter in the - // hope that the next branch has one. - return false; - } + const char* shorty = method->GetShorty(); + std::string method_name(VLOG_IS_ON(jit) ? PrettyMethod(method) : ""); + void** memory = nullptr; + size_t frame_size = 0; + ShadowFrame* shadow_frame = nullptr; + const uint8_t* native_pc = nullptr; - // We found a stack map, now fill the frame with dex register values from the interpreter's - // shadow frame. - DexRegisterMap vreg_map = - code_info.GetDexRegisterMapOf(stack_map, encoding, number_of_vregs); - - ShadowFrame* shadow_frame = thread->PopShadowFrame(); - - size_t frame_size = osr_method->GetFrameSizeInBytes(); - - // Allocate memory to put shadow frame values. The osr stub will copy that memory to - // stack. - // Note that we could pass the shadow frame to the stub, and let it copy the values there, - // but that is engineering complexity not worth the effort for something like OSR. - void** memory = reinterpret_cast<void**>(malloc(frame_size)); - CHECK(memory != nullptr); - memset(memory, 0, frame_size); - - // Art ABI: ArtMethod is at the bottom of the stack. - memory[0] = method; - - if (!vreg_map.IsValid()) { - // If we don't have a dex register map, then there are no live dex registers at - // this dex pc. - } else { - for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) { - DexRegisterLocation::Kind location = - vreg_map.GetLocationKind(vreg, number_of_vregs, code_info, encoding); - if (location == DexRegisterLocation::Kind::kNone) { - // Dex register is dead or unitialized. - continue; - } + { + ScopedAssertNoThreadSuspension sts(thread, "Holding OSR method"); + const OatQuickMethodHeader* osr_method = jit->GetCodeCache()->LookupOsrMethodHeader(method); + if (osr_method == nullptr) { + // No osr method yet, just return to the interpreter. + return false; + } - if (location == DexRegisterLocation::Kind::kConstant) { - // We skip constants because the compiled code knows how to handle them. - continue; - } + CodeInfo code_info = osr_method->GetOptimizedCodeInfo(); + StackMapEncoding encoding = code_info.ExtractEncoding(); - DCHECK(location == DexRegisterLocation::Kind::kInStack); + // Find stack map starting at the target dex_pc. + StackMap stack_map = code_info.GetOsrStackMapForDexPc(dex_pc + dex_pc_offset, encoding); + if (!stack_map.IsValid()) { + // There is no OSR stack map for this dex pc offset. Just return to the interpreter in the + // hope that the next branch has one. + return false; + } - int32_t vreg_value = shadow_frame->GetVReg(vreg); - int32_t slot_offset = vreg_map.GetStackOffsetInBytes(vreg, - number_of_vregs, - code_info, - encoding); - DCHECK_LT(slot_offset, static_cast<int32_t>(frame_size)); - DCHECK_GT(slot_offset, 0); - (reinterpret_cast<int32_t*>(memory))[slot_offset / sizeof(int32_t)] = vreg_value; + // We found a stack map, now fill the frame with dex register values from the interpreter's + // shadow frame. + DexRegisterMap vreg_map = + code_info.GetDexRegisterMapOf(stack_map, encoding, number_of_vregs); + + frame_size = osr_method->GetFrameSizeInBytes(); + + // Allocate memory to put shadow frame values. The osr stub will copy that memory to + // stack. + // Note that we could pass the shadow frame to the stub, and let it copy the values there, + // but that is engineering complexity not worth the effort for something like OSR. + memory = reinterpret_cast<void**>(malloc(frame_size)); + CHECK(memory != nullptr); + memset(memory, 0, frame_size); + + // Art ABI: ArtMethod is at the bottom of the stack. + memory[0] = method; + + shadow_frame = thread->PopShadowFrame(); + if (!vreg_map.IsValid()) { + // If we don't have a dex register map, then there are no live dex registers at + // this dex pc. + } else { + for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) { + DexRegisterLocation::Kind location = + vreg_map.GetLocationKind(vreg, number_of_vregs, code_info, encoding); + if (location == DexRegisterLocation::Kind::kNone) { + // Dex register is dead or uninitialized. + continue; + } + + if (location == DexRegisterLocation::Kind::kConstant) { + // We skip constants because the compiled code knows how to handle them. + continue; + } + + DCHECK(location == DexRegisterLocation::Kind::kInStack) + << DexRegisterLocation::PrettyDescriptor(location); + + int32_t vreg_value = shadow_frame->GetVReg(vreg); + int32_t slot_offset = vreg_map.GetStackOffsetInBytes(vreg, + number_of_vregs, + code_info, + encoding); + DCHECK_LT(slot_offset, static_cast<int32_t>(frame_size)); + DCHECK_GT(slot_offset, 0); + (reinterpret_cast<int32_t*>(memory))[slot_offset / sizeof(int32_t)] = vreg_value; + } } + + native_pc = stack_map.GetNativePcOffset(encoding) + osr_method->GetEntryPoint(); + VLOG(jit) << "Jumping to " + << method_name + << "@" + << std::hex << reinterpret_cast<uintptr_t>(native_pc); } - const uint8_t* native_pc = stack_map.GetNativePcOffset(encoding) + osr_method->GetEntryPoint(); - VLOG(jit) << "Jumping to " - << PrettyMethod(method) - << "@" - << std::hex << reinterpret_cast<uintptr_t>(native_pc); { ManagedStack fragment; thread->PushManagedStackFragment(&fragment); @@ -384,8 +398,9 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, frame_size, native_pc, result, - method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty(), + shorty, thread); + if (UNLIKELY(thread->GetException() == Thread::GetDeoptimizationException())) { thread->DeoptimizeWithDeoptimizationException(result); } @@ -393,7 +408,7 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, } free(memory); thread->PushShadowFrame(shadow_frame); - VLOG(jit) << "Done running OSR code for " << PrettyMethod(method); + VLOG(jit) << "Done running OSR code for " << method_name; return true; } diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index a7881ac52f..a092b9f248 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -66,6 +66,7 @@ static void EnableDebugFeatures(uint32_t debug_flags) { DEBUG_ENABLE_JNI_LOGGING = 1 << 4, DEBUG_GENERATE_DEBUG_INFO = 1 << 5, DEBUG_ALWAYS_JIT = 1 << 6, + DEBUG_NATIVE_DEBUGGABLE = 1 << 7, }; Runtime* const runtime = Runtime::Current(); @@ -117,6 +118,11 @@ static void EnableDebugFeatures(uint32_t debug_flags) { debug_flags &= ~DEBUG_ALWAYS_JIT; } + if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) { + runtime->AddCompilerOption("--native-debuggable"); + debug_flags &= ~DEBUG_NATIVE_DEBUGGABLE; + } + if (debug_flags != 0) { LOG(ERROR) << StringPrintf("Unknown bits set in debug_flags: %#x", debug_flags); } diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 2ea4b1453d..f9d916a92e 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -146,6 +146,10 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define({"-XX:EnableHSpaceCompactForOOM", "-XX:DisableHSpaceCompactForOOM"}) .WithValues({true, false}) .IntoKey(M::EnableHSpaceCompactForOOM) + .Define("-XX:DumpNativeStackOnSigQuit:_") + .WithType<bool>() + .WithValueMap({{"false", false}, {"true", true}}) + .IntoKey(M::DumpNativeStackOnSigQuit) .Define("-Xusejit:_") .WithType<bool>() .WithValueMap({{"false", false}, {"true", true}}) @@ -667,6 +671,7 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -XX:BackgroundGC=none\n"); UsageMessage(stream, " -XX:LargeObjectSpace={disabled,map,freelist}\n"); UsageMessage(stream, " -XX:LargeObjectThreshold=N\n"); + UsageMessage(stream, " -XX:DumpNativeStackOnSigQuit=booleanvalue\n"); UsageMessage(stream, " -Xmethod-trace\n"); UsageMessage(stream, " -Xmethod-trace-file:filename"); UsageMessage(stream, " -Xmethod-trace-file-size:integervalue\n"); diff --git a/runtime/runtime.cc b/runtime/runtime.cc index da28da8f07..2aeb7921ce 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -918,6 +918,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { is_explicit_gc_disabled_ = runtime_options.Exists(Opt::DisableExplicitGC); dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::Dex2Oat); image_dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::ImageDex2Oat); + dump_native_stack_on_sig_quit_ = runtime_options.GetOrDefault(Opt::DumpNativeStackOnSigQuit); vfprintf_ = runtime_options.GetOrDefault(Opt::HookVfprintf); exit_ = runtime_options.GetOrDefault(Opt::HookExit); diff --git a/runtime/runtime.h b/runtime/runtime.h index bec26f8eaa..1956bae52a 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -603,6 +603,10 @@ class Runtime { safe_mode_ = mode; } + bool GetDumpNativeStackOnSigQuit() const { + return dump_native_stack_on_sig_quit_; + } + private: static void InitPlatformSignalHandlers(); @@ -813,6 +817,9 @@ class Runtime { // Whether the application should run in safe mode, that is, interpreter only. bool safe_mode_; + // Whether threads should dump their native stack on SIGQUIT. + bool dump_native_stack_on_sig_quit_; + DISALLOW_COPY_AND_ASSIGN(Runtime); }; std::ostream& operator<<(std::ostream& os, const Runtime::CalleeSaveType& rhs); diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 097bccb372..838d1a9649 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -67,6 +67,7 @@ RUNTIME_OPTIONS_KEY (Unit, LowMemoryMode) RUNTIME_OPTIONS_KEY (bool, UseTLAB, (kUseTlab || kUseReadBarrier)) RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true) RUNTIME_OPTIONS_KEY (bool, UseJIT, false) +RUNTIME_OPTIONS_KEY (bool, DumpNativeStackOnSigQuit, true) RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold, jit::Jit::kDefaultCompileThreshold) RUNTIME_OPTIONS_KEY (unsigned int, JITWarmupThreshold, jit::Jit::kDefaultWarmupThreshold) RUNTIME_OPTIONS_KEY (MemoryKiB, JITCodeCacheInitialCapacity, jit::JitCodeCache::kInitialCapacity) diff --git a/runtime/thread.cc b/runtime/thread.cc index 7a455949e6..2ee160571e 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -936,9 +936,9 @@ void Thread::ShortDump(std::ostream& os) const { << "]"; } -void Thread::Dump(std::ostream& os, BacktraceMap* backtrace_map) const { +void Thread::Dump(std::ostream& os, bool dump_native_stack, BacktraceMap* backtrace_map) const { DumpState(os); - DumpStack(os, backtrace_map); + DumpStack(os, dump_native_stack, backtrace_map); } mirror::String* Thread::GetThreadName(const ScopedObjectAccessAlreadyRunnable& soa) const { @@ -1497,7 +1497,9 @@ void Thread::DumpJavaStack(std::ostream& os) const { } } -void Thread::DumpStack(std::ostream& os, BacktraceMap* backtrace_map) const { +void Thread::DumpStack(std::ostream& os, + bool dump_native_stack, + BacktraceMap* backtrace_map) const { // TODO: we call this code when dying but may not have suspended the thread ourself. The // IsSuspended check is therefore racy with the use for dumping (normally we inhibit // the race with the thread_suspend_count_lock_). @@ -1510,7 +1512,7 @@ void Thread::DumpStack(std::ostream& os, BacktraceMap* backtrace_map) const { } if (safe_to_dump) { // If we're currently in native code, dump that stack before dumping the managed stack. - if (dump_for_abort || ShouldShowNativeStack(this)) { + if (dump_native_stack && (dump_for_abort || ShouldShowNativeStack(this))) { DumpKernelStack(os, GetTid(), " kernel: ", false); ArtMethod* method = GetCurrentMethod(nullptr, !dump_for_abort); DumpNativeStack(os, GetTid(), backtrace_map, " native: ", method); diff --git a/runtime/thread.h b/runtime/thread.h index 3a5d72efcb..2726e91130 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -187,7 +187,9 @@ class Thread { void ShortDump(std::ostream& os) const; // Dumps the detailed thread state and the thread stack (used for SIGQUIT). - void Dump(std::ostream& os, BacktraceMap* backtrace_map = nullptr) const + void Dump(std::ostream& os, + bool dump_native_stack = true, + BacktraceMap* backtrace_map = nullptr) const REQUIRES(!Locks::thread_suspend_count_lock_) SHARED_REQUIRES(Locks::mutator_lock_); @@ -1111,7 +1113,9 @@ class Thread { void VerifyStackImpl() SHARED_REQUIRES(Locks::mutator_lock_); void DumpState(std::ostream& os) const SHARED_REQUIRES(Locks::mutator_lock_); - void DumpStack(std::ostream& os, BacktraceMap* backtrace_map = nullptr) const + void DumpStack(std::ostream& os, + bool dump_native_stack = true, + BacktraceMap* backtrace_map = nullptr) const REQUIRES(!Locks::thread_suspend_count_lock_) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index c8714a60a8..49d54fda00 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -140,7 +140,7 @@ void ThreadList::DumpForSigQuit(std::ostream& os) { suspend_all_historam_.PrintConfidenceIntervals(os, 0.99, data); // Dump time to suspend. } } - Dump(os); + Dump(os, Runtime::Current()->GetDumpNativeStackOnSigQuit()); DumpUnattachedThreads(os); } @@ -189,8 +189,11 @@ static constexpr uint32_t kDumpWaitTimeout = kIsTargetBuild ? 10000 : 20000; // A closure used by Thread::Dump. class DumpCheckpoint FINAL : public Closure { public: - explicit DumpCheckpoint(std::ostream* os) - : os_(os), barrier_(0), backtrace_map_(BacktraceMap::Create(getpid())) {} + DumpCheckpoint(std::ostream* os, bool dump_native_stack) + : os_(os), + barrier_(0), + backtrace_map_(dump_native_stack ? BacktraceMap::Create(getpid()) : nullptr), + dump_native_stack_(dump_native_stack) {} void Run(Thread* thread) OVERRIDE { // Note thread and self may not be equal if thread was already suspended at the point of the @@ -199,7 +202,7 @@ class DumpCheckpoint FINAL : public Closure { std::ostringstream local_os; { ScopedObjectAccess soa(self); - thread->Dump(local_os, backtrace_map_.get()); + thread->Dump(local_os, dump_native_stack_, backtrace_map_.get()); } local_os << "\n"; { @@ -228,14 +231,16 @@ class DumpCheckpoint FINAL : public Closure { Barrier barrier_; // A backtrace map, so that all threads use a shared info and don't reacquire/parse separately. std::unique_ptr<BacktraceMap> backtrace_map_; + // Whether we should dump the native stack. + const bool dump_native_stack_; }; -void ThreadList::Dump(std::ostream& os) { +void ThreadList::Dump(std::ostream& os, bool dump_native_stack) { { MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); os << "DALVIK THREADS (" << list_.size() << "):\n"; } - DumpCheckpoint checkpoint(&os); + DumpCheckpoint checkpoint(&os, dump_native_stack); size_t threads_running_checkpoint = RunCheckpoint(&checkpoint); if (threads_running_checkpoint != 0) { checkpoint.WaitForThreadsToRunThroughCheckpoint(threads_running_checkpoint); diff --git a/runtime/thread_list.h b/runtime/thread_list.h index 2e73f6af7f..363cab83f4 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -49,7 +49,7 @@ class ThreadList { void DumpForSigQuit(std::ostream& os) REQUIRES(!Locks::thread_list_lock_, !Locks::mutator_lock_); // For thread suspend timeout dumps. - void Dump(std::ostream& os) + void Dump(std::ostream& os, bool dump_native_stack = true) REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); pid_t GetLockOwner(); // For SignalCatcher. diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 1d31408cf0..a6cf9eaf86 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -1960,8 +1960,8 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { // We need to ensure the work line is consistent while performing validation. When we spot a // peephole pattern we compute a new line for either the fallthrough instruction or the // branch target. - ArenaUniquePtr<RegisterLine> branch_line; - ArenaUniquePtr<RegisterLine> fallthrough_line; + RegisterLineArenaUniquePtr branch_line; + RegisterLineArenaUniquePtr fallthrough_line; switch (inst->Opcode()) { case Instruction::NOP: @@ -4824,7 +4824,7 @@ bool MethodVerifier::UpdateRegisters(uint32_t next_insn, RegisterLine* merge_lin AdjustReturnLine(this, ret_inst, target_line); } } else { - ArenaUniquePtr<RegisterLine> copy; + RegisterLineArenaUniquePtr copy; if (kDebugVerify) { copy.reset(RegisterLine::Create(target_line->NumRegs(), this)); copy->CopyFromLine(target_line); diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index c7d1e6bc90..b53a45cf41 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -30,6 +30,7 @@ #include "handle.h" #include "instruction_flags.h" #include "method_reference.h" +#include "register_line.h" #include "reg_type_cache.h" namespace art { @@ -45,6 +46,7 @@ namespace verifier { class DexPcToReferenceMap; class MethodVerifier; class RegisterLine; +using RegisterLineArenaUniquePtr = std::unique_ptr<RegisterLine, RegisterLineArenaDelete>; class RegType; /* @@ -127,7 +129,7 @@ class PcToRegisterLineTable { } private: - ScopedArenaVector<ArenaUniquePtr<RegisterLine>> register_lines_; + ScopedArenaVector<RegisterLineArenaUniquePtr> register_lines_; DISALLOW_COPY_AND_ASSIGN(PcToRegisterLineTable); }; @@ -771,14 +773,14 @@ class MethodVerifier { PcToRegisterLineTable reg_table_; // Storage for the register status we're currently working on. - ArenaUniquePtr<RegisterLine> work_line_; + RegisterLineArenaUniquePtr work_line_; // The address of the instruction we're currently working on, note that this is in 2 byte // quantities uint32_t work_insn_idx_; // Storage for the register status we're saving for later. - ArenaUniquePtr<RegisterLine> saved_line_; + RegisterLineArenaUniquePtr saved_line_; const uint32_t dex_method_idx_; // The method we're working on. // Its object representation if known. diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h index 330c06ab46..bfbb78f58a 100644 --- a/runtime/verifier/register_line-inl.h +++ b/runtime/verifier/register_line-inl.h @@ -185,9 +185,12 @@ inline void RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) cons } } +inline size_t RegisterLine::ComputeSize(size_t num_regs) { + return OFFSETOF_MEMBER(RegisterLine, line_) + num_regs * sizeof(uint16_t); +} + inline RegisterLine* RegisterLine::Create(size_t num_regs, MethodVerifier* verifier) { - void* memory = verifier->GetArena().Alloc(OFFSETOF_MEMBER(RegisterLine, line_) + - (num_regs * sizeof(uint16_t))); + void* memory = verifier->GetArena().Alloc(ComputeSize(num_regs)); return new (memory) RegisterLine(num_regs, verifier); } @@ -200,6 +203,12 @@ inline RegisterLine::RegisterLine(size_t num_regs, MethodVerifier* verifier) SetResultTypeToUnknown(verifier); } +inline void RegisterLineArenaDelete::operator()(RegisterLine* ptr) const { + const size_t size = ptr != nullptr ? RegisterLine::ComputeSize(ptr->NumRegs()) : 0u; + ptr->~RegisterLine(); + ProtectMemory(ptr, size); +} + } // namespace verifier } // namespace art diff --git a/runtime/verifier/register_line.h b/runtime/verifier/register_line.h index b2f5555d8b..d50845421f 100644 --- a/runtime/verifier/register_line.h +++ b/runtime/verifier/register_line.h @@ -197,6 +197,9 @@ class RegisterLine { return num_regs_; } + // Return how many bytes of memory a register line uses. + ALWAYS_INLINE static size_t ComputeSize(size_t num_regs); + /* * Get the "this" pointer from a non-static method invocation. This returns the RegType so the * caller can decide whether it needs the reference to be initialized or not. (Can also return @@ -401,6 +404,13 @@ class RegisterLine { DISALLOW_COPY_AND_ASSIGN(RegisterLine); }; +class RegisterLineArenaDelete : public ArenaDelete<RegisterLine> { + public: + void operator()(RegisterLine* ptr) const; +}; + + + } // namespace verifier } // namespace art diff --git a/test/003-omnibus-opcodes/build b/test/003-omnibus-opcodes/build index faa298337c..56e87844c0 100644 --- a/test/003-omnibus-opcodes/build +++ b/test/003-omnibus-opcodes/build @@ -23,8 +23,8 @@ rm classes/UnresClass.class ${JAVAC} -d classes `find src2 -name '*.java'` if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else ${DX} -JXmx256m --debug --dex --output=classes.dex classes fi diff --git a/test/005-annotations/build b/test/005-annotations/build index 057b351dab..93bee507df 100644 --- a/test/005-annotations/build +++ b/test/005-annotations/build @@ -29,8 +29,8 @@ rm 'classes/android/test/anno/MissingAnnotation.class' rm 'classes/android/test/anno/ClassWithInnerAnnotationClass$MissingInnerAnnotationClass.class' if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else ${DX} -JXmx256m --debug --dex --output=classes.dex classes fi diff --git a/test/022-interface/build b/test/022-interface/build index 3f8915c27e..5cfc7f25b7 100644 --- a/test/022-interface/build +++ b/test/022-interface/build @@ -20,8 +20,8 @@ set -e # Use classes that are compiled with ecj that exposes an invokeinterface # issue when interfaces override methods in Object if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else ${DX} --debug --dex --dump-to=classes.lst --output=classes.dex classes fi diff --git a/test/085-old-style-inner-class/build b/test/085-old-style-inner-class/build index 6f50a76863..21dc66269d 100644 --- a/test/085-old-style-inner-class/build +++ b/test/085-old-style-inner-class/build @@ -23,8 +23,8 @@ mkdir classes ${JAVAC} -source 1.4 -target 1.4 -d classes `find src -name '*.java'` if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else # Suppress stderr to keep the inner class warnings out of the expected output. ${DX} --debug --dex --dump-to=classes.lst --output=classes.dex --dump-width=1000 classes 2>/dev/null diff --git a/test/091-override-package-private-method/build b/test/091-override-package-private-method/build index 5a340dcf6d..073a4ba9bc 100755 --- a/test/091-override-package-private-method/build +++ b/test/091-override-package-private-method/build @@ -24,14 +24,12 @@ mkdir classes-ex mv classes/OverridePackagePrivateMethodSuper.class classes-ex if [ ${USE_JACK} = "true" ]; then - # Create .jack files from classes generated with javac. - ${JILL} classes --output classes.jack - ${JILL} classes-ex --output classes-ex.jack + jar cf classes.jill.jar -C classes . + jar cf classes-ex.jill.jar -C classes-ex . - # Create DEX files from .jack files. - ${JACK} --import classes.jack --output-dex . + ${JACK} --import classes.jill.jar --output-dex . zip $TEST_NAME.jar classes.dex - ${JACK} --import classes-ex.jack --output-dex . + ${JACK} --import classes-ex.jill.jar --output-dex . zip ${TEST_NAME}-ex.jar classes.dex else if [ ${NEED_DEX} = "true" ]; then diff --git a/test/097-duplicate-method/build b/test/097-duplicate-method/build index a8558739de..4525549905 100644 --- a/test/097-duplicate-method/build +++ b/test/097-duplicate-method/build @@ -23,10 +23,10 @@ if [ ${USE_JACK} = "true" ]; then ${JACK} --output-jack src.jack src ${JASMIN} -d classes src/*.j - ${JILL} classes --output jasmin.jack + jar cf jasmin.jill.jar -C classes . # We set jack.import.type.policy=keep-first to consider class definitions from jasmin first. - ${JACK} --import jasmin.jack --import src.jack -D jack.import.type.policy=keep-first --output-dex . + ${JACK} --import jasmin.jill.jar --import src.jack -D jack.import.type.policy=keep-first --output-dex . else ${JAVAC} -d classes src/*.java ${JASMIN} -d classes src/*.j diff --git a/test/111-unresolvable-exception/build b/test/111-unresolvable-exception/build index e772fb812f..58ac26d836 100644 --- a/test/111-unresolvable-exception/build +++ b/test/111-unresolvable-exception/build @@ -22,8 +22,8 @@ ${JAVAC} -d classes `find src -name '*.java'` rm classes/TestException.class if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex classes fi diff --git a/test/113-multidex/build b/test/113-multidex/build index 8ef5c0eb0f..4557ccd22a 100644 --- a/test/113-multidex/build +++ b/test/113-multidex/build @@ -28,14 +28,12 @@ ${JAVAC} -d classes2 `find src -name '*.java'` rm classes2/Second.class classes2/FillerA.class classes2/FillerB.class classes2/Inf*.class if [ ${USE_JACK} = "true" ]; then - # Create .jack files from classes generated with javac. - ${JILL} classes --output classes.jack - ${JILL} classes2 --output classes2.jack + jar cf classes.jill.jar -C classes . + jar cf classes2.jill.jar -C classes2 . - # Create DEX files from .jack files. - ${JACK} --import classes.jack --output-dex . + ${JACK} --import classes.jill.jar --output-dex . mv classes.dex classes-1.dex - ${JACK} --import classes2.jack --output-dex . + ${JACK} --import classes2.jill.jar --output-dex . mv classes.dex classes2.dex mv classes-1.dex classes.dex else diff --git a/test/115-native-bridge/expected.txt b/test/115-native-bridge/expected.txt index b003307ab7..852ec2e5e9 100644 --- a/test/115-native-bridge/expected.txt +++ b/test/115-native-bridge/expected.txt @@ -1,4 +1,3 @@ -Code cache exists: './code_cache'. Native bridge initialized. Checking for getEnvValues. Ready for native bridge tests. diff --git a/test/115-native-bridge/nativebridge.cc b/test/115-native-bridge/nativebridge.cc index b70ca4ff3e..aca356b5b9 100644 --- a/test/115-native-bridge/nativebridge.cc +++ b/test/115-native-bridge/nativebridge.cc @@ -269,16 +269,12 @@ extern "C" bool native_bridge_initialize(const android::NativeBridgeRuntimeCallb struct stat st; if (app_code_cache_dir != nullptr) { if (stat(app_code_cache_dir, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - printf("Code cache exists: '%s'.\n", app_code_cache_dir); - } else { + if (!S_ISDIR(st.st_mode)) { printf("Code cache is not a directory.\n"); } } else { perror("Error when stat-ing the code_cache:"); } - } else { - printf("app_code_cache_dir is null.\n"); } if (art_cbs != nullptr) { diff --git a/test/121-modifiers/build b/test/121-modifiers/build index 85b69e92a6..771dd51829 100644 --- a/test/121-modifiers/build +++ b/test/121-modifiers/build @@ -31,9 +31,9 @@ set -e # mv Main.class A.class A\$B.class A\$C.class classes/ if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack + jar cf classes.jill.jar -C classes . # Workaround b/19561685: disable sanity checks to produce a DEX file with invalid modifiers. - ${JACK} --sanity-checks off --import classes.jack --output-dex . + ${JACK} --sanity-checks off --import classes.jill.jar --output-dex . else ${DX} --debug --dex --dump-to=classes.lst --output=classes.dex classes fi diff --git a/test/124-missing-classes/build b/test/124-missing-classes/build index b92ecf9382..0a340a26d6 100644 --- a/test/124-missing-classes/build +++ b/test/124-missing-classes/build @@ -27,8 +27,8 @@ rm 'classes/MissingClass.class' rm 'classes/Main$MissingInnerClass.class' if [ ${USE_JACK} = "true" ]; then - ${JILL} classes --output classes.jack - ${JACK} --import classes.jack --output-dex . + jar cf classes.jill.jar -C classes . + ${JACK} --import classes.jill.jar --output-dex . else ${DX} -JXmx256m --debug --dex --output=classes.dex classes fi diff --git a/test/126-miranda-multidex/build b/test/126-miranda-multidex/build index b7f2118d2f..00b9ba0ac2 100644 --- a/test/126-miranda-multidex/build +++ b/test/126-miranda-multidex/build @@ -28,14 +28,12 @@ ${JAVAC} -d classes2 `find src -name '*.java'` rm classes2/Main.class classes2/MirandaAbstract.class classes2/MirandaClass*.class classes2/MirandaInterface2*.class if [ ${USE_JACK} = "true" ]; then - # Create .jack files from classes generated with javac. - ${JILL} classes --output classes.jack - ${JILL} classes2 --output classes2.jack + jar cf classes.jill.jar -C classes . + jar cf classes2.jill.jar -C classes2 . - # Create DEX files from .jack files. - ${JACK} --import classes.jack --output-dex . + ${JACK} --import classes.jill.jar --output-dex . mv classes.dex classes-1.dex - ${JACK} --import classes2.jack --output-dex . + ${JACK} --import classes2.jill.jar --output-dex . mv classes.dex classes2.dex mv classes-1.dex classes.dex else diff --git a/test/127-checker-secondarydex/build b/test/127-checker-secondarydex/build index 0d9f4d6291..7ce46acfed 100755 --- a/test/127-checker-secondarydex/build +++ b/test/127-checker-secondarydex/build @@ -24,14 +24,12 @@ mkdir classes-ex mv classes/Super.class classes-ex if [ ${USE_JACK} = "true" ]; then - # Create .jack files from classes generated with javac. - ${JILL} classes --output classes.jack - ${JILL} classes-ex --output classes-ex.jack + jar cf classes.jill.jar -C classes . + jar cf classes-ex.jill.jar -C classes-ex . - # Create DEX files from .jack files. - ${JACK} --import classes.jack --output-dex . + ${JACK} --import classes.jill.jar --output-dex . zip $TEST_NAME.jar classes.dex - ${JACK} --import classes-ex.jack --output-dex . + ${JACK} --import classes-ex.jill.jar --output-dex . zip ${TEST_NAME}-ex.jar classes.dex else if [ ${NEED_DEX} = "true" ]; then diff --git a/test/127-checker-secondarydex/src/Test.java b/test/127-checker-secondarydex/src/Test.java index 266ed191bc..438e8545ff 100644 --- a/test/127-checker-secondarydex/src/Test.java +++ b/test/127-checker-secondarydex/src/Test.java @@ -23,7 +23,7 @@ public class Test extends Super { System.out.println("Test"); } - /// CHECK-START: java.lang.Integer Test.toInteger() ssa_builder (after) + /// CHECK-START: java.lang.Integer Test.toInteger() builder (after) /// CHECK: LoadClass needs_access_check:false klass:java.lang.Integer public Integer toInteger() { diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc index 87656bcf0e..45251b8f7d 100644 --- a/test/137-cfi/cfi.cc +++ b/test/137-cfi/cfi.cc @@ -53,6 +53,7 @@ static void CauseSegfault() { extern "C" JNIEXPORT jboolean JNICALL Java_Main_sleep(JNIEnv*, jobject, jint, jboolean, jdouble) { // Keep pausing. + printf("Going to sleep\n"); for (;;) { pause(); } diff --git a/test/137-cfi/expected.txt b/test/137-cfi/expected.txt index 8db7853696..6a5618ebc6 100644 --- a/test/137-cfi/expected.txt +++ b/test/137-cfi/expected.txt @@ -1,2 +1 @@ JNI_OnLoad called -JNI_OnLoad called diff --git a/test/137-cfi/run b/test/137-cfi/run index 6f4bcfe658..8ec98c11dc 100755 --- a/test/137-cfi/run +++ b/test/137-cfi/run @@ -20,4 +20,5 @@ ${RUN} "$@" -Xcompiler-option --generate-debug-info --args --full-signatures # Test with minimal compressed debugging information. # Check only method names (parameters are omitted to save space). -${RUN} "$@" -Xcompiler-option --generate-mini-debug-info +# Temporarily disable due to bug 27172087 (leak/race in libunwind). +# ${RUN} "$@" -Xcompiler-option --generate-mini-debug-info diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java index 77553380c8..d60a4ebba8 100644 --- a/test/137-cfi/src/Main.java +++ b/test/137-cfi/src/Main.java @@ -16,10 +16,7 @@ import java.io.BufferedReader; import java.io.FileReader; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.io.InputStreamReader; import java.util.Arrays; import java.util.Comparator; @@ -98,9 +95,12 @@ public class Main implements Comparator<Main> { throw new RuntimeException("Couldn't parse process"); } - // Wait a bit, so the forked process has time to run until its sleep phase. + // Wait until the forked process had time to run until its sleep phase. try { - Thread.sleep(5000); + InputStreamReader stdout = new InputStreamReader(p.getInputStream(), "UTF-8"); + BufferedReader lineReader = new BufferedReader(stdout); + while (!lineReader.readLine().contains("Going to sleep")) { + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/test/444-checker-nce/src/Main.java b/test/444-checker-nce/src/Main.java index 865355ce97..c96b18c71b 100644 --- a/test/444-checker-nce/src/Main.java +++ b/test/444-checker-nce/src/Main.java @@ -27,7 +27,7 @@ public class Main { return m.g(); } - /// CHECK-START: Main Main.thisTest() ssa_builder (after) + /// CHECK-START: Main Main.thisTest() builder (after) /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect @@ -38,7 +38,7 @@ public class Main { return g(); } - /// CHECK-START: Main Main.newInstanceRemoveTest() ssa_builder (after) + /// CHECK-START: Main Main.newInstanceRemoveTest() builder (after) /// CHECK: NewInstance /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect @@ -52,7 +52,7 @@ public class Main { return m.g(); } - /// CHECK-START: Main Main.newArrayRemoveTest() ssa_builder (after) + /// CHECK-START: Main Main.newArrayRemoveTest() builder (after) /// CHECK: NewArray /// CHECK: NullCheck /// CHECK: ArrayGet @@ -178,7 +178,7 @@ public class Main { return n.g(); } - /// CHECK-START: Main Main.scopeRemoveTest(int, Main) ssa_builder (after) + /// CHECK-START: Main Main.scopeRemoveTest(int, Main) builder (after) /// CHECK: NullCheck /// CHECK-START: Main Main.scopeRemoveTest(int, Main) instruction_simplifier (after) diff --git a/test/450-checker-types/src/Main.java b/test/450-checker-types/src/Main.java index d48b30e324..027a9d9487 100644 --- a/test/450-checker-types/src/Main.java +++ b/test/450-checker-types/src/Main.java @@ -205,7 +205,7 @@ 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) ssa_builder (after) + /// 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 @@ -229,7 +229,7 @@ public class Main { } } - /// CHECK-START: void Main.testNotInstanceOf_NotInlined(java.lang.Object) ssa_builder (after) + /// 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 @@ -487,7 +487,7 @@ public class Main { ((SubclassA)a[0]).$noinline$g(); } - /// CHECK-START: int Main.testLoadExceptionInCatchNonExact(int, int) ssa_builder (after) + /// CHECK-START: int Main.testLoadExceptionInCatchNonExact(int, int) builder (after) /// CHECK: LoadException klass:java.lang.ArithmeticException can_be_null:false exact:false public int testLoadExceptionInCatchNonExact(int x, int y) { try { @@ -497,7 +497,7 @@ public class Main { } } - /// CHECK-START: int Main.testLoadExceptionInCatchExact(int) ssa_builder (after) + /// CHECK-START: int Main.testLoadExceptionInCatchExact(int) builder (after) /// CHECK: LoadException klass:FinalException can_be_null:false exact:true public int testLoadExceptionInCatchExact(int x) { try { @@ -511,7 +511,7 @@ public class Main { } } - /// CHECK-START: int Main.testLoadExceptionInCatchAll(int, int) ssa_builder (after) + /// CHECK-START: int Main.testLoadExceptionInCatchAll(int, int) builder (after) /// CHECK: LoadException klass:java.lang.Throwable can_be_null:false exact:false public int testLoadExceptionInCatchAll(int x, int y) { try { @@ -532,7 +532,7 @@ public class Main { return genericFinal.get(); } - /// CHECK-START: SubclassC Main.inlineGenerics() ssa_builder (after) + /// CHECK-START: SubclassC Main.inlineGenerics() builder (after) /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:SubclassC exact:false /// CHECK-NEXT: Return [<<Invoke>>] @@ -544,7 +544,7 @@ public class Main { return c; } - /// CHECK-START: Final Main.inlineGenericsFinal() ssa_builder (after) + /// CHECK-START: Final Main.inlineGenericsFinal() builder (after) /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:Final exact:true /// CHECK-NEXT: Return [<<Invoke>>] @@ -586,7 +586,7 @@ public class Main { return new SubclassA(); } - /// CHECK-START: void Main.updateNodesInTheSameBlockAsPhi(boolean) ssa_builder (after) + /// CHECK-START: void Main.updateNodesInTheSameBlockAsPhi(boolean) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:Super /// CHECK: NullCheck [<<Phi>>] klass:Super @@ -620,7 +620,7 @@ public class Main { } - /// CHECK-START: void Main.argumentCheck(Super, double, SubclassA, Final) ssa_builder (after) + /// CHECK-START: void Main.argumentCheck(Super, double, SubclassA, Final) builder (after) /// CHECK: ParameterValue klass:Main can_be_null:false exact:false /// CHECK: ParameterValue klass:Super can_be_null:true exact:false /// CHECK: ParameterValue @@ -636,7 +636,7 @@ public class Main { private int mainField = 0; - /// CHECK-START: SuperInterface Main.getWiderType(boolean, Interface, OtherInterface) ssa_builder (after) + /// CHECK-START: SuperInterface Main.getWiderType(boolean, Interface, OtherInterface) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: Return [<<Phi>>] private SuperInterface getWiderType(boolean cond, Interface a, OtherInterface b) { @@ -692,7 +692,7 @@ public class Main { getSuper(); } - /// CHECK-START: void Main.testLoopPhiWithNullFirstInput(boolean) ssa_builder (after) + /// CHECK-START: void Main.testLoopPhiWithNullFirstInput(boolean) builder (after) /// CHECK-DAG: <<Null:l\d+>> NullConstant /// CHECK-DAG: <<Main:l\d+>> NewInstance klass:Main exact:true /// CHECK-DAG: <<LoopPhi:l\d+>> Phi [<<Null>>,<<LoopPhi>>,<<Main>>] klass:Main exact:true @@ -705,7 +705,7 @@ public class Main { } } - /// CHECK-START: java.lang.Object[] Main.testInstructionsWithUntypedParent() ssa_builder (after) + /// CHECK-START: java.lang.Object[] Main.testInstructionsWithUntypedParent() builder (after) /// CHECK-DAG: <<Null:l\d+>> NullConstant /// CHECK-DAG: <<LoopPhi:l\d+>> Phi [<<Null>>,<<Phi:l\d+>>] klass:java.lang.Object[] exact:true /// CHECK-DAG: <<Array:l\d+>> NewArray klass:java.lang.Object[] exact:true diff --git a/test/458-checker-instruction-simplification/src/Main.java b/test/458-checker-instruction-simplification/src/Main.java index 2f80470cb3..8d6bb653f4 100644 --- a/test/458-checker-instruction-simplification/src/Main.java +++ b/test/458-checker-instruction-simplification/src/Main.java @@ -46,6 +46,12 @@ public class Main { } } + public static void assertStringEquals(String expected, String result) { + if (expected == null ? result != null : !expected.equals(result)) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + /** * Tiny programs exercising optimizations of arithmetic identities. */ @@ -1401,7 +1407,7 @@ public class Main { // Test that conditions on float/double are not flipped. - /// CHECK-START: int Main.floatConditionNotEqualOne(float) ssa_builder (after) + /// CHECK-START: int Main.floatConditionNotEqualOne(float) builder (after) /// CHECK: LessThanOrEqual /// CHECK-START: int Main.floatConditionNotEqualOne(float) instruction_simplifier_before_codegen (after) @@ -1417,7 +1423,7 @@ public class Main { return ((f > 42.0f) == true) ? 13 : 54; } - /// CHECK-START: int Main.doubleConditionEqualZero(double) ssa_builder (after) + /// CHECK-START: int Main.doubleConditionEqualZero(double) builder (after) /// CHECK: LessThanOrEqual /// CHECK-START: int Main.doubleConditionEqualZero(double) instruction_simplifier_before_codegen (after) @@ -1433,6 +1439,337 @@ public class Main { return ((d > 42.0) != false) ? 13 : 54; } + /// CHECK-START: int Main.intToDoubleToInt(int) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Double>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.intToDoubleToInt(int) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: Return [<<Arg>>] + + /// CHECK-START: int Main.intToDoubleToInt(int) instruction_simplifier (after) + /// CHECK-NOT: TypeConversion + + public static int intToDoubleToInt(int value) { + // Lossless conversion followed by a conversion back. + return (int) (double) value; + } + + /// CHECK-START: java.lang.String Main.intToDoubleToIntPrint(int) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: {{i\d+}} TypeConversion [<<Double>>] + + /// CHECK-START: java.lang.String Main.intToDoubleToIntPrint(int) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: {{d\d+}} TypeConversion [<<Arg>>] + + /// CHECK-START: java.lang.String Main.intToDoubleToIntPrint(int) instruction_simplifier (after) + /// CHECK-DAG: TypeConversion + /// CHECK-NOT: TypeConversion + + public static String intToDoubleToIntPrint(int value) { + // Lossless conversion followed by a conversion back + // with another use of the intermediate result. + double d = (double) value; + int i = (int) d; + return "d=" + d + ", i=" + i; + } + + /// CHECK-START: int Main.byteToDoubleToInt(byte) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:b\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Double>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.byteToDoubleToInt(byte) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:b\d+>> ParameterValue + /// CHECK-DAG: Return [<<Arg>>] + + /// CHECK-START: int Main.byteToDoubleToInt(byte) instruction_simplifier (after) + /// CHECK-NOT: TypeConversion + + public static int byteToDoubleToInt(byte value) { + // Lossless conversion followed by another conversion, use implicit conversion. + return (int) (double) value; + } + + /// CHECK-START: int Main.floatToDoubleToInt(float) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Double>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.floatToDoubleToInt(float) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.floatToDoubleToInt(float) instruction_simplifier (after) + /// CHECK-DAG: TypeConversion + /// CHECK-NOT: TypeConversion + + public static int floatToDoubleToInt(float value) { + // Lossless conversion followed by another conversion. + return (int) (double) value; + } + + /// CHECK-START: java.lang.String Main.floatToDoubleToIntPrint(float) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: {{i\d+}} TypeConversion [<<Double>>] + + /// CHECK-START: java.lang.String Main.floatToDoubleToIntPrint(float) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: {{i\d+}} TypeConversion [<<Double>>] + + public static String floatToDoubleToIntPrint(float value) { + // Lossless conversion followed by another conversion with + // an extra use of the intermediate result. + double d = (double) value; + int i = (int) d; + return "d=" + d + ", i=" + i; + } + + /// CHECK-START: short Main.byteToDoubleToShort(byte) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:b\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Double>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.byteToDoubleToShort(byte) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:b\d+>> ParameterValue + /// CHECK-DAG: Return [<<Arg>>] + + /// CHECK-START: short Main.byteToDoubleToShort(byte) instruction_simplifier (after) + /// CHECK-NOT: TypeConversion + + public static short byteToDoubleToShort(byte value) { + // Originally, this is byte->double->int->short. The first conversion is lossless, + // so we merge this with the second one to byte->int which we omit as it's an implicit + // conversion. Then we eliminate the resulting byte->short as an implicit conversion. + return (short) (double) value; + } + + /// CHECK-START: short Main.charToDoubleToShort(char) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:c\d+>> ParameterValue + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Double>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.charToDoubleToShort(char) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:c\d+>> ParameterValue + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.charToDoubleToShort(char) instruction_simplifier (after) + /// CHECK-DAG: TypeConversion + /// CHECK-NOT: TypeConversion + + public static short charToDoubleToShort(char value) { + // Originally, this is char->double->int->short. The first conversion is lossless, + // so we merge this with the second one to char->int which we omit as it's an implicit + // conversion. Then we are left with the resulting char->short conversion. + return (short) (double) value; + } + + /// CHECK-START: short Main.floatToIntToShort(float) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.floatToIntToShort(float) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:f\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Short>>] + + public static short floatToIntToShort(float value) { + // Lossy FP to integral conversion followed by another conversion: no simplification. + return (short) value; + } + + /// CHECK-START: int Main.intToFloatToInt(int) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Float:f\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Float>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.intToFloatToInt(int) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Float:f\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Float>>] + /// CHECK-DAG: Return [<<Int>>] + + public static int intToFloatToInt(int value) { + // Lossy integral to FP conversion followed another conversion: no simplification. + return (int) (float) value; + } + + /// CHECK-START: double Main.longToIntToDouble(long) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Double>>] + + /// CHECK-START: double Main.longToIntToDouble(long) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Double:d\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Double>>] + + public static double longToIntToDouble(long value) { + // Lossy long-to-int conversion followed an integral to FP conversion: no simplification. + return (double) (int) value; + } + + /// CHECK-START: long Main.longToIntToLong(long) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Long:j\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Long>>] + + /// CHECK-START: long Main.longToIntToLong(long) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Long:j\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Long>>] + + public static long longToIntToLong(long value) { + // Lossy long-to-int conversion followed an int-to-long conversion: no simplification. + return (long) (int) value; + } + + /// CHECK-START: short Main.shortToCharToShort(short) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<Char>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.shortToCharToShort(short) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: Return [<<Arg>>] + + public static short shortToCharToShort(short value) { + // Integral conversion followed by non-widening integral conversion to original type. + return (short) (char) value; + } + + /// CHECK-START: int Main.shortToLongToInt(short) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Long:j\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<Long>>] + /// CHECK-DAG: Return [<<Int>>] + + /// CHECK-START: int Main.shortToLongToInt(short) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: Return [<<Arg>>] + + public static int shortToLongToInt(short value) { + // Integral conversion followed by non-widening integral conversion, use implicit conversion. + return (int) (long) value; + } + + /// CHECK-START: byte Main.shortToCharToByte(short) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: <<Byte:b\d+>> TypeConversion [<<Char>>] + /// CHECK-DAG: Return [<<Byte>>] + + /// CHECK-START: byte Main.shortToCharToByte(short) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Byte:b\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: Return [<<Byte>>] + + public static byte shortToCharToByte(short value) { + // Integral conversion followed by non-widening integral conversion losing bits + // from the original type. Simplify to use only one conversion. + return (byte) (char) value; + } + + /// CHECK-START: java.lang.String Main.shortToCharToBytePrint(short) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: {{b\d+}} TypeConversion [<<Char>>] + + /// CHECK-START: java.lang.String Main.shortToCharToBytePrint(short) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:s\d+>> ParameterValue + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: {{b\d+}} TypeConversion [<<Char>>] + + public static String shortToCharToBytePrint(short value) { + // Integral conversion followed by non-widening integral conversion losing bits + // from the original type with an extra use of the intermediate result. + char c = (char) value; + byte b = (byte) c; + return "c=" + ((int) c) + ", b=" + ((int) b); // implicit conversions. + } + + /// CHECK-START: byte Main.longAnd0xffToByte(long) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Mask:j\d+>> LongConstant 255 + /// CHECK-DAG: <<And:j\d+>> And [<<Mask>>,<<Arg>>] + /// CHECK-DAG: <<Int:i\d+>> TypeConversion [<<And>>] + /// CHECK-DAG: <<Byte:b\d+>> TypeConversion [<<Int>>] + /// CHECK-DAG: Return [<<Byte>>] + + /// CHECK-START: byte Main.longAnd0xffToByte(long) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:j\d+>> ParameterValue + /// CHECK-DAG: <<Byte:b\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: Return [<<Byte>>] + + /// CHECK-START: byte Main.longAnd0xffToByte(long) instruction_simplifier (after) + /// CHECK-NOT: And + + public static byte longAnd0xffToByte(long value) { + return (byte) (value & 0xff); + } + + /// CHECK-START: char Main.intAnd0x1ffffToChar(int) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Mask:i\d+>> IntConstant 131071 + /// CHECK-DAG: <<And:i\d+>> And [<<Mask>>,<<Arg>>] + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<And>>] + /// CHECK-DAG: Return [<<Char>>] + + /// CHECK-START: char Main.intAnd0x1ffffToChar(int) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Char:c\d+>> TypeConversion [<<Arg>>] + /// CHECK-DAG: Return [<<Char>>] + + /// CHECK-START: char Main.intAnd0x1ffffToChar(int) instruction_simplifier (after) + /// CHECK-NOT: And + + public static char intAnd0x1ffffToChar(int value) { + // Keeping all significant bits and one more. + return (char) (value & 0x1ffff); + } + + /// CHECK-START: short Main.intAnd0x17fffToShort(int) instruction_simplifier (before) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Mask:i\d+>> IntConstant 98303 + /// CHECK-DAG: <<And:i\d+>> And [<<Mask>>,<<Arg>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<And>>] + /// CHECK-DAG: Return [<<Short>>] + + /// CHECK-START: short Main.intAnd0x17fffToShort(int) instruction_simplifier (after) + /// CHECK-DAG: <<Arg:i\d+>> ParameterValue + /// CHECK-DAG: <<Mask:i\d+>> IntConstant 98303 + /// CHECK-DAG: <<And:i\d+>> And [<<Mask>>,<<Arg>>] + /// CHECK-DAG: <<Short:s\d+>> TypeConversion [<<And>>] + /// CHECK-DAG: Return [<<Short>>] + + public static short intAnd0x17fffToShort(int value) { + // No simplification: clearing a significant bit. + return (short) (value & 0x17fff); + } + public static void main(String[] args) { int arg = 123456; @@ -1518,6 +1855,57 @@ public class Main { assertIntEquals(floatConditionNotEqualOne(43.0f), 13); assertIntEquals(doubleConditionEqualZero(6.0), 54); assertIntEquals(doubleConditionEqualZero(43.0), 13); + + assertIntEquals(intToDoubleToInt(1234567), 1234567); + assertIntEquals(intToDoubleToInt(Integer.MIN_VALUE), Integer.MIN_VALUE); + assertIntEquals(intToDoubleToInt(Integer.MAX_VALUE), Integer.MAX_VALUE); + assertStringEquals(intToDoubleToIntPrint(7654321), "d=7654321.0, i=7654321"); + assertIntEquals(byteToDoubleToInt((byte) 12), 12); + assertIntEquals(byteToDoubleToInt(Byte.MIN_VALUE), Byte.MIN_VALUE); + assertIntEquals(byteToDoubleToInt(Byte.MAX_VALUE), Byte.MAX_VALUE); + assertIntEquals(floatToDoubleToInt(11.3f), 11); + assertStringEquals(floatToDoubleToIntPrint(12.25f), "d=12.25, i=12"); + assertIntEquals(byteToDoubleToShort((byte) 123), 123); + assertIntEquals(byteToDoubleToShort(Byte.MIN_VALUE), Byte.MIN_VALUE); + assertIntEquals(byteToDoubleToShort(Byte.MAX_VALUE), Byte.MAX_VALUE); + assertIntEquals(charToDoubleToShort((char) 1234), 1234); + assertIntEquals(charToDoubleToShort(Character.MIN_VALUE), Character.MIN_VALUE); + assertIntEquals(charToDoubleToShort(Character.MAX_VALUE), /* sign-extended */ -1); + assertIntEquals(floatToIntToShort(12345.75f), 12345); + assertIntEquals(floatToIntToShort((float)(Short.MIN_VALUE - 1)), Short.MAX_VALUE); + assertIntEquals(floatToIntToShort((float)(Short.MAX_VALUE + 1)), Short.MIN_VALUE); + assertIntEquals(intToFloatToInt(-54321), -54321); + assertDoubleEquals(longToIntToDouble(0x1234567812345678L), (double) 0x12345678); + assertDoubleEquals(longToIntToDouble(Long.MIN_VALUE), 0.0); + assertDoubleEquals(longToIntToDouble(Long.MAX_VALUE), -1.0); + assertLongEquals(longToIntToLong(0x1234567812345678L), 0x0000000012345678L); + assertLongEquals(longToIntToLong(0x1234567887654321L), 0xffffffff87654321L); + assertLongEquals(longToIntToLong(Long.MIN_VALUE), 0L); + assertLongEquals(longToIntToLong(Long.MAX_VALUE), -1L); + assertIntEquals(shortToCharToShort((short) -5678), (short) -5678); + assertIntEquals(shortToCharToShort(Short.MIN_VALUE), Short.MIN_VALUE); + assertIntEquals(shortToCharToShort(Short.MAX_VALUE), Short.MAX_VALUE); + assertIntEquals(shortToLongToInt((short) 5678), 5678); + assertIntEquals(shortToLongToInt(Short.MIN_VALUE), Short.MIN_VALUE); + assertIntEquals(shortToLongToInt(Short.MAX_VALUE), Short.MAX_VALUE); + assertIntEquals(shortToCharToByte((short) 0x1234), 0x34); + assertIntEquals(shortToCharToByte((short) 0x12f0), -0x10); + assertIntEquals(shortToCharToByte(Short.MIN_VALUE), 0); + assertIntEquals(shortToCharToByte(Short.MAX_VALUE), -1); + assertStringEquals(shortToCharToBytePrint((short) 1025), "c=1025, b=1"); + assertStringEquals(shortToCharToBytePrint((short) 1023), "c=1023, b=-1"); + assertStringEquals(shortToCharToBytePrint((short) -1), "c=65535, b=-1"); + + assertIntEquals(longAnd0xffToByte(0x1234432112344321L), 0x21); + assertIntEquals(longAnd0xffToByte(Long.MIN_VALUE), 0); + assertIntEquals(longAnd0xffToByte(Long.MAX_VALUE), -1); + assertIntEquals(intAnd0x1ffffToChar(0x43211234), 0x1234); + assertIntEquals(intAnd0x1ffffToChar(Integer.MIN_VALUE), 0); + assertIntEquals(intAnd0x1ffffToChar(Integer.MAX_VALUE), Character.MAX_VALUE); + assertIntEquals(intAnd0x17fffToShort(0x87654321), 0x4321); + assertIntEquals(intAnd0x17fffToShort(0x88888888), 0x0888); + assertIntEquals(intAnd0x17fffToShort(Integer.MIN_VALUE), 0); + assertIntEquals(intAnd0x17fffToShort(Integer.MAX_VALUE), Short.MAX_VALUE); } public static boolean booleanField; diff --git a/test/464-checker-inline-sharpen-calls/src/Main.java b/test/464-checker-inline-sharpen-calls/src/Main.java index 2222e0fa0c..3f256352b7 100644 --- a/test/464-checker-inline-sharpen-calls/src/Main.java +++ b/test/464-checker-inline-sharpen-calls/src/Main.java @@ -39,7 +39,7 @@ public final class Main { m.invokeVirtual(); } - /// CHECK-START: int Main.inlineSharpenHelperInvoke() ssa_builder (after) + /// CHECK-START: int Main.inlineSharpenHelperInvoke() builder (after) /// CHECK-DAG: <<Invoke:i\d+>> InvokeVirtual {{.*\.getFoo.*}} /// CHECK-DAG: Return [<<Invoke>>] diff --git a/test/477-checker-bound-type/src/Main.java b/test/477-checker-bound-type/src/Main.java index 0f65e44678..2504ab2839 100644 --- a/test/477-checker-bound-type/src/Main.java +++ b/test/477-checker-bound-type/src/Main.java @@ -17,7 +17,7 @@ public class Main { - /// CHECK-START: java.lang.Object Main.boundTypeForIf(java.lang.Object) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.boundTypeForIf(java.lang.Object) builder (after) /// CHECK: BoundType public static Object boundTypeForIf(Object a) { if (a != null) { @@ -27,7 +27,7 @@ public class Main { } } - /// CHECK-START: java.lang.Object Main.boundTypeForInstanceOf(java.lang.Object) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.boundTypeForInstanceOf(java.lang.Object) builder (after) /// CHECK: BoundType public static Object boundTypeForInstanceOf(Object a) { if (a instanceof Main) { @@ -37,7 +37,7 @@ public class Main { } } - /// CHECK-START: java.lang.Object Main.noBoundTypeForIf(java.lang.Object) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.noBoundTypeForIf(java.lang.Object) builder (after) /// CHECK-NOT: BoundType public static Object noBoundTypeForIf(Object a) { if (a == null) { @@ -47,7 +47,7 @@ public class Main { } } - /// CHECK-START: java.lang.Object Main.noBoundTypeForInstanceOf(java.lang.Object) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.noBoundTypeForInstanceOf(java.lang.Object) builder (after) /// CHECK-NOT: BoundType public static Object noBoundTypeForInstanceOf(Object a) { if (a instanceof Main) { diff --git a/test/492-checker-inline-invoke-interface/src/Main.java b/test/492-checker-inline-invoke-interface/src/Main.java index 3106ce4f3e..a919690000 100644 --- a/test/492-checker-inline-invoke-interface/src/Main.java +++ b/test/492-checker-inline-invoke-interface/src/Main.java @@ -31,7 +31,7 @@ public class Main implements Itf { int a = ForceStatic.field; } - /// CHECK-START: void Main.main(java.lang.String[]) ssa_builder (after) + /// CHECK-START: void Main.main(java.lang.String[]) builder (after) /// CHECK: InvokeStaticOrDirect {{.*Main.<init>.*}} /// CHECK: InvokeInterface diff --git a/test/510-checker-try-catch/smali/Builder.smali b/test/510-checker-try-catch/smali/Builder.smali index c91b83e18a..8ec840d159 100644 --- a/test/510-checker-try-catch/smali/Builder.smali +++ b/test/510-checker-try-catch/smali/Builder.smali @@ -41,28 +41,35 @@ ## CHECK: predecessors "<<BEnterTry2>>" ## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: DivZeroCheck +## CHECK: <<Div:i\d+>> Div -## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExitTry2>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" "<<BCatch3:B\d+>>" +## CHECK: name "<<BAfterTry2:B\d+>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BReturn:B\d+>>" +## CHECK: Goto + +## CHECK: name "<<BReturn>>" +## CHECK: predecessors "<<BAfterTry2>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" "<<BCatch3:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>,<<Minus2>>,<<Minus3>>] ## CHECK: Return ## CHECK: name "<<BCatch1>>" ## CHECK: predecessors "<<BEnterTry1>>" "<<BExitTry1>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BCatch2>>" ## CHECK: predecessors "<<BEnterTry2>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus2>>] +## CHECK: Goto ## CHECK: name "<<BCatch3>>" ## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus3>>] +## CHECK: Goto ## CHECK: name "<<BEnterTry1>>" ## CHECK: predecessors "B0" @@ -84,7 +91,7 @@ ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BAfterTry2>>" ## CHECK: xhandlers "<<BCatch2>>" "<<BCatch3>>" ## CHECK: TryBoundary kind:exit @@ -105,6 +112,8 @@ .catch Ljava/lang/OutOfMemoryError; {:try_start_2 .. :try_end_2} :catch_mem .catchall {:try_start_2 .. :try_end_2} :catch_other + nop + :return return p0 @@ -131,7 +140,7 @@ ## CHECK: name "<<BIf>>" ## CHECK: predecessors "B0" -## CHECK: successors "<<BEnterTry2:B\d+>>" "<<BThen:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" "<<BThen:B\d+>>" ## CHECK: If ## CHECK: name "<<BThen>>" @@ -145,19 +154,19 @@ ## CHECK: Div ## CHECK: name "<<BTry2:B\d+>>" -## CHECK: predecessors "<<BEnterTry2>>" +## CHECK: predecessors "<<BEnterTry2:B\d+>>" ## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: Div ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExitTry2>>" "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BSplit3:B\d+>>" "<<BCatch:B\d+>>" ## CHECK: Return ## CHECK: name "<<BCatch>>" ## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BEnterTry1>>" ## CHECK: predecessors "<<BThen>>" @@ -166,23 +175,38 @@ ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" -## CHECK: predecessors "<<BIf>>" "<<BExitTry1>>" +## CHECK: predecessors "<<BSplit1>>" "<<BSplit2:B\d+>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry1>>" ## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BEnterTry2>>" +## CHECK: successors "<<BSplit2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit3>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BIf>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BExitTry1>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit3>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testMultipleEntries(IIII)I .registers 4 @@ -220,23 +244,24 @@ ## CHECK: name "<<BTry:B\d+>>" ## CHECK: predecessors "<<BEnterTry>>" ## CHECK: successors "<<BExitTry1:B\d+>>" "<<BExitTry2:B\d+>>" -## CHECK: Div +## CHECK: <<Div:i\d+>> Div ## CHECK: If ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExitTry2>>" "<<BThen:B\d+>>" "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BSplit:B\d+>>" "<<BThen:B\d+>>" "<<BCatch:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>,<<Minus2>>] ## CHECK: Return ## CHECK: name "<<BThen>>" ## CHECK: predecessors "<<BExitTry1>>" ## CHECK: successors "<<BReturn>>" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BCatch>>" ## CHECK: predecessors "<<BEnterTry>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus2>>] +## CHECK: Goto ## CHECK: name "<<BEnterTry>>" ## CHECK: predecessors "B0" @@ -252,10 +277,15 @@ ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testMultipleExits(II)I .registers 2 @@ -295,23 +325,25 @@ ## CHECK: name "<<BTry2:B\d+>>" ## CHECK: predecessors "<<BEnter2:B\d+>>" ## CHECK: successors "<<BExit2:B\d+>>" -## CHECK: Div +## CHECK: <<Div:i\d+>> Div +## CHECK: Goto ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExit2>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" +## CHECK: predecessors "<<BSplit:B\d+>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>,<<Minus2>>] ## CHECK: Return ## CHECK: name "<<BCatch1>>" ## CHECK: predecessors "<<BEnter1>>" "<<BExit1>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BCatch2>>" ## CHECK: predecessors "<<BEnter2>>" "<<BExit2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus2>>] +## CHECK: Goto ## CHECK: name "<<BEnter1>>" ## CHECK: predecessors "B0" @@ -333,10 +365,15 @@ ## CHECK: name "<<BExit2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit>>" ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit>>" +## CHECK: predecessors "<<BExit2>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testSharedBoundary(III)I .registers 3 @@ -378,28 +415,31 @@ ## CHECK: name "<<BTry1:B\d+>>" ## CHECK: predecessors "<<BEnter1:B\d+>>" ## CHECK: successors "<<BExit1:B\d+>>" -## CHECK: Div +## CHECK: <<Div:i\d+>> Div +## CHECK: Goto ## CHECK: name "<<BTry2:B\d+>>" ## CHECK: predecessors "<<BEnter2>>" ## CHECK: successors "<<BExit2:B\d+>>" ## CHECK: Div +## CHECK: Goto ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExit1>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" +## CHECK: predecessors "<<BSplit:B\d+>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>,<<Minus2>>] ## CHECK: Return ## CHECK: name "<<BCatch1>>" ## CHECK: predecessors "<<BEnter1>>" "<<BExit1>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BCatch2>>" ## CHECK: predecessors "<<BEnter2>>" "<<BExit2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus2>>] +## CHECK: Goto ## CHECK: name "<<BEnter1>>" ## CHECK: predecessors "<<BExit2>>" @@ -415,7 +455,7 @@ ## CHECK: name "<<BExit1>>" ## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit>>" ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:exit @@ -425,6 +465,11 @@ ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit>>" +## CHECK: predecessors "<<BExit1>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testSharedBoundary_Reverse(III)I .registers 3 @@ -472,26 +517,30 @@ ## CHECK: predecessors "<<BEnter2:B\d+>>" ## CHECK: successors "<<BExit2:B\d+>>" ## CHECK: Div +## CHECK: Goto ## CHECK: name "<<BTry3:B\d+>>" ## CHECK: predecessors "<<BEnter3:B\d+>>" ## CHECK: successors "<<BExit3:B\d+>>" -## CHECK: Div +## CHECK: <<Div:i\d+>> Div +## CHECK: Goto ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExit3>>" "<<BCatchArith:B\d+>>" "<<BCatchAll:B\d+>>" +## CHECK: predecessors "<<BSplit:B\d+>>" "<<BCatchArith:B\d+>>" "<<BCatchAll:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>,<<Minus2>>] +## CHECK: Return ## CHECK: name "<<BCatchArith>>" ## CHECK: predecessors "<<BEnter2>>" "<<BExit2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BCatchAll>>" ## CHECK: predecessors "<<BEnter1>>" "<<BEnter2>>" "<<BEnter3>>" "<<BExit1>>" "<<BExit2>>" "<<BExit3>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus2>>] +## CHECK: Goto ## CHECK: name "<<BEnter1>>" ## CHECK: predecessors "B0" @@ -525,10 +574,15 @@ ## CHECK: name "<<BExit3>>" ## CHECK: predecessors "<<BTry3>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit>>" ## CHECK: xhandlers "<<BCatchAll>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit>>" +## CHECK: predecessors "<<BExit3>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testNestedTry(IIII)I .registers 4 @@ -567,14 +621,18 @@ ## CHECK: predecessors "<<BEnterTry1:B\d+>>" ## CHECK: successors "<<BExitTry1:B\d+>>" ## CHECK: Div +## CHECK: Goto ## CHECK: name "<<BTry2:B\d+>>" ## CHECK: predecessors "<<BEnterTry2:B\d+>>" ## CHECK: successors "<<BExitTry2:B\d+>>" -## CHECK: Div +## CHECK: <<Div:i\d+>> Div +## CHECK: Goto ## CHECK: name "<<BReturn:B\d+>>" -## CHECK: predecessors "<<BExitTry2>>" "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BSplit:B\d+>>" "<<BCatch:B\d+>>" +## CHECK: Phi [<<Div>>,<<Minus1>>] +## CHECK: Return ## CHECK: name "<<BOutside:B\d+>>" ## CHECK: predecessors "<<BExitTry1>>" @@ -585,7 +643,7 @@ ## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Goto ## CHECK: name "<<BEnterTry1>>" ## CHECK: predecessors "B0" @@ -607,10 +665,15 @@ ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BReturn>>" +## CHECK: successors "<<BSplit>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BReturn>>" +## CHECK: Goto + .method public static testIncontinuousTry(IIII)I .registers 4 @@ -642,12 +705,12 @@ ## CHECK: name "<<BPSwitch0>>" ## CHECK: predecessors "B0" -## CHECK: successors "<<BEnterTry2:B\d+>>" "<<BPSwitch1:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" "<<BPSwitch1:B\d+>>" ## CHECK: If ## CHECK: name "<<BPSwitch1>>" ## CHECK: predecessors "<<BPSwitch0>>" -## CHECK: successors "<<BOutside:B\d+>>" "<<BEnterTry1:B\d+>>" +## CHECK: successors "<<BSplit2:B\d+>>" "<<BEnterTry1:B\d+>>" ## CHECK: If ## CHECK: name "<<BTry1:B\d+>>" @@ -656,44 +719,68 @@ ## CHECK: Div ## CHECK: name "<<BTry2:B\d+>>" -## CHECK: predecessors "<<BEnterTry2>>" +## CHECK: predecessors "<<BEnterTry2:B\d+>>" ## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: Div -## CHECK: name "<<BOutside>>" -## CHECK: predecessors "<<BPSwitch1>>" "<<BExitTry2>>" -## CHECK: successors "<<BCatchReturn:B\d+>>" +## CHECK: name "<<BOutside:B\d+>>" +## CHECK: predecessors "<<BSplit2>>" "<<BSplit4:B\d+>>" +## CHECK: successors "<<BReturn:B\d+>>" ## CHECK: Div -## CHECK: name "<<BCatchReturn>>" -## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" +## CHECK: name "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" ## CHECK: flags "catch_block" -## CHECK: Return +## CHECK: Goto ## CHECK: name "<<BEnterTry1>>" ## CHECK: predecessors "<<BPSwitch1>>" ## CHECK: successors "<<BTry1>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" -## CHECK: predecessors "<<BPSwitch0>>" +## CHECK: predecessors "<<BSplit1>>" "<<BSplit3:B\d+>>" ## CHECK: successors "<<BTry2>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry1>>" ## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BEnterTry2>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: successors "<<BSplit3>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BOutside>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: successors "<<BSplit4>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BReturn>>" +## CHECK: predecessors "<<BCatch>>" "<<BOutside>>" +## CHECK: Return + +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BPSwitch0>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BPSwitch1>>" +## CHECK: successors "<<BOutside>>" +## CHECK: Goto + +## CHECK: name "<<BSplit3>>" +## CHECK: predecessors "<<BExitTry1>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit4>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BOutside>>" +## CHECK: Goto + .method public static testSwitchTryEnter(IIII)I .registers 4 @@ -728,58 +815,78 @@ ## CHECK: name "<<BPSwitch0:B\d+>>" ## CHECK: predecessors "<<BEnterTry1>>" -## CHECK: successors "<<BTry2:B\d+>>" "<<BExitTry1:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" "<<BExitTry1:B\d+>>" ## CHECK: If ## CHECK: name "<<BPSwitch1:B\d+>>" ## CHECK: predecessors "<<BExitTry1>>" -## CHECK: successors "<<BOutside:B\d+>>" "<<BEnterTry2:B\d+>>" +## CHECK: successors "<<BSplit2:B\d+>>" "<<BEnterTry2:B\d+>>" ## CHECK: If ## CHECK: name "<<BTry1:B\d+>>" ## CHECK: predecessors "<<BEnterTry2>>" -## CHECK: successors "<<BTry2>>" +## CHECK: successors "<<BTry2:B\d+>>" ## CHECK: Div ## CHECK: name "<<BTry2>>" -## CHECK: predecessors "<<BPSwitch0>>" +## CHECK: predecessors "<<BSplit1>>" "<<BTry1>>" ## CHECK: successors "<<BExitTry2:B\d+>>" ## CHECK: Div -## CHECK: name "<<BOutside>>" -## CHECK: predecessors "<<BPSwitch1>>" "<<BExitTry2>>" -## CHECK: successors "<<BCatchReturn:B\d+>>" +## CHECK: name "<<BOutside:B\d+>>" +## CHECK: predecessors "<<BSplit2>>" "<<BSplit3:B\d+>>" +## CHECK: successors "<<BReturn:B\d+>>" ## CHECK: Div -## CHECK: name "<<BCatchReturn>>" -## CHECK: predecessors "<<BOutside>>" "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" +## CHECK: name "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry1>>" "<<BExitTry2>>" +## CHECK: successors "<<BReturn>>" ## CHECK: flags "catch_block" -## CHECK: Return +## CHECK: Goto ## CHECK: name "<<BEnterTry1>>" ## CHECK: predecessors "B0" ## CHECK: successors "<<BPSwitch0>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" ## CHECK: predecessors "<<BPSwitch1>>" ## CHECK: successors "<<BTry1>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry1>>" ## CHECK: predecessors "<<BPSwitch0>>" ## CHECK: successors "<<BPSwitch1>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit ## CHECK: name "<<BExitTry2>>" ## CHECK: predecessors "<<BTry2>>" -## CHECK: successors "<<BOutside>>" -## CHECK: xhandlers "<<BCatchReturn>>" +## CHECK: successors "<<BSplit3>>" +## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BReturn>>" +## CHECK: predecessors "<<BCatch>>" "<<BOutside>>" +## CHECK: Return + +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BPSwitch0>>" +## CHECK: successors "<<BTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BPSwitch1>>" +## CHECK: successors "<<BOutside>>" +## CHECK: Goto + +## CHECK: name "<<BSplit3>>" +## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BOutside>>" +## CHECK: Goto + .method public static testSwitchTryExit(IIII)I .registers 4 @@ -825,7 +932,7 @@ ## CHECK: predecessors "<<BEnterTry>>" "<<BExitTry>>" ## CHECK: successors "<<BExit:B\d+>>" ## CHECK: flags "catch_block" -## CHECK: StoreLocal [v0,<<Minus1>>] +## CHECK: Return [<<Minus1>>] ## CHECK: name "<<BExit>>" ## CHECK: predecessors "<<BExitTry>>" "<<BCatch>>" @@ -861,18 +968,21 @@ ## CHECK-START: int Builder.testCatchLoop(int, int, int) builder (after) ## CHECK: name "B0" -## CHECK: successors "<<BCatch:B\d+>>" +## CHECK: successors "<<BSplit2:B\d+>>" -## CHECK: name "<<BCatch>>" -## CHECK: predecessors "B0" "<<BEnterTry:B\d+>>" "<<BExitTry:B\d+>>" -## CHECK: successors "<<BEnterTry>>" +## CHECK: name "<<BCatch:B\d+>>" +## CHECK: predecessors "<<BEnterTry:B\d+>>" "<<BExitTry:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" ## CHECK: flags "catch_block" ## CHECK: name "<<BReturn:B\d+>>" ## CHECK: predecessors "<<BExitTry>>" ## CHECK: successors "<<BExit:B\d+>>" +## CHECK: Return ## CHECK: name "<<BExit>>" +## CHECK: predecessors "<<BReturn>>" +## CHECK: Exit ## CHECK: name "<<BTry:B\d+>>" ## CHECK: predecessors "<<BEnterTry>>" @@ -880,7 +990,7 @@ ## CHECK: Div ## CHECK: name "<<BEnterTry>>" -## CHECK: predecessors "<<BCatch>>" +## CHECK: predecessors "<<BSplit1>>" ## CHECK: successors "<<BTry>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry @@ -891,6 +1001,16 @@ ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BSplit2>>" "<<BCatch>>" +## CHECK: successors "<<BEnterTry>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "B0" +## CHECK: successors "<<BSplit1>>" +## CHECK: Goto + .method public static testCatchLoop(III)I .registers 4 @@ -918,14 +1038,16 @@ ## CHECK: Div ## CHECK: name "<<BCatch:B\d+>>" -## CHECK: predecessors "<<BExitTry1>>" "<<BEnterTry1>>" "<<BEnterTry2:B\d+>>" "<<BExitTry1>>" "<<BExitTry2:B\d+>>" -## CHECK: successors "<<BEnterTry2>>" +## CHECK: predecessors "<<BEnterTry1>>" "<<BEnterTry2:B\d+>>" "<<BExitTry1>>" "<<BExitTry2:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" ## CHECK: flags "catch_block" ## CHECK: name "<<BReturn:B\d+>>" ## CHECK: predecessors "<<BExitTry2>>" +## CHECK: successors "<<BExit:B\d+>>" -## CHECK: name "{{B\d+}}" +## CHECK: name "<<BExit>>" +## CHECK: predecessors "<<BReturn>>" ## CHECK: Exit ## CHECK: name "<<BTry2:B\d+>>" @@ -940,14 +1062,14 @@ ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" -## CHECK: predecessors "<<BCatch>>" +## CHECK: predecessors "<<BSplit1>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry1>>" ## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BCatch>>" +## CHECK: successors "<<BSplit2:B\d+>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit @@ -957,6 +1079,16 @@ ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BSplit2>>" "<<BCatch>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BExitTry1>>" +## CHECK: successors "<<BSplit1>>" +## CHECK: Goto + .method public static testHandlerEdge1(III)I .registers 4 @@ -977,16 +1109,16 @@ ## CHECK-START: int Builder.testHandlerEdge2(int, int, int) builder (after) ## CHECK: name "B0" -## CHECK: successors "<<BCatch1:B\d+>>" +## CHECK: successors "<<BSplit4:B\d+>>" -## CHECK: name "<<BCatch1>>" -## CHECK: predecessors "B0" "<<BEnterTry2:B\d+>>" "<<BExitTry2:B\d+>>" -## CHECK: successors "<<BEnterTry1:B\d+>>" +## CHECK: name "<<BCatch1:B\d+>>" +## CHECK: predecessors "<<BEnterTry2:B\d+>>" "<<BExitTry2:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" ## CHECK: flags "catch_block" ## CHECK: name "<<BCatch2:B\d+>>" -## CHECK: predecessors "<<BExitTry1:B\d+>>" "<<BEnterTry1>>" "<<BExitTry1>>" -## CHECK: successors "<<BEnterTry2>>" +## CHECK: predecessors "<<BEnterTry1:B\d+>>" "<<BExitTry1:B\d+>>" +## CHECK: successors "<<BSplit2:B\d+>>" ## CHECK: flags "catch_block" ## CHECK: name "<<BReturn:B\d+>>" @@ -995,6 +1127,7 @@ ## CHECK: Return ## CHECK: name "<<BExit>>" +## CHECK: Exit ## CHECK: name "<<BTry1:B\d+>>" ## CHECK: predecessors "<<BEnterTry1>>" @@ -1007,20 +1140,20 @@ ## CHECK: Div ## CHECK: name "<<BEnterTry1>>" -## CHECK: predecessors "<<BCatch1>>" +## CHECK: predecessors "<<BSplit1>>" ## CHECK: successors "<<BTry1>>" ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BEnterTry2>>" -## CHECK: predecessors "<<BCatch2>>" +## CHECK: predecessors "<<BSplit2>>" ## CHECK: successors "<<BTry2>>" ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry1>>" ## CHECK: predecessors "<<BTry1>>" -## CHECK: successors "<<BCatch2>>" +## CHECK: successors "<<BSplit3:B\d+>>" ## CHECK: xhandlers "<<BCatch2>>" ## CHECK: TryBoundary kind:exit @@ -1030,6 +1163,26 @@ ## CHECK: xhandlers "<<BCatch1>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "<<BSplit4>>" "<<BCatch1>>" +## CHECK: successors "<<BEnterTry1>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BCatch2>>" "<<BSplit3>>" +## CHECK: successors "<<BEnterTry2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit3>>" +## CHECK: predecessors "<<BExitTry1>>" +## CHECK: successors "<<BSplit2>>" +## CHECK: Goto + +## CHECK: name "<<BSplit4>>" +## CHECK: predecessors "B0" +## CHECK: successors "<<BSplit1>>" +## CHECK: Goto + .method public static testHandlerEdge2(III)I .registers 4 @@ -1053,10 +1206,10 @@ ## CHECK-START: int Builder.testTryInLoop(int, int) builder (after) ## CHECK: name "B0" -## CHECK: successors "<<BEnterTry:B\d+>>" +## CHECK: successors "<<BSplit1:B\d+>>" ## CHECK: name "<<BTry:B\d+>>" -## CHECK: predecessors "<<BEnterTry>>" +## CHECK: predecessors "<<BEnterTry:B\d+>>" ## CHECK: successors "<<BExitTry:B\d+>>" ## CHECK: Div @@ -1065,22 +1218,28 @@ ## CHECK: successors "<<BEnterTry>>" ## CHECK: flags "catch_block" -## CHECK: name "<<BExit:B\d+>>" -## CHECK-NOT: predecessors "{{B\d+}}" -## CHECK: end_block - ## CHECK: name "<<BEnterTry>>" -## CHECK: predecessors "B0" +## CHECK: predecessors "<<BSplit1>>" ## CHECK: successors "<<BTry>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:entry ## CHECK: name "<<BExitTry>>" ## CHECK: predecessors "<<BTry>>" -## CHECK: successors "<<BEnterTry>>" +## CHECK: successors "<<BSplit2:B\d+>>" ## CHECK: xhandlers "<<BCatch>>" ## CHECK: TryBoundary kind:exit +## CHECK: name "<<BSplit1>>" +## CHECK: predecessors "B0" +## CHECK: successors "<<BEnterTry>>" +## CHECK: Goto + +## CHECK: name "<<BSplit2>>" +## CHECK: predecessors "<<BExitTry>>" +## CHECK: successors "<<BEnterTry>>" +## CHECK: Goto + .method public static testTryInLoop(II)I .registers 3 @@ -1098,9 +1257,10 @@ # INVOKE it follows, even if there is a try boundary between them. ## CHECK-START: int Builder.testMoveResult_Invoke(int, int, int) builder (after) - -## CHECK: <<Res:i\d+>> InvokeStaticOrDirect -## CHECK-NEXT: StoreLocal [v0,<<Res>>] +## CHECK-DAG: <<M1:i\d+>> IntConstant -1 +## CHECK-DAG: <<Res:i\d+>> InvokeStaticOrDirect +## CHECK-DAG: <<Phi:i\d+>> Phi [<<Res>>,<<M1>>] +## CHECK-DAG: Return [<<Phi>>] .method public static testMoveResult_Invoke(III)I .registers 3 @@ -1124,15 +1284,16 @@ # FILLED_NEW_ARRAY it follows, even if there is a try boundary between them. ## CHECK-START: int[] Builder.testMoveResult_FilledNewArray(int, int, int) builder (after) - -## CHECK: <<Res:l\d+>> NewArray -## CHECK-NEXT: <<Local1:i\d+>> LoadLocal [v0] -## CHECK-NEXT: ArraySet [<<Res>>,{{i\d+}},<<Local1>>] -## CHECK-NEXT: <<Local2:i\d+>> LoadLocal [v1] -## CHECK-NEXT: ArraySet [<<Res>>,{{i\d+}},<<Local2>>] -## CHECK-NEXT: <<Local3:i\d+>> LoadLocal [v2] -## CHECK-NEXT: ArraySet [<<Res>>,{{i\d+}},<<Local3>>] -## CHECK-NEXT: StoreLocal [v0,<<Res>>] +## CHECK-DAG: <<Arg1:i\d+>> ParameterValue +## CHECK-DAG: <<Arg2:i\d+>> ParameterValue +## CHECK-DAG: <<Arg3:i\d+>> ParameterValue +## CHECK-DAG: <<Null:l\d+>> NullConstant +## CHECK-DAG: <<Res:l\d+>> NewArray +## CHECK-DAG: ArraySet [<<Res>>,{{i\d+}},<<Arg1>>] +## CHECK-DAG: ArraySet [<<Res>>,{{i\d+}},<<Arg2>>] +## CHECK-DAG: ArraySet [<<Res>>,{{i\d+}},<<Arg3>>] +## CHECK-DAG: <<Phi:l\d+>> Phi [<<Res>>,<<Null>>] +## CHECK-DAG: Return [<<Phi>>] .method public static testMoveResult_FilledNewArray(III)[I .registers 3 diff --git a/test/510-checker-try-catch/smali/SsaBuilder.smali b/test/510-checker-try-catch/smali/SsaBuilder.smali index a6a5bfebee..1fd5fb2dd6 100644 --- a/test/510-checker-try-catch/smali/SsaBuilder.smali +++ b/test/510-checker-try-catch/smali/SsaBuilder.smali @@ -19,7 +19,7 @@ # Tests that catch blocks with both normal and exceptional predecessors are # split in two. -## CHECK-START: int SsaBuilder.testSimplifyCatchBlock(int, int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testSimplifyCatchBlock(int, int, int) builder (after) ## CHECK: name "B1" ## CHECK-NEXT: from_bci @@ -62,7 +62,7 @@ # Should be rejected because :catch_all is a loop header. -## CHECK-START: int SsaBuilder.testCatchLoopHeader(int, int, int) ssa_builder (after, bad_state) +## CHECK-START: int SsaBuilder.testCatchLoopHeader(int, int, int) builder (after, bad_state) .method public static testCatchLoopHeader(III)I .registers 4 @@ -84,7 +84,7 @@ # Tests creation of catch Phis. -## CHECK-START: int SsaBuilder.testPhiCreation(int, int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testPhiCreation(int, int, int) builder (after) ## CHECK-DAG: <<P0:i\d+>> ParameterValue ## CHECK-DAG: <<P1:i\d+>> ParameterValue ## CHECK-DAG: <<P2:i\d+>> ParameterValue @@ -127,7 +127,7 @@ # Tests that phi elimination does not remove catch phis where the value does # not dominate the phi. -## CHECK-START: int SsaBuilder.testPhiElimination_Domination(int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testPhiElimination_Domination(int, int) builder (after) ## CHECK-DAG: <<P0:i\d+>> ParameterValue ## CHECK-DAG: <<P1:i\d+>> ParameterValue ## CHECK-DAG: <<Cst5:i\d+>> IntConstant 5 @@ -168,7 +168,7 @@ # Tests that phi elimination loops until no more phis can be removed. -## CHECK-START: int SsaBuilder.testPhiElimination_Dependencies(int, int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testPhiElimination_Dependencies(int, int, int) builder (after) ## CHECK-NOT: Phi .method public static testPhiElimination_Dependencies(III)I @@ -200,10 +200,7 @@ # Tests that dead catch blocks are removed. -## CHECK-START: int SsaBuilder.testDeadCatchBlock(int, int, int) ssa_builder (before) -## CHECK: Mul - -## CHECK-START: int SsaBuilder.testDeadCatchBlock(int, int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testDeadCatchBlock(int, int, int) builder (after) ## CHECK-DAG: <<P0:i\d+>> ParameterValue ## CHECK-DAG: <<P1:i\d+>> ParameterValue ## CHECK-DAG: <<P2:i\d+>> ParameterValue @@ -211,7 +208,7 @@ ## CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<P2>>] ## CHECK-DAG: Return [<<Add2>>] -## CHECK-START: int SsaBuilder.testDeadCatchBlock(int, int, int) ssa_builder (after) +## CHECK-START: int SsaBuilder.testDeadCatchBlock(int, int, int) builder (after) ## CHECK-NOT: flags "catch_block" ## CHECK-NOT: Mul diff --git a/test/517-checker-builder-fallthrough/smali/TestCase.smali b/test/517-checker-builder-fallthrough/smali/TestCase.smali index bc9502b31a..946f169948 100644 --- a/test/517-checker-builder-fallthrough/smali/TestCase.smali +++ b/test/517-checker-builder-fallthrough/smali/TestCase.smali @@ -25,8 +25,8 @@ ## CHECK: name "B1" ## CHECK: successors "B5" "B2" -## CHECK: StoreLocal [v0,<<Const0>>] -## CHECK: If +## CHECK: <<Cond:z\d+>> Equal [<<Const0>>,<<Const0>>] +## CHECK: If [<<Cond>>] ## CHECK: name "B2" ## CHECK: successors "B4" diff --git a/test/523-checker-can-throw-regression/smali/Test.smali b/test/523-checker-can-throw-regression/smali/Test.smali index 87192ea123..4b737a9057 100644 --- a/test/523-checker-can-throw-regression/smali/Test.smali +++ b/test/523-checker-can-throw-regression/smali/Test.smali @@ -46,8 +46,10 @@ div-int/2addr p0, p1 :else div-int/2addr p0, p2 - return p0 :try_end_2 - .catchall {:try_start_2 .. :try_end_2} :catchall + .catchall {:try_start_2 .. :try_end_2} :catchall2 + + :catchall2 + return p0 .end method diff --git a/test/529-checker-unresolved/build b/test/529-checker-unresolved/build index 8c3c4f8952..d85035b669 100644 --- a/test/529-checker-unresolved/build +++ b/test/529-checker-unresolved/build @@ -29,14 +29,12 @@ mv classes/UnresolvedInterface.class classes-ex mv classes/UnresolvedSuperClass.class classes-ex if [ ${USE_JACK} = "true" ]; then - # Create .jack files from classes generated with javac. - ${JILL} classes --output classes.jack - ${JILL} classes-ex --output classes-ex.jack + jar cf classes.jill.jar -C classes . + jar cf classes-ex.jill.jar -C classes-ex . - # Create DEX files from .jack files. - ${JACK} --import classes.jack --output-dex . + ${JACK} --import classes.jill.jar --output-dex . zip $TEST_NAME.jar classes.dex - ${JACK} --import classes-ex.jack --output-dex . + ${JACK} --import classes-ex.jill.jar --output-dex . zip ${TEST_NAME}-ex.jar classes.dex else if [ ${NEED_DEX} = "true" ]; then diff --git a/test/537-checker-debuggable/smali/TestCase.smali b/test/537-checker-debuggable/smali/TestCase.smali index 8e6c7ef727..5714d3aeae 100644 --- a/test/537-checker-debuggable/smali/TestCase.smali +++ b/test/537-checker-debuggable/smali/TestCase.smali @@ -20,10 +20,10 @@ # be eliminated in normal mode but kept live in debuggable mode. Test that # Checker runs the correct test for each compilation mode. -## CHECK-START: int TestCase.deadPhi(int, int, int) ssa_builder (after) +## CHECK-START: int TestCase.deadPhi(int, int, int) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: int TestCase.deadPhi(int, int, int) ssa_builder (after) +## CHECK-START-DEBUGGABLE: int TestCase.deadPhi(int, int, int) builder (after) ## CHECK: Phi .method public static deadPhi(III)I diff --git a/test/540-checker-rtp-bug/src/Main.java b/test/540-checker-rtp-bug/src/Main.java index 9a9f0b6048..17b11db295 100644 --- a/test/540-checker-rtp-bug/src/Main.java +++ b/test/540-checker-rtp-bug/src/Main.java @@ -21,7 +21,7 @@ final class Final { } public class Main { - /// CHECK-START: Final Main.testKeepCheckCast(java.lang.Object, boolean) ssa_builder (after) + /// CHECK-START: Final Main.testKeepCheckCast(java.lang.Object, boolean) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: <<Class:l\d+>> LoadClass /// CHECK: CheckCast [<<Phi>>,<<Class>>] @@ -43,7 +43,7 @@ public class Main { return (Final) x; } - /// CHECK-START: void Main.testKeepInstanceOf(java.lang.Object, boolean) ssa_builder (after) + /// CHECK-START: void Main.testKeepInstanceOf(java.lang.Object, boolean) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: <<Class:l\d+>> LoadClass /// CHECK: InstanceOf [<<Phi>>,<<Class>>] @@ -65,7 +65,7 @@ public class Main { } } - /// CHECK-START: java.lang.String Main.testNoInline(java.lang.Object, boolean) ssa_builder (after) + /// CHECK-START: java.lang.String Main.testNoInline(java.lang.Object, boolean) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: <<NC:l\d+>> NullCheck [<<Phi>>] /// CHECK: <<Ret:l\d+>> InvokeVirtual [<<NC>>] method_name:java.lang.Object.toString diff --git a/test/549-checker-types-merge/src/Main.java b/test/549-checker-types-merge/src/Main.java index 917073b1c9..51af3cff10 100644 --- a/test/549-checker-types-merge/src/Main.java +++ b/test/549-checker-types-merge/src/Main.java @@ -38,14 +38,14 @@ class ClassImplementsInterfaceA extends ClassSuper implements InterfaceA {} public class Main { - /// CHECK-START: java.lang.Object Main.testMergeNullContant(boolean) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeNullContant(boolean) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:Main /// CHECK: Return [<<Phi>>] private Object testMergeNullContant(boolean cond) { return cond ? null : new Main(); } - /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassExtendsA, ClassExtendsB) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassExtendsA, ClassExtendsB) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:ClassSuper /// CHECK: Return [<<Phi>>] private Object testMergeClasses(boolean cond, ClassExtendsA a, ClassExtendsB b) { @@ -53,7 +53,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassExtendsA, ClassSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassExtendsA, ClassSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:ClassSuper /// CHECK: Return [<<Phi>>] private Object testMergeClasses(boolean cond, ClassExtendsA a, ClassSuper b) { @@ -61,7 +61,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassSuper, ClassSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassSuper, ClassSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:ClassSuper /// CHECK: Return [<<Phi>>] private Object testMergeClasses(boolean cond, ClassSuper a, ClassSuper b) { @@ -69,7 +69,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassOtherSuper, ClassSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClasses(boolean, ClassOtherSuper, ClassSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: Return [<<Phi>>] private Object testMergeClasses(boolean cond, ClassOtherSuper a, ClassSuper b) { @@ -77,7 +77,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeClassWithInterface(boolean, ClassImplementsInterfaceA, InterfaceSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClassWithInterface(boolean, ClassImplementsInterfaceA, InterfaceSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:InterfaceSuper /// CHECK: Return [<<Phi>>] private Object testMergeClassWithInterface(boolean cond, ClassImplementsInterfaceA a, InterfaceSuper b) { @@ -85,7 +85,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeClassWithInterface(boolean, ClassSuper, InterfaceSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeClassWithInterface(boolean, ClassSuper, InterfaceSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: Return [<<Phi>>] private Object testMergeClassWithInterface(boolean cond, ClassSuper a, InterfaceSuper b) { @@ -93,7 +93,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceExtendsA, InterfaceSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceExtendsA, InterfaceSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:InterfaceSuper /// CHECK: Return [<<Phi>>] private Object testMergeInterfaces(boolean cond, InterfaceExtendsA a, InterfaceSuper b) { @@ -101,7 +101,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceSuper, InterfaceSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceSuper, InterfaceSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:InterfaceSuper /// CHECK: Return [<<Phi>>] private Object testMergeInterfaces(boolean cond, InterfaceSuper a, InterfaceSuper b) { @@ -109,7 +109,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceExtendsA, InterfaceExtendsB) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceExtendsA, InterfaceExtendsB) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: Return [<<Phi>>] private Object testMergeInterfaces(boolean cond, InterfaceExtendsA a, InterfaceExtendsB b) { @@ -117,7 +117,7 @@ public class Main { return cond ? a : b; } - /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceSuper, InterfaceOtherSuper) ssa_builder (after) + /// CHECK-START: java.lang.Object Main.testMergeInterfaces(boolean, InterfaceSuper, InterfaceOtherSuper) builder (after) /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object /// CHECK: Return [<<Phi>>] private Object testMergeInterfaces(boolean cond, InterfaceSuper a, InterfaceOtherSuper b) { diff --git a/test/550-checker-regression-wide-store/smali/TestCase.smali b/test/550-checker-regression-wide-store/smali/TestCase.smali index 7974d56a8f..9133c82365 100644 --- a/test/550-checker-regression-wide-store/smali/TestCase.smali +++ b/test/550-checker-regression-wide-store/smali/TestCase.smali @@ -25,7 +25,7 @@ # Test storing into the high vreg of a wide pair. This scenario has runtime # behaviour implications so we run it from Main.main. -## CHECK-START: int TestCase.invalidateLow(long) ssa_builder (after) +## CHECK-START: int TestCase.invalidateLow(long) builder (after) ## CHECK-DAG: <<Cst0:i\d+>> IntConstant 0 ## CHECK-DAG: <<Arg:j\d+>> ParameterValue ## CHECK-DAG: <<Cast:i\d+>> TypeConversion [<<Arg>>] @@ -53,7 +53,7 @@ # Test that storing a wide invalidates the value in the high vreg. This # cannot be detected from runtime so we only test the environment with Checker. -## CHECK-START: void TestCase.invalidateHigh1(long) ssa_builder (after) +## CHECK-START: void TestCase.invalidateHigh1(long) builder (after) ## CHECK-DAG: <<Arg:j\d+>> ParameterValue ## CHECK-DAG: InvokeStaticOrDirect method_name:java.lang.System.nanoTime env:[[<<Arg>>,_,<<Arg>>,_]] @@ -67,7 +67,7 @@ .end method -## CHECK-START: void TestCase.invalidateHigh2(long) ssa_builder (after) +## CHECK-START: void TestCase.invalidateHigh2(long) builder (after) ## CHECK-DAG: <<Arg:j\d+>> ParameterValue ## CHECK-DAG: InvokeStaticOrDirect method_name:java.lang.System.nanoTime env:[[<<Arg>>,_,_,<<Arg>>,_]] diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java index decdd1f324..8d73d69db9 100644 --- a/test/551-checker-shifter-operand/src/Main.java +++ b/test/551-checker-shifter-operand/src/Main.java @@ -241,13 +241,14 @@ public class Main { /// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after) /// CHECK: Arm64DataProcWithShifterOp - /// CHECK: Arm64DataProcWithShifterOp + /// CHECK-NOT: Arm64DataProcWithShifterOp /// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after) /// CHECK-NOT: TypeConversion public static void $opt$validateExtendByteInt1(int a, byte b) { assertIntEquals(a + $noinline$byteToChar (b), a + (char)b); + // Conversions byte->short and short->int are implicit; nothing to merge. assertIntEquals(a + $noinline$byteToShort(b), a + (short)b); } @@ -266,17 +267,24 @@ public class Main { /// CHECK: Arm64DataProcWithShifterOp /// CHECK: Arm64DataProcWithShifterOp /// CHECK: Arm64DataProcWithShifterOp + /// CHECK: Arm64DataProcWithShifterOp + /// CHECK: Arm64DataProcWithShifterOp + /// CHECK-NOT: Arm64DataProcWithShifterOp /// CHECK-START-ARM64: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm64 (after) /// CHECK: TypeConversion - /// CHECK: TypeConversion /// CHECK-NOT: TypeConversion public static void $opt$validateExtendByteLong(long a, byte b) { - // The first two tests have a type conversion. + // In each of the following tests, there will be a merge on the LHS. + + // The first test has an explicit byte->char conversion on RHS, + // followed by a conversion that is merged with the Add. assertLongEquals(a + $noinline$byteToChar (b), a + (char)b); + // Since conversions byte->short and byte->int are implicit, the RHS + // for the two tests below is the same and one is eliminated by GVN. + // The other is then merged to a shifter operand instruction. assertLongEquals(a + $noinline$byteToShort(b), a + (short)b); - // This test does not because the conversion to `int` is optimized away. assertLongEquals(a + $noinline$byteToInt (b), a + (int)b); } diff --git a/test/552-checker-primitive-typeprop/smali/ArrayGet.smali b/test/552-checker-primitive-typeprop/smali/ArrayGet.smali index 042fa0c80c..de3229064c 100644 --- a/test/552-checker-primitive-typeprop/smali/ArrayGet.smali +++ b/test/552-checker-primitive-typeprop/smali/ArrayGet.smali @@ -19,10 +19,10 @@ # Test phi with fixed-type ArrayGet as an input and a matching second input. # The phi should be typed accordingly. -## CHECK-START: void ArrayGet.matchingFixedType(float[], float) ssa_builder (after) +## CHECK-START: void ArrayGet.matchingFixedType(float[], float) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFixedType(float[], float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFixedType(float[], float) builder (after) ## CHECK-DAG: <<Arg1:f\d+>> ParameterValue ## CHECK-DAG: <<Aget:f\d+>> ArrayGet ## CHECK-DAG: {{f\d+}} Phi [<<Aget>>,<<Arg1>>] reg:0 @@ -49,10 +49,10 @@ # Test phi with fixed-type ArrayGet as an input and a conflicting second input. # The phi should be eliminated due to the conflict. -## CHECK-START: void ArrayGet.conflictingFixedType(float[], int) ssa_builder (after) +## CHECK-START: void ArrayGet.conflictingFixedType(float[], int) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType(float[], int) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType(float[], int) builder (after) ## CHECK-NOT: Phi .method public static conflictingFixedType([FI)V .registers 8 @@ -76,13 +76,13 @@ # Same test as the one above, only this time tests that type of ArrayGet is not # changed. -## CHECK-START: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after) +## CHECK-START: void ArrayGet.conflictingFixedType2(int[], float) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) builder (after) ## CHECK: {{i\d+}} ArrayGet .method public static conflictingFixedType2([IF)V .registers 8 @@ -107,10 +107,10 @@ # Test phi with free-type ArrayGet as an input and a matching second input. # The phi should be typed accordingly. -## CHECK-START: void ArrayGet.matchingFreeType(float[], float) ssa_builder (after) +## CHECK-START: void ArrayGet.matchingFreeType(float[], float) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFreeType(float[], float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFreeType(float[], float) builder (after) ## CHECK-DAG: <<Arg1:f\d+>> ParameterValue ## CHECK-DAG: <<Aget:f\d+>> ArrayGet ## CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<Aget>>] @@ -139,10 +139,10 @@ # The phi will be kept and typed according to the second input despite the # conflict. -## CHECK-START: void ArrayGet.conflictingFreeType(int[], float) ssa_builder (after) +## CHECK-START: void ArrayGet.conflictingFreeType(int[], float) builder (after) ## CHECK-NOT: Phi -## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFreeType(int[], float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFreeType(int[], float) builder (after) ## CHECK-NOT: Phi .method public static conflictingFreeType([IF)V @@ -169,7 +169,7 @@ # case uses ArrayGet indirectly through two phis. It also creates an unused # conflicting phi which should not be preserved. -## CHECK-START: void ArrayGet.conflictingPhiUses(int[], float, boolean, boolean, boolean) ssa_builder (after) +## CHECK-START: void ArrayGet.conflictingPhiUses(int[], float, boolean, boolean, boolean) builder (after) ## CHECK: InvokeStaticOrDirect env:[[{{i\d+}},{{i\d+}},_,{{i\d+}},{{.*}} .method public static conflictingPhiUses([IFZZZ)V @@ -209,10 +209,10 @@ # another. The situation needs to be resolved so that only one instruction # remains. -## CHECK-START: void ArrayGet.typedVsUntypedPhiUse(float[], float, boolean, boolean) ssa_builder (after) +## CHECK-START: void ArrayGet.typedVsUntypedPhiUse(float[], float, boolean, boolean) builder (after) ## CHECK: {{f\d+}} ArrayGet -## CHECK-START: void ArrayGet.typedVsUntypedPhiUse(float[], float, boolean, boolean) ssa_builder (after) +## CHECK-START: void ArrayGet.typedVsUntypedPhiUse(float[], float, boolean, boolean) builder (after) ## CHECK-NOT: {{i\d+}} ArrayGet .method public static typedVsUntypedPhiUse([FFZZ)V diff --git a/test/552-checker-primitive-typeprop/smali/ArraySet.smali b/test/552-checker-primitive-typeprop/smali/ArraySet.smali index 57d8606abb..087460aef2 100644 --- a/test/552-checker-primitive-typeprop/smali/ArraySet.smali +++ b/test/552-checker-primitive-typeprop/smali/ArraySet.smali @@ -19,7 +19,7 @@ # Note that the input is a Phi to make sure primitive type propagation is re-run # on the replaced inputs. -## CHECK-START: void ArraySet.ambiguousSet(int[], float[], boolean) ssa_builder (after) +## CHECK-START: void ArraySet.ambiguousSet(int[], float[], boolean) builder (after) ## CHECK-DAG: <<IntArray:l\d+>> ParameterValue klass:int[] ## CHECK-DAG: <<IntA:i\d+>> IntConstant 0 ## CHECK-DAG: <<IntB:i\d+>> IntConstant 1073741824 diff --git a/test/552-checker-primitive-typeprop/smali/SsaBuilder.smali b/test/552-checker-primitive-typeprop/smali/SsaBuilder.smali index 395feaaf61..0d067ed1ca 100644 --- a/test/552-checker-primitive-typeprop/smali/SsaBuilder.smali +++ b/test/552-checker-primitive-typeprop/smali/SsaBuilder.smali @@ -22,7 +22,7 @@ # otherwise running the code with an array short enough to throw will crash at # runtime because v0 is undefined. -## CHECK-START: int SsaBuilder.environmentPhi(boolean, int[]) ssa_builder (after) +## CHECK-START: int SsaBuilder.environmentPhi(boolean, int[]) builder (after) ## CHECK-DAG: <<Cst0:f\d+>> FloatConstant 0 ## CHECK-DAG: <<Cst2:f\d+>> FloatConstant 2 ## CHECK-DAG: <<Phi:f\d+>> Phi [<<Cst0>>,<<Cst2>>] diff --git a/test/552-checker-primitive-typeprop/smali/TypePropagation.smali b/test/552-checker-primitive-typeprop/smali/TypePropagation.smali index 58682a1923..d34e43e160 100644 --- a/test/552-checker-primitive-typeprop/smali/TypePropagation.smali +++ b/test/552-checker-primitive-typeprop/smali/TypePropagation.smali @@ -15,7 +15,7 @@ .class public LTypePropagation; .super Ljava/lang/Object; -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDeadPhi(boolean, boolean, int, float, float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDeadPhi(boolean, boolean, int, float, float) builder (after) ## CHECK-NOT: Phi .method public static mergeDeadPhi(ZZIFF)V .registers 8 @@ -34,7 +34,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeSameType(boolean, int, int) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeSameType(boolean, int, int) builder (after) ## CHECK: {{i\d+}} Phi ## CHECK-NOT: Phi .method public static mergeSameType(ZII)V @@ -47,7 +47,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeVoidInput(boolean, boolean, int, int) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeVoidInput(boolean, boolean, int, int) builder (after) ## CHECK: {{i\d+}} Phi ## CHECK: {{i\d+}} Phi ## CHECK-NOT: Phi @@ -64,7 +64,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDifferentSize(boolean, int, long) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDifferentSize(boolean, int, long) builder (after) ## CHECK-NOT: Phi .method public static mergeDifferentSize(ZIJ)V .registers 8 @@ -76,7 +76,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeRefFloat(boolean, float, java.lang.Object) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeRefFloat(boolean, float, java.lang.Object) builder (after) ## CHECK-NOT: Phi .method public static mergeRefFloat(ZFLjava/lang/Object;)V .registers 8 @@ -88,7 +88,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Success(boolean, float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Success(boolean, float) builder (after) ## CHECK: {{f\d+}} Phi ## CHECK-NOT: Phi .method public static mergeIntFloat_Success(ZF)V @@ -101,7 +101,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Fail(boolean, int, float) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Fail(boolean, int, float) builder (after) ## CHECK-NOT: Phi .method public static mergeIntFloat_Fail(ZIF)V .registers 8 @@ -113,7 +113,7 @@ return-void .end method -## CHECK-START-DEBUGGABLE: void TypePropagation.updateAllUsersOnConflict(boolean, boolean, int, float, int) ssa_builder (after) +## CHECK-START-DEBUGGABLE: void TypePropagation.updateAllUsersOnConflict(boolean, boolean, int, float, int) builder (after) ## CHECK-NOT: Phi .method public static updateAllUsersOnConflict(ZZIFI)V .registers 8 diff --git a/test/554-checker-rtp-checkcast/src/Main.java b/test/554-checker-rtp-checkcast/src/Main.java index 607f71afb5..5bf766ff4e 100644 --- a/test/554-checker-rtp-checkcast/src/Main.java +++ b/test/554-checker-rtp-checkcast/src/Main.java @@ -19,7 +19,7 @@ public class Main { public static Object returnIntArray() { return new int[10]; } - /// CHECK-START: void Main.boundTypeForMergingPhi() ssa_builder (after) + /// CHECK-START: void Main.boundTypeForMergingPhi() builder (after) /// CHECK-DAG: ArraySet [<<NC:l\d+>>,{{i\d+}},{{i\d+}}] /// CHECK-DAG: <<NC>> NullCheck [<<Phi:l\d+>>] /// CHECK-DAG: <<Phi>> Phi klass:int[] @@ -32,7 +32,7 @@ public class Main { array[0] = 14; } - /// CHECK-START: void Main.boundTypeForLoopPhi() ssa_builder (after) + /// CHECK-START: void Main.boundTypeForLoopPhi() builder (after) /// CHECK-DAG: ArraySet [<<NC:l\d+>>,{{i\d+}},{{i\d+}}] /// CHECK-DAG: <<NC>> NullCheck [<<Phi:l\d+>>] /// CHECK-DAG: <<Phi>> Phi klass:int[] @@ -50,7 +50,7 @@ public class Main { array[0] = 14; } - /// CHECK-START: void Main.boundTypeForCatchPhi() ssa_builder (after) + /// CHECK-START: void Main.boundTypeForCatchPhi() builder (after) /// CHECK-DAG: ArraySet [<<NC:l\d+>>,{{i\d+}},{{i\d+}}] /// CHECK-DAG: <<NC>> NullCheck [<<Phi:l\d+>>] /// CHECK-DAG: <<Phi>> Phi is_catch_phi:true klass:int[] diff --git a/test/557-checker-ref-equivalent/smali/TestCase.smali b/test/557-checker-ref-equivalent/smali/TestCase.smali index 24729572c4..1347554aad 100644 --- a/test/557-checker-ref-equivalent/smali/TestCase.smali +++ b/test/557-checker-ref-equivalent/smali/TestCase.smali @@ -16,7 +16,7 @@ .super Ljava/lang/Object; -## CHECK-START: void TestCase.testIntRefEquivalent() ssa_builder (after) +## CHECK-START: void TestCase.testIntRefEquivalent() builder (after) ## CHECK-NOT: Phi .method public static testIntRefEquivalent()V .registers 4 diff --git a/test/557-checker-ref-equivalent/src/Main.java b/test/557-checker-ref-equivalent/src/Main.java index a970af5cdf..9323757d92 100644 --- a/test/557-checker-ref-equivalent/src/Main.java +++ b/test/557-checker-ref-equivalent/src/Main.java @@ -16,7 +16,7 @@ public class Main { - /// CHECK-START: void Main.testRedundantPhiCycle(boolean) ssa_builder (after) + /// CHECK-START: void Main.testRedundantPhiCycle(boolean) builder (after) /// CHECK-NOT: Phi private void testRedundantPhiCycle(boolean cond) { Object o = null; @@ -28,7 +28,7 @@ public class Main { } } - /// CHECK-START: void Main.testLoopPhisWithNullAndCrossUses(boolean) ssa_builder (after) + /// CHECK-START: void Main.testLoopPhisWithNullAndCrossUses(boolean) builder (after) /// CHECK-NOT: Phi private void testLoopPhisWithNullAndCrossUses(boolean cond) { Main a = null; diff --git a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali index 971ad84241..7ce60a304b 100644 --- a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali +++ b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali @@ -323,7 +323,7 @@ # - / \- # irreducible_loop_back_edge loop_within_back_edge # -## CHECK-START: void IrreducibleLoop.analyze1(int) ssa_builder (after) +## CHECK-START: void IrreducibleLoop.analyze1(int) builder (after) ## CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none irreducible:true ## CHECK-DAG: Goto outer_loop:<<OuterLoop>> irreducible:false .method public static analyze1(I)V @@ -371,7 +371,7 @@ # exit \- / # irreducible_loop_body # -## CHECK-START: void IrreducibleLoop.analyze2(int) ssa_builder (after) +## CHECK-START: void IrreducibleLoop.analyze2(int) builder (after) ## CHECK-DAG: Goto outer_loop:none irreducible:false ## CHECK-DAG: Goto outer_loop:none irreducible:true .method public static analyze2(I)V @@ -418,7 +418,7 @@ # | # exit # -## CHECK-START: void IrreducibleLoop.analyze3(int) ssa_builder (after) +## CHECK-START: void IrreducibleLoop.analyze3(int) builder (after) ## CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none irreducible:true ## CHECK-DAG: Goto outer_loop:<<OuterLoop>> irreducible:true .method public static analyze3(I)V @@ -467,7 +467,7 @@ # | # exit # -## CHECK-START: void IrreducibleLoop.analyze4(int) ssa_builder (after) +## CHECK-START: void IrreducibleLoop.analyze4(int) builder (after) ## CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none irreducible:true ## CHECK-DAG: Goto outer_loop:<<OuterLoop>> irreducible:true .method public static analyze4(I)V @@ -519,7 +519,7 @@ # | # exit # -## CHECK-START: void IrreducibleLoop.analyze5(int) ssa_builder (after) +## CHECK-START: void IrreducibleLoop.analyze5(int) builder (after) ## CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none irreducible:true ## CHECK-DAG: Goto outer_loop:<<OuterLoop>> irreducible:true .method public static analyze5(I)V diff --git a/test/559-checker-rtp-ifnotnull/src/Main.java b/test/559-checker-rtp-ifnotnull/src/Main.java index 8f401292da..2dc5666e18 100644 --- a/test/559-checker-rtp-ifnotnull/src/Main.java +++ b/test/559-checker-rtp-ifnotnull/src/Main.java @@ -17,7 +17,7 @@ public class Main { - /// CHECK-START: void Main.boundTypeForIfNotNull() ssa_builder (after) + /// CHECK-START: void Main.boundTypeForIfNotNull() builder (after) /// CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod /// CHECK-DAG: <<Null:l\d+>> NullConstant /// CHECK-DAG: <<Cst5:i\d+>> IntConstant 5 diff --git a/test/570-checker-osr/osr.cc b/test/570-checker-osr/osr.cc index c1774f3d02..4c58b39319 100644 --- a/test/570-checker-osr/osr.cc +++ b/test/570-checker-osr/osr.cc @@ -17,6 +17,7 @@ #include "art_method.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" +#include "jit/profiling_info.h" #include "oat_quick_method_header.h" #include "scoped_thread_state_change.h" #include "stack_map.h" @@ -28,7 +29,8 @@ class OsrVisitor : public StackVisitor { explicit OsrVisitor(Thread* thread) SHARED_REQUIRES(Locks::mutator_lock_) : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), - in_osr_method_(false) {} + in_osr_method_(false), + in_interpreter_(false) {} bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { ArtMethod* m = GetMethod(); @@ -44,6 +46,8 @@ class OsrVisitor : public StackVisitor { Runtime::Current()->GetJit()->GetCodeCache()->LookupOsrMethodHeader(m); if (header != nullptr && header == GetCurrentOatQuickMethodHeader()) { in_osr_method_ = true; + } else if (IsCurrentFrameInInterpreter()) { + in_interpreter_ = true; } return false; } @@ -51,6 +55,7 @@ class OsrVisitor : public StackVisitor { } bool in_osr_method_; + bool in_interpreter_; }; extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInOsrCode(JNIEnv*, jclass) { @@ -65,4 +70,75 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInOsrCode(JNIEnv*, jclass) return visitor.in_osr_method_; } +extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInInterpreter(JNIEnv*, jclass) { + if (!Runtime::Current()->UseJit()) { + // The return value is irrelevant if we're not using JIT. + return false; + } + ScopedObjectAccess soa(Thread::Current()); + OsrVisitor visitor(soa.Self()); + visitor.WalkStack(); + return visitor.in_interpreter_; +} + +class ProfilingInfoVisitor : public StackVisitor { + public: + explicit ProfilingInfoVisitor(Thread* thread) + SHARED_REQUIRES(Locks::mutator_lock_) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} + + bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { + ArtMethod* m = GetMethod(); + std::string m_name(m->GetName()); + + if (m_name.compare("$noinline$inlineCache") == 0) { + ProfilingInfo::Create(Thread::Current(), m, /* retry_allocation */ true); + return false; + } + return true; + } +}; + +extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasProfilingInfo(JNIEnv*, jclass) { + if (!Runtime::Current()->UseJit()) { + return; + } + ScopedObjectAccess soa(Thread::Current()); + ProfilingInfoVisitor visitor(soa.Self()); + visitor.WalkStack(); +} + +class OsrCheckVisitor : public StackVisitor { + public: + explicit OsrCheckVisitor(Thread* thread) + SHARED_REQUIRES(Locks::mutator_lock_) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} + + bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { + ArtMethod* m = GetMethod(); + std::string m_name(m->GetName()); + + jit::Jit* jit = Runtime::Current()->GetJit(); + if (m_name.compare("$noinline$inlineCache") == 0 && jit != nullptr) { + while (jit->GetCodeCache()->LookupOsrMethodHeader(m) == nullptr) { + // Sleep to yield to the compiler thread. + sleep(0); + // Will either ensure it's compiled or do the compilation itself. + jit->CompileMethod(m, Thread::Current(), /* osr */ true); + } + return false; + } + return true; + } +}; + +extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasOsrCode(JNIEnv*, jclass) { + if (!Runtime::Current()->UseJit()) { + return; + } + ScopedObjectAccess soa(Thread::Current()); + OsrCheckVisitor visitor(soa.Self()); + visitor.WalkStack(); +} + } // namespace art diff --git a/test/570-checker-osr/src/Main.java b/test/570-checker-osr/src/Main.java index bc6612f337..828908a582 100644 --- a/test/570-checker-osr/src/Main.java +++ b/test/570-checker-osr/src/Main.java @@ -36,8 +36,8 @@ public class Main { } catch (Exception e) {} DeoptimizationController.stopDeoptimization(); - $noinline$inlineCache(new Main(), 0); - if ($noinline$inlineCache(new SubMain(), 1) != SubMain.class) { + $noinline$inlineCache(new Main(), /* isSecondInvocation */ false); + if ($noinline$inlineCache(new SubMain(), /* isSecondInvocation */ true) != SubMain.class) { throw new Error("Unexpected return value"); } } @@ -91,29 +91,29 @@ public class Main { DeoptimizationController.startDeoptimization(); } - public static Class $noinline$inlineCache(Main m, int count) { - for (int i = 0; i < 500; ++i) { - // Warm me up. + public static Class $noinline$inlineCache(Main m, boolean isSecondInvocation) { + // If we are running in non-JIT mode, or were unlucky enough to get this method + // already JITted, just return the expected value. + if (!ensureInInterpreter()) { + return SubMain.class; } - if (count == 1) { - // Lots of back edges to trigger OSR compilation. - for (int i = 0; i < 1000; ++i) { - } - // Best effort to wait for OSR compilation. - try { - Thread.sleep(1); - } catch (Exception e) {} + + ensureHasProfilingInfo(); + + // Ensure that we have OSR code to jump to. + if (isSecondInvocation) { + ensureHasOsrCode(); } // This call will be optimized in the OSR compiled code // to check and deoptimize if m is not of type 'Main'. Main other = m.inlineCache(); - if (count == 1) { - // Jump to OSR compiled code. The second run - // of this method will have 'm' as a SubMain, and the compiled - // code we are jumping to will have wrongly optimize other as being a - // 'Main'. + // Jump to OSR compiled code. The second run + // of this method will have 'm' as a SubMain, and the compiled + // code we are jumping to will have wrongly optimize other as being a + // 'Main'. + if (isSecondInvocation) { while (!ensureInOsrCode()) {} } @@ -131,7 +131,10 @@ public class Main { public static int[] array = new int[4]; + public static native boolean ensureInInterpreter(); public static native boolean ensureInOsrCode(); + public static native void ensureHasProfilingInfo(); + public static native void ensureHasOsrCode(); public static boolean doThrow = false; } diff --git a/test/570-checker-select/src/Main.java b/test/570-checker-select/src/Main.java index ec60240e90..8a4cf603af 100644 --- a/test/570-checker-select/src/Main.java +++ b/test/570-checker-select/src/Main.java @@ -19,6 +19,11 @@ public class Main { /// CHECK-START: int Main.BoolCond_IntVarVar(boolean, int, int) register (after) /// CHECK: Select [{{i\d+}},{{i\d+}},{{z\d+}}] + /// CHECK-START-ARM64: int Main.BoolCond_IntVarVar(boolean, int, int) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csel ne + /// CHECK-START-X86_64: int Main.BoolCond_IntVarVar(boolean, int, int) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{i\d+}},{{i\d+}},<<Cond>>] @@ -31,6 +36,11 @@ public class Main { /// CHECK-START: int Main.BoolCond_IntVarCst(boolean, int) register (after) /// CHECK: Select [{{i\d+}},{{i\d+}},{{z\d+}}] + /// CHECK-START-ARM64: int Main.BoolCond_IntVarCst(boolean, int) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csinc ne + /// CHECK-START-X86_64: int Main.BoolCond_IntVarCst(boolean, int) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{i\d+}},{{i\d+}},<<Cond>>] @@ -43,6 +53,11 @@ public class Main { /// CHECK-START: int Main.BoolCond_IntCstVar(boolean, int) register (after) /// CHECK: Select [{{i\d+}},{{i\d+}},{{z\d+}}] + /// CHECK-START-ARM64: int Main.BoolCond_IntCstVar(boolean, int) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csinc eq + /// CHECK-START-X86_64: int Main.BoolCond_IntCstVar(boolean, int) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{i\d+}},{{i\d+}},<<Cond>>] @@ -55,6 +70,11 @@ public class Main { /// CHECK-START: long Main.BoolCond_LongVarVar(boolean, long, long) register (after) /// CHECK: Select [{{j\d+}},{{j\d+}},{{z\d+}}] + /// CHECK-START-ARM64: long Main.BoolCond_LongVarVar(boolean, long, long) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csel ne + /// CHECK-START-X86_64: long Main.BoolCond_LongVarVar(boolean, long, long) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -67,6 +87,11 @@ public class Main { /// CHECK-START: long Main.BoolCond_LongVarCst(boolean, long) register (after) /// CHECK: Select [{{j\d+}},{{j\d+}},{{z\d+}}] + /// CHECK-START-ARM64: long Main.BoolCond_LongVarCst(boolean, long) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csinc ne + /// CHECK-START-X86_64: long Main.BoolCond_LongVarCst(boolean, long) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -79,6 +104,11 @@ public class Main { /// CHECK-START: long Main.BoolCond_LongCstVar(boolean, long) register (after) /// CHECK: Select [{{j\d+}},{{j\d+}},{{z\d+}}] + /// CHECK-START-ARM64: long Main.BoolCond_LongCstVar(boolean, long) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csinc eq + /// CHECK-START-X86_64: long Main.BoolCond_LongCstVar(boolean, long) disassembly (after) /// CHECK: <<Cond:z\d+>> ParameterValue /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -91,6 +121,11 @@ public class Main { /// CHECK-START: float Main.BoolCond_FloatVarVar(boolean, float, float) register (after) /// CHECK: Select [{{f\d+}},{{f\d+}},{{z\d+}}] + /// CHECK-START-ARM64: float Main.BoolCond_FloatVarVar(boolean, float, float) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: fcsel ne + public static float BoolCond_FloatVarVar(boolean cond, float x, float y) { return cond ? x : y; } @@ -98,6 +133,11 @@ public class Main { /// CHECK-START: float Main.BoolCond_FloatVarCst(boolean, float) register (after) /// CHECK: Select [{{f\d+}},{{f\d+}},{{z\d+}}] + /// CHECK-START-ARM64: float Main.BoolCond_FloatVarCst(boolean, float) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: fcsel ne + public static float BoolCond_FloatVarCst(boolean cond, float x) { return cond ? x : 1.0f; } @@ -105,6 +145,11 @@ public class Main { /// CHECK-START: float Main.BoolCond_FloatCstVar(boolean, float) register (after) /// CHECK: Select [{{f\d+}},{{f\d+}},{{z\d+}}] + /// CHECK-START-ARM64: float Main.BoolCond_FloatCstVar(boolean, float) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: fcsel ne + public static float BoolCond_FloatCstVar(boolean cond, float y) { return cond ? 1.0f : y; } @@ -113,6 +158,11 @@ public class Main { /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK-NEXT: Select [{{i\d+}},{{i\d+}},<<Cond>>] + /// CHECK-START-ARM64: int Main.IntNonmatCond_IntVarVar(int, int, int, int) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: int Main.IntNonmatCond_IntVarVar(int, int, int, int) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK-NEXT: Select [{{i\d+}},{{i\d+}},<<Cond>>] @@ -127,6 +177,13 @@ public class Main { /// CHECK-NEXT: <<Sel:i\d+>> Select [{{i\d+}},{{i\d+}},{{z\d+}}] /// CHECK-NEXT: Add [<<Cond>>,<<Sel>>] + /// CHECK-START-ARM64: int Main.IntMatCond_IntVarVar(int, int, int, int) disassembly (after) + /// CHECK: LessThanOrEqual + /// CHECK-NEXT: cmp + /// CHECK-NEXT: cset le + /// CHECK: Select + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: int Main.IntMatCond_IntVarVar(int, int, int, int) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK: Select [{{i\d+}},{{i\d+}},<<Cond>>] @@ -141,6 +198,11 @@ public class Main { /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK-NEXT: Select [{{j\d+}},{{j\d+}},<<Cond>>] + /// CHECK-START-ARM64: long Main.IntNonmatCond_LongVarVar(int, int, long, long) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: long Main.IntNonmatCond_LongVarVar(int, int, long, long) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK-NEXT: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -156,6 +218,13 @@ public class Main { /// CHECK: <<Sel2:j\d+>> Select [{{j\d+}},{{j\d+}},<<Cond>>] /// CHECK: Add [<<Sel2>>,<<Sel1>>] + /// CHECK-START-ARM64: long Main.IntMatCond_LongVarVar(int, int, long, long) disassembly (after) + /// CHECK: LessThanOrEqual + /// CHECK-NEXT: cmp + /// CHECK-NEXT: cset le + /// CHECK: Select + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: long Main.IntMatCond_LongVarVar(int, int, long, long) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{i\d+}},{{i\d+}}] /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -172,6 +241,11 @@ public class Main { /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{j\d+}},{{j\d+}}] /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] + /// CHECK-START-ARM64: long Main.LongNonmatCond_LongVarVar(long, long, long, long) disassembly (after) + /// CHECK: Select + /// CHECK-NEXT: cmp + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: long Main.LongNonmatCond_LongVarVar(long, long, long, long) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{j\d+}},{{j\d+}}] /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -187,6 +261,13 @@ public class Main { /// CHECK: <<Sel2:j\d+>> Select [{{j\d+}},{{j\d+}},<<Cond>>] /// CHECK: Add [<<Sel2>>,<<Sel1>>] + /// CHECK-START-ARM64: long Main.LongMatCond_LongVarVar(long, long, long, long) disassembly (after) + /// CHECK: LessThanOrEqual + /// CHECK-NEXT: cmp + /// CHECK-NEXT: cset le + /// CHECK: Select + /// CHECK-NEXT: csel le + /// CHECK-START-X86_64: long Main.LongMatCond_LongVarVar(long, long, long, long) disassembly (after) /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{j\d+}},{{j\d+}}] /// CHECK: Select [{{j\d+}},{{j\d+}},<<Cond>>] @@ -203,6 +284,12 @@ public class Main { /// CHECK: <<Cond:z\d+>> LessThanOrEqual [{{f\d+}},{{f\d+}}] /// CHECK-NEXT: Select [{{i\d+}},{{i\d+}},<<Cond>>] + /// CHECK-START-ARM64: int Main.FloatLtNonmatCond_IntVarVar(float, float, int, int) disassembly (after) + /// CHECK: LessThanOrEqual + /// CHECK: Select + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: csel le + public static int FloatLtNonmatCond_IntVarVar(float a, float b, int x, int y) { return a > b ? x : y; } @@ -211,6 +298,12 @@ public class Main { /// CHECK: <<Cond:z\d+>> GreaterThanOrEqual [{{f\d+}},{{f\d+}}] /// CHECK-NEXT: Select [{{i\d+}},{{i\d+}},<<Cond>>] + /// CHECK-START-ARM64: int Main.FloatGtNonmatCond_IntVarVar(float, float, int, int) disassembly (after) + /// CHECK: GreaterThanOrEqual + /// CHECK: Select + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: csel hs + public static int FloatGtNonmatCond_IntVarVar(float a, float b, int x, int y) { return a < b ? x : y; } @@ -219,6 +312,12 @@ public class Main { /// CHECK: <<Cond:z\d+>> GreaterThanOrEqual [{{f\d+}},{{f\d+}}] /// CHECK-NEXT: Select [{{f\d+}},{{f\d+}},<<Cond>>] + /// CHECK-START-ARM64: float Main.FloatGtNonmatCond_FloatVarVar(float, float, float, float) disassembly (after) + /// CHECK: GreaterThanOrEqual + /// CHECK: Select + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: fcsel hs + public static float FloatGtNonmatCond_FloatVarVar(float a, float b, float x, float y) { return a < b ? x : y; } @@ -228,6 +327,13 @@ public class Main { /// CHECK-NEXT: <<Sel:i\d+>> Select [{{i\d+}},{{i\d+}},<<Cond>>] /// CHECK-NEXT: Add [<<Cond>>,<<Sel>>] + /// CHECK-START-ARM64: int Main.FloatLtMatCond_IntVarVar(float, float, int, int) disassembly (after) + /// CHECK: LessThanOrEqual + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: cset le + /// CHECK: Select + /// CHECK-NEXT: csel le + public static int FloatLtMatCond_IntVarVar(float a, float b, int x, int y) { int result = (a > b ? x : y); return result + (a > b ? 0 : 1); @@ -238,6 +344,13 @@ public class Main { /// CHECK-NEXT: <<Sel:i\d+>> Select [{{i\d+}},{{i\d+}},<<Cond>>] /// CHECK-NEXT: Add [<<Cond>>,<<Sel>>] + /// CHECK-START-ARM64: int Main.FloatGtMatCond_IntVarVar(float, float, int, int) disassembly (after) + /// CHECK: GreaterThanOrEqual + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: cset hs + /// CHECK: Select + /// CHECK-NEXT: csel hs + public static int FloatGtMatCond_IntVarVar(float a, float b, int x, int y) { int result = (a < b ? x : y); return result + (a < b ? 0 : 1); @@ -248,6 +361,13 @@ public class Main { /// CHECK-NEXT: <<Sel:f\d+>> Select [{{f\d+}},{{f\d+}},<<Cond>>] /// CHECK-NEXT: TypeConversion [<<Cond>>] + /// CHECK-START-ARM64: float Main.FloatGtMatCond_FloatVarVar(float, float, float, float) disassembly (after) + /// CHECK: GreaterThanOrEqual + /// CHECK-NEXT: fcmp + /// CHECK-NEXT: cset hs + /// CHECK: Select + /// CHECK-NEXT: fcsel hs + public static float FloatGtMatCond_FloatVarVar(float a, float b, float x, float y) { float result = (a < b ? x : y); return result + (a < b ? 0 : 1); diff --git a/test/572-checker-array-get-regression/src/Main.java b/test/572-checker-array-get-regression/src/Main.java index a9bf326427..b55be706f4 100644 --- a/test/572-checker-array-get-regression/src/Main.java +++ b/test/572-checker-array-get-regression/src/Main.java @@ -20,7 +20,7 @@ public class Main { System.out.println(test().intValue()); } - /// CHECK-START: java.lang.Integer Main.test() ssa_builder (after) + /// CHECK-START: java.lang.Integer Main.test() builder (after) /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod /// CHECK-DAG: <<Const2P19:i\d+>> IntConstant 524288 /// CHECK-DAG: <<ConstM1:i\d+>> IntConstant -1 diff --git a/test/574-irreducible-and-constant-area/expected.txt b/test/574-irreducible-and-constant-area/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/574-irreducible-and-constant-area/expected.txt diff --git a/test/574-irreducible-and-constant-area/info.txt b/test/574-irreducible-and-constant-area/info.txt new file mode 100644 index 0000000000..e957a5ae6b --- /dev/null +++ b/test/574-irreducible-and-constant-area/info.txt @@ -0,0 +1,3 @@ +Regression test for intrinsics on x86, which used to wrongly assume +a HInvokeStaticOrDirect must have a special input (does not apply for irreducible +loops). diff --git a/test/574-irreducible-and-constant-area/run b/test/574-irreducible-and-constant-area/run new file mode 100755 index 0000000000..ffdbcc9cf2 --- /dev/null +++ b/test/574-irreducible-and-constant-area/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Don't do relocation, as this affects this test. +exec ${RUN} "$@" --no-relocate diff --git a/test/574-irreducible-and-constant-area/smali/IrreducibleLoop.smali b/test/574-irreducible-and-constant-area/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..d7d43466d1 --- /dev/null +++ b/test/574-irreducible-and-constant-area/smali/IrreducibleLoop.smali @@ -0,0 +1,35 @@ +# 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 LIrreducibleLoop; + +.super Ljava/lang/Object; + +.method public static simpleLoop(I)I + .registers 5 + const/16 v0, 42 + const/16 v1, 42 + const-wide/high16 v2, 0x4000000000000000L + if-eq p0, v0, :other_loop_entry + :loop_entry + invoke-static {v1, v1}, LMain;->$inline$foo(FF)V + invoke-static {v2, v3, v2, v3}, LMain;->$inline$foo(DD)V + if-ne p0, v0, :exit + add-int v0, v0, v0 + :other_loop_entry + add-int v0, v0, v0 + goto :loop_entry + :exit + return v0 +.end method diff --git a/test/574-irreducible-and-constant-area/src/Main.java b/test/574-irreducible-and-constant-area/src/Main.java new file mode 100644 index 0000000000..3cdd92456c --- /dev/null +++ b/test/574-irreducible-and-constant-area/src/Main.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Method; + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Exception { + Class<?> c = Class.forName("IrreducibleLoop"); + Method m = c.getMethod("simpleLoop", int.class); + Object[] arguments = { 42 }; + m.invoke(null, arguments); + } + + public static void $inline$foo(float a, float b) { + Math.abs(a); + Math.max(a, b); + Math.min(a, b); + } + + public static void $inline$foo(double a, double b) { + Math.abs(a); + Math.max(a, b); + Math.min(a, b); + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 7c71ce3c6a..364be59919 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -42,8 +42,7 @@ TEST_ART_RUN_TEST_ORDERONLY_DEPENDENCIES := ifeq ($(ANDROID_COMPILE_WITH_JACK),true) TEST_ART_RUN_TEST_DEPENDENCIES += \ - $(JACK) \ - $(JILL_JAR) + $(JACK) TEST_ART_RUN_TEST_ORDERONLY_DEPENDENCIES += setup-jack-server endif @@ -72,8 +71,8 @@ $$(dmart_target): $(TEST_ART_RUN_TEST_DEPENDENCIES) $(TARGET_JACK_CLASSPATH_DEPE DXMERGER=$(abspath $(HOST_OUT_EXECUTABLES)/dexmerger) \ JACK_VERSION=$(JACK_DEFAULT_VERSION) \ JACK=$(abspath $(JACK)) \ + JACK_VERSION=$(JACK_DEFAULT_VERSION) \ JACK_CLASSPATH=$(TARGET_JACK_CLASSPATH) \ - JILL_JAR=$(abspath $(JILL_JAR)) \ $(LOCAL_PATH)/run-test $$(PRIVATE_RUN_TEST_OPTIONS) --output-path $$(abspath $$(dir $$@)) $(1) $(hide) touch $$@ @@ -446,9 +445,7 @@ TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := # Known broken tests for the JIT. # CFI unwinding expects managed frames, and the test does not iterate enough to even compile. JIT # also uses Generic JNI instead of the JNI compiler. -# Disable 570 while investigating OSR issues. TEST_ART_BROKEN_JIT_RUN_TESTS := \ - 570-checker-osr \ 137-cfi ifneq (,$(filter jit,$(COMPILER_TYPES))) @@ -964,8 +961,8 @@ $$(run_test_rule_name): $(TEST_ART_RUN_TEST_DEPENDENCIES) $(HOST_OUT_EXECUTABLES DXMERGER=$(abspath $(HOST_OUT_EXECUTABLES)/dexmerger) \ JACK_VERSION=$(JACK_DEFAULT_VERSION) \ JACK=$(abspath $(JACK)) \ + JACK_VERSION=$(JACK_DEFAULT_VERSION) \ JACK_CLASSPATH=$$(PRIVATE_JACK_CLASSPATH) \ - JILL_JAR=$(abspath $(JILL_JAR)) \ art/test/run-test $$(PRIVATE_RUN_TEST_OPTIONS) $(12) \ && $$(call ART_TEST_PASSED,$$@) || $$(call ART_TEST_FAILED,$$@) $$(hide) (echo $(MAKECMDGOALS) | grep -q $$@ && \ diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 8245ccfde5..2db1e6c947 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -406,6 +406,9 @@ if [ "$HOST" = "n" ]; then TMP_DIR_OPTION="-Djava.io.tmpdir=/data/local/tmp" fi +# We set DumpNativeStackOnSigQuit to false to avoid stressing libunwind. +# b/27185632 +# b/24664297 dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ROOT/bin/$DALVIKVM \ $GDB_ARGS \ $FLAGS \ @@ -420,6 +423,7 @@ dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ROOT/bin/$DALVIKVM \ $DEBUGGER_OPTS \ $DALVIKVM_BOOT_OPT \ $TMP_DIR_OPTION \ + -XX:DumpNativeStackOnSigQuit:false \ -cp $DEX_LOCATION/$TEST_NAME.jar$SECONDARY_DEX $MAIN $ARGS" # Remove whitespace. diff --git a/test/run-test b/test/run-test index faa597e1ca..f1875d71a5 100755 --- a/test/run-test +++ b/test/run-test @@ -88,13 +88,7 @@ if [ -z "$JACK_CLASSPATH" ]; then export JACK_CLASSPATH="${OUT_DIR:-$ANDROID_BUILD_TOP/out}/host/common/obj/JAVA_LIBRARIES/core-libart-hostdex_intermediates/classes.jack:${OUT_DIR:-$ANDROID_BUILD_TOP/out}/host/common/obj/JAVA_LIBRARIES/core-oj-hostdex_intermediates/classes.jack" fi -# If JILL_JAR is not set, assume it is located in the prebuilts directory. -if [ -z "$JILL_JAR" ]; then - export JILL_JAR="$ANDROID_BUILD_TOP/prebuilts/sdk/tools/jill.jar" -fi - export JACK="$JACK -g -cp $JACK_CLASSPATH" -export JILL="java -jar $JILL_JAR" info="info.txt" build="build" diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index da5225c066..d9b26bcf58 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -78,6 +78,7 @@ Things to move to perflib: Release History: 0.4 Pending + Annotate char[] objects with their string values. Show registered native allocations for heap dumps that support it. 0.3 Dec 15, 2015 diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java index 8b7f9ea41a..d7b64e2afd 100644 --- a/tools/ahat/src/InstanceUtils.java +++ b/tools/ahat/src/InstanceUtils.java @@ -76,11 +76,15 @@ class InstanceUtils { * If maxChars is negative, the returned string is not truncated. */ public static String asString(Instance inst, int maxChars) { - if (!isInstanceOfClass(inst, "java.lang.String")) { - return null; + // The inst object could either be a java.lang.String or a char[]. If it + // is a char[], use that directly as the value, otherwise use the value + // field of the string object. The field accesses for count and offset + // later on will work okay regardless of what type the inst object is. + Object value = inst; + if (isInstanceOfClass(inst, "java.lang.String")) { + value = getField(inst, "value"); } - Object value = getField(inst, "value"); if (!(value instanceof ArrayInstance)) { return null; } diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index 701d60ef1f..d61a98da54 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -35,6 +35,7 @@ public class Main { // class and reading the desired field. public static class DumpedStuff { public String basicString = "hello, world"; + public char[] charArray = "char thing".toCharArray(); public String nullString = null; public Object anObject = new Object(); public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>(); diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java index 32f48ce560..59b1c90e10 100644 --- a/tools/ahat/test/InstanceUtilsTest.java +++ b/tools/ahat/test/InstanceUtilsTest.java @@ -32,6 +32,13 @@ public class InstanceUtilsTest { } @Test + public void asStringCharArray() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("charArray"); + assertEquals("char thing", InstanceUtils.asString(str)); + } + + @Test public void asStringTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("basicString"); @@ -39,6 +46,13 @@ public class InstanceUtilsTest { } @Test + public void asStringCharArrayTruncated() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("charArray"); + assertEquals("char ", InstanceUtils.asString(str, 5)); + } + + @Test public void asStringExactMax() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("basicString"); @@ -46,6 +60,13 @@ public class InstanceUtilsTest { } @Test + public void asStringCharArrayExactMax() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("charArray"); + assertEquals("char thing", InstanceUtils.asString(str, 10)); + } + + @Test public void asStringNotTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("basicString"); @@ -53,6 +74,13 @@ public class InstanceUtilsTest { } @Test + public void asStringCharArrayNotTruncated() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("charArray"); + assertEquals("char thing", InstanceUtils.asString(str, 50)); + } + + @Test public void asStringNegativeMax() throws IOException { TestDump dump = TestDump.getTestDump(); Instance str = (Instance)dump.getDumpedThing("basicString"); @@ -60,6 +88,13 @@ public class InstanceUtilsTest { } @Test + public void asStringCharArrayNegativeMax() throws IOException { + TestDump dump = TestDump.getTestDump(); + Instance str = (Instance)dump.getDumpedThing("charArray"); + assertEquals("char thing", InstanceUtils.asString(str, -3)); + } + + @Test public void asStringNull() throws IOException { TestDump dump = TestDump.getTestDump(); Instance obj = (Instance)dump.getDumpedThing("nullString"); diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index 9e02ce2f90..2eb52bcad9 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -21,7 +21,7 @@ fi out_dir=${OUT_DIR-out} java_libraries_dir=${out_dir}/target/common/obj/JAVA_LIBRARIES -common_targets="vogar ${java_libraries_dir}/core-tests_intermediates/javalib.jar apache-harmony-jdwp-tests-hostdex ${java_libraries_dir}/jsr166-tests_intermediates/javalib.jar ${out_dir}/host/linux-x86/bin/jack" +common_targets="vogar core-tests apache-harmony-jdwp-tests-hostdex jsr166-tests ${out_dir}/host/linux-x86/bin/jack" mode="target" j_arg="-j$(nproc)" showcommands= diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index f346239763..45fb4b4dec 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -20,13 +20,13 @@ if [ ! -d libcore ]; then fi # Jar containing jsr166 tests. -jsr166_test_jar=${OUT_DIR-out}/target/common/obj/JAVA_LIBRARIES/jsr166-tests_intermediates/javalib.jar +jsr166_test_jack=${OUT_DIR-out}/target/common/obj/JAVA_LIBRARIES/jsr166-tests_intermediates/classes.jack # Jar containing all the other tests. -test_jar=${OUT_DIR-out}/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/javalib.jar +test_jack=${OUT_DIR-out}/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jack -if [ ! -f $test_jar ]; then +if [ ! -f $test_jack ]; then echo "Before running, you must build core-tests, jsr166-tests and vogar: \ make core-tests jsr166-tests vogar vogar.jar" exit 1 @@ -108,7 +108,11 @@ done # the default timeout. vogar_args="$vogar_args --timeout 480" +# Use Jack with "1.8" configuration. +export JACK_VERSION=`basename prebuilts/sdk/tools/jacks/*ALPHA* | sed 's/^jack-//' | sed 's/.jar$//'` +vogar_args="$vogar_args --toolchain jack --language JN" + # Run the tests using vogar. echo "Running tests for the following test packages:" echo ${working_packages[@]} | tr " " "\n" -vogar $vogar_args --vm-arg -Xusejit:true $expectations --classpath $jsr166_test_jar --classpath $test_jar ${working_packages[@]} +vogar $vogar_args --vm-arg -Xusejit:true $expectations --classpath $jsr166_test_jack --classpath $test_jack ${working_packages[@]} |