diff options
83 files changed, 4127 insertions, 818 deletions
diff --git a/compiler/optimizing/code_generator_vector_arm.cc b/compiler/optimizing/code_generator_vector_arm.cc index e7f7b3019c..6e82123e56 100644 --- a/compiler/optimizing/code_generator_vector_arm.cc +++ b/compiler/optimizing/code_generator_vector_arm.cc @@ -124,6 +124,14 @@ void InstructionCodeGeneratorARM::VisitVecAdd(HVecAdd* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderARM::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderARM::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -148,6 +156,22 @@ void InstructionCodeGeneratorARM::VisitVecDiv(HVecDiv* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderARM::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderARM::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderARM::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_arm64.cc b/compiler/optimizing/code_generator_vector_arm64.cc index 0923920366..2dfccfff85 100644 --- a/compiler/optimizing/code_generator_vector_arm64.cc +++ b/compiler/optimizing/code_generator_vector_arm64.cc @@ -318,6 +318,47 @@ void InstructionCodeGeneratorARM64::VisitVecAdd(HVecAdd* instruction) { } } +void LocationsBuilderARM64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LocationSummary* locations = instruction->GetLocations(); + VRegister lhs = VRegisterFrom(locations->InAt(0)); + VRegister rhs = VRegisterFrom(locations->InAt(1)); + VRegister dst = VRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(16u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + instruction->IsRounded() + ? __ Urhadd(dst.V16B(), lhs.V16B(), rhs.V16B()) + : __ Uhadd(dst.V16B(), lhs.V16B(), rhs.V16B()); + } else { + instruction->IsRounded() + ? __ Srhadd(dst.V16B(), lhs.V16B(), rhs.V16B()) + : __ Shadd(dst.V16B(), lhs.V16B(), rhs.V16B()); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + instruction->IsRounded() + ? __ Urhadd(dst.V8H(), lhs.V8H(), rhs.V8H()) + : __ Uhadd(dst.V8H(), lhs.V8H(), rhs.V8H()); + } else { + instruction->IsRounded() + ? __ Srhadd(dst.V8H(), lhs.V8H(), rhs.V8H()) + : __ Shadd(dst.V8H(), lhs.V8H(), rhs.V8H()); + } + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } +} + void LocationsBuilderARM64::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -420,6 +461,22 @@ void InstructionCodeGeneratorARM64::VisitVecDiv(HVecDiv* instruction) { } } +void LocationsBuilderARM64::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM64::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "Unsupported SIMD instruction " << instruction->GetId(); +} + +void LocationsBuilderARM64::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARM64::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "Unsupported SIMD instruction " << instruction->GetId(); +} + void LocationsBuilderARM64::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_arm_vixl.cc b/compiler/optimizing/code_generator_vector_arm_vixl.cc index 74fa584e09..990178b31b 100644 --- a/compiler/optimizing/code_generator_vector_arm_vixl.cc +++ b/compiler/optimizing/code_generator_vector_arm_vixl.cc @@ -124,6 +124,14 @@ void InstructionCodeGeneratorARMVIXL::VisitVecAdd(HVecAdd* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderARMVIXL::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARMVIXL::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderARMVIXL::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -148,6 +156,22 @@ void InstructionCodeGeneratorARMVIXL::VisitVecDiv(HVecDiv* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderARMVIXL::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARMVIXL::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderARMVIXL::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorARMVIXL::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderARMVIXL::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_mips.cc b/compiler/optimizing/code_generator_vector_mips.cc index 6969abd422..8ea1ca7d90 100644 --- a/compiler/optimizing/code_generator_vector_mips.cc +++ b/compiler/optimizing/code_generator_vector_mips.cc @@ -124,6 +124,14 @@ void InstructionCodeGeneratorMIPS::VisitVecAdd(HVecAdd* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderMIPS::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderMIPS::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -148,6 +156,22 @@ void InstructionCodeGeneratorMIPS::VisitVecDiv(HVecDiv* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderMIPS::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderMIPS::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderMIPS::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_mips64.cc b/compiler/optimizing/code_generator_vector_mips64.cc index 87118cefa5..a484bb4774 100644 --- a/compiler/optimizing/code_generator_vector_mips64.cc +++ b/compiler/optimizing/code_generator_vector_mips64.cc @@ -124,6 +124,14 @@ void InstructionCodeGeneratorMIPS64::VisitVecAdd(HVecAdd* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderMIPS64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderMIPS64::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -148,6 +156,22 @@ void InstructionCodeGeneratorMIPS64::VisitVecDiv(HVecDiv* instruction) { LOG(FATAL) << "No SIMD for " << instruction->GetId(); } +void LocationsBuilderMIPS64::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS64::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderMIPS64::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorMIPS64::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderMIPS64::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_x86.cc b/compiler/optimizing/code_generator_vector_x86.cc index 8dabb4d08f..a86d060821 100644 --- a/compiler/optimizing/code_generator_vector_x86.cc +++ b/compiler/optimizing/code_generator_vector_x86.cc @@ -350,6 +350,35 @@ void InstructionCodeGeneratorX86::VisitVecAdd(HVecAdd* instruction) { } } +void LocationsBuilderX86::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LocationSummary* locations = instruction->GetLocations(); + DCHECK(locations->InAt(0).Equals(locations->Out())); + XmmRegister src = locations->InAt(1).AsFpuRegister<XmmRegister>(); + XmmRegister dst = locations->Out().AsFpuRegister<XmmRegister>(); + + DCHECK(instruction->IsRounded()); + DCHECK(instruction->IsUnsigned()); + + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(16u, instruction->GetVectorLength()); + __ pavgb(dst, src); + return; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ pavgw(dst, src); + return; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } +} + void LocationsBuilderX86::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -448,6 +477,22 @@ void InstructionCodeGeneratorX86::VisitVecDiv(HVecDiv* instruction) { } } +void LocationsBuilderX86::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderX86::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderX86::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc index e95608839b..696735367e 100644 --- a/compiler/optimizing/code_generator_vector_x86_64.cc +++ b/compiler/optimizing/code_generator_vector_x86_64.cc @@ -343,6 +343,31 @@ void InstructionCodeGeneratorX86_64::VisitVecAdd(HVecAdd* instruction) { } } +void LocationsBuilderX86_64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86_64::VisitVecHalvingAdd(HVecHalvingAdd* instruction) { + LocationSummary* locations = instruction->GetLocations(); + DCHECK(locations->InAt(0).Equals(locations->Out())); + XmmRegister src = locations->InAt(1).AsFpuRegister<XmmRegister>(); + XmmRegister dst = locations->Out().AsFpuRegister<XmmRegister>(); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(16u, instruction->GetVectorLength()); + __ pavgb(dst, src); + return; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(8u, instruction->GetVectorLength()); + __ pavgw(dst, src); + return; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } +} + void LocationsBuilderX86_64::VisitVecSub(HVecSub* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } @@ -441,6 +466,22 @@ void InstructionCodeGeneratorX86_64::VisitVecDiv(HVecDiv* instruction) { } } +void LocationsBuilderX86_64::VisitVecMin(HVecMin* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86_64::VisitVecMin(HVecMin* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + +void LocationsBuilderX86_64::VisitVecMax(HVecMax* instruction) { + CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); +} + +void InstructionCodeGeneratorX86_64::VisitVecMax(HVecMax* instruction) { + LOG(FATAL) << "No SIMD for " << instruction->GetId(); +} + void LocationsBuilderX86_64::VisitVecAnd(HVecAnd* instruction) { CreateVecBinOpLocations(GetGraph()->GetArena(), instruction); } diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index cc3c143b15..1b2b9f80ac 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -509,6 +509,11 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { StartAttributeStream("kind") << deoptimize->GetKind(); } + void VisitVecHalvingAdd(HVecHalvingAdd* hadd) OVERRIDE { + StartAttributeStream("unsigned") << std::boolalpha << hadd->IsUnsigned() << std::noboolalpha; + StartAttributeStream("rounded") << std::boolalpha << hadd->IsRounded() << std::noboolalpha; + } + #if defined(ART_ENABLE_CODEGEN_arm) || defined(ART_ENABLE_CODEGEN_arm64) void VisitMultiplyAccumulate(HMultiplyAccumulate* instruction) OVERRIDE { StartAttributeStream("kind") << instruction->GetOpKind(); diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index 1c8674d522..7c833cf70c 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -45,18 +45,6 @@ static bool IsSafeDiv(int32_t c1, int32_t c2) { return c2 != 0 && CanLongValueFitIntoInt(static_cast<int64_t>(c1) / static_cast<int64_t>(c2)); } -/** Returns true for 32/64-bit constant instruction. */ -static bool IsIntAndGet(HInstruction* instruction, int64_t* value) { - if (instruction->IsIntConstant()) { - *value = instruction->AsIntConstant()->GetValue(); - return true; - } else if (instruction->IsLongConstant()) { - *value = instruction->AsLongConstant()->GetValue(); - return true; - } - return false; -} - /** Computes a * b for a,b > 0 (at least until first overflow happens). */ static int64_t SafeMul(int64_t a, int64_t b, /*out*/ bool* overflow) { if (a > 0 && b > 0 && a > (std::numeric_limits<int64_t>::max() / b)) { @@ -106,7 +94,7 @@ static bool IsGEZero(HInstruction* instruction) { } } int64_t value = -1; - return IsIntAndGet(instruction, &value) && value >= 0; + return IsInt64AndGet(instruction, &value) && value >= 0; } /** Hunts "under the hood" for a suitable instruction at the hint. */ @@ -149,7 +137,7 @@ static InductionVarRange::Value SimplifyMax(InductionVarRange::Value v, HInstruc int64_t value; if (v.instruction->IsDiv() && v.instruction->InputAt(0)->IsArrayLength() && - IsIntAndGet(v.instruction->InputAt(1), &value) && v.a_constant == value) { + IsInt64AndGet(v.instruction->InputAt(1), &value) && v.a_constant == value) { return InductionVarRange::Value(v.instruction->InputAt(0), 1, v.b_constant); } // If a == 1, the most suitable one suffices as maximum value. @@ -444,7 +432,7 @@ bool InductionVarRange::IsConstant(HInductionVarAnalysis::InductionInfo* info, // any of the three requests (kExact, kAtMost, and KAtLeast). if (info->induction_class == HInductionVarAnalysis::kInvariant && info->operation == HInductionVarAnalysis::kFetch) { - if (IsIntAndGet(info->fetch, value)) { + if (IsInt64AndGet(info->fetch, value)) { return true; } } @@ -635,7 +623,7 @@ InductionVarRange::Value InductionVarRange::GetGeometric(HInductionVarAnalysis:: int64_t f = 0; if (IsConstant(info->op_a, kExact, &a) && CanLongValueFitIntoInt(a) && - IsIntAndGet(info->fetch, &f) && f >= 1) { + IsInt64AndGet(info->fetch, &f) && f >= 1) { // Conservative bounds on a * f^-i + b with f >= 1 can be computed without // trip count. Other forms would require a much more elaborate evaluation. const bool is_min_a = a >= 0 ? is_min : !is_min; @@ -663,7 +651,7 @@ InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction, // Unless at a constant or hint, chase the instruction a bit deeper into the HIR tree, so that // it becomes more likely range analysis will compare the same instructions as terminal nodes. int64_t value; - if (IsIntAndGet(instruction, &value) && CanLongValueFitIntoInt(value)) { + if (IsInt64AndGet(instruction, &value) && CanLongValueFitIntoInt(value)) { // Proper constant reveals best information. return Value(static_cast<int32_t>(value)); } else if (instruction == chase_hint_) { @@ -671,10 +659,10 @@ InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction, return Value(instruction, 1, 0); } else if (instruction->IsAdd()) { // Incorporate suitable constants in the chased value. - if (IsIntAndGet(instruction->InputAt(0), &value) && CanLongValueFitIntoInt(value)) { + if (IsInt64AndGet(instruction->InputAt(0), &value) && CanLongValueFitIntoInt(value)) { return AddValue(Value(static_cast<int32_t>(value)), GetFetch(instruction->InputAt(1), trip, in_body, is_min)); - } else if (IsIntAndGet(instruction->InputAt(1), &value) && CanLongValueFitIntoInt(value)) { + } else if (IsInt64AndGet(instruction->InputAt(1), &value) && CanLongValueFitIntoInt(value)) { return AddValue(GetFetch(instruction->InputAt(0), trip, in_body, is_min), Value(static_cast<int32_t>(value))); } @@ -1074,7 +1062,7 @@ bool InductionVarRange::GenerateLastValueGeometric(HInductionVarAnalysis::Induct // Detect known base and trip count (always taken). int64_t f = 0; int64_t m = 0; - if (IsIntAndGet(info->fetch, &f) && f >= 1 && IsConstant(trip->op_a, kExact, &m) && m >= 1) { + if (IsInt64AndGet(info->fetch, &f) && f >= 1 && IsConstant(trip->op_a, kExact, &m) && m >= 1) { HInstruction* opa = nullptr; HInstruction* opb = nullptr; if (GenerateCode(info->op_a, nullptr, graph, block, &opa, false, false) && diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 82d0567ef9..b57b41f686 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -2093,6 +2093,199 @@ void IntrinsicCodeGeneratorMIPS64::VisitStringGetCharsNoCheck(HInvoke* invoke) { __ Bind(&done); } +// static void java.lang.System.arraycopy(Object src, int srcPos, +// Object dest, int destPos, +// int length) +void IntrinsicLocationsBuilderMIPS64::VisitSystemArrayCopyChar(HInvoke* invoke) { + HIntConstant* src_pos = invoke->InputAt(1)->AsIntConstant(); + HIntConstant* dest_pos = invoke->InputAt(3)->AsIntConstant(); + HIntConstant* length = invoke->InputAt(4)->AsIntConstant(); + + // As long as we are checking, we might as well check to see if the src and dest + // positions are >= 0. + if ((src_pos != nullptr && src_pos->GetValue() < 0) || + (dest_pos != nullptr && dest_pos->GetValue() < 0)) { + // We will have to fail anyways. + return; + } + + // And since we are already checking, check the length too. + if (length != nullptr) { + int32_t len = length->GetValue(); + if (len < 0) { + // Just call as normal. + return; + } + } + + // Okay, it is safe to generate inline code. + LocationSummary* locations = + new (arena_) LocationSummary(invoke, LocationSummary::kCallOnSlowPath, kIntrinsified); + // arraycopy(Object src, int srcPos, Object dest, int destPos, int length). + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RegisterOrConstant(invoke->InputAt(1))); + locations->SetInAt(2, Location::RequiresRegister()); + locations->SetInAt(3, Location::RegisterOrConstant(invoke->InputAt(3))); + locations->SetInAt(4, Location::RegisterOrConstant(invoke->InputAt(4))); + + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); +} + +// Utility routine to verify that "length(input) - pos >= length" +static void EnoughItems(Mips64Assembler* assembler, + GpuRegister length_input_minus_pos, + Location length, + SlowPathCodeMIPS64* slow_path) { + if (length.IsConstant()) { + int32_t length_constant = length.GetConstant()->AsIntConstant()->GetValue(); + + if (IsInt<16>(length_constant)) { + __ Slti(TMP, length_input_minus_pos, length_constant); + __ Bnezc(TMP, slow_path->GetEntryLabel()); + } else { + __ LoadConst32(TMP, length_constant); + __ Bltc(length_input_minus_pos, TMP, slow_path->GetEntryLabel()); + } + } else { + __ Bltc(length_input_minus_pos, length.AsRegister<GpuRegister>(), slow_path->GetEntryLabel()); + } +} + +static void CheckPosition(Mips64Assembler* assembler, + Location pos, + GpuRegister input, + Location length, + SlowPathCodeMIPS64* slow_path, + bool length_is_input_length = false) { + // Where is the length in the Array? + const uint32_t length_offset = mirror::Array::LengthOffset().Uint32Value(); + + // Calculate length(input) - pos. + if (pos.IsConstant()) { + int32_t pos_const = pos.GetConstant()->AsIntConstant()->GetValue(); + if (pos_const == 0) { + if (!length_is_input_length) { + // Check that length(input) >= length. + __ LoadFromOffset(kLoadWord, AT, input, length_offset); + EnoughItems(assembler, AT, length, slow_path); + } + } else { + // Check that (length(input) - pos) >= zero. + __ LoadFromOffset(kLoadWord, AT, input, length_offset); + DCHECK_GT(pos_const, 0); + __ Addiu32(AT, AT, -pos_const); + __ Bltzc(AT, slow_path->GetEntryLabel()); + + // Verify that (length(input) - pos) >= length. + EnoughItems(assembler, AT, length, slow_path); + } + } else if (length_is_input_length) { + // The only way the copy can succeed is if pos is zero. + GpuRegister pos_reg = pos.AsRegister<GpuRegister>(); + __ Bnezc(pos_reg, slow_path->GetEntryLabel()); + } else { + // Verify that pos >= 0. + GpuRegister pos_reg = pos.AsRegister<GpuRegister>(); + __ Bltzc(pos_reg, slow_path->GetEntryLabel()); + + // Check that (length(input) - pos) >= zero. + __ LoadFromOffset(kLoadWord, AT, input, length_offset); + __ Subu(AT, AT, pos_reg); + __ Bltzc(AT, slow_path->GetEntryLabel()); + + // Verify that (length(input) - pos) >= length. + EnoughItems(assembler, AT, length, slow_path); + } +} + +void IntrinsicCodeGeneratorMIPS64::VisitSystemArrayCopyChar(HInvoke* invoke) { + Mips64Assembler* assembler = GetAssembler(); + LocationSummary* locations = invoke->GetLocations(); + + GpuRegister src = locations->InAt(0).AsRegister<GpuRegister>(); + Location src_pos = locations->InAt(1); + GpuRegister dest = locations->InAt(2).AsRegister<GpuRegister>(); + Location dest_pos = locations->InAt(3); + Location length = locations->InAt(4); + + Mips64Label loop; + + GpuRegister dest_base = locations->GetTemp(0).AsRegister<GpuRegister>(); + GpuRegister src_base = locations->GetTemp(1).AsRegister<GpuRegister>(); + GpuRegister count = locations->GetTemp(2).AsRegister<GpuRegister>(); + + SlowPathCodeMIPS64* slow_path = new (GetAllocator()) IntrinsicSlowPathMIPS64(invoke); + codegen_->AddSlowPath(slow_path); + + // Bail out if the source and destination are the same (to handle overlap). + __ Beqc(src, dest, slow_path->GetEntryLabel()); + + // Bail out if the source is null. + __ Beqzc(src, slow_path->GetEntryLabel()); + + // Bail out if the destination is null. + __ Beqzc(dest, slow_path->GetEntryLabel()); + + // Load length into register for count. + if (length.IsConstant()) { + __ LoadConst32(count, length.GetConstant()->AsIntConstant()->GetValue()); + } else { + // If the length is negative, bail out. + // We have already checked in the LocationsBuilder for the constant case. + __ Bltzc(length.AsRegister<GpuRegister>(), slow_path->GetEntryLabel()); + + __ Move(count, length.AsRegister<GpuRegister>()); + } + + // Validity checks: source. + CheckPosition(assembler, src_pos, src, Location::RegisterLocation(count), slow_path); + + // Validity checks: dest. + CheckPosition(assembler, dest_pos, dest, Location::RegisterLocation(count), slow_path); + + // If count is zero, we're done. + __ Beqzc(count, slow_path->GetExitLabel()); + + // Okay, everything checks out. Finally time to do the copy. + // Check assumption that sizeof(Char) is 2 (used in scaling below). + const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar); + DCHECK_EQ(char_size, 2u); + + const size_t char_shift = Primitive::ComponentSizeShift(Primitive::kPrimChar); + + const uint32_t data_offset = mirror::Array::DataOffset(char_size).Uint32Value(); + + // Calculate source and destination addresses. + if (src_pos.IsConstant()) { + int32_t src_pos_const = src_pos.GetConstant()->AsIntConstant()->GetValue(); + + __ Daddiu64(src_base, src, data_offset + char_size * src_pos_const, TMP); + } else { + __ Daddiu64(src_base, src, data_offset, TMP); + __ Dlsa(src_base, src_pos.AsRegister<GpuRegister>(), src_base, char_shift); + } + if (dest_pos.IsConstant()) { + int32_t dest_pos_const = dest_pos.GetConstant()->AsIntConstant()->GetValue(); + + __ Daddiu64(dest_base, dest, data_offset + char_size * dest_pos_const, TMP); + } else { + __ Daddiu64(dest_base, dest, data_offset, TMP); + __ Dlsa(dest_base, dest_pos.AsRegister<GpuRegister>(), dest_base, char_shift); + } + + __ Bind(&loop); + __ Lh(TMP, src_base, 0); + __ Daddiu(src_base, src_base, char_size); + __ Daddiu(count, count, -1); + __ Sh(TMP, dest_base, 0); + __ Daddiu(dest_base, dest_base, char_size); + __ Bnezc(count, &loop); + + __ Bind(slow_path->GetExitLabel()); +} + static void GenHighestOneBit(LocationSummary* locations, Primitive::Type type, Mips64Assembler* assembler) { @@ -2372,7 +2565,6 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathTanh(HInvoke* invoke) { } UNIMPLEMENTED_INTRINSIC(MIPS64, ReferenceGetReferent) -UNIMPLEMENTED_INTRINSIC(MIPS64, SystemArrayCopyChar) UNIMPLEMENTED_INTRINSIC(MIPS64, SystemArrayCopy) UNIMPLEMENTED_INTRINSIC(MIPS64, StringStringIndexOf); diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index 8e88c1ec7f..5a95abdb50 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -63,12 +63,122 @@ static bool IsEarlyExit(HLoopInformation* loop_info) { return false; } +// Detect a sign extension from the given type. Returns the promoted operand on success. +static bool IsSignExtensionAndGet(HInstruction* instruction, + Primitive::Type type, + /*out*/ HInstruction** operand) { + // Accept any already wider constant that would be handled properly by sign + // extension when represented in the *width* of the given narrower data type + // (the fact that char normally zero extends does not matter here). + int64_t value = 0; + if (IsInt64AndGet(instruction, &value)) { + switch (type) { + case Primitive::kPrimByte: + if (std::numeric_limits<int8_t>::min() <= value && + std::numeric_limits<int8_t>::max() >= value) { + *operand = instruction; + return true; + } + return false; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + if (std::numeric_limits<int16_t>::min() <= value && + std::numeric_limits<int16_t>::max() <= value) { + *operand = instruction; + return true; + } + return false; + default: + return false; + } + } + // An implicit widening conversion of a signed integer to an integral type sign-extends + // the two's-complement representation of the integer value to fill the wider format. + if (instruction->GetType() == type && (instruction->IsArrayGet() || + instruction->IsStaticFieldGet() || + instruction->IsInstanceFieldGet())) { + switch (type) { + case Primitive::kPrimByte: + case Primitive::kPrimShort: + *operand = instruction; + return true; + default: + return false; + } + } + // TODO: perhaps explicit conversions later too? + // (this may return something different from instruction) + return false; +} + +// Detect a zero extension from the given type. Returns the promoted operand on success. +static bool IsZeroExtensionAndGet(HInstruction* instruction, + Primitive::Type type, + /*out*/ HInstruction** operand) { + // Accept any already wider constant that would be handled properly by zero + // extension when represented in the *width* of the given narrower data type + // (the fact that byte/short normally sign extend does not matter here). + int64_t value = 0; + if (IsInt64AndGet(instruction, &value)) { + switch (type) { + case Primitive::kPrimByte: + if (std::numeric_limits<uint8_t>::min() <= value && + std::numeric_limits<uint8_t>::max() >= value) { + *operand = instruction; + return true; + } + return false; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + if (std::numeric_limits<uint16_t>::min() <= value && + std::numeric_limits<uint16_t>::max() <= value) { + *operand = instruction; + return true; + } + return false; + default: + return false; + } + } + // An implicit widening conversion of a char to an integral type zero-extends + // the representation of the char value to fill the wider format. + if (instruction->GetType() == type && (instruction->IsArrayGet() || + instruction->IsStaticFieldGet() || + instruction->IsInstanceFieldGet())) { + if (type == Primitive::kPrimChar) { + *operand = instruction; + return true; + } + } + // A sign (or zero) extension followed by an explicit removal of just the + // higher sign bits is equivalent to a zero extension of the underlying operand. + if (instruction->IsAnd()) { + int64_t mask = 0; + HInstruction* a = instruction->InputAt(0); + HInstruction* b = instruction->InputAt(1); + // In (a & b) find (mask & b) or (a & mask) with sign or zero extension on the non-mask. + if ((IsInt64AndGet(a, /*out*/ &mask) && (IsSignExtensionAndGet(b, type, /*out*/ operand) || + IsZeroExtensionAndGet(b, type, /*out*/ operand))) || + (IsInt64AndGet(b, /*out*/ &mask) && (IsSignExtensionAndGet(a, type, /*out*/ operand) || + IsZeroExtensionAndGet(a, type, /*out*/ operand)))) { + switch ((*operand)->GetType()) { + case Primitive::kPrimByte: return mask == std::numeric_limits<uint8_t>::max(); + case Primitive::kPrimChar: + case Primitive::kPrimShort: return mask == std::numeric_limits<uint16_t>::max(); + default: return false; + } + } + } + // TODO: perhaps explicit conversions later too? + return false; +} + // Test vector restrictions. static bool HasVectorRestrictions(uint64_t restrictions, uint64_t tested) { return (restrictions & tested) != 0; } -// Inserts an instruction. +// Insert an instruction. static HInstruction* Insert(HBasicBlock* block, HInstruction* instruction) { DCHECK(block != nullptr); DCHECK(instruction != nullptr); @@ -713,6 +823,10 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node, return true; } } else if (instruction->IsShl() || instruction->IsShr() || instruction->IsUShr()) { + // Recognize vectorization idioms. + if (VectorizeHalvingAddIdiom(node, instruction, generate_code, type, restrictions)) { + return true; + } // Deal with vector restrictions. if ((HasVectorRestrictions(restrictions, kNoShift)) || (instruction->IsShr() && HasVectorRestrictions(restrictions, kNoShr))) { @@ -806,11 +920,11 @@ bool HLoopOptimization::TrySetVectorType(Primitive::Type type, uint64_t* restric switch (type) { case Primitive::kPrimBoolean: case Primitive::kPrimByte: - *restrictions |= kNoMul | kNoDiv | kNoShift | kNoAbs; + *restrictions |= kNoMul | kNoDiv | kNoShift | kNoAbs | kNoSignedHAdd | kNoUnroundedHAdd; return TrySetVectorLength(16); case Primitive::kPrimChar: case Primitive::kPrimShort: - *restrictions |= kNoDiv | kNoAbs; + *restrictions |= kNoDiv | kNoAbs | kNoSignedHAdd | kNoUnroundedHAdd; return TrySetVectorLength(8); case Primitive::kPrimInt: *restrictions |= kNoDiv; @@ -1039,6 +1153,90 @@ void HLoopOptimization::GenerateVecOp(HInstruction* org, #undef GENERATE_VEC // +// Vectorization idioms. +// + +// Method recognizes the following idioms: +// rounding halving add (a + b + 1) >> 1 for unsigned/signed operands a, b +// regular halving add (a + b) >> 1 for unsigned/signed operands a, b +// Provided that the operands are promoted to a wider form to do the arithmetic and +// then cast back to narrower form, the idioms can be mapped into efficient SIMD +// implementation that operates directly in narrower form (plus one extra bit). +// TODO: current version recognizes implicit byte/short/char widening only; +// explicit widening from int to long could be added later. +bool HLoopOptimization::VectorizeHalvingAddIdiom(LoopNode* node, + HInstruction* instruction, + bool generate_code, + Primitive::Type type, + uint64_t restrictions) { + // Test for top level arithmetic shift right x >> 1 or logical shift right x >>> 1 + // (note whether the sign bit in higher precision is shifted in has no effect + // on the narrow precision computed by the idiom). + int64_t value = 0; + if ((instruction->IsShr() || + instruction->IsUShr()) && + IsInt64AndGet(instruction->InputAt(1), &value) && value == 1) { + // + // TODO: make following code less sensitive to associativity and commutativity differences. + // + HInstruction* x = instruction->InputAt(0); + // Test for an optional rounding part (x + 1) >> 1. + bool is_rounded = false; + if (x->IsAdd() && IsInt64AndGet(x->InputAt(1), &value) && value == 1) { + x = x->InputAt(0); + is_rounded = true; + } + // Test for a core addition (a + b) >> 1 (possibly rounded), either unsigned or signed. + if (x->IsAdd()) { + HInstruction* a = x->InputAt(0); + HInstruction* b = x->InputAt(1); + HInstruction* r = nullptr; + HInstruction* s = nullptr; + bool is_unsigned = false; + if (IsZeroExtensionAndGet(a, type, &r) && IsZeroExtensionAndGet(b, type, &s)) { + is_unsigned = true; + } else if (IsSignExtensionAndGet(a, type, &r) && IsSignExtensionAndGet(b, type, &s)) { + is_unsigned = false; + } else { + return false; + } + // Deal with vector restrictions. + if ((!is_unsigned && HasVectorRestrictions(restrictions, kNoSignedHAdd)) || + (!is_rounded && HasVectorRestrictions(restrictions, kNoUnroundedHAdd))) { + return false; + } + // Accept recognized halving add for vectorizable operands. Vectorized code uses the + // shorthand idiomatic operation. Sequential code uses the original scalar expressions. + DCHECK(r != nullptr && s != nullptr); + if (VectorizeUse(node, r, generate_code, type, restrictions) && + VectorizeUse(node, s, generate_code, type, restrictions)) { + if (generate_code) { + if (vector_mode_ == kVector) { + vector_map_->Put(instruction, new (global_allocator_) HVecHalvingAdd( + global_allocator_, + vector_map_->Get(r), + vector_map_->Get(s), + type, + vector_length_, + is_unsigned, + is_rounded)); + } else { + VectorizeUse(node, instruction->InputAt(0), generate_code, type, restrictions); + VectorizeUse(node, instruction->InputAt(1), generate_code, type, restrictions); + GenerateVecOp(instruction, + vector_map_->Get(instruction->InputAt(0)), + vector_map_->Get(instruction->InputAt(1)), + type); + } + } + return true; + } + } + } + return false; +} + +// // Helpers. // diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index d8f50aab28..4a7da86e32 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -62,13 +62,15 @@ class HLoopOptimization : public HOptimization { * Vectorization restrictions (bit mask). */ enum VectorRestrictions { - kNone = 0, // no restrictions - kNoMul = 1, // no multiplication - kNoDiv = 2, // no division - kNoShift = 4, // no shift - kNoShr = 8, // no arithmetic shift right - kNoHiBits = 16, // "wider" operations cannot bring in higher order bits - kNoAbs = 32, // no absolute value + kNone = 0, // no restrictions + kNoMul = 1, // no multiplication + kNoDiv = 2, // no division + kNoShift = 4, // no shift + kNoShr = 8, // no arithmetic shift right + kNoHiBits = 16, // "wider" operations cannot bring in higher order bits + kNoSignedHAdd = 32, // no signed halving add + kNoUnroundedHAdd = 64, // no unrounded halving add + kNoAbs = 128, // no absolute value }; /* @@ -136,6 +138,13 @@ class HLoopOptimization : public HOptimization { Primitive::Type type); void GenerateVecOp(HInstruction* org, HInstruction* opa, HInstruction* opb, Primitive::Type type); + // Vectorization idioms. + bool VectorizeHalvingAddIdiom(LoopNode* node, + HInstruction* instruction, + bool generate_code, + Primitive::Type type, + uint64_t restrictions); + // Helpers. bool TrySetPhiInduction(HPhi* phi, bool restrict_uses); bool TrySetSimpleLoopHeader(HBasicBlock* block); diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index c109369106..6be237e612 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -1369,9 +1369,12 @@ class HLoopInformationOutwardIterator : public ValueObject { M(VecAbs, VecUnaryOperation) \ M(VecNot, VecUnaryOperation) \ M(VecAdd, VecBinaryOperation) \ + M(VecHalvingAdd, VecBinaryOperation) \ M(VecSub, VecBinaryOperation) \ M(VecMul, VecBinaryOperation) \ M(VecDiv, VecBinaryOperation) \ + M(VecMin, VecBinaryOperation) \ + M(VecMax, VecBinaryOperation) \ M(VecAnd, VecBinaryOperation) \ M(VecAndNot, VecBinaryOperation) \ M(VecOr, VecBinaryOperation) \ @@ -6845,6 +6848,7 @@ class HBlocksInLoopReversePostOrderIterator : public ValueObject { DISALLOW_COPY_AND_ASSIGN(HBlocksInLoopReversePostOrderIterator); }; +// Returns int64_t value of a properly typed constant. inline int64_t Int64FromConstant(HConstant* constant) { if (constant->IsIntConstant()) { return constant->AsIntConstant()->GetValue(); @@ -6856,6 +6860,21 @@ inline int64_t Int64FromConstant(HConstant* constant) { } } +// Returns true iff instruction is an integral constant (and sets value on success). +inline bool IsInt64AndGet(HInstruction* instruction, /*out*/ int64_t* value) { + if (instruction->IsIntConstant()) { + *value = instruction->AsIntConstant()->GetValue(); + return true; + } else if (instruction->IsLongConstant()) { + *value = instruction->AsLongConstant()->GetValue(); + return true; + } else if (instruction->IsNullConstant()) { + *value = 0; + return true; + } + return false; +} + #define INSTRUCTION_TYPE_CHECK(type, super) \ inline bool HInstruction::Is##type() const { return GetKind() == k##type; } \ inline const H##type* HInstruction::As##type() const { \ diff --git a/compiler/optimizing/nodes_vector.h b/compiler/optimizing/nodes_vector.h index 0cbbf2a215..bff58d0910 100644 --- a/compiler/optimizing/nodes_vector.h +++ b/compiler/optimizing/nodes_vector.h @@ -338,6 +338,42 @@ class HVecAdd FINAL : public HVecBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HVecAdd); }; +// Performs halving add on every component in the two vectors, viz. +// rounded [ x1, .. , xn ] hradd [ y1, .. , yn ] = [ (x1 + y1 + 1) >> 1, .. , (xn + yn + 1) >> 1 ] +// or [ x1, .. , xn ] hadd [ y1, .. , yn ] = [ (x1 + y1) >> 1, .. , (xn + yn ) >> 1 ] +// for signed operands x, y (sign extension) or unsigned operands x, y (zero extension). +class HVecHalvingAdd FINAL : public HVecBinaryOperation { + public: + HVecHalvingAdd(ArenaAllocator* arena, + HInstruction* left, + HInstruction* right, + Primitive::Type packed_type, + size_t vector_length, + bool is_unsigned, + bool is_rounded, + uint32_t dex_pc = kNoDexPc) + : HVecBinaryOperation(arena, packed_type, vector_length, dex_pc), + is_unsigned_(is_unsigned), + is_rounded_(is_rounded) { + DCHECK(left->IsVecOperation() && right->IsVecOperation()); + DCHECK_EQ(left->AsVecOperation()->GetPackedType(), packed_type); + DCHECK_EQ(right->AsVecOperation()->GetPackedType(), packed_type); + SetRawInputAt(0, left); + SetRawInputAt(1, right); + } + + bool IsUnsigned() const { return is_unsigned_; } + bool IsRounded() const { return is_rounded_; } + + DECLARE_INSTRUCTION(VecHalvingAdd); + + private: + bool is_unsigned_; + bool is_rounded_; + + DISALLOW_COPY_AND_ASSIGN(HVecHalvingAdd); +}; + // Subtracts every component in the two vectors, // viz. [ x1, .. , xn ] - [ y1, .. , yn ] = [ x1 - y1, .. , xn - yn ]. class HVecSub FINAL : public HVecBinaryOperation { @@ -404,6 +440,50 @@ class HVecDiv FINAL : public HVecBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HVecDiv); }; +// Takes minimum of every component in the two vectors, +// viz. MIN( [ x1, .. , xn ] , [ y1, .. , yn ]) = [ min(x1, y1), .. , min(xn, yn) ]. +class HVecMin FINAL : public HVecBinaryOperation { + public: + HVecMin(ArenaAllocator* arena, + HInstruction* left, + HInstruction* right, + Primitive::Type packed_type, + size_t vector_length, + uint32_t dex_pc = kNoDexPc) + : HVecBinaryOperation(arena, packed_type, vector_length, dex_pc) { + DCHECK(left->IsVecOperation() && right->IsVecOperation()); + DCHECK_EQ(left->AsVecOperation()->GetPackedType(), packed_type); + DCHECK_EQ(right->AsVecOperation()->GetPackedType(), packed_type); + SetRawInputAt(0, left); + SetRawInputAt(1, right); + } + DECLARE_INSTRUCTION(VecMin); + private: + DISALLOW_COPY_AND_ASSIGN(HVecMin); +}; + +// Takes maximum of every component in the two vectors, +// viz. MAX( [ x1, .. , xn ] , [ y1, .. , yn ]) = [ max(x1, y1), .. , max(xn, yn) ]. +class HVecMax FINAL : public HVecBinaryOperation { + public: + HVecMax(ArenaAllocator* arena, + HInstruction* left, + HInstruction* right, + Primitive::Type packed_type, + size_t vector_length, + uint32_t dex_pc = kNoDexPc) + : HVecBinaryOperation(arena, packed_type, vector_length, dex_pc) { + DCHECK(left->IsVecOperation() && right->IsVecOperation()); + DCHECK_EQ(left->AsVecOperation()->GetPackedType(), packed_type); + DCHECK_EQ(right->AsVecOperation()->GetPackedType(), packed_type); + SetRawInputAt(0, left); + SetRawInputAt(1, right); + } + DECLARE_INSTRUCTION(VecMax); + private: + DISALLOW_COPY_AND_ASSIGN(HVecMax); +}; + // Bitwise-ands every component in the two vectors, // viz. [ x1, .. , xn ] & [ y1, .. , yn ] = [ x1 & y1, .. , xn & yn ]. class HVecAnd FINAL : public HVecBinaryOperation { diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc index 0cff44d830..57223b52a3 100644 --- a/compiler/utils/mips64/assembler_mips64.cc +++ b/compiler/utils/mips64/assembler_mips64.cc @@ -1703,6 +1703,7 @@ void Mips64Assembler::Addiu32(GpuRegister rt, GpuRegister rs, int32_t value) { // TODO: don't use rtmp, use daui, dahi, dati. void Mips64Assembler::Daddiu64(GpuRegister rt, GpuRegister rs, int64_t value, GpuRegister rtmp) { + CHECK_NE(rs, rtmp); if (IsInt<16>(value)) { Daddiu(rt, rs, value); } else { diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index fa7e98586c..3b6b9cc7f0 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -207,9 +207,9 @@ class VerifierDepsTest : public CommonCompilerTest { ScopedObjectAccess soa(Thread::Current()); LoadDexFile(&soa); mirror::Class* klass_dst = FindClassByName(dst, &soa); - DCHECK(klass_dst != nullptr); + DCHECK(klass_dst != nullptr) << dst; mirror::Class* klass_src = FindClassByName(src, &soa); - DCHECK(klass_src != nullptr); + DCHECK(klass_src != nullptr) << src; verifier_deps_->AddAssignability(*primary_dex_file_, klass_dst, klass_src, @@ -1536,5 +1536,16 @@ TEST_F(VerifierDepsTest, NotAssignable_InterfaceWithClassInBoot) { ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "LIface;", false)); } +TEST_F(VerifierDepsTest, Assignable_Arrays) { + ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "[LIface;", + /* src */ "[LMyClassExtendingInterface;", + /* is_strict */ false, + /* is_assignable */ true)); + ASSERT_FALSE(HasAssignable( + "LIface;", "LMyClassExtendingInterface;", /* expected_is_assignable */ true)); + ASSERT_FALSE(HasAssignable( + "LIface;", "LMyClassExtendingInterface;", /* expected_is_assignable */ false)); +} + } // namespace verifier } // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 81566c40e9..a9108e0eb0 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -385,6 +385,8 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" This option is incompatible with read barriers (e.g., if dex2oat has been"); UsageError(" built with the environment variable `ART_USE_READ_BARRIER` set to `true`)."); UsageError(""); + UsageError(" --classpath-dir=<directory-path>: directory used to resolve relative class paths."); + UsageError(""); std::cerr << "See log for usage error information\n"; exit(EXIT_FAILURE); } @@ -1234,6 +1236,8 @@ class Dex2Oat FINAL { Usage("Cannot use --force-determinism with read barriers or non-CMS garbage collector"); } force_determinism_ = true; + } else if (option.starts_with("--classpath-dir=")) { + classpath_dir_ = option.substr(strlen("--classpath-dir=")).data(); } else if (!compiler_options_->ParseCompilerOption(option, Usage)) { Usage("Unknown argument %s", option.data()); } @@ -1486,12 +1490,13 @@ class Dex2Oat FINAL { } // Open dex files for class path. - const std::vector<std::string> class_path_locations = + std::vector<std::string> class_path_locations = GetClassPathLocations(runtime_->GetClassPathString()); OpenClassPathFiles(class_path_locations, &class_path_files_, &opened_oat_files_, - runtime_->GetInstructionSet()); + runtime_->GetInstructionSet(), + classpath_dir_); // Store the classpath we have right now. std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_); @@ -1501,7 +1506,7 @@ class Dex2Oat FINAL { // When passing the special shared library as the classpath, it is the only path. encoded_class_path = OatFile::kSpecialSharedLibrary; } else { - encoded_class_path = OatFile::EncodeDexFileDependencies(class_path_files); + encoded_class_path = OatFile::EncodeDexFileDependencies(class_path_files, classpath_dir_); } key_value_store_->Put(OatHeader::kClassPathKey, encoded_class_path); } @@ -2180,18 +2185,23 @@ class Dex2Oat FINAL { // Opens requested class path files and appends them to opened_dex_files. If the dex files have // been stripped, this opens them from their oat files and appends them to opened_oat_files. - static void OpenClassPathFiles(const std::vector<std::string>& class_path_locations, + static void OpenClassPathFiles(std::vector<std::string>& class_path_locations, std::vector<std::unique_ptr<const DexFile>>* opened_dex_files, std::vector<std::unique_ptr<OatFile>>* opened_oat_files, - InstructionSet isa) { + InstructionSet isa, + std::string& classpath_dir) { DCHECK(opened_dex_files != nullptr) << "OpenClassPathFiles dex out-param is nullptr"; DCHECK(opened_oat_files != nullptr) << "OpenClassPathFiles oat out-param is nullptr"; - for (const std::string& location : class_path_locations) { + for (std::string& location : class_path_locations) { // Stop early if we detect the special shared library, which may be passed as the classpath // for dex2oat when we want to skip the shared libraries check. if (location == OatFile::kSpecialSharedLibrary) { break; } + // If path is relative, append it to the provided base directory. + if (!classpath_dir.empty() && location[0] != '/') { + location = classpath_dir + '/' + location; + } static constexpr bool kVerifyChecksum = true; std::string error_msg; if (!DexFile::Open( @@ -2743,6 +2753,9 @@ class Dex2Oat FINAL { // See CompilerOptions.force_determinism_. bool force_determinism_; + // Directory of relative classpaths. + std::string classpath_dir_; + // Whether the given input vdex is also the output. bool update_input_vdex_ = false; diff --git a/runtime/Android.bp b/runtime/Android.bp index 6c3bc0450b..8972e91321 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -37,6 +37,7 @@ cc_defaults { "base/hex_dump.cc", "base/logging.cc", "base/mutex.cc", + "base/safe_copy.cc", "base/scoped_arena_allocator.cc", "base/scoped_flock.cc", "base/stringpiece.cc", @@ -522,6 +523,7 @@ art_cc_test { "base/hex_dump_test.cc", "base/histogram_test.cc", "base/mutex_test.cc", + "base/safe_copy_test.cc", "base/scoped_flock_test.cc", "base/time_utils_test.cc", "base/timing_logger_test.cc", diff --git a/runtime/arch/x86/fault_handler_x86.cc b/runtime/arch/x86/fault_handler_x86.cc index f407ebf1d1..014cc15a40 100644 --- a/runtime/arch/x86/fault_handler_x86.cc +++ b/runtime/arch/x86/fault_handler_x86.cc @@ -25,6 +25,7 @@ #include "globals.h" #include "base/logging.h" #include "base/hex_dump.h" +#include "base/safe_copy.h" #include "thread.h" #include "thread-inl.h" @@ -78,6 +79,30 @@ extern "C" void art_quick_test_suspend(); // Get the size of an instruction in bytes. // Return 0 if the instruction is not handled. static uint32_t GetInstructionSize(const uint8_t* pc) { + // Don't segfault if pc points to garbage. + char buf[15]; // x86/x86-64 have a maximum instruction length of 15 bytes. + ssize_t bytes = SafeCopy(buf, pc, sizeof(buf)); + + if (bytes == 0) { + // Nothing was readable. + return 0; + } + + if (bytes == -1) { + // SafeCopy not supported, assume that the entire range is readable. + bytes = 16; + } else { + pc = reinterpret_cast<uint8_t*>(buf); + } + +#define INCREMENT_PC() \ + do { \ + pc++; \ + if (pc - startpc > bytes) { \ + return 0; \ + } \ + } while (0) + #if defined(__x86_64) const bool x86_64 = true; #else @@ -86,7 +111,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { const uint8_t* startpc = pc; - uint8_t opcode = *pc++; + uint8_t opcode = *pc; + INCREMENT_PC(); uint8_t modrm; bool has_modrm = false; bool two_byte = false; @@ -118,7 +144,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { // Group 4 case 0x67: - opcode = *pc++; + opcode = *pc; + INCREMENT_PC(); prefix_present = true; break; } @@ -128,13 +155,15 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { } if (x86_64 && opcode >= 0x40 && opcode <= 0x4f) { - opcode = *pc++; + opcode = *pc; + INCREMENT_PC(); } if (opcode == 0x0f) { // Two byte opcode two_byte = true; - opcode = *pc++; + opcode = *pc; + INCREMENT_PC(); } bool unhandled_instruction = false; @@ -147,7 +176,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { case 0xb7: case 0xbe: // movsx case 0xbf: - modrm = *pc++; + modrm = *pc; + INCREMENT_PC(); has_modrm = true; break; default: @@ -166,28 +196,32 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { case 0x3c: case 0x3d: case 0x85: // test. - modrm = *pc++; + modrm = *pc; + INCREMENT_PC(); has_modrm = true; break; case 0x80: // group 1, byte immediate. case 0x83: case 0xc6: - modrm = *pc++; + modrm = *pc; + INCREMENT_PC(); has_modrm = true; immediate_size = 1; break; case 0x81: // group 1, word immediate. case 0xc7: // mov - modrm = *pc++; + modrm = *pc; + INCREMENT_PC(); has_modrm = true; immediate_size = operand_size_prefix ? 2 : 4; break; case 0xf6: case 0xf7: - modrm = *pc++; + modrm = *pc; + INCREMENT_PC(); has_modrm = true; switch ((modrm >> 3) & 7) { // Extract "reg/opcode" from "modr/m". case 0: // test @@ -222,7 +256,7 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { // Check for SIB. if (mod != 3U /* 0b11 */ && (modrm & 7U /* 0b111 */) == 4) { - ++pc; // SIB + INCREMENT_PC(); // SIB } switch (mod) { @@ -238,6 +272,9 @@ static uint32_t GetInstructionSize(const uint8_t* pc) { pc += displacement_size + immediate_size; VLOG(signals) << "x86 instruction length calculated as " << (pc - startpc); + if (pc - startpc > bytes) { + return 0; + } return pc - startpc; } diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 5a71be6eb9..76fdd43992 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -43,6 +43,7 @@ #include "mirror/object-inl.h" #include "mirror/string.h" #include "oat_file-inl.h" +#include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" #include "well_known_classes.h" @@ -372,20 +373,25 @@ void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* self->PopManagedStackFragment(fragment); } -void ArtMethod::RegisterNative(const void* native_method, bool is_fast) { +const void* ArtMethod::RegisterNative(const void* native_method, bool is_fast) { CHECK(IsNative()) << PrettyMethod(); CHECK(!IsFastNative()) << PrettyMethod(); CHECK(native_method != nullptr) << PrettyMethod(); if (is_fast) { AddAccessFlags(kAccFastNative); } - SetEntryPointFromJni(native_method); + void* new_native_method = nullptr; + Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this, + native_method, + /*out*/&new_native_method); + SetEntryPointFromJni(new_native_method); + return new_native_method; } void ArtMethod::UnregisterNative() { CHECK(IsNative() && !IsFastNative()) << PrettyMethod(); // restore stub to lookup native pointer via dlsym - RegisterNative(GetJniDlsymLookupStub(), false); + SetEntryPointFromJni(GetJniDlsymLookupStub()); } bool ArtMethod::IsOverridableByDefaultMethod() { diff --git a/runtime/art_method.h b/runtime/art_method.h index 51b65760a1..b01b344bda 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -398,8 +398,10 @@ class ArtMethod FINAL { pointer_size); } - void RegisterNative(const void* native_method, bool is_fast) - REQUIRES_SHARED(Locks::mutator_lock_); + // Registers the native method and returns the new entry point. NB The returned entry point might + // be different from the native_method argument if some MethodCallback modifies it. + const void* RegisterNative(const void* native_method, bool is_fast) + REQUIRES_SHARED(Locks::mutator_lock_) WARN_UNUSED; void UnregisterNative() REQUIRES_SHARED(Locks::mutator_lock_); @@ -744,6 +746,16 @@ class ArtMethod FINAL { DISALLOW_COPY_AND_ASSIGN(ArtMethod); // Need to use CopyFrom to deal with 32 vs 64 bits. }; +class MethodCallback { + public: + virtual ~MethodCallback() {} + + virtual void RegisterNativeMethod(ArtMethod* method, + const void* original_implementation, + /*out*/void** new_implementation) + REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + } // namespace art #endif // ART_RUNTIME_ART_METHOD_H_ diff --git a/runtime/base/safe_copy.cc b/runtime/base/safe_copy.cc new file mode 100644 index 0000000000..06249acb44 --- /dev/null +++ b/runtime/base/safe_copy.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "safe_copy.h" + +#include <unistd.h> +#include <sys/uio.h> +#include <sys/user.h> + +#include <algorithm> + +#include <android-base/macros.h> + +#include "runtime/base/bit_utils.h" + +namespace art { + +ssize_t SafeCopy(void *dst, const void *src, size_t len) { +#if defined(__linux__) + struct iovec dst_iov = { + .iov_base = dst, + .iov_len = len, + }; + + // Split up the remote read across page boundaries. + // From the manpage: + // A partial read/write may result if one of the remote_iov elements points to an invalid + // memory region in the remote process. + // + // Partial transfers apply at the granularity of iovec elements. These system calls won't + // perform a partial transfer that splits a single iovec element. + constexpr size_t kMaxIovecs = 64; + struct iovec src_iovs[kMaxIovecs]; + size_t iovecs_used = 0; + + const char* cur = static_cast<const char*>(src); + while (len > 0) { + if (iovecs_used == kMaxIovecs) { + errno = EINVAL; + return -1; + } + + src_iovs[iovecs_used].iov_base = const_cast<char*>(cur); + if (!IsAlignedParam(cur, PAGE_SIZE)) { + src_iovs[iovecs_used].iov_len = AlignUp(cur, PAGE_SIZE) - cur; + } else { + src_iovs[iovecs_used].iov_len = PAGE_SIZE; + } + + src_iovs[iovecs_used].iov_len = std::min(src_iovs[iovecs_used].iov_len, len); + + len -= src_iovs[iovecs_used].iov_len; + cur += src_iovs[iovecs_used].iov_len; + ++iovecs_used; + } + + ssize_t rc = process_vm_readv(getpid(), &dst_iov, 1, src_iovs, iovecs_used, 0); + if (rc == -1) { + return 0; + } + return rc; +#else + UNUSED(dst, src, len); + return -1; +#endif +} + +} // namespace art diff --git a/test/912-classes/src-ex/C.java b/runtime/base/safe_copy.h index 97f8021486..d0f497c0bd 100644 --- a/test/912-classes/src-ex/C.java +++ b/runtime/base/safe_copy.h @@ -14,5 +14,18 @@ * limitations under the License. */ -public class C extends A { -} +#ifndef ART_RUNTIME_BASE_SAFE_COPY_H_ +#define ART_RUNTIME_BASE_SAFE_COPY_H_ + +#include <sys/types.h> + +namespace art { + +// Safely dereference a pointer. +// Returns -1 if safe copy isn't implemented on the platform, or if the transfer is too large. +// Returns 0 if src is unreadable. +ssize_t SafeCopy(void *dst, const void *src, size_t len); + +} // namespace art + +#endif // ART_RUNTIME_BASE_SAFE_COPY_H_ diff --git a/runtime/base/safe_copy_test.cc b/runtime/base/safe_copy_test.cc new file mode 100644 index 0000000000..987895e6b7 --- /dev/null +++ b/runtime/base/safe_copy_test.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "safe_copy.h" + +#include "common_runtime_test.h" + +#include <errno.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/user.h> + +namespace art { + +#if defined(__linux__) + +TEST(SafeCopyTest, smoke) { + // Map four pages, mark the second one as PROT_NONE, unmap the last one. + void* map = mmap(nullptr, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, map); + char* page1 = static_cast<char*>(map); + char* page2 = page1 + PAGE_SIZE; + char* page3 = page2 + PAGE_SIZE; + char* page4 = page3 + PAGE_SIZE; + ASSERT_EQ(0, mprotect(page1 + PAGE_SIZE, PAGE_SIZE, PROT_NONE)); + ASSERT_EQ(0, munmap(page4, PAGE_SIZE)); + + page1[0] = 'a'; + page1[PAGE_SIZE - 1] = 'z'; + + page3[0] = 'b'; + page3[PAGE_SIZE - 1] = 'y'; + + char buf[PAGE_SIZE]; + + // Completely valid read. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE), SafeCopy(buf, page1, PAGE_SIZE)) << strerror(errno); + EXPECT_EQ(0, memcmp(buf, page1, PAGE_SIZE)); + + // Reading into a guard page. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE - 1), SafeCopy(buf, page1 + 1, PAGE_SIZE)); + EXPECT_EQ(0, memcmp(buf, page1 + 1, PAGE_SIZE - 1)); + + // Reading from a guard page into a real page. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(0, SafeCopy(buf, page2 + PAGE_SIZE - 1, PAGE_SIZE)); + + // Reading off of the end of a mapping. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE), SafeCopy(buf, page3, PAGE_SIZE * 2)); + EXPECT_EQ(0, memcmp(buf, page3, PAGE_SIZE)); + + // Completely invalid. + EXPECT_EQ(0, SafeCopy(buf, page1 + PAGE_SIZE, PAGE_SIZE)); + + // Clean up. + ASSERT_EQ(0, munmap(map, PAGE_SIZE * 3)); +} + +TEST(SafeCopyTest, alignment) { + // Copy the middle of a mapping to the end of another one. + void* src_map = mmap(nullptr, PAGE_SIZE * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, src_map); + + // Add a guard page to make sure we don't write past the end of the mapping. + void* dst_map = mmap(nullptr, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, dst_map); + + char* src = static_cast<char*>(src_map); + char* dst = static_cast<char*>(dst_map); + ASSERT_EQ(0, mprotect(dst + 3 * PAGE_SIZE, PAGE_SIZE, PROT_NONE)); + + src[512] = 'a'; + src[PAGE_SIZE * 3 - 512 - 1] = 'z'; + + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE * 3 - 1024), + SafeCopy(dst + 1024, src + 512, PAGE_SIZE * 3 - 1024)); + EXPECT_EQ(0, memcmp(dst + 1024, src + 512, PAGE_SIZE * 3 - 1024)); + + ASSERT_EQ(0, munmap(src_map, PAGE_SIZE * 3)); + ASSERT_EQ(0, munmap(dst_map, PAGE_SIZE * 4)); +} + +#endif // defined(__linux__) + +} // namespace art diff --git a/runtime/entrypoints/jni/jni_entrypoints.cc b/runtime/entrypoints/jni/jni_entrypoints.cc index fd23ced7f7..546e59d223 100644 --- a/runtime/entrypoints/jni/jni_entrypoints.cc +++ b/runtime/entrypoints/jni/jni_entrypoints.cc @@ -25,10 +25,10 @@ namespace art { // Used by the JNI dlsym stub to find the native method to invoke if none is registered. #if defined(__arm__) || defined(__aarch64__) -extern "C" void* artFindNativeMethod() { +extern "C" const void* artFindNativeMethod() { Thread* self = Thread::Current(); #else -extern "C" void* artFindNativeMethod(Thread* self) { +extern "C" const void* artFindNativeMethod(Thread* self) { DCHECK_EQ(self, Thread::Current()); #endif Locks::mutator_lock_->AssertNotHeld(self); // We come here as Native. @@ -45,8 +45,7 @@ extern "C" void* artFindNativeMethod(Thread* self) { return nullptr; } else { // Register so that future calls don't come here - method->RegisterNative(native_code, false); - return native_code; + return method->RegisterNative(native_code, false); } } diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 354ae205e8..2b349e39a0 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -2025,9 +2025,9 @@ void BuildGenericJniFrameVisitor::FinalizeHandleScope(Thread* self) { } #if defined(__arm__) || defined(__aarch64__) -extern "C" void* artFindNativeMethod(); +extern "C" const void* artFindNativeMethod(); #else -extern "C" void* artFindNativeMethod(Thread* self); +extern "C" const void* artFindNativeMethod(Thread* self); #endif static uint64_t artQuickGenericJniEndJNIRef(Thread* self, @@ -2126,7 +2126,7 @@ extern "C" TwoWordReturn artQuickGenericJniTrampoline(Thread* self, ArtMethod** } // Retrieve the stored native code. - void* nativeCode = called->GetEntryPointFromJni(); + void const* nativeCode = called->GetEntryPointFromJni(); // There are two cases for the content of nativeCode: // 1) Pointer to the native function. diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 4220250c38..7f738bf5a9 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -21,8 +21,10 @@ #include <sys/ucontext.h> #include "art_method-inl.h" +#include "base/safe_copy.h" #include "base/stl_util.h" #include "mirror/class.h" +#include "mirror/object_reference.h" #include "oat_quick_method_header.h" #include "sigchain.h" #include "thread-inl.h" @@ -42,6 +44,82 @@ static bool art_fault_handler(int sig, siginfo_t* info, void* context) { return fault_manager.HandleFault(sig, info, context); } +#if defined(__linux__) + +// Change to verify the safe implementations against the original ones. +constexpr bool kVerifySafeImpls = false; + +// Provide implementations of ArtMethod::GetDeclaringClass and VerifyClassClass that use SafeCopy +// to safely dereference pointers which are potentially garbage. +// Only available on Linux due to availability of SafeCopy. + +static mirror::Class* SafeGetDeclaringClass(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + char* method_declaring_class = + reinterpret_cast<char*>(method) + ArtMethod::DeclaringClassOffset().SizeValue(); + + // ArtMethod::declaring_class_ is a GcRoot<mirror::Class>. + // Read it out into as a CompressedReference directly for simplicity's sake. + mirror::CompressedReference<mirror::Class> cls; + ssize_t rc = SafeCopy(&cls, method_declaring_class, sizeof(cls)); + CHECK_NE(-1, rc); + + if (kVerifySafeImpls) { + mirror::Class* actual_class = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>(); + CHECK_EQ(actual_class, cls.AsMirrorPtr()); + } + + if (rc != sizeof(cls)) { + return nullptr; + } + + return cls.AsMirrorPtr(); +} + +static mirror::Class* SafeGetClass(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) { + char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue(); + + mirror::HeapReference<mirror::Class> cls = + mirror::HeapReference<mirror::Class>::FromMirrorPtr(nullptr); + ssize_t rc = SafeCopy(&cls, obj_cls, sizeof(cls)); + CHECK_NE(-1, rc); + + if (kVerifySafeImpls) { + mirror::Class* actual_class = obj->GetClass<kVerifyNone>(); + CHECK_EQ(actual_class, cls.AsMirrorPtr()); + } + + if (rc != sizeof(cls)) { + return nullptr; + } + + return cls.AsMirrorPtr(); +} + +static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) { + mirror::Class* c_c = SafeGetClass(cls); + bool result = c_c != nullptr && c_c == SafeGetClass(c_c); + + if (kVerifySafeImpls) { + CHECK_EQ(VerifyClassClass(cls), result); + } + + return result; +} + +#else + +static mirror::Class* SafeGetDeclaringClass(ArtMethod* method_obj) + REQUIRES_SHARED(Locks::mutator_lock_) { + return method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>(); +} + +static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) { + return VerifyClassClass(cls); +} +#endif + + FaultManager::FaultManager() : initialized_(false) { sigaction(SIGSEGV, nullptr, &oldaction_); } @@ -191,20 +269,19 @@ bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context, bool che // Verify that the potential method is indeed a method. // TODO: check the GC maps to make sure it's an object. // Check that the class pointer inside the object is not null and is aligned. - // TODO: Method might be not a heap address, and GetClass could fault. // No read barrier because method_obj may not be a real object. - mirror::Class* cls = method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>(); + mirror::Class* cls = SafeGetDeclaringClass(method_obj); if (cls == nullptr) { VLOG(signals) << "not a class"; return false; } + if (!IsAligned<kObjectAlignment>(cls)) { VLOG(signals) << "not aligned"; return false; } - - if (!VerifyClassClass(cls)) { + if (!SafeVerifyClassClass(cls)) { VLOG(signals) << "not a class class"; return false; } diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index 5418d3569e..b146b51033 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -2277,7 +2277,8 @@ class JNI { // TODO: make this a hard register error in the future. } - m->RegisterNative(fnPtr, is_fast); + const void* final_function_ptr = m->RegisterNative(fnPtr, is_fast); + UNUSED(final_function_ptr); } return JNI_OK; } diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 493da271d1..a00674a9fe 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1497,11 +1497,18 @@ CompilerFilter::Filter OatFile::GetCompilerFilter() const { static constexpr char kDexClassPathEncodingSeparator = '*'; -std::string OatFile::EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files) { +std::string OatFile::EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files, + std::string& base_dir) { std::ostringstream out; for (const DexFile* dex_file : dex_files) { - out << dex_file->GetLocation().c_str(); + const std::string& location = dex_file->GetLocation(); + // Find paths that were relative and convert them back from absolute. + if (!base_dir.empty() && location.substr(0, base_dir.length()) == base_dir) { + out << location.substr(base_dir.length() + 1).c_str(); + } else { + out << dex_file->GetLocation().c_str(); + } out << kDexClassPathEncodingSeparator; out << dex_file->GetLocationChecksum(); out << kDexClassPathEncodingSeparator; diff --git a/runtime/oat_file.h b/runtime/oat_file.h index d24283afee..06c76b5464 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -288,7 +288,9 @@ class OatFile { const char* abs_dex_location, const std::string& rel_dex_location); // Create a dependency list (dex locations and checksums) for the given dex files. - static std::string EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files); + // Removes dex file paths prefixed with base_dir to convert them back to relative paths. + static std::string EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files, + std::string& base_dir); // Finds the associated oat class for a dex_file and descriptor. Returns an invalid OatClass on // error and sets found to false. diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index a950980e6b..139022210c 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -440,8 +440,12 @@ static bool AreSharedLibrariesOk(const std::string& shared_libraries, return false; } + // Check that the loaded dex files have the same order and checksums as the shared libraries. for (size_t i = 0; i < dex_files.size(); ++i) { - if (dex_files[i]->GetLocation() != shared_libraries_split[i * 2]) { + std::string absolute_library_path = + OatFile::ResolveRelativeEncodedDexLocation(dex_files[i]->GetLocation().c_str(), + shared_libraries_split[i * 2]); + if (dex_files[i]->GetLocation() != absolute_library_path) { return false; } char* end; diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 39e603e1e7..c3a94b93a0 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -1556,6 +1556,7 @@ extern "C" bool ArtPlugin_Initialize() { ThreadUtil::Register(&gEventHandler); ClassUtil::Register(&gEventHandler); DumpUtil::Register(&gEventHandler); + MethodUtil::Register(&gEventHandler); SearchUtil::Register(); HeapUtil::Register(); @@ -1569,6 +1570,7 @@ extern "C" bool ArtPlugin_Deinitialize() { ThreadUtil::Unregister(); ClassUtil::Unregister(); DumpUtil::Unregister(); + MethodUtil::Unregister(); SearchUtil::Unregister(); HeapUtil::Unregister(); diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 2ff3a478c4..2a2aa4c199 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -223,7 +223,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_compiled_method_load_events = 0, .can_generate_monitor_events = 0, .can_generate_vm_object_alloc_events = 1, - .can_generate_native_method_bind_events = 0, + .can_generate_native_method_bind_events = 1, .can_generate_garbage_collection_events = 1, .can_generate_object_free_events = 1, .can_force_early_return = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 233b45cda8..57abf3142d 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -191,6 +191,27 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A } } +// Need to give a custom specialization for NativeMethodBind since it has to deal with an out +// variable. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID method, + void* cur_method, + void** new_method) const { + *new_method = cur_method; + for (ArtJvmTiEnv* env : envs) { + if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) { + auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env); + (*callback)(env, jnienv, jni_thread, method, cur_method, new_method); + if (*new_method != nullptr) { + cur_method = *new_method; + } + } + } +} + // C++ does not allow partial template function specialization. The dispatch for our separated // ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper. // The following two DispatchEvent specializations dispatch to it. diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc index 01bf21d53e..2adabbaff4 100644 --- a/runtime/openjdkjvmti/ti_method.cc +++ b/runtime/openjdkjvmti/ti_method.cc @@ -35,14 +35,59 @@ #include "art_method-inl.h" #include "base/enums.h" #include "dex_file_annotations.h" +#include "events-inl.h" #include "jni_internal.h" #include "mirror/object_array-inl.h" #include "modifiers.h" +#include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" #include "thread-inl.h" +#include "thread_list.h" namespace openjdkjvmti { +struct TiMethodCallback : public art::MethodCallback { + void RegisterNativeMethod(art::ArtMethod* method, + const void* cur_method, + /*out*/void** new_method) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) { + art::Thread* thread = art::Thread::Current(); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); + art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + art::jni::EncodeArtMethod(method), + const_cast<void*>(cur_method), + new_method); + } + } + + EventHandler* event_handler = nullptr; +}; + +TiMethodCallback gMethodCallback; + +void MethodUtil::Register(EventHandler* handler) { + gMethodCallback.event_handler = handler; + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add method callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback); +} + +void MethodUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove method callback"); + art::Runtime* runtime = art::Runtime::Current(); + runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback); +} + jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED, jmethodID method, jint* size_ptr) { diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h index e5c1705ada..cc161c8fed 100644 --- a/runtime/openjdkjvmti/ti_method.h +++ b/runtime/openjdkjvmti/ti_method.h @@ -37,8 +37,13 @@ namespace openjdkjvmti { +class EventHandler; + class MethodUtil { public: + static void Register(EventHandler* event_handler); + static void Unregister(); + static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr); static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr); diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc index 25324b52d1..16d6c13722 100644 --- a/runtime/runtime_callbacks.cc +++ b/runtime/runtime_callbacks.cc @@ -18,6 +18,7 @@ #include <algorithm> +#include "art_method.h" #include "base/macros.h" #include "class_linker.h" #include "thread.h" @@ -131,4 +132,25 @@ void RuntimeCallbacks::NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase } } +void RuntimeCallbacks::AddMethodCallback(MethodCallback* cb) { + method_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveMethodCallback(MethodCallback* cb) { + Remove(cb, &method_callbacks_); +} + +void RuntimeCallbacks::RegisterNativeMethod(ArtMethod* method, + const void* in_cur_method, + /*out*/void** new_method) { + void* cur_method = const_cast<void*>(in_cur_method); + *new_method = cur_method; + for (MethodCallback* cb : method_callbacks_) { + cb->RegisterNativeMethod(method, cur_method, new_method); + if (*new_method != nullptr) { + cur_method = *new_method; + } + } +} + } // namespace art diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h index d321254e17..e8f1824262 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -31,8 +31,10 @@ class Class; class ClassLoader; } // namespace mirror +class ArtMethod; class ClassLoadCallback; class Thread; +class MethodCallback; class ThreadLifecycleCallback; // Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must @@ -110,6 +112,14 @@ class RuntimeCallbacks { /*out*/DexFile::ClassDef const** final_class_def) REQUIRES_SHARED(Locks::mutator_lock_); + void AddMethodCallback(MethodCallback* cb) REQUIRES(Locks::mutator_lock_); + void RemoveMethodCallback(MethodCallback* cb) REQUIRES(Locks::mutator_lock_); + + void RegisterNativeMethod(ArtMethod* method, + const void* original_implementation, + /*out*/void** new_implementation) + REQUIRES_SHARED(Locks::mutator_lock_); + private: std::vector<ThreadLifecycleCallback*> thread_callbacks_ GUARDED_BY(Locks::mutator_lock_); @@ -118,7 +128,9 @@ class RuntimeCallbacks { std::vector<RuntimeSigQuitCallback*> sigquit_callbacks_ GUARDED_BY(Locks::mutator_lock_); std::vector<RuntimePhaseCallback*> phase_callbacks_ - GUARDED_BY(Locks::mutator_lock_); + GUARDED_BY(Locks::mutator_lock_); + std::vector<MethodCallback*> method_callbacks_ + GUARDED_BY(Locks::mutator_lock_); }; } // namespace art diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc index 8e4c166492..55e793527e 100644 --- a/runtime/verifier/verifier_deps.cc +++ b/runtime/verifier/verifier_deps.cc @@ -428,8 +428,6 @@ void VerifierDeps::AddAssignability(const DexFile& dex_file, return; } - DCHECK_EQ(is_assignable, destination->IsAssignableFrom(source)); - if (destination->IsArrayClass() && source->IsArrayClass()) { // Both types are arrays. Break down to component types and add recursively. // This helps filter out destinations from compiled DEX files (see below) @@ -447,6 +445,10 @@ void VerifierDeps::AddAssignability(const DexFile& dex_file, is_assignable); return; } + } else { + // We only do this check for non-array types, as arrays might have erroneous + // component types which makes the IsAssignableFrom check unreliable. + DCHECK_EQ(is_assignable, destination->IsAssignableFrom(source)); } DexFileDeps* dex_deps = GetDexFileDeps(dex_file); diff --git a/test/646-checker-hadd-alt-byte/expected.txt b/test/646-checker-hadd-alt-byte/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-alt-byte/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-alt-byte/info.txt b/test/646-checker-hadd-alt-byte/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-alt-byte/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-alt-byte/src/Main.java b/test/646-checker-hadd-alt-byte/src/Main.java new file mode 100644 index 0000000000..d1b33ea0da --- /dev/null +++ b/test/646-checker-hadd-alt-byte/src/Main.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + * + * Alternative version expressed with logical shift right + * in the higher precision (has no impact on idiom). + */ +public class Main { + + private static final int N = 256; + private static final int M = N * N + 15; + + static byte[] sB1 = new byte[M]; + static byte[] sB2 = new byte[M]; + static byte[] sBo = new byte[M]; + + /// CHECK-START: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + b2[i]) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + (b2[i] & 0xff)) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_signed(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + b2[i] + 1) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + (b2[i] & 0xff) + 1) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<I127>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed_constant(byte[] b1, byte[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + 0x7f) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(byte[] b1, byte[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + 0xff) >>> 1); + } + } + + public static void main(String[] args) { + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + int k = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + sB1[k] = (byte) i; + sB2[k] = (byte) j; + k++; + } + } + for (int i = 0; i < 15; i++) { + sB1[k] = (byte) i; + sB2[k] = 100; + k++; + } + expectEquals(k, M); + + // Test halving add idioms. Note that the expected result is computed + // with the arithmetic >> to demonstrate the computed narrower result + // does not depend on the wider >> or >>>. + halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + (sB2[i] & 0xff)) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + (sB2[i] & 0xff) + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_signed_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + 0x7f) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + 0xff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/646-checker-hadd-alt-char/expected.txt b/test/646-checker-hadd-alt-char/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-alt-char/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-alt-char/info.txt b/test/646-checker-hadd-alt-char/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-alt-char/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-alt-char/src/Main.java b/test/646-checker-hadd-alt-char/src/Main.java new file mode 100644 index 0000000000..1ea8d3fe07 --- /dev/null +++ b/test/646-checker-hadd-alt-char/src/Main.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + * + * Alternative version expressed with logical shift right + * in the higher precision (has no impact on idiom). + */ +public class Main { + + private static final int N = 64 * 1024; + private static final int M = N + 31; + + static char[] sB1 = new char[M]; + static char[] sB2 = new char[M]; + static char[] sBo = new char[M]; + + /// CHECK-START: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + b2[i]) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<IMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void halving_add_also_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + (b2[i] & 0xffff)) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + b2[i] + 1) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<IMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void rounding_halving_add_also_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + (b2[i] & 0xffff) + 1) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(char[] b1, char[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + 0xffff) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void halving_add_also_unsigned_constant(char[] b1, char[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + 0xffff) >>> 1); + } + } + + public static void main(String[] args) { + // Some interesting values. + char[] interesting = { + (char) 0x0000, + (char) 0x0001, + (char) 0x0002, + (char) 0x1234, + (char) 0x8000, + (char) 0x8001, + (char) 0x7fff, + (char) 0xffff + }; + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + for (int i = 0; i < M; i++) { + sB1[i] = (char) i; + sB2[i] = interesting[i & 7]; + } + + // Test halving add idioms. Note that the expected result is computed + // with the arithmetic >> to demonstrate the computed narrower result + // does not depend on the wider >> or >>>. + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_also_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_also_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_also_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/646-checker-hadd-alt-short/expected.txt b/test/646-checker-hadd-alt-short/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-alt-short/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-alt-short/info.txt b/test/646-checker-hadd-alt-short/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-alt-short/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-alt-short/src/Main.java b/test/646-checker-hadd-alt-short/src/Main.java new file mode 100644 index 0000000000..269e6183b4 --- /dev/null +++ b/test/646-checker-hadd-alt-short/src/Main.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + * + * Alternative version expressed with logical shift right + * in the higher precision (has no impact on idiom). + */ +public class Main { + + private static final int N = 64 * 1024; + private static final int M = N + 31; + + static short[] sB1 = new short[M]; + static short[] sB2 = new short[M]; + static short[] sBo = new short[M]; + + /// CHECK-START: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + b2[i]) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + (b2[i] & 0xffff)) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_signed(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + b2[i] + 1) >>> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + (b2[i] & 0xffff) + 1) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<SMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed_constant(short[] b1, short[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + 0x7fff) >>> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<UShr:i\d+>> UShr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<UShr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(short[] b1, short[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + 0xffff) >>> 1); + } + } + + public static void main(String[] args) { + // Some interesting values. + short[] interesting = { + (short) 0x0000, + (short) 0x0001, + (short) 0x0002, + (short) 0x1234, + (short) 0x8000, + (short) 0x8001, + (short) 0x7fff, + (short) 0xffff + }; + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + for (int i = 0; i < M; i++) { + sB1[i] = (short) i; + sB2[i] = interesting[i & 7]; + } + + // Test halving add idioms. Note that the expected result is computed + // with the arithmetic >> to demonstrate the computed narrower result + // does not depend on the wider >> or >>>. + halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + (sB2[i] & 0xffff)) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + (sB2[i] & 0xffff) + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_signed_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + 0x7fff) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/646-checker-hadd-byte/expected.txt b/test/646-checker-hadd-byte/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-byte/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-byte/info.txt b/test/646-checker-hadd-byte/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-byte/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-byte/src/Main.java b/test/646-checker-hadd-byte/src/Main.java new file mode 100644 index 0000000000..7e29a7e60b --- /dev/null +++ b/test/646-checker-hadd-byte/src/Main.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + */ +public class Main { + + private static final int N = 256; + private static final int M = N * N + 15; + + static byte[] sB1 = new byte[M]; + static byte[] sB2 = new byte[M]; + static byte[] sBo = new byte[M]; + + /// CHECK-START: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + b2[i]) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + (b2[i] & 0xff)) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_signed(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + b2[i] + 1) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(byte[] b1, byte[] b2, byte[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + (b2[i] & 0xff) + 1) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<I127>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I127:i\d+>> IntConstant 127 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I127>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed_constant(byte[] b1, byte[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) ((b1[i] + 0x7f) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:b\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<I255>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:b\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<I255:i\d+>> IntConstant 255 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<I255>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(byte[] b1, byte[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (byte) (((b1[i] & 0xff) + 0xff) >> 1); + } + } + + public static void main(String[] args) { + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + int k = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + sB1[k] = (byte) i; + sB2[k] = (byte) j; + k++; + } + } + for (int i = 0; i < 15; i++) { + sB1[k] = (byte) i; + sB2[k] = 100; + k++; + } + expectEquals(k, M); + + // Test halving add idioms. + halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + (sB2[i] & 0xff)) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + (sB2[i] & 0xff) + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_signed_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) ((sB1[i] + 0x7f) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + byte e = (byte) (((sB1[i] & 0xff) + 0xff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/646-checker-hadd-char/expected.txt b/test/646-checker-hadd-char/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-char/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-char/info.txt b/test/646-checker-hadd-char/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-char/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-char/src/Main.java b/test/646-checker-hadd-char/src/Main.java new file mode 100644 index 0000000000..d24608f5af --- /dev/null +++ b/test/646-checker-hadd-char/src/Main.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + */ +public class Main { + + private static final int N = 64 * 1024; + private static final int M = N + 31; + + static char[] sB1 = new char[M]; + static char[] sB2 = new char[M]; + static char[] sBo = new char[M]; + + /// CHECK-START: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + b2[i]) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<IMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void halving_add_also_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + (b2[i] & 0xffff)) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + b2[i] + 1) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<IMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<IMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_also_unsigned(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void rounding_halving_add_also_unsigned(char[] b1, char[] b2, char[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + (b2[i] & 0xffff) + 1) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(char[] b1, char[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) ((b1[i] + 0xffff) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:c\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:c\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_also_unsigned_constant(char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + // + // Note: HAnd has no impact (already a zero extension). + // + private static void halving_add_also_unsigned_constant(char[] b1, char[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (char) (((b1[i] & 0xffff) + 0xffff) >> 1); + } + } + + public static void main(String[] args) { + // Some interesting values. + char[] interesting = { + (char) 0x0000, + (char) 0x0001, + (char) 0x0002, + (char) 0x1234, + (char) 0x8000, + (char) 0x8001, + (char) 0x7fff, + (char) 0xffff + }; + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + for (int i = 0; i < M; i++) { + sB1[i] = (char) i; + sB2[i] = interesting[i & 7]; + } + + // Test halving add idioms. + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_also_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_also_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_also_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + char e = (char) ((sB1[i] + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/646-checker-hadd-short/expected.txt b/test/646-checker-hadd-short/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/646-checker-hadd-short/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/646-checker-hadd-short/info.txt b/test/646-checker-hadd-short/info.txt new file mode 100644 index 0000000000..46e73345d8 --- /dev/null +++ b/test/646-checker-hadd-short/info.txt @@ -0,0 +1 @@ +Functional tests on halving-add SIMD vectorization. diff --git a/test/646-checker-hadd-short/src/Main.java b/test/646-checker-hadd-short/src/Main.java new file mode 100644 index 0000000000..db495f6433 --- /dev/null +++ b/test/646-checker-hadd-short/src/Main.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests for halving-add idiomatic vectorization. + */ +public class Main { + + private static final int N = 64 * 1024; + private static final int M = N + 31; + + static short[] sB1 = new short[M]; + static short[] sB2 = new short[M]; + static short[] sBo = new short[M]; + + /// CHECK-START: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + b2[i]) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + (b2[i] & 0xffff)) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_signed(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:false rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_signed(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + b2[i] + 1) >> 1); + } + } + + /// CHECK-START: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And1:i\d+>> And [<<Get1>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And2:i\d+>> And [<<Get2>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add1:i\d+>> Add [<<And1>>,<<And2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add2:i\d+>> Add [<<Add1>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add2>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.rounding_halving_add_unsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get1>>,<<Get2>>] unsigned:true rounded:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void rounding_halving_add_unsigned(short[] b1, short[] b2, short[] bo) { + int min_length = Math.min(bo.length, Math.min(b1.length, b2.length)); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + (b2[i] & 0xffff) + 1) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Get>>,<<SMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_signed_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<SMAX:i\d+>> IntConstant 32767 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<SMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:false rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_signed_constant(short[] b1, short[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) ((b1[i] + 0x7fff) >> 1); + } + } + + /// CHECK-START: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (before) + /// CHECK-DAG: <<I1:i\d+>> IntConstant 1 loop:none + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:s\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<And:i\d+>> And [<<Get>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<And>>,<<UMAX>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Shr:i\d+>> Shr [<<Add>>,<<I1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Cnv:s\d+>> TypeConversion [<<Shr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Cnv>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.halving_add_unsigned_constant(short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<UMAX:i\d+>> IntConstant 65535 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<UMAX>>] loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<HAdd:d\d+>> VecHalvingAdd [<<Get>>,<<Repl>>] unsigned:true rounded:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<HAdd>>] loop:<<Loop>> outer_loop:none + private static void halving_add_unsigned_constant(short[] b1, short[] bo) { + int min_length = Math.min(bo.length, b1.length); + for (int i = 0; i < min_length; i++) { + bo[i] = (short) (((b1[i] & 0xffff) + 0xffff) >> 1); + } + } + + public static void main(String[] args) { + // Some interesting values. + short[] interesting = { + (short) 0x0000, + (short) 0x0001, + (short) 0x0002, + (short) 0x1234, + (short) 0x8000, + (short) 0x8001, + (short) 0x7fff, + (short) 0xffff + }; + // Initialize cross-values to test all cases, and also + // set up some extra values to exercise the cleanup loop. + for (int i = 0; i < M; i++) { + sB1[i] = (short) i; + sB2[i] = interesting[i & 7]; + } + + // Test halving add idioms. + halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + sB2[i]) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + (sB2[i] & 0xffff)) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_signed(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + sB2[i] + 1) >> 1); + expectEquals(e, sBo[i]); + } + rounding_halving_add_unsigned(sB1, sB2, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + (sB2[i] & 0xffff) + 1) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_signed_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + short e = (short) ((sB1[i] + 0x7fff) >> 1); + expectEquals(e, sBo[i]); + } + halving_add_unsigned_constant(sB1, sBo); + for (int i = 0; i < M; i++) { + short e = (short) (((sB1[i] & 0xffff) + 0xffff) >> 1); + expectEquals(e, sBo[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index 2636367548..869eacd82c 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -16,50 +16,39 @@ #include <stdio.h> +#include <mutex> +#include <vector> + #include "android-base/macros.h" +#include "android-base/stringprintf.h" -#include "class_linker.h" #include "jni.h" -#include "mirror/class_loader.h" #include "jvmti.h" -#include "runtime.h" -#include "scoped_local_ref.h" -#include "scoped_utf_chars.h" -#include "scoped_thread_state_change-inl.h" -#include "thread-inl.h" // Test infrastructure #include "jni_helper.h" #include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "scoped_utf_chars.h" #include "test_env.h" namespace art { namespace Test912Classes { -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isModifiableClass( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isModifiableClass( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jboolean res = JNI_FALSE; jvmtiError result = jvmti_env->IsModifiableClass(klass, &res); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running IsModifiableClass: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return JNI_FALSE; - } + JvmtiErrorToException(env, jvmti_env, result); return res; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassSignature( +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassSignature( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { char* sig; char* gen; jvmtiError result = jvmti_env->GetClassSignature(klass, &sig, &gen); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassSignature: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, jvmti_env, result)) { return nullptr; } @@ -83,57 +72,36 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassSignature( return ret; } -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterface( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isInterface( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jboolean is_interface = JNI_FALSE; jvmtiError result = jvmti_env->IsInterface(klass, &is_interface); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running IsInterface: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return JNI_FALSE; - } + JvmtiErrorToException(env, jvmti_env, result); return is_interface; } -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isArrayClass( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isArrayClass( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jboolean is_array_class = JNI_FALSE; jvmtiError result = jvmti_env->IsArrayClass(klass, &is_array_class); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running IsArrayClass: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return JNI_FALSE; - } + JvmtiErrorToException(env, jvmti_env, result); return is_array_class; } -extern "C" JNIEXPORT jint JNICALL Java_Main_getClassModifiers( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassModifiers( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint mod; jvmtiError result = jvmti_env->GetClassModifiers(klass, &mod); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassModifiers: %s\n", err); - return JNI_FALSE; - } + JvmtiErrorToException(env, jvmti_env, result); return mod; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassFields( +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassFields( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint count = 0; jfieldID* fields = nullptr; jvmtiError result = jvmti_env->GetClassFields(klass, &count, &fields); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassFields: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, jvmti_env, result)) { return nullptr; } @@ -153,15 +121,12 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassFields( return ret; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassMethods( +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassMethods( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint count = 0; jmethodID* methods = nullptr; jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassMethods: %s\n", err); + if (JvmtiErrorToException(env, jvmti_env, result)) { return nullptr; } @@ -181,15 +146,12 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassMethods( return ret; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getImplementedInterfaces( +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getImplementedInterfaces( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint count = 0; jclass* classes = nullptr; jvmtiError result = jvmti_env->GetImplementedInterfaces(klass, &count, &classes); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetImplementedInterfaces: %s\n", err); + if (JvmtiErrorToException(env, jvmti_env, result)) { return nullptr; } @@ -203,35 +165,23 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getImplementedInterfaces( return ret; } -extern "C" JNIEXPORT jint JNICALL Java_Main_getClassStatus( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassStatus( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint status; jvmtiError result = jvmti_env->GetClassStatus(klass, &status); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassStatus: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return JNI_FALSE; - } + JvmtiErrorToException(env, jvmti_env, result); return status; } -extern "C" JNIEXPORT jobject JNICALL Java_Main_getClassLoader( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { +extern "C" JNIEXPORT jobject JNICALL Java_art_Test912_getClassLoader( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jobject classloader; jvmtiError result = jvmti_env->GetClassLoader(klass, &classloader); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetClassLoader: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return nullptr; - } + JvmtiErrorToException(env, jvmti_env, result); return classloader; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassLoaderClasses( +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoaderClasses( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jobject jclassloader) { jint count = 0; jclass* classes = nullptr; @@ -250,7 +200,7 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassLoaderClasses( return ret; } -extern "C" JNIEXPORT jintArray JNICALL Java_Main_getClassVersion( +extern "C" JNIEXPORT jintArray JNICALL Java_art_Test912_getClassVersion( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { jint major, minor; jvmtiError result = jvmti_env->GetClassVersionNumbers(klass, &minor, &major); @@ -325,6 +275,22 @@ static void EnableEvents(JNIEnv* env, JvmtiErrorToException(env, jvmti_env, ret); } +static std::mutex gEventsMutex; +static std::vector<std::string> gEvents; + +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoadMessages( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + std::lock_guard<std::mutex> guard(gEventsMutex); + jobjectArray ret = CreateObjectArray(env, + static_cast<jint>(gEvents.size()), + "java/lang/String", + [&](jint i) { + return env->NewStringUTF(gEvents[i].c_str()); + }); + gEvents.clear(); + return ret; +} + class ClassLoadPreparePrinter { public: static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, @@ -339,7 +305,14 @@ class ClassLoadPreparePrinter { if (thread_name == "") { return; } - printf("Load: %s on %s\n", name.c_str(), thread_name.c_str()); + if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) { + return; + } + + std::lock_guard<std::mutex> guard(gEventsMutex); + gEvents.push_back(android::base::StringPrintf("Load: %s on %s", + name.c_str(), + thread_name.c_str())); } static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, @@ -354,14 +327,18 @@ class ClassLoadPreparePrinter { if (thread_name == "") { return; } - std::string cur_thread_name = GetThreadName(Thread::Current()); - printf("Prepare: %s on %s (cur=%s)\n", - name.c_str(), - thread_name.c_str(), - cur_thread_name.c_str()); + if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) { + return; + } + std::string cur_thread_name = GetThreadName(jenv, jni_env, nullptr); + + std::lock_guard<std::mutex> guard(gEventsMutex); + gEvents.push_back(android::base::StringPrintf("Prepare: %s on %s (cur=%s)", + name.c_str(), + thread_name.c_str(), + cur_thread_name.c_str())); } - private: static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) { jvmtiThreadInfo info; jvmtiError result = jenv->GetThreadInfo(thread, &info); @@ -382,60 +359,28 @@ class ClassLoadPreparePrinter { return tmp; } - static std::string GetThreadName(Thread* thread) { - std::string tmp; - thread->GetThreadName(tmp); - return tmp; - } + static std::string thread_name_filter_; }; +std::string ClassLoadPreparePrinter::thread_name_filter_; + +extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPreparePrintEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable, jthread thread) { + if (thread != nullptr) { + ClassLoadPreparePrinter::thread_name_filter_ = + ClassLoadPreparePrinter::GetThreadName(jvmti_env, env, thread); + } else { + ClassLoadPreparePrinter::thread_name_filter_ = ""; + } -extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPreparePrintEvents( - JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable) { EnableEvents(env, enable, ClassLoadPreparePrinter::ClassLoadCallback, ClassLoadPreparePrinter::ClassPrepareCallback); } -struct ClassLoadSeen { - static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED, - JNIEnv* jni_env ATTRIBUTE_UNUSED, - jthread thread ATTRIBUTE_UNUSED, - jclass klass ATTRIBUTE_UNUSED) { - saw_event = true; - } - - static bool saw_event; -}; -bool ClassLoadSeen::saw_event = false; - -extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadSeenEvents( - JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { - EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr); -} - -extern "C" JNIEXPORT jboolean JNICALL Java_Main_hadLoadEvent( - JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) { - return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE; -} - -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isLoadedClass( - JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) { - ScopedUtfChars name(env, class_name); - ScopedObjectAccess soa(Thread::Current()); - Runtime* current = Runtime::Current(); - ClassLinker* class_linker = current->GetClassLinker(); - bool found = - class_linker->LookupClass( - soa.Self(), - name.c_str(), - soa.Decode<mirror::ClassLoader>(current->GetSystemClassLoader())) != nullptr; - return found ? JNI_TRUE : JNI_FALSE; -} - class ClassLoadPrepareEquality { public: - static constexpr const char* kClassName = "LMain$ClassE;"; + static constexpr const char* kClassName = "Lart/Test912$ClassE;"; static constexpr const char* kStorageFieldName = "STATIC"; static constexpr const char* kStorageFieldSig = "Ljava/lang/Object;"; static constexpr const char* kStorageWeakFieldName = "WEAK"; @@ -553,13 +498,13 @@ jobject ClassLoadPrepareEquality::local_stored_class_ = nullptr; bool ClassLoadPrepareEquality::found_ = false; bool ClassLoadPrepareEquality::compared_ = false; -extern "C" JNIEXPORT void JNICALL Java_Main_setEqualityEventStorageClass( +extern "C" JNIEXPORT void JNICALL Java_art_Test912_setEqualityEventStorageClass( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { ClassLoadPrepareEquality::storage_class_ = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); } -extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPrepareEqualityEvents( +extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPrepareEqualityEvents( JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { EnableEvents(env, b, diff --git a/test/912-classes/classes_art.cc b/test/912-classes/classes_art.cc new file mode 100644 index 0000000000..de2e456a53 --- /dev/null +++ b/test/912-classes/classes_art.cc @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#include <mutex> +#include <vector> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" + +// Test infrastructure +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "scoped_utf_chars.h" +#include "test_env.h" + +namespace art { +namespace Test912ArtClasses { + +static void EnableEvents(JNIEnv* env, + jboolean enable, + decltype(jvmtiEventCallbacks().ClassLoad) class_load, + decltype(jvmtiEventCallbacks().ClassPrepare) class_prepare) { + if (enable == JNI_FALSE) { + jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_LOAD, + nullptr); + if (JvmtiErrorToException(env, jvmti_env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr); + JvmtiErrorToException(env, jvmti_env, ret); + return; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.ClassLoad = class_load; + callbacks.ClassPrepare = class_prepare; + jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (JvmtiErrorToException(env, jvmti_env, ret)) { + return; + } + + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_LOAD, + nullptr); + if (JvmtiErrorToException(env, jvmti_env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr); + JvmtiErrorToException(env, jvmti_env, ret); +} + +struct ClassLoadSeen { + static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jthread thread ATTRIBUTE_UNUSED, + jclass klass ATTRIBUTE_UNUSED) { + saw_event = true; + } + + static bool saw_event; +}; +bool ClassLoadSeen::saw_event = false; + +extern "C" JNIEXPORT void JNICALL Java_art_Test912Art_enableClassLoadSeenEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { + EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_hadLoadEvent( + JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) { + return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_isLoadedClass( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) { + ScopedUtfChars name(env, class_name); + + jint class_count; + jclass* classes; + jvmtiError res = jvmti_env->GetLoadedClasses(&class_count, &classes); + if (JvmtiErrorToException(env, jvmti_env, res)) { + return JNI_FALSE; + } + + bool found = false; + for (jint i = 0; !found && i < class_count; ++i) { + char* sig; + jvmtiError res2 = jvmti_env->GetClassSignature(classes[i], &sig, nullptr); + if (JvmtiErrorToException(env, jvmti_env, res2)) { + return JNI_FALSE; + } + + found = strcmp(name.c_str(), sig) == 0; + + CheckJvmtiError(jvmti_env, jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig))); + } + + CheckJvmtiError(jvmti_env, jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(classes))); + + return found; +} + +// We use the implementations from runtime_state.cc. + +extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env, + jclass, + jclass cls, + jstring method_name); +extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJit(JNIEnv*, jclass); + +extern "C" JNIEXPORT void JNICALL Java_art_Test912Art_ensureJitCompiled( + JNIEnv* env, jclass klass, jclass test_class, jstring name) { + Java_Main_ensureJitCompiled(env, klass, test_class, name); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_hasJit(JNIEnv* env, jclass klass) { + return Java_Main_hasJit(env, klass); +} + +} // namespace Test912ArtClasses +} // namespace art diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt index 0f2920a0c2..9dcc5f9c90 100644 --- a/test/912-classes/expected.txt +++ b/test/912-classes/expected.txt @@ -6,14 +6,14 @@ 11 [Ljava/util/List;, <E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;] 601 -[L$Proxy0;, null] +[L$Proxy20;, null] 11 [I, null] 411 [[D, null] 411 int interface=false array=false modifiable=false -$Proxy0 interface=false array=false modifiable=false +$Proxy20 interface=false array=false modifiable=false java.lang.Runnable interface=true array=false modifiable=false java.lang.String interface=false array=false modifiable=false java.util.ArrayList interface=false array=false modifiable=true @@ -29,70 +29,65 @@ java.util.ArrayList interface=false array=false modifiable=true int 100000 class [Ljava.lang.String; 10000 class java.lang.Object 111 -class Main$TestForNonInit 11 -class Main$TestForInitFail 1011 +class art.Test912$TestForNonInit 11 +class art.Test912$TestForInitFail 1011 int [] class [Ljava.lang.String; [] class java.lang.Object [] -interface Main$InfA [] -interface Main$InfB [interface Main$InfA] -interface Main$InfC [interface Main$InfB] -class Main$ClassA [interface Main$InfA] -class Main$ClassB [interface Main$InfB] -class Main$ClassC [interface Main$InfA, interface Main$InfC] +interface art.Test912$InfA [] +interface art.Test912$InfB [interface art.Test912$InfA] +interface art.Test912$InfC [interface art.Test912$InfB] +class art.Test912$ClassA [interface art.Test912$InfA] +class art.Test912$ClassB [interface art.Test912$InfB] +class art.Test912$ClassC [interface art.Test912$InfA, interface art.Test912$InfC] class java.lang.String null class [Ljava.lang.String; null -interface Main$InfA dalvik.system.PathClassLoader -class $Proxy0 dalvik.system.PathClassLoader +interface art.Test912$InfA dalvik.system.PathClassLoader +class $Proxy20 dalvik.system.PathClassLoader -boot <- src <- src-ex (A,B) -912-classes-ex.jar+ -> 912-classes.jar+ -> +boot <- (B) <- (A,C) [class A, class B, class java.lang.Object] -912-classes.jar+ -> [class B, class java.lang.Object] -boot <- src (B) <- src-ex (A, List) -912-classes-ex.jar+ -> 912-classes.jar+ -> +boot <- (B) <- (A, List) [class A, class java.lang.Object, interface java.util.List] -912-classes.jar+ -> [class B, class java.lang.Object] -boot <- src+src-ex (A,B) -912-classes.jar+ -> +boot <- 1+2 (A,B) [class A, class B, class java.lang.Object] [37, 0] B, false -Load: LB; on main -Prepare: LB; on main (cur=main) +Load: LB; on ClassEvents +Prepare: LB; on ClassEvents (cur=ClassEvents) B, true -Load: LB; on main -Prepare: LB; on main (cur=main) +Load: LB; on ClassEvents +Prepare: LB; on ClassEvents (cur=ClassEvents) C, false -Load: LA; on main -Prepare: LA; on main (cur=main) -Load: LC; on main -Prepare: LC; on main (cur=main) +Load: LA; on ClassEvents +Prepare: LA; on ClassEvents (cur=ClassEvents) +Load: LC; on ClassEvents +Prepare: LC; on ClassEvents (cur=ClassEvents) A, false C, true -Load: LA; on main -Prepare: LA; on main (cur=main) -Load: LC; on main -Prepare: LC; on main (cur=main) +Load: LA; on ClassEvents +Prepare: LA; on ClassEvents (cur=ClassEvents) +Load: LC; on ClassEvents +Prepare: LC; on ClassEvents (cur=ClassEvents) A, true A, true -Load: LA; on main -Prepare: LA; on main (cur=main) +Load: LA; on ClassEvents +Prepare: LA; on ClassEvents (cur=ClassEvents) C, true -Load: LC; on main -Prepare: LC; on main (cur=main) +Load: LC; on ClassEvents +Prepare: LC; on ClassEvents (cur=ClassEvents) C, true Load: LA; on TestRunner Prepare: LA; on TestRunner (cur=TestRunner) Load: LC; on TestRunner Prepare: LC; on TestRunner (cur=TestRunner) -Load: L$Proxy1; on main -Prepare: L$Proxy1; on main (cur=main) -Load: [LMain; on main -Prepare: [LMain; on main (cur=main) +Load: L$Proxy21; on ClassEvents +Prepare: L$Proxy21; on ClassEvents (cur=ClassEvents) +Load: [Lart/Test912; on ClassEvents +Prepare: [Lart/Test912; on ClassEvents (cur=ClassEvents) diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java deleted file mode 100644 index 52ce4dd58e..0000000000 --- a/test/912-classes/src/B.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -public class B { -} diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java index 6c8858ab65..395cf6fb98 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src/Main.java @@ -14,452 +14,9 @@ * limitations under the License. */ -import java.lang.ref.Reference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; - public class Main { public static void main(String[] args) throws Exception { - art.Main.bindAgentJNIForClass(Main.class); - doTest(); - } - - public static void doTest() throws Exception { - testClass("java.lang.Object"); - testClass("java.lang.String"); - testClass("java.lang.Math"); - testClass("java.util.List"); - - testClass(getProxyClass()); - - testClass(int.class); - testClass(double[].class); - - testClassType(int.class); - testClassType(getProxyClass()); - testClassType(Runnable.class); - testClassType(String.class); - testClassType(ArrayList.class); - - testClassType(int[].class); - testClassType(Runnable[].class); - testClassType(String[].class); - - testClassFields(Integer.class); - testClassFields(int.class); - testClassFields(String[].class); - - testClassMethods(Integer.class); - testClassMethods(int.class); - testClassMethods(String[].class); - - testClassStatus(int.class); - testClassStatus(String[].class); - testClassStatus(Object.class); - testClassStatus(TestForNonInit.class); - try { - System.out.println(TestForInitFail.dummy); - } catch (ExceptionInInitializerError e) { - } - testClassStatus(TestForInitFail.class); - - testInterfaces(int.class); - testInterfaces(String[].class); - testInterfaces(Object.class); - testInterfaces(InfA.class); - testInterfaces(InfB.class); - testInterfaces(InfC.class); - testInterfaces(ClassA.class); - testInterfaces(ClassB.class); - testInterfaces(ClassC.class); - - testClassLoader(String.class); - testClassLoader(String[].class); - testClassLoader(InfA.class); - testClassLoader(getProxyClass()); - - testClassLoaderClasses(); - - System.out.println(); - - testClassVersion(); - - System.out.println(); - - testClassEvents(); - } - - private static Class<?> proxyClass = null; - - private static Class<?> getProxyClass() throws Exception { - if (proxyClass != null) { - return proxyClass; - } - - proxyClass = Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Runnable.class }); - return proxyClass; - } - - private static void testClass(String className) throws Exception { - Class<?> base = Class.forName(className); - testClass(base); - } - - private static void testClass(Class<?> base) throws Exception { - String[] result = getClassSignature(base); - System.out.println(Arrays.toString(result)); - int mod = getClassModifiers(base); - if (mod != base.getModifiers()) { - throw new RuntimeException("Unexpected modifiers: " + base.getModifiers() + " vs " + mod); - } - System.out.println(Integer.toHexString(mod)); - } - - private static void testClassType(Class<?> c) throws Exception { - boolean isInterface = isInterface(c); - boolean isArray = isArrayClass(c); - boolean isModifiable = isModifiableClass(c); - System.out.println(c.getName() + " interface=" + isInterface + " array=" + isArray + - " modifiable=" + isModifiable); - } - - private static void testClassFields(Class<?> c) throws Exception { - System.out.println(Arrays.toString(getClassFields(c))); - } - - private static void testClassMethods(Class<?> c) throws Exception { - System.out.println(Arrays.toString(getClassMethods(c))); - } - - private static void testClassStatus(Class<?> c) { - System.out.println(c + " " + Integer.toBinaryString(getClassStatus(c))); - } - - private static void testInterfaces(Class<?> c) { - System.out.println(c + " " + Arrays.toString(getImplementedInterfaces(c))); - } - - private static boolean IsBootClassLoader(ClassLoader l) { - // Hacky check for Android's fake boot classloader. - return l.getClass().getName().equals("java.lang.BootClassLoader"); - } - - private static void testClassLoader(Class<?> c) { - Object cl = getClassLoader(c); - System.out.println(c + " " + (cl != null ? cl.getClass().getName() : "null")); - if (cl == null) { - if (c.getClassLoader() != null && !IsBootClassLoader(c.getClassLoader())) { - throw new RuntimeException("Expected " + c.getClassLoader() + ", but got null."); - } - } else { - if (!(cl instanceof ClassLoader)) { - throw new RuntimeException("Unexpected \"classloader\": " + cl + " (" + cl.getClass() + - ")"); - } - if (cl != c.getClassLoader()) { - throw new RuntimeException("Unexpected classloader: " + c.getClassLoader() + " vs " + cl); - } - } - } - - private static void testClassLoaderClasses() throws Exception { - ClassLoader boot = ClassLoader.getSystemClassLoader().getParent(); - while (boot.getParent() != null) { - boot = boot.getParent(); - } - - System.out.println(); - System.out.println("boot <- src <- src-ex (A,B)"); - ClassLoader cl1 = create(create(boot, DEX1), DEX2); - Class.forName("B", false, cl1); - Class.forName("A", false, cl1); - printClassLoaderClasses(cl1); - - System.out.println(); - System.out.println("boot <- src (B) <- src-ex (A, List)"); - ClassLoader cl2 = create(create(boot, DEX1), DEX2); - Class.forName("A", false, cl2); - Class.forName("java.util.List", false, cl2); - Class.forName("B", false, cl2.getParent()); - printClassLoaderClasses(cl2); - - System.out.println(); - System.out.println("boot <- src+src-ex (A,B)"); - ClassLoader cl3 = create(boot, DEX1, DEX2); - Class.forName("B", false, cl3); - Class.forName("A", false, cl3); - printClassLoaderClasses(cl3); - - // Check that the boot classloader dumps something non-empty. - Class<?>[] bootClasses = getClassLoaderClasses(boot); - if (bootClasses.length == 0) { - throw new RuntimeException("No classes initiated by boot classloader."); - } - // Check that at least java.util.List is loaded. - boolean foundList = false; - for (Class<?> c : bootClasses) { - if (c == java.util.List.class) { - foundList = true; - break; - } - } - if (!foundList) { - System.out.println(Arrays.toString(bootClasses)); - throw new RuntimeException("Could not find class java.util.List."); - } - } - - private static void testClassVersion() { - System.out.println(Arrays.toString(getClassVersion(Main.class))); - } - - private static void testClassEvents() throws Exception { - ClassLoader cl = Main.class.getClassLoader(); - while (cl.getParent() != null) { - cl = cl.getParent(); - } - final ClassLoader boot = cl; - - // The JIT may deeply inline and load some classes. Preload these for test determinism. - final String PRELOAD_FOR_JIT[] = { - "java.nio.charset.CoderMalfunctionError", - "java.util.NoSuchElementException" - }; - for (String s : PRELOAD_FOR_JIT) { - Class.forName(s); - } - - Runnable r = new Runnable() { - @Override - public void run() { - try { - ClassLoader cl6 = create(boot, DEX1, DEX2); - System.out.println("C, true"); - Class.forName("C", true, cl6); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }; - - Thread dummyThread = new Thread(); - dummyThread.start(); - dummyThread.join(); - - ensureJitCompiled(Main.class, "testClassEvents"); - - enableClassLoadPreparePrintEvents(true); - - ClassLoader cl1 = create(boot, DEX1, DEX2); - System.out.println("B, false"); - Class.forName("B", false, cl1); - - ClassLoader cl2 = create(boot, DEX1, DEX2); - System.out.println("B, true"); - Class.forName("B", true, cl2); - - ClassLoader cl3 = create(boot, DEX1, DEX2); - System.out.println("C, false"); - Class.forName("C", false, cl3); - System.out.println("A, false"); - Class.forName("A", false, cl3); - - ClassLoader cl4 = create(boot, DEX1, DEX2); - System.out.println("C, true"); - Class.forName("C", true, cl4); - System.out.println("A, true"); - Class.forName("A", true, cl4); - - ClassLoader cl5 = create(boot, DEX1, DEX2); - System.out.println("A, true"); - Class.forName("A", true, cl5); - System.out.println("C, true"); - Class.forName("C", true, cl5); - - Thread t = new Thread(r, "TestRunner"); - t.start(); - t.join(); - - // Check creation of arrays and proxies. - Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Comparable.class }); - Class.forName("[LMain;"); - - enableClassLoadPreparePrintEvents(false); - - // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by - // anything else in the system. This could be the verifier or the interpreter. We - // block the interpreter by calling ensureJitCompiled. The verifier, however, must - // run in configurations where dex2oat didn't verify the class itself. So explicitly - // check whether the class has been already loaded, and skip then. - // TODO: Add multiple configurations to the run script once that becomes easier to do. - if (hasJit() && !isLoadedClass("Main$ClassD")) { - testClassEventsJit(); - } - - testClassLoadPrepareEquality(); - } - - private static void testClassEventsJit() throws Exception { - enableClassLoadSeenEvents(true); - - testClassEventsJitImpl(); - - enableClassLoadSeenEvents(false); - - if (!hadLoadEvent()) { - throw new RuntimeException("Did not get expected load event."); - } - } - - private static void testClassEventsJitImpl() throws Exception { - ensureJitCompiled(Main.class, "testClassEventsJitImpl"); - - if (ClassD.x != 1) { - throw new RuntimeException("Unexpected value"); - } - } - - private static void testClassLoadPrepareEquality() throws Exception { - setEqualityEventStorageClass(ClassF.class); - - enableClassLoadPrepareEqualityEvents(true); - - Class.forName("Main$ClassE"); - - enableClassLoadPrepareEqualityEvents(false); - } - - private static void printClassLoaderClasses(ClassLoader cl) { - for (;;) { - if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) { - break; - } - - ClassLoader saved = cl; - for (;;) { - if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) { - break; - } - String s = cl.toString(); - int index1 = s.indexOf("zip file"); - int index2 = s.indexOf(']', index1); - if (index2 < 0) { - throw new RuntimeException("Unexpected classloader " + s); - } - String zip_file = s.substring(index1, index2); - int index3 = zip_file.indexOf('"'); - int index4 = zip_file.indexOf('"', index3 + 1); - if (index4 < 0) { - throw new RuntimeException("Unexpected classloader " + s); - } - String paths = zip_file.substring(index3 + 1, index4); - String pathArray[] = paths.split(":"); - for (String path : pathArray) { - int index5 = path.lastIndexOf('/'); - System.out.print(path.substring(index5 + 1)); - System.out.print('+'); - } - System.out.print(" -> "); - cl = cl.getParent(); - } - System.out.println(); - Class<?> classes[] = getClassLoaderClasses(saved); - Arrays.sort(classes, new ClassNameComparator()); - System.out.println(Arrays.toString(classes)); - - cl = saved.getParent(); - } - } - - private static native boolean isModifiableClass(Class<?> c); - private static native String[] getClassSignature(Class<?> c); - - private static native boolean isInterface(Class<?> c); - private static native boolean isArrayClass(Class<?> c); - - private static native int getClassModifiers(Class<?> c); - - private static native Object[] getClassFields(Class<?> c); - private static native Object[] getClassMethods(Class<?> c); - private static native Class<?>[] getImplementedInterfaces(Class<?> c); - - private static native int getClassStatus(Class<?> c); - - private static native Object getClassLoader(Class<?> c); - - private static native Class<?>[] getClassLoaderClasses(ClassLoader cl); - - private static native int[] getClassVersion(Class<?> c); - - private static native void enableClassLoadPreparePrintEvents(boolean b); - - private static native void ensureJitCompiled(Class<?> c, String name); - - private static native boolean hasJit(); - private static native boolean isLoadedClass(String name); - private static native void enableClassLoadSeenEvents(boolean b); - private static native boolean hadLoadEvent(); - - private static native void setEqualityEventStorageClass(Class<?> c); - private static native void enableClassLoadPrepareEqualityEvents(boolean b); - - private static class TestForNonInit { - public static double dummy = Math.random(); // So it can't be compile-time initialized. - } - - private static class TestForInitFail { - public static int dummy = ((int)Math.random())/0; // So it throws when initializing. - } - - public static interface InfA { - } - public static interface InfB extends InfA { - } - public static interface InfC extends InfB { - } - - public abstract static class ClassA implements InfA { - } - public abstract static class ClassB extends ClassA implements InfB { - } - public abstract static class ClassC implements InfA, InfC { - } - - public static class ClassD { - static int x = 1; - } - - public static class ClassE { - public void foo() { - } - public void bar() { - } - } - - public static class ClassF { - public static Object STATIC = null; - public static Reference<Object> WEAK = null; - } - - private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar"; - private static final String DEX2 = System.getenv("DEX_LOCATION") + "/912-classes-ex.jar"; - - private static ClassLoader create(ClassLoader parent, String... elements) throws Exception { - // Note: We use a PathClassLoader, as we do not care about code performance. We only load - // the classes, and they're empty. - Class<?> pathClassLoaderClass = Class.forName("dalvik.system.PathClassLoader"); - Constructor<?> pathClassLoaderInit = pathClassLoaderClass.getConstructor(String.class, - ClassLoader.class); - String path = String.join(":", elements); - return (ClassLoader) pathClassLoaderInit.newInstance(path, parent); - } - - private static class ClassNameComparator implements Comparator<Class<?>> { - public int compare(Class<?> c1, Class<?> c2) { - return c1.getName().compareTo(c2.getName()); - } + art.Test912.run(); + art.Test912Art.run(); } } diff --git a/test/912-classes/src/art/DexData.java b/test/912-classes/src/art/DexData.java new file mode 100644 index 0000000000..7d150322ca --- /dev/null +++ b/test/912-classes/src/art/DexData.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.nio.ByteBuffer; +import java.util.Base64; + +import dalvik.system.InMemoryDexClassLoader; + +public class DexData { + public static ClassLoader getBootClassLoader() { + ClassLoader cl = DexData.class.getClassLoader(); + while (cl.getParent() != null) { + cl = cl.getParent(); + } + return cl; + } + + public static ClassLoader create1() { + return create1(getBootClassLoader()); + } + public static ClassLoader create1(ClassLoader parent) { + return create(parent, DEX_DATA_B); + } + + public static ClassLoader create2() { + return create2(getBootClassLoader()); + } + public static ClassLoader create2(ClassLoader parent) { + return create(parent, DEX_DATA_AC); + } + + public static ClassLoader create12() { + return create12(getBootClassLoader()); + } + public static ClassLoader create12(ClassLoader parent) { + return create(parent, DEX_DATA_AC, DEX_DATA_B); + } + + private static ClassLoader create(ClassLoader parent, String... stringData) { + ByteBuffer byteBuffers[] = new ByteBuffer[stringData.length]; + for (int i = 0; i < stringData.length; i++) { + byteBuffers[i] = ByteBuffer.wrap(Base64.getDecoder().decode(stringData[i])); + } + return new InMemoryDexClassLoader(byteBuffers, parent); + } + + /* + * Derived from: + * + * public class A { + * } + * + * public class C extends A { + * } + * + */ + private final static String DEX_DATA_AC = + "ZGV4CjAzNQD5KyH7WmGuqVEyL+2aKG1nyb27UJaCjFwQAgAAcAAAAHhWNBIAAAAAAAAAAIgBAAAH" + + "AAAAcAAAAAQAAACMAAAAAQAAAJwAAAAAAAAAAAAAAAMAAACoAAAAAgAAAMAAAAAQAQAAAAEAADAB" + + "AAA4AQAAQAEAAEgBAABNAQAAUgEAAGYBAAADAAAABAAAAAUAAAAGAAAABgAAAAMAAAAAAAAAAAAA" + + "AAAAAAABAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAEAAAAAAAAAcwEAAAAAAAABAAAA" + + "AQAAAAAAAAAAAAAAAgAAAAAAAAB9AQAAAAAAAAEAAQABAAAAaQEAAAQAAABwEAIAAAAOAAEAAQAB" + + "AAAAbgEAAAQAAABwEAAAAAAOAAY8aW5pdD4ABkEuamF2YQAGQy5qYXZhAANMQTsAA0xDOwASTGph" + + "dmEvbGFuZy9PYmplY3Q7AAFWABEABw4AEQAHDgAAAAEAAIGABIACAAABAAGBgASYAgALAAAAAAAA" + + "AAEAAAAAAAAAAQAAAAcAAABwAAAAAgAAAAQAAACMAAAAAwAAAAEAAACcAAAABQAAAAMAAACoAAAA" + + "BgAAAAIAAADAAAAAASAAAAIAAAAAAQAAAiAAAAcAAAAwAQAAAyAAAAIAAABpAQAAACAAAAIAAABz" + + "AQAAABAAAAEAAACIAQAA"; + + /* + * Derived from: + * + * public class B { + * } + * + */ + private final static String DEX_DATA_B = + "ZGV4CjAzNQBgKV6iWFG4aOm5WEy8oGtDZjqsftBgwJ2oAQAAcAAAAHhWNBIAAAAAAAAAACABAAAF" + + "AAAAcAAAAAMAAACEAAAAAQAAAJAAAAAAAAAAAAAAAAIAAACcAAAAAQAAAKwAAADcAAAAzAAAAOQA" + + "AADsAAAA9AAAAPkAAAANAQAAAgAAAAMAAAAEAAAABAAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAA" + + "AAAAAAABAAAAAQAAAAAAAAABAAAAAAAAABUBAAAAAAAAAQABAAEAAAAQAQAABAAAAHAQAQAAAA4A" + + "Bjxpbml0PgAGQi5qYXZhAANMQjsAEkxqYXZhL2xhbmcvT2JqZWN0OwABVgARAAcOAAAAAQAAgYAE" + + "zAEACwAAAAAAAAABAAAAAAAAAAEAAAAFAAAAcAAAAAIAAAADAAAAhAAAAAMAAAABAAAAkAAAAAUA" + + "AAACAAAAnAAAAAYAAAABAAAArAAAAAEgAAABAAAAzAAAAAIgAAAFAAAA5AAAAAMgAAABAAAAEAEA" + + "AAAgAAABAAAAFQEAAAAQAAABAAAAIAEAAA=="; +} diff --git a/test/912-classes/src/art/Test912.java b/test/912-classes/src/art/Test912.java new file mode 100644 index 0000000000..f3ff2b0668 --- /dev/null +++ b/test/912-classes/src/art/Test912.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.ref.Reference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +public class Test912 { + public static void run() throws Exception { + art.Main.bindAgentJNIForClass(Test912.class); + doTest(); + } + + public static void doTest() throws Exception { + testClass("java.lang.Object"); + testClass("java.lang.String"); + testClass("java.lang.Math"); + testClass("java.util.List"); + + testClass(getProxyClass()); + + testClass(int.class); + testClass(double[].class); + + testClassType(int.class); + testClassType(getProxyClass()); + testClassType(Runnable.class); + testClassType(String.class); + testClassType(ArrayList.class); + + testClassType(int[].class); + testClassType(Runnable[].class); + testClassType(String[].class); + + testClassFields(Integer.class); + testClassFields(int.class); + testClassFields(String[].class); + + testClassMethods(Integer.class); + testClassMethods(int.class); + testClassMethods(String[].class); + + testClassStatus(int.class); + testClassStatus(String[].class); + testClassStatus(Object.class); + testClassStatus(TestForNonInit.class); + try { + System.out.println(TestForInitFail.dummy); + } catch (ExceptionInInitializerError e) { + } + testClassStatus(TestForInitFail.class); + + testInterfaces(int.class); + testInterfaces(String[].class); + testInterfaces(Object.class); + testInterfaces(InfA.class); + testInterfaces(InfB.class); + testInterfaces(InfC.class); + testInterfaces(ClassA.class); + testInterfaces(ClassB.class); + testInterfaces(ClassC.class); + + testClassLoader(String.class); + testClassLoader(String[].class); + testClassLoader(InfA.class); + testClassLoader(getProxyClass()); + + testClassLoaderClasses(); + + System.out.println(); + + testClassVersion(); + + System.out.println(); + + // Use a dedicated thread to have a well-defined current thread. + Thread classEventsThread = new Thread("ClassEvents") { + @Override + public void run() { + try { + testClassEvents(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + classEventsThread.start(); + classEventsThread.join(); + } + + private static void testClass(String className) throws Exception { + Class<?> base = Class.forName(className); + testClass(base); + } + + private static void testClass(Class<?> base) throws Exception { + String[] result = getClassSignature(base); + System.out.println(Arrays.toString(result)); + int mod = getClassModifiers(base); + if (mod != base.getModifiers()) { + throw new RuntimeException("Unexpected modifiers: " + base.getModifiers() + " vs " + mod); + } + System.out.println(Integer.toHexString(mod)); + } + + private static void testClassType(Class<?> c) throws Exception { + boolean isInterface = isInterface(c); + boolean isArray = isArrayClass(c); + boolean isModifiable = isModifiableClass(c); + System.out.println(c.getName() + " interface=" + isInterface + " array=" + isArray + + " modifiable=" + isModifiable); + } + + private static void testClassFields(Class<?> c) throws Exception { + System.out.println(Arrays.toString(getClassFields(c))); + } + + private static void testClassMethods(Class<?> c) throws Exception { + System.out.println(Arrays.toString(getClassMethods(c))); + } + + private static void testClassStatus(Class<?> c) { + System.out.println(c + " " + Integer.toBinaryString(getClassStatus(c))); + } + + private static void testInterfaces(Class<?> c) { + System.out.println(c + " " + Arrays.toString(getImplementedInterfaces(c))); + } + + private static boolean IsBootClassLoader(ClassLoader l) { + // Hacky check for Android's fake boot classloader. + return l.getClass().getName().equals("java.lang.BootClassLoader"); + } + + private static void testClassLoader(Class<?> c) { + Object cl = getClassLoader(c); + System.out.println(c + " " + (cl != null ? cl.getClass().getName() : "null")); + if (cl == null) { + if (c.getClassLoader() != null && !IsBootClassLoader(c.getClassLoader())) { + throw new RuntimeException("Expected " + c.getClassLoader() + ", but got null."); + } + } else { + if (!(cl instanceof ClassLoader)) { + throw new RuntimeException("Unexpected \"classloader\": " + cl + " (" + cl.getClass() + + ")"); + } + if (cl != c.getClassLoader()) { + throw new RuntimeException("Unexpected classloader: " + c.getClassLoader() + " vs " + cl); + } + } + } + + private static void testClassLoaderClasses() throws Exception { + System.out.println(); + System.out.println("boot <- (B) <- (A,C)"); + ClassLoader cl1 = DexData.create2(DexData.create1()); + Class.forName("B", false, cl1); + Class.forName("A", false, cl1); + printClassLoaderClasses(cl1); + + System.out.println(); + System.out.println("boot <- (B) <- (A, List)"); + ClassLoader cl2 = DexData.create2(DexData.create1()); + Class.forName("A", false, cl2); + Class.forName("java.util.List", false, cl2); + Class.forName("B", false, cl2.getParent()); + printClassLoaderClasses(cl2); + + System.out.println(); + System.out.println("boot <- 1+2 (A,B)"); + ClassLoader cl3 = DexData.create12(); + Class.forName("B", false, cl3); + Class.forName("A", false, cl3); + printClassLoaderClasses(cl3); + + // Check that the boot classloader dumps something non-empty. + ClassLoader boot = ClassLoader.getSystemClassLoader().getParent(); + while (boot.getParent() != null) { + boot = boot.getParent(); + } + + Class<?>[] bootClasses = getClassLoaderClasses(boot); + if (bootClasses.length == 0) { + throw new RuntimeException("No classes initiated by boot classloader."); + } + // Check that at least java.util.List is loaded. + boolean foundList = false; + for (Class<?> c : bootClasses) { + if (c == java.util.List.class) { + foundList = true; + break; + } + } + if (!foundList) { + System.out.println(Arrays.toString(bootClasses)); + throw new RuntimeException("Could not find class java.util.List."); + } + } + + private static void testClassVersion() { + System.out.println(Arrays.toString(getClassVersion(Main.class))); + } + + private static void testClassEvents() throws Exception { + ClassLoader cl = Main.class.getClassLoader(); + while (cl.getParent() != null) { + cl = cl.getParent(); + } + final ClassLoader boot = cl; + + // The JIT may deeply inline and load some classes. Preload these for test determinism. + final String PRELOAD_FOR_JIT[] = { + "java.nio.charset.CoderMalfunctionError", + "java.util.NoSuchElementException" + }; + for (String s : PRELOAD_FOR_JIT) { + Class.forName(s); + } + + Runnable r = new Runnable() { + @Override + public void run() { + try { + ClassLoader cl6 = DexData.create12(); + System.out.println("C, true"); + Class.forName("C", true, cl6); + printClassLoadMessages(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + + Thread dummyThread = new Thread(); + dummyThread.start(); + dummyThread.join(); + + enableClassLoadPreparePrintEvents(true, Thread.currentThread()); + + ClassLoader cl1 = DexData.create12(); + System.out.println("B, false"); + Class.forName("B", false, cl1); + printClassLoadMessages(); + + ClassLoader cl2 = DexData.create12(); + System.out.println("B, true"); + Class.forName("B", true, cl2); + printClassLoadMessages(); + + ClassLoader cl3 = DexData.create12(); + System.out.println("C, false"); + Class.forName("C", false, cl3); + printClassLoadMessages(); + System.out.println("A, false"); + Class.forName("A", false, cl3); + printClassLoadMessages(); + + ClassLoader cl4 = DexData.create12(); + System.out.println("C, true"); + Class.forName("C", true, cl4); + printClassLoadMessages(); + System.out.println("A, true"); + Class.forName("A", true, cl4); + printClassLoadMessages(); + + ClassLoader cl5 = DexData.create12(); + System.out.println("A, true"); + Class.forName("A", true, cl5); + printClassLoadMessages(); + System.out.println("C, true"); + Class.forName("C", true, cl5); + printClassLoadMessages(); + + enableClassLoadPreparePrintEvents(false, null); + + Thread t = new Thread(r, "TestRunner"); + enableClassLoadPreparePrintEvents(true, t); + t.start(); + t.join(); + enableClassLoadPreparePrintEvents(false, null); + + enableClassLoadPreparePrintEvents(true, Thread.currentThread()); + + // Check creation of arrays and proxies. + Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Comparable.class, I0.class }); + Class.forName("[Lart.Test912;"); + printClassLoadMessages(); + + enableClassLoadPreparePrintEvents(false, null); + + testClassLoadPrepareEquality(); + } + + private static void testClassLoadPrepareEquality() throws Exception { + setEqualityEventStorageClass(ClassF.class); + + enableClassLoadPrepareEqualityEvents(true); + + Class.forName("art.Test912$ClassE"); + + enableClassLoadPrepareEqualityEvents(false); + } + + private static void printClassLoaderClasses(ClassLoader cl) { + for (;;) { + if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) { + break; + } + + Class<?> classes[] = getClassLoaderClasses(cl); + Arrays.sort(classes, new ClassNameComparator()); + System.out.println(Arrays.toString(classes)); + + cl = cl.getParent(); + } + } + + private static void printClassLoadMessages() { + for (String s : getClassLoadMessages()) { + System.out.println(s); + } + } + + private static native boolean isModifiableClass(Class<?> c); + private static native String[] getClassSignature(Class<?> c); + + private static native boolean isInterface(Class<?> c); + private static native boolean isArrayClass(Class<?> c); + + private static native int getClassModifiers(Class<?> c); + + private static native Object[] getClassFields(Class<?> c); + private static native Object[] getClassMethods(Class<?> c); + private static native Class<?>[] getImplementedInterfaces(Class<?> c); + + private static native int getClassStatus(Class<?> c); + + private static native Object getClassLoader(Class<?> c); + + private static native Class<?>[] getClassLoaderClasses(ClassLoader cl); + + private static native int[] getClassVersion(Class<?> c); + + private static native void enableClassLoadPreparePrintEvents(boolean b, Thread filter); + private static native String[] getClassLoadMessages(); + + private static native void setEqualityEventStorageClass(Class<?> c); + private static native void enableClassLoadPrepareEqualityEvents(boolean b); + + private static class TestForNonInit { + public static double dummy = Math.random(); // So it can't be compile-time initialized. + } + + private static class TestForInitFail { + public static int dummy = ((int)Math.random())/0; // So it throws when initializing. + } + + public static interface InfA { + } + public static interface InfB extends InfA { + } + public static interface InfC extends InfB { + } + + public abstract static class ClassA implements InfA { + } + public abstract static class ClassB extends ClassA implements InfB { + } + public abstract static class ClassC implements InfA, InfC { + } + + public static class ClassE { + public void foo() { + } + public void bar() { + } + } + + public static class ClassF { + public static Object STATIC = null; + public static Reference<Object> WEAK = null; + } + + private static class ClassNameComparator implements Comparator<Class<?>> { + public int compare(Class<?> c1, Class<?> c2) { + return c1.getName().compareTo(c2.getName()); + } + } + + // See run-test 910 for an explanation. + + private static Class<?> proxyClass = null; + + private static Class<?> getProxyClass() throws Exception { + if (proxyClass != null) { + return proxyClass; + } + + for (int i = 1; i <= 21; i++) { + proxyClass = createProxyClass(i); + String name = proxyClass.getName(); + if (name.equals("$Proxy20")) { + return proxyClass; + } + } + return proxyClass; + } + + private static Class<?> createProxyClass(int i) throws Exception { + int count = Integer.bitCount(i); + Class<?>[] input = new Class<?>[count + 1]; + input[0] = Runnable.class; + int inputIndex = 1; + int bitIndex = 0; + while (i != 0) { + if ((i & 1) != 0) { + input[inputIndex++] = Class.forName("art.Test912$I" + bitIndex); + } + i >>>= 1; + bitIndex++; + } + return Proxy.getProxyClass(Test912.class.getClassLoader(), input); + } + + // Need this for the proxy naming. + public static interface I0 { + } + public static interface I1 { + } + public static interface I2 { + } + public static interface I3 { + } + public static interface I4 { + } +} diff --git a/test/912-classes/src/art/Test912Art.java b/test/912-classes/src/art/Test912Art.java new file mode 100644 index 0000000000..e4384734d8 --- /dev/null +++ b/test/912-classes/src/art/Test912Art.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.ref.Reference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +public class Test912Art { + public static void run() throws Exception { + art.Main.bindAgentJNIForClass(Test912Art.class); + doTest(); + } + + public static void doTest() throws Exception { + testClassEvents(); + } + + private static void testClassEvents() throws Exception { + // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by + // anything else in the system. This could be the verifier or the interpreter. We + // block the interpreter by calling ensureJitCompiled. The verifier, however, must + // run in configurations where dex2oat didn't verify the class itself. So explicitly + // check whether the class has been already loaded, and skip then. + // TODO: Add multiple configurations to the run script once that becomes easier to do. + if (hasJit() && !isLoadedClass("art.Test912Art$ClassD")) { + testClassEventsJit(); + } + } + + private static void testClassEventsJit() throws Exception { + enableClassLoadSeenEvents(true); + + testClassEventsJitImpl(); + + enableClassLoadSeenEvents(false); + + if (!hadLoadEvent()) { + throw new RuntimeException("Did not get expected load event."); + } + } + + private static void testClassEventsJitImpl() throws Exception { + ensureJitCompiled(Test912Art.class, "testClassEventsJitImpl"); + + if (ClassD.x != 1) { + throw new RuntimeException("Unexpected value"); + } + } + + private static native void ensureJitCompiled(Class<?> c, String name); + + private static native boolean hasJit(); + private static native boolean isLoadedClass(String name); + private static native void enableClassLoadSeenEvents(boolean b); + private static native boolean hadLoadEvent(); + + public static class ClassD { + static int x = 1; + } +} diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc index 19e12ae731..e319f7d98c 100644 --- a/test/913-heaps/heaps.cc +++ b/test/913-heaps/heaps.cc @@ -137,9 +137,9 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test913_followReferences( if (reference_kind == JVMTI_HEAP_REFERENCE_JNI_GLOBAL && class_tag == 0) { return 0; } - // Ignore classes (1000-1002@0) for thread objects. These can be held by the JIT. + // Ignore classes (1000 <= tag < 3000) for thread objects. These can be held by the JIT. if (reference_kind == JVMTI_HEAP_REFERENCE_THREAD && class_tag == 0 && - (1000 <= *tag_ptr && *tag_ptr <= 1002)) { + (1000 <= *tag_ptr && *tag_ptr < 3000)) { return 0; } // Ignore stack-locals of untagged threads. That is the environment. diff --git a/test/924-threads/expected.txt b/test/924-threads/expected.txt index 4c0f4eaa5b..1eb2e1bd52 100644 --- a/test/924-threads/expected.txt +++ b/test/924-threads/expected.txt @@ -1,10 +1,10 @@ currentThread OK -main +TestThread 5 false java.lang.ThreadGroup[name=main,maxpri=10] class dalvik.system.PathClassLoader -main +TestThread 5 false java.lang.ThreadGroup[name=main,maxpri=10] @@ -33,10 +33,11 @@ class dalvik.system.PathClassLoader e1 = ALIVE|WAITING_WITH_TIMEOUT|SLEEPING|WAITING 5 = ALIVE|RUNNABLE 2 = TERMINATED -[Thread[FinalizerDaemon,5,system], Thread[FinalizerWatchdogDaemon,5,system], Thread[HeapTaskDaemon,5,system], Thread[ReferenceQueueDaemon,5,system], Thread[Signal Catcher,5,system], Thread[main,5,main]] +[Thread[FinalizerDaemon,5,system], Thread[FinalizerWatchdogDaemon,5,system], Thread[HeapTaskDaemon,5,system], Thread[ReferenceQueueDaemon,5,system], Thread[TestThread,5,main], Thread[main,5,main]] JVMTI_ERROR_THREAD_NOT_ALIVE JVMTI_ERROR_THREAD_NOT_ALIVE Constructed thread -Thread(EventTestThread): start -Thread(EventTestThread): end +[] +[Thread(EventTestThread): start] +[Thread(EventTestThread): end] Thread joined diff --git a/test/924-threads/src/art/Test924.java b/test/924-threads/src/art/Test924.java index 160bf8ea67..5445939cbc 100644 --- a/test/924-threads/src/art/Test924.java +++ b/test/924-threads/src/art/Test924.java @@ -25,17 +25,35 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; public class Test924 { public static void run() throws Exception { Main.bindAgentJNIForClass(Test924.class); - doTest(); + + // Run the test on its own thread, so we have a known state for the "current" thread. + Thread t = new Thread("TestThread") { + @Override + public void run() { + try { + doTest(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + t.start(); + t.join(); } private static void doTest() throws Exception { Thread t1 = Thread.currentThread(); Thread t2 = getCurrentThread(); + // Need to adjust priority, as on-device this may be unexpected (and we prefer not + // to special-case this.) + t1.setPriority(5); + if (t1 != t2) { throw new RuntimeException("Expected " + t1 + " but got " + t2); } @@ -188,7 +206,32 @@ public class Test924 { } Collections.sort(threadList, THREAD_COMP); - System.out.println(threadList); + + List<Thread> expectedList = new ArrayList<>(); + Set<Thread> threadsFromTraces = Thread.getAllStackTraces().keySet(); + + expectedList.add(findThreadByName(threadsFromTraces, "FinalizerDaemon")); + expectedList.add(findThreadByName(threadsFromTraces, "FinalizerWatchdogDaemon")); + expectedList.add(findThreadByName(threadsFromTraces, "HeapTaskDaemon")); + expectedList.add(findThreadByName(threadsFromTraces, "ReferenceQueueDaemon")); + // We can't get the signal catcher through getAllStackTraces. So ignore it. + // expectedList.add(findThreadByName(threadsFromTraces, "Signal Catcher")); + expectedList.add(findThreadByName(threadsFromTraces, "TestThread")); + expectedList.add(findThreadByName(threadsFromTraces, "main")); + + if (!threadList.containsAll(expectedList)) { + throw new RuntimeException("Expected " + expectedList + " as subset, got " + threadList); + } + System.out.println(expectedList); + } + + private static Thread findThreadByName(Set<Thread> threads, String name) { + for (Thread t : threads) { + if (t.getName().equals(name)) { + return t; + } + } + throw new RuntimeException("Did not find thread " + name + ": " + threads); } private static void doTLSTests() throws Exception { @@ -256,13 +299,35 @@ public class Test924 { private static void doTestEvents() throws Exception { enableThreadEvents(true); - Thread t = new Thread("EventTestThread"); + final CountDownLatch cdl1 = new CountDownLatch(1); + final CountDownLatch cdl2 = new CountDownLatch(1); + + Runnable r = new Runnable() { + @Override + public void run() { + try { + cdl1.countDown(); + cdl2.await(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + Thread t = new Thread(r, "EventTestThread"); System.out.println("Constructed thread"); Thread.yield(); + Thread.sleep(100); + System.out.println(Arrays.toString(getThreadEventMessages())); t.start(); + cdl1.await(); + + System.out.println(Arrays.toString(getThreadEventMessages())); + + cdl2.countDown(); t.join(); + System.out.println(Arrays.toString(getThreadEventMessages())); System.out.println("Thread joined"); @@ -337,4 +402,5 @@ public class Test924 { private static native void setTLS(Thread t, long l); private static native long getTLS(Thread t); private static native void enableThreadEvents(boolean b); + private static native String[] getThreadEventMessages(); } diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc index 701ab1def3..e21dcc240e 100644 --- a/test/924-threads/threads.cc +++ b/test/924-threads/threads.cc @@ -16,6 +16,10 @@ #include <stdio.h> +#include <mutex> +#include <string> +#include <vector> + #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "jni.h" @@ -139,17 +143,27 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test924_setTLS( JvmtiErrorToException(env, jvmti_env, result); } +static std::mutex gEventsMutex; +static std::vector<std::string> gEvents; + static void JNICALL ThreadEvent(jvmtiEnv* jvmti_env, JNIEnv* jni_env, jthread thread, bool is_start) { jvmtiThreadInfo info; - jvmtiError result = jvmti_env->GetThreadInfo(thread, &info); - if (result != JVMTI_ERROR_NONE) { - printf("Error getting thread info"); - return; + { + std::lock_guard<std::mutex> guard(gEventsMutex); + + jvmtiError result = jvmti_env->GetThreadInfo(thread, &info); + if (result != JVMTI_ERROR_NONE) { + gEvents.push_back("Error getting thread info"); + return; + } + + gEvents.push_back(android::base::StringPrintf("Thread(%s): %s", + info.name, + is_start ? "start" : "end")); } - printf("Thread(%s): %s\n", info.name, is_start ? "start" : "end"); jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(info.name)); jni_env->DeleteLocalRef(info.thread_group); @@ -205,5 +219,18 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test924_enableThreadEvents( JvmtiErrorToException(env, jvmti_env, ret); } +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test924_getThreadEventMessages( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + std::lock_guard<std::mutex> guard(gEventsMutex); + jobjectArray ret = CreateObjectArray(env, + static_cast<jint>(gEvents.size()), + "java/lang/String", + [&](jint i) { + return env->NewStringUTF(gEvents[i].c_str()); + }); + gEvents.clear(); + return ret; +} + } // namespace Test924Threads } // namespace art diff --git a/test/986-native-method-bind/expected.txt b/test/986-native-method-bind/expected.txt new file mode 100644 index 0000000000..189217d761 --- /dev/null +++ b/test/986-native-method-bind/expected.txt @@ -0,0 +1,8 @@ +private static native void art.Test986$Transform.sayHi() = Java_art_Test986_00024Transform_sayHi -> Java_art_Test986_00024Transform_sayHi +Hello +private static native void art.Test986$Transform.sayHi() = Java_art_Test986_00024Transform_sayHi -> NoReallySayGoodbye +Bye +public static native void art.Main.bindAgentJNI(java.lang.String,java.lang.ClassLoader) = Java_art_Main_bindAgentJNI -> Java_art_Main_bindAgentJNI +public static native void art.Main.bindAgentJNIForClass(java.lang.Class) = Java_art_Main_bindAgentJNIForClass -> Java_art_Main_bindAgentJNIForClass +private static native void art.Test986.setNativeBindNotify(boolean) = Java_art_Test986_setNativeBindNotify -> Java_art_Test986_setNativeBindNotify +private static native void art.Test986.setupNativeBindNotify() = Java_art_Test986_setupNativeBindNotify -> Java_art_Test986_setupNativeBindNotify diff --git a/test/986-native-method-bind/info.txt b/test/986-native-method-bind/info.txt new file mode 100644 index 0000000000..1939936ca9 --- /dev/null +++ b/test/986-native-method-bind/info.txt @@ -0,0 +1 @@ +Tests native-method-bind callback and native method replacement. diff --git a/test/986-native-method-bind/native_bind.cc b/test/986-native-method-bind/native_bind.cc new file mode 100644 index 0000000000..4f93f87dfe --- /dev/null +++ b/test/986-native-method-bind/native_bind.cc @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> +#include <dlfcn.h> + +#include "android-base/stringprintf.h" +#include "jni.h" +#include "jvmti.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "scoped_local_ref.h" + +namespace art { +namespace Test986NativeBind { + +static void doUpPrintCall(JNIEnv* env, const char* function) { + ScopedLocalRef<jclass> klass(env, env->FindClass("art/Test986")); + jmethodID targetMethod = env->GetStaticMethodID(klass.get(), function, "()V"); + env->CallStaticVoidMethod(klass.get(), targetMethod); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test986_00024Transform_sayHi( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { + doUpPrintCall(env, "doSayHi"); +} + +extern "C" JNIEXPORT void JNICALL NoReallySayGoodbye(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { + doUpPrintCall(env, "doSayBye"); +} + +static void doJvmtiMethodBind(jvmtiEnv* jvmtienv ATTRIBUTE_UNUSED, + JNIEnv* env, + jthread thread ATTRIBUTE_UNUSED, + jmethodID m, + void* address, + /*out*/void** out_address) { + ScopedLocalRef<jclass> method_class(env, env->FindClass("java/lang/reflect/Method")); + ScopedLocalRef<jobject> method_obj(env, env->ToReflectedMethod(method_class.get(), m, false)); + Dl_info addr_info; + if (dladdr(address, &addr_info) == 0 || addr_info.dli_sname == nullptr) { + ScopedLocalRef<jclass> exception_class(env, env->FindClass("java/lang/Exception")); + env->ThrowNew(exception_class.get(), "dladdr failure!"); + return; + } + ScopedLocalRef<jstring> sym_name(env, env->NewStringUTF(addr_info.dli_sname)); + ScopedLocalRef<jclass> klass(env, env->FindClass("art/Test986")); + jmethodID upcallMethod = env->GetStaticMethodID( + klass.get(), + "doNativeMethodBind", + "(Ljava/lang/reflect/Method;Ljava/lang/String;)Ljava/lang/String;"); + if (env->ExceptionCheck()) { + return; + } + ScopedLocalRef<jstring> new_symbol(env, + reinterpret_cast<jstring>( + env->CallStaticObjectMethod(klass.get(), + upcallMethod, + method_obj.get(), + sym_name.get()))); + const char* new_symbol_chars = env->GetStringUTFChars(new_symbol.get(), nullptr); + if (strcmp(new_symbol_chars, addr_info.dli_sname) != 0) { + *out_address = dlsym(RTLD_DEFAULT, new_symbol_chars); + if (*out_address == nullptr) { + ScopedLocalRef<jclass> exception_class(env, env->FindClass("java/lang/Exception")); + env->ThrowNew(exception_class.get(), "dlsym failure!"); + return; + } + } + env->ReleaseStringUTFChars(new_symbol.get(), new_symbol_chars); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test986_setupNativeBindNotify( + JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.NativeMethodBind = doJvmtiMethodBind; + jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test986_setNativeBindNotify( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) { + jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, + JVMTI_EVENT_NATIVE_METHOD_BIND, + nullptr); + if (res != JVMTI_ERROR_NONE) { + JvmtiErrorToException(env, jvmti_env, res); + } +} + +} // namespace Test986NativeBind +} // namespace art diff --git a/test/986-native-method-bind/run b/test/986-native-method-bind/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/986-native-method-bind/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-run "$@" --jvmti diff --git a/test/912-classes/src-ex/A.java b/test/986-native-method-bind/src/Main.java index 2c43cfbd79..fac9d8e2a9 100644 --- a/test/912-classes/src-ex/A.java +++ b/test/986-native-method-bind/src/Main.java @@ -14,5 +14,8 @@ * limitations under the License. */ -public class A { +public class Main { + public static void main(String[] args) throws Exception { + art.Test986.run(); + } } diff --git a/test/986-native-method-bind/src/art/Main.java b/test/986-native-method-bind/src/art/Main.java new file mode 100644 index 0000000000..8b01920638 --- /dev/null +++ b/test/986-native-method-bind/src/art/Main.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +// Binder class so the agent's C code has something that can be bound and exposed to tests. +// In a package to separate cleanly and work around CTS reference issues (though this class +// should be replaced in the CTS version). +public class Main { + // Load the given class with the given classloader, and bind all native methods to corresponding + // C methods in the agent. Will abort if any of the steps fail. + public static native void bindAgentJNI(String className, ClassLoader classLoader); + // Same as above, giving the class directly. + public static native void bindAgentJNIForClass(Class<?> klass); +} diff --git a/test/986-native-method-bind/src/art/Redefinition.java b/test/986-native-method-bind/src/art/Redefinition.java new file mode 100644 index 0000000000..0350ab42ad --- /dev/null +++ b/test/986-native-method-bind/src/art/Redefinition.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + // Bind native functions. + static { + Main.bindAgentJNIForClass(Redefinition.class); + } + + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/986-native-method-bind/src/art/Test986.java b/test/986-native-method-bind/src/art/Test986.java new file mode 100644 index 0000000000..edd2e9d79f --- /dev/null +++ b/test/986-native-method-bind/src/art/Test986.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.HashMap; + +public class Test986 { + static { + // NB This is called before any setup is done so we don't need to worry about getting bind + // events. + Main.bindAgentJNIForClass(Test986.class); + } + + + private static final HashMap<Method, String> SymbolMap = new HashMap<>(); + + // A class with a native method we can play with. + static class Transform { + private static native void sayHi(); + } + + public static void run() throws Exception { + setupNativeBindNotify(); + setNativeBindNotify(true); + doTest(); + } + + private static void setNativeTransform(Method method, String dest) { + SymbolMap.put(method, dest); + } + + /** + * Notifies java that a native method bind has occurred and requests the new symbol to bind to. + */ + public static String doNativeMethodBind(Method method, String nativeSym) { + // Disable native bind notify for now to avoid infinite loops. + setNativeBindNotify(false); + String transSym = SymbolMap.getOrDefault(method, nativeSym); + System.out.println(method + " = " + nativeSym + " -> " + transSym); + setNativeBindNotify(true); + return transSym; + } + + public static void doTest() throws Exception { + Method say_hi_method = Transform.class.getDeclaredMethod("sayHi"); + // TODO We should test auto-binding but due to the way this is run that will be annoying. + Main.bindAgentJNIForClass(Transform.class); + Transform.sayHi(); + setNativeTransform(say_hi_method, "NoReallySayGoodbye"); + Main.bindAgentJNIForClass(Transform.class); + Transform.sayHi(); + Main.bindAgentJNIForClass(Main.class); + Main.bindAgentJNIForClass(Test986.class); + } + + // Functions called from native code. + public static void doSayHi() { + System.out.println("Hello"); + } + + public static void doSayBye() { + System.out.println("Bye"); + } + + private static native void setNativeBindNotify(boolean enable); + private static native void setupNativeBindNotify(); +} diff --git a/test/Android.bp b/test/Android.bp index 1522f07556..93fd607c24 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -261,6 +261,7 @@ art_cc_defaults { "908-gc-start-finish/gc_callbacks.cc", "910-methods/methods.cc", "911-get-stack-trace/stack_trace.cc", + "912-classes/classes.cc", "913-heaps/heaps.cc", "918-fields/fields.cc", "920-objects/objects.cc", @@ -275,6 +276,7 @@ art_cc_defaults { "933-misc-events/misc_events.cc", "945-obsolete-native/obsolete_native.cc", "984-obsolete-invoke/obsolete_invoke.cc", + "986-native-method-bind/native_bind.cc", ], shared_libs: [ "libbase", @@ -295,7 +297,7 @@ art_cc_defaults { // make this list smaller. "901-hello-ti-agent/basics.cc", "909-attach-agent/attach.cc", - "912-classes/classes.cc", + "912-classes/classes_art.cc", "936-search-onload/search_onload.cc", "983-source-transform-verify/source_transform.cc", ], diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk index dcb238cd9f..70ee693a60 100644 --- a/test/Android.run-test-jvmti-java-library.mk +++ b/test/Android.run-test-jvmti-java-library.mk @@ -45,6 +45,8 @@ LOCAL_SRC_FILES += \ 911-get-stack-trace/src/art/Recurse.java \ 911-get-stack-trace/src/art/SameThread.java \ 911-get-stack-trace/src/art/ThreadListTraces.java \ + 912-classes/src/art/Test912.java \ + 912-classes/src/art/DexData.java \ 913-heaps/src/art/Test913.java \ 914-hello-obsolescence/src/art/Test914.java \ 915-obsolete-2/src/art/Test915.java \ @@ -84,6 +86,7 @@ JVMTI_RUN_TEST_GENERATED_NUMBERS := \ 908 \ 910 \ 911 \ + 912 \ 913 \ 914 \ 915 \ diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 4415b2ccaa..afd914432e 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -17,22 +17,6 @@ LOCAL_PATH := $(call my-dir) include art/build/Android.common_test.mk -# List of all tests of the form 003-omnibus-opcodes. -TEST_ART_RUN_TESTS := $(wildcard $(LOCAL_PATH)/[0-9]*) -TEST_ART_RUN_TESTS := $(subst $(LOCAL_PATH)/,, $(TEST_ART_RUN_TESTS)) - -######################################################################## -# The art-run-tests module, used to build all run-tests into an image. - -# The path where build only targets will be output, e.g. -# out/target/product/generic_x86_64/obj/PACKAGING/art-run-tests_intermediates/DATA -art_run_tests_build_dir := $(call intermediates-dir-for,JAVA_LIBRARIES,art-run-tests)/DATA -art_run_tests_install_dir := $(call intermediates-dir-for,PACKAGING,art-run-tests)/DATA - -# A generated list of prerequisites that call 'run-test --build-only', the actual prerequisite is -# an empty file touched in the intermediate directory. -TEST_ART_RUN_TEST_BUILD_RULES := - # Dependencies for actually running a run-test. TEST_ART_RUN_TEST_DEPENDENCIES := \ $(DX) \ @@ -41,54 +25,6 @@ TEST_ART_RUN_TEST_DEPENDENCIES := \ $(HOST_OUT_EXECUTABLES)/dexmerger \ $(JACK) -TEST_ART_RUN_TEST_ORDERONLY_DEPENDENCIES := setup-jack-server - -# Helper to create individual build targets for tests. Must be called with $(eval). -# $(1): the test number -define define-build-art-run-test - dmart_target := $(art_run_tests_build_dir)/art-run-tests/$(1)/touch - dmart_install_target := $(art_run_tests_install_dir)/art-run-tests/$(1)/touch - run_test_options = --build-only - ifeq ($(ART_TEST_QUIET),true) - run_test_options += --quiet - endif -$$(dmart_target): PRIVATE_RUN_TEST_OPTIONS := $$(run_test_options) -$$(dmart_target): $(TEST_ART_RUN_TEST_DEPENDENCIES) | $(TEST_ART_RUN_TEST_ORDERONLY_DEPENDENCIES) -ifeq ($(ANDROID_COMPILE_WITH_JACK),true) -$$(dmart_target): $(TARGET_JACK_CLASSPATH_DEPENDENCIES) -endif -$$(dmart_target): - $(hide) rm -rf $$(dir $$@) && mkdir -p $$(dir $$@) - $(hide) DX=$(abspath $(DX)) JASMIN=$(abspath $(HOST_OUT_EXECUTABLES)/jasmin) \ - SMALI=$(abspath $(HOST_OUT_EXECUTABLES)/smali) \ - DXMERGER=$(abspath $(HOST_OUT_EXECUTABLES)/dexmerger) \ - JACK_VERSION=$(JACK_DEFAULT_VERSION) \ - JACK=$(abspath $(JACK)) \ - JACK_VERSION=$(JACK_DEFAULT_VERSION) \ - JACK_CLASSPATH=$(TARGET_JACK_CLASSPATH) \ - $(LOCAL_PATH)/run-test $$(PRIVATE_RUN_TEST_OPTIONS) --output-path $$(abspath $$(dir $$@)) $(1) - $(hide) touch $$@ - -$$(dmart_install_target): $$(dmart_target) - $(hide) rm -rf $$(dir $$@) && mkdir -p $$(dir $$@) - $(hide) cp $$(dir $$<)/* $$(dir $$@)/ - - TEST_ART_RUN_TEST_BUILD_RULES += $$(dmart_install_target) - dmart_target := - dmart_install_target := - run_test_options := -endef -$(foreach test, $(TEST_ART_RUN_TESTS), $(eval $(call define-build-art-run-test,$(test)))) - -include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := tests -LOCAL_MODULE := art-run-tests -LOCAL_ADDITIONAL_DEPENDENCIES := $(TEST_ART_RUN_TEST_BUILD_RULES) -# The build system use this flag to pick up files generated by declare-make-art-run-test. -LOCAL_PICKUP_FILES := $(art_run_tests_install_dir) - -include $(BUILD_PHONY_PACKAGE) - # Convert's a rule name to the form used in variables, e.g. no-relocate to NO_RELOCATE define name-to-var $(shell echo $(1) | tr '[:lower:]' '[:upper:]' | tr '-' '_') diff --git a/test/VerifierDeps/MyClassExtendingInterface.smali b/test/VerifierDeps/MyClassExtendingInterface.smali new file mode 100644 index 0000000000..43cf13bb30 --- /dev/null +++ b/test/VerifierDeps/MyClassExtendingInterface.smali @@ -0,0 +1,16 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LMyClassExtendingInterface; +.super LIface; diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc index df7fa20226..ceb4ba241b 100644 --- a/test/common/stack_inspect.cc +++ b/test/common/stack_inspect.cc @@ -144,22 +144,11 @@ extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jcl } } -static jboolean IsManaged(JNIEnv* env, jclass cls, size_t level) { +static jboolean IsManaged(JNIEnv* env, jclass, size_t level) { ScopedObjectAccess soa(env); - - ObjPtr<mirror::Class> klass = soa.Decode<mirror::Class>(cls); - const DexFile& dex_file = klass->GetDexFile(); - const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile(); - if (oat_dex_file == nullptr) { - // No oat file, this must be a test configuration that doesn't compile at all. Ignore that the - // result will be that we're running the interpreter. - return JNI_FALSE; - } - NthCallerVisitor caller(soa.Self(), level, false); caller.WalkStack(); CHECK(caller.caller != nullptr); - return caller.GetCurrentShadowFrame() != nullptr ? JNI_FALSE : JNI_TRUE; } diff --git a/test/knownfailures.json b/test/knownfailures.json index e7343a0fd5..0e42a29bf6 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -220,7 +220,6 @@ "tests": ["604-hot-static-interface", "612-jit-dex-cache", "613-inlining-dex-cache", - "616-cha", "626-set-resolved-string"], "variant": "trace | stream", "description": ["These tests expect JIT compilation, which is", @@ -330,14 +329,8 @@ "variant": "interpreter | optimizing | regalloc_gc | jit" }, { - "tests": ["912-classes", - "616-cha", - "616-cha-abstract", - "616-cha-interface", - "616-cha-interface-default", - "616-cha-miranda", - "616-cha-proxy-method-inline"], - "bug": "http://b/36344364 http://b/36344221", + "tests": ["912-classes"], + "bug": "http://b/36344364", "variant": "no-dex2oat | relocate-npatchoat" }, { |