diff options
95 files changed, 3676 insertions, 176 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index ed34a8df5f..f27db0eed9 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -170,6 +170,12 @@ ART_GTEST_dex2oat_test_TARGET_DEPS := \ # TODO: document why this is needed. ART_GTEST_proxy_test_HOST_DEPS := $(HOST_CORE_IMAGE_DEFAULT_64) $(HOST_CORE_IMAGE_DEFAULT_32) +# The dexdiag test requires the dexdiag utility. +ART_GTEST_dexdiag_test_HOST_DEPS := \ + $(HOST_OUT_EXECUTABLES)/dexdiag +ART_GTEST_dexdiag_test_TARGET_DEPS := \ + dexdiag + # The dexdump test requires an image and the dexdump utility. # TODO: rename into dexdump when migration completes ART_GTEST_dexdump_test_HOST_DEPS := \ @@ -241,6 +247,7 @@ ART_TEST_MODULES := \ art_compiler_tests \ art_compiler_host_tests \ art_dex2oat_tests \ + art_dexdiag_tests \ art_dexdump_tests \ art_dexlayout_tests \ art_dexlist_tests \ diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index e823f67d3c..1a4452429e 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -2181,6 +2181,10 @@ class VerifyClassVisitor : public CompilationVisitor { CHECK(klass->ShouldVerifyAtRuntime() || klass->IsVerified() || klass->IsErroneous()) << klass->PrettyDescriptor() << ": state=" << klass->GetStatus(); + // Class has a meaningful status for the compiler now, record it. + ClassReference ref(manager_->GetDexFile(), class_def_index); + manager_->GetCompiler()->RecordClassStatus(ref, klass->GetStatus()); + // It is *very* problematic if there are verification errors in the boot classpath. For example, // we rely on things working OK without verification when the decryption dialog is brought up. // So abort in a debug build if we find this violated. diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index fa1b3a304a..42ff1e748a 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -23,6 +23,7 @@ #include "art_method-inl.h" #include "class_linker-inl.h" #include "common_compiler_test.h" +#include "compiled_class.h" #include "dex_file.h" #include "dex_file_types.h" #include "gc/heap.h" @@ -319,6 +320,47 @@ TEST_F(CompilerDriverProfileTest, ProfileGuidedCompilation) { CheckCompiledMethods(class_loader, "LSecond;", s); } +// Test that a verify only compiler filter updates the CompiledClass map, +// which will be used for OatClass. +class CompilerDriverVerifyTest : public CompilerDriverTest { + protected: + CompilerFilter::Filter GetCompilerFilter() const OVERRIDE { + return CompilerFilter::kVerifyProfile; + } + + void CheckVerifiedClass(jobject class_loader, const std::string& clazz) const { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); + mirror::Class* klass = class_linker->FindClass(self, clazz.c_str(), h_loader); + ASSERT_NE(klass, nullptr); + EXPECT_TRUE(klass->IsVerified()); + + CompiledClass* compiled_class = compiler_driver_->GetCompiledClass( + ClassReference(&klass->GetDexFile(), klass->GetDexTypeIndex().index_)); + ASSERT_NE(compiled_class, nullptr); + EXPECT_EQ(compiled_class->GetStatus(), mirror::Class::kStatusVerified); + } +}; + +TEST_F(CompilerDriverVerifyTest, VerifyCompilation) { + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("ProfileTestMultiDex"); + } + ASSERT_NE(class_loader, nullptr); + + CompileAll(class_loader); + + CheckVerifiedClass(class_loader, "LMain;"); + CheckVerifiedClass(class_loader, "LSecond;"); +} + // TODO: need check-cast test (when stub complete & we can throw/catch } // namespace art diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc index 53797d280a..551c73b2a4 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.cc +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -383,9 +383,14 @@ static void EmitGrayCheckAndFastPath(arm64::Arm64Assembler& assembler, static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0"); static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1"); __ Tbnz(ip0.W(), LockWord::kReadBarrierStateShift, slow_path); - static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == -4, "Check field LDR offset"); - static_assert(BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET == -4, "Check array LDR offset"); - __ Sub(lr, lr, 4); // Adjust the return address one instruction back to the LDR. + static_assert( + BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET == BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET, + "Field and array LDR offsets must be the same to reuse the same code."); + // Adjust the return address back to the LDR (1 instruction; 2 for heap poisoning). + static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4), + "Field LDR must be 1 instruction (4B) before the return address label; " + " 2 instructions (8B) for heap poisoning."); + __ Add(lr, lr, BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET); // Introduce a dependency on the lock_word including rb_state, // to prevent load-load reordering, and without using // a memory barrier (which would be more expensive). @@ -431,8 +436,9 @@ std::vector<uint8_t> Arm64RelativePatcher::CompileThunk(const ThunkKey& key) { __ Bind(&slow_path); MemOperand ldr_address(lr, BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET); __ Ldr(ip0.W(), ldr_address); // Load the LDR (immediate) unsigned offset. - __ Ubfx(ip0, ip0, 10, 12); // Extract the offset. + __ Ubfx(ip0.W(), ip0.W(), 10, 12); // Extract the offset. __ Ldr(ip0.W(), MemOperand(base_reg, ip0, LSL, 2)); // Load the reference. + // Do not unpoison. With heap poisoning enabled, the entrypoint expects a poisoned reference. __ Br(ip1); // Jump to the entrypoint. if (holder_reg.Is(base_reg)) { // Add null check slow path. The stack map is at the address pointed to by LR. diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 4955562f82..4629c54a17 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -90,9 +90,8 @@ static constexpr uint32_t kPackedSwitchCompareJumpThreshold = 7; constexpr uint32_t kReferenceLoadMinFarOffset = 16 * KB; // Flags controlling the use of link-time generated thunks for Baker read barriers. -// Not yet implemented for heap poisoning. -constexpr bool kBakerReadBarrierLinkTimeThunksEnableForFields = !kPoisonHeapReferences; -constexpr bool kBakerReadBarrierLinkTimeThunksEnableForGcRoots = !kPoisonHeapReferences; +constexpr bool kBakerReadBarrierLinkTimeThunksEnableForFields = true; +constexpr bool kBakerReadBarrierLinkTimeThunksEnableForGcRoots = true; // Some instructions have special requirements for a temporary, for example // LoadClass/kBssEntry and LoadString/kBssEntry for Baker read barrier require @@ -3053,6 +3052,11 @@ void InstructionCodeGeneratorARM64::VisitArraySet(HArraySet* instruction) { if (!index.IsConstant()) { __ Add(temp, array, offset); + } else { + // We no longer need the `temp` here so release it as the store below may + // need a scratch register (if the constant index makes the offset too large) + // and the poisoned `source` could be using the other scratch register. + temps.Release(temp); } { // Ensure that between store and MaybeRecordImplicitNullCheck there are no pools emitted. @@ -6093,17 +6097,21 @@ void CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier(HInstruction* ins const int32_t entry_point_offset = CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ip0.GetCode()); __ Ldr(ip1, MemOperand(tr, entry_point_offset)); - EmissionCheckScope guard(GetVIXLAssembler(), 3 * vixl::aarch64::kInstructionSize); + EmissionCheckScope guard(GetVIXLAssembler(), + (kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize); vixl::aarch64::Label return_address; __ adr(lr, &return_address); __ Bind(cbnz_label); __ cbnz(ip1, static_cast<int64_t>(0)); // Placeholder, patched at link-time. - static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == -4, - "Field LDR must be 1 instruction (4B) before the return address label."); - __ ldr(RegisterFrom(ref, Primitive::kPrimNot), MemOperand(base.X(), offset)); + static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4), + "Field LDR must be 1 instruction (4B) before the return address label; " + " 2 instructions (8B) for heap poisoning."); + Register ref_reg = RegisterFrom(ref, Primitive::kPrimNot); + __ ldr(ref_reg, MemOperand(base.X(), offset)); if (needs_null_check) { MaybeRecordImplicitNullCheck(instruction); } + GetAssembler()->MaybeUnpoisonHeapReference(ref_reg); __ Bind(&return_address); return; } 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/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/dexlayout/Android.bp b/dexlayout/Android.bp index 4b65c5299a..16e6809f4a 100644 --- a/dexlayout/Android.bp +++ b/dexlayout/Android.bp @@ -20,7 +20,7 @@ art_cc_defaults { "dexlayout.cc", "dex_ir.cc", "dex_ir_builder.cc", - "dex_verify.cc", + "dex_verify.cc", "dex_visualize.cc", "dex_writer.cc", ], @@ -43,6 +43,7 @@ art_cc_library { art_cc_binary { name: "dexlayout", + defaults: ["art_defaults"], host_supported: true, srcs: ["dexlayout_main.cc"], cflags: ["-Wall"], @@ -60,13 +61,28 @@ art_cc_test { art_cc_binary { name: "dexdiag", - host_supported: false, + defaults: ["art_defaults"], + host_supported: true, srcs: ["dexdiag.cc"], cflags: ["-Wall"], shared_libs: [ "libart", "libart-dexlayout", - "libpagemap", ], + target: { + android: { + shared_libs: [ + "libpagemap", + ] + }, + } } +art_cc_test { + name: "art_dexdiag_tests", + host_supported: true, + defaults: [ + "art_gtest_defaults", + ], + srcs: ["dexdiag_test.cc"], +} diff --git a/dexlayout/dexdiag.cc b/dexlayout/dexdiag.cc index 688201b6b8..bc56bbfa81 100644 --- a/dexlayout/dexdiag.cc +++ b/dexlayout/dexdiag.cc @@ -15,6 +15,7 @@ */ #include <errno.h> +#include <inttypes.h> #include <stdint.h> #include <stdlib.h> #include <string.h> @@ -30,7 +31,9 @@ #include "dex_file.h" #include "dex_ir.h" #include "dex_ir_builder.h" +#ifdef ART_TARGET_ANDROID #include "pagemap/pagemap.h" +#endif #include "runtime.h" #include "vdex_file.h" @@ -38,8 +41,6 @@ namespace art { using android::base::StringPrintf; -static constexpr size_t kLineLength = 32; - static bool g_verbose = false; // The width needed to print a file page offset (32-bit). @@ -164,6 +165,7 @@ static void PrintLetterKey() { std::cout << ". (Mapped page not resident)" << std::endl; } +#ifdef ART_TARGET_ANDROID static char PageTypeChar(uint16_t type) { if (kDexSectionInfoMap.find(type) == kDexSectionInfoMap.end()) { return '-'; @@ -194,6 +196,7 @@ static void ProcessPageMap(uint64_t* pagemap, size_t end, const std::vector<dex_ir::DexFileSection>& sections, PageCount* page_counts) { + static constexpr size_t kLineLength = 32; for (size_t page = start; page < end; ++page) { char type_char = '.'; if (PM_PAGEMAP_PRESENT(pagemap[page])) { @@ -268,7 +271,7 @@ static void ProcessOneDexMapping(uint64_t* pagemap, std::cerr << "Dex file start offset for " << dex_file->GetLocation().c_str() << " is incorrect: map start " - << StringPrintf("%zx > dex start %zx\n", map_start, dex_file_start) + << StringPrintf("%" PRIx64 " > dex start %" PRIx64 "\n", map_start, dex_file_start) << std::endl; return; } @@ -277,7 +280,7 @@ static void ProcessOneDexMapping(uint64_t* pagemap, uint64_t end_page = RoundUp(start_address + dex_file_size, kPageSize) / kPageSize; std::cout << "DEX " << dex_file->GetLocation().c_str() - << StringPrintf(": %zx-%zx", + << StringPrintf(": %" PRIx64 "-%" PRIx64, map_start + start_page * kPageSize, map_start + end_page * kPageSize) << std::endl; @@ -341,7 +344,7 @@ static bool DisplayMappingIfFromVdexFile(pm_map_t* map, Printer* printer) { // Process the dex files. std::cout << "MAPPING " << pm_map_name(map) - << StringPrintf(": %zx-%zx", pm_map_start(map), pm_map_end(map)) + << StringPrintf(": %" PRIx64 "-%" PRIx64, pm_map_start(map), pm_map_end(map)) << std::endl; for (const auto& dex_file : dex_files) { ProcessOneDexMapping(pagemap, @@ -355,6 +358,7 @@ static bool DisplayMappingIfFromVdexFile(pm_map_t* map, Printer* printer) { } static void ProcessOneOatMapping(uint64_t* pagemap, size_t size, Printer* printer) { + static constexpr size_t kLineLength = 32; size_t resident_page_count = 0; for (size_t page = 0; page < size; ++page) { char type_char = '.'; @@ -405,7 +409,7 @@ static bool DisplayMappingIfFromOatFile(pm_map_t* map, Printer* printer) { // Process the dex files. std::cout << "MAPPING " << pm_map_name(map) - << StringPrintf(": %zx-%zx", pm_map_start(map), pm_map_end(map)) + << StringPrintf(": %" PRIx64 "-%" PRIx64, pm_map_start(map), pm_map_end(map)) << std::endl; ProcessOneOatMapping(pagemap, len, printer); free(pagemap); @@ -425,9 +429,10 @@ static bool FilterByNameContains(const std::string& mapped_file_name, } return false; } +#endif static void Usage(const char* cmd) { - std::cerr << "Usage: " << cmd << " [options] pid" << std::endl + std::cout << "Usage: " << cmd << " [options] pid" << std::endl << " --contains=<string>: Display sections containing string." << std::endl << " --help: Shows this message." << std::endl << " --verbose: Makes displays verbose." << std::endl; @@ -462,6 +467,7 @@ static int DexDiagMain(int argc, char* argv[]) { InitLogging(argv, Runtime::Aborter); MemMap::Init(); +#ifdef ART_TARGET_ANDROID pid_t pid; char* endptr; pid = (pid_t)strtol(argv[argc - 1], &endptr, 10); @@ -495,7 +501,7 @@ static int DexDiagMain(int argc, char* argv[]) { return EXIT_FAILURE; } - // Process the mappings that are due to DEX files. + // Process the mappings that are due to vdex or oat files. Printer printer; for (size_t i = 0; i < num_maps; ++i) { std::string mapped_file_name = pm_map_name(maps[i]); @@ -509,6 +515,7 @@ static int DexDiagMain(int argc, char* argv[]) { return EXIT_FAILURE; } } +#endif return EXIT_SUCCESS; } diff --git a/dexlayout/dexdiag_test.cc b/dexlayout/dexdiag_test.cc new file mode 100644 index 0000000000..dfe42b20fe --- /dev/null +++ b/dexlayout/dexdiag_test.cc @@ -0,0 +1,155 @@ +/* + * 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 <string> +#include <vector> + +#include "common_runtime_test.h" + +#include "runtime/exec_utils.h" +#include "runtime/oat_file.h" +#include "runtime/os.h" + +namespace art { + +static const char* kDexDiagContains = "--contains=boot.vdex"; +static const char* kDexDiagContainsFails = "--contains=anything_other_than_boot.vdex"; +static const char* kDexDiagHelp = "--help"; +static const char* kDexDiagVerbose = "--verbose"; +static const char* kDexDiagBinaryName = "dexdiag"; + +class DexDiagTest : public CommonRuntimeTest { + protected: + virtual void SetUp() { + CommonRuntimeTest::SetUp(); + } + + // Path to the dexdiag(d?)[32|64] binary. + std::string GetDexDiagFilePath() { + std::string root = GetTestAndroidRoot(); + + root += "/bin/"; + root += kDexDiagBinaryName; + + std::string root32 = root + "32"; + // If we have both a 32-bit and a 64-bit build, the 32-bit file will have a 32 suffix. + if (OS::FileExists(root32.c_str()) && !Is64BitInstructionSet(kRuntimeISA)) { + return root32; + } else { + // This is a 64-bit build or only a single build exists. + return root; + } + } + + void OpenOatAndVdexFiles() { + // Open the boot.oat file. + // This is a little convoluted because we have to + // get the location of the default boot image (/system/framework/boot.art), + // find it in the right architecture subdirectory (/system/framework/arm/boot.art), + // find the oat file next to the image (/system/framework/arm/boot.oat). + // Then, opening the oat file has the side-effect of opening the corresponding + // vdex file (/system/framework/arm/boot.vdex). + std::string error_msg; + const std::string default_location = GetDefaultBootImageLocation(&error_msg); + EXPECT_TRUE(!default_location.empty()) << error_msg; + std::string oat_location = GetSystemImageFilename(default_location.c_str(), kRuntimeISA); + EXPECT_TRUE(!oat_location.empty()); + const std::string kImageFileSuffix = ".art"; + size_t suffix_pos = oat_location.rfind(kImageFileSuffix); + EXPECT_TRUE(suffix_pos != std::string::npos); + const std::string kOatFileSuffix = ".oat"; + oat_location = oat_location.replace(suffix_pos, kImageFileSuffix.length(), kOatFileSuffix); + std::unique_ptr<OatFile> oat(OatFile::Open(oat_location.c_str(), + oat_location.c_str(), + nullptr, + nullptr, + false, + /*low_4gb*/false, + nullptr, + &error_msg)); + EXPECT_TRUE(oat != nullptr) << error_msg; + } + + // Run dexdiag with a custom boot image location. + bool Exec(pid_t this_pid, const std::vector<std::string>& args, std::string* error_msg) { + // Invoke 'dexdiag' against the current process. + // This should succeed because we have a runtime and so it should + // be able to map in the boot.art and do a diff for it. + std::vector<std::string> exec_argv; + + // Build the command line "dexdiag <args> this_pid". + std::string executable_path = GetDexDiagFilePath(); + EXPECT_TRUE(OS::FileExists(executable_path.c_str())) << executable_path + << " should be a valid file path"; + exec_argv.push_back(executable_path); + for (const auto& arg : args) { + exec_argv.push_back(arg); + } + exec_argv.push_back(std::to_string(this_pid)); + + return ::art::Exec(exec_argv, error_msg); + } +}; + +// We can't run these tests on the host, as they will fail when trying to open +// /proc/pid/pagemap. +// On the target, we invoke 'dexdiag' against the current process. +// This should succeed because we have a runtime and so dexdiag should +// be able to find the map for, e.g., boot.vdex and friends. +TEST_F(DexDiagTest, DexDiagHelpTest) { + // TODO: test the resulting output. + std::string error_msg; + ASSERT_TRUE(Exec(getpid(), { kDexDiagHelp }, &error_msg)) << "Failed to execute -- because: " + << error_msg; +} + +#if defined (ART_TARGET) +TEST_F(DexDiagTest, DexDiagContainsTest) { +#else +TEST_F(DexDiagTest, DISABLED_DexDiagContainsTest) { +#endif + OpenOatAndVdexFiles(); + // TODO: test the resulting output. + std::string error_msg; + ASSERT_TRUE(Exec(getpid(), { kDexDiagContains }, &error_msg)) << "Failed to execute -- because: " + << error_msg; +} + +#if defined (ART_TARGET) +TEST_F(DexDiagTest, DexDiagContainsFailsTest) { +#else +TEST_F(DexDiagTest, DISABLED_DexDiagContainsFailsTest) { +#endif + OpenOatAndVdexFiles(); + // TODO: test the resulting output. + std::string error_msg; + ASSERT_TRUE(Exec(getpid(), { kDexDiagContainsFails }, &error_msg)) + << "Failed to execute -- because: " + << error_msg; +} + +#if defined (ART_TARGET) +TEST_F(DexDiagTest, DexDiagVerboseTest) { +#else +TEST_F(DexDiagTest, DISABLED_DexDiagVerboseTest) { +#endif + // TODO: test the resulting output. + std::string error_msg; + ASSERT_TRUE(Exec(getpid(), { kDexDiagVerbose }, &error_msg)) << "Failed to execute -- because: " + << error_msg; +} + +} // namespace art diff --git a/runtime/arch/arm64/asm_support_arm64.h b/runtime/arch/arm64/asm_support_arm64.h index cfcd6a7e00..6b7720023e 100644 --- a/runtime/arch/arm64/asm_support_arm64.h +++ b/runtime/arch/arm64/asm_support_arm64.h @@ -32,9 +32,17 @@ #define BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRYPOINT_OFFSET 0x300 // The offset of the reference load LDR from the return address in LR for field loads. +#ifdef USE_HEAP_POISONING +#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -8 +#else #define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -4 +#endif // The offset of the reference load LDR from the return address in LR for array loads. +#ifdef USE_HEAP_POISONING +#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -8 +#else #define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -4 +#endif // The offset of the reference load LDR from the return address in LR for GC root loads. #define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET -8 diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index c7fa7f5d2b..d043962b96 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -2649,7 +2649,8 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, w29, x29 * * For field accesses and array loads with a constant index the thunk loads * the reference into IP0 using introspection and calls the main entrypoint, - * art_quick_read_barrier_mark_introspection. + * art_quick_read_barrier_mark_introspection. With heap poisoning enabled, + * the passed reference is poisoned. * * For array accesses with non-constant index, the thunk inserts the bits * 16-21 of the LDR instruction to the entrypoint address, effectively @@ -2663,6 +2664,7 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, w29, x29 * * For GC root accesses we cannot use the main entrypoint because of the * different offset where the LDR instruction in generated code is located. + * (And even with heap poisoning enabled, GC roots are not poisoned.) * To re-use the same entrypoint pointer in generated code, we make sure * that the gc root entrypoint (a copy of the entrypoint with a different * offset for introspection loads) is located at a known offset (768 bytes, @@ -2686,6 +2688,8 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, w29, x29 .balign 512 ENTRY art_quick_read_barrier_mark_introspection // At this point, IP0 contains the reference, IP1 can be freely used. + // For heap poisoning, the reference is poisoned, so unpoison it first. + UNPOISON_HEAP_REF wIP0 // If reference is null, just return it in the right register. cbz wIP0, .Lmark_introspection_return // Use wIP1 as temp and check the mark bit of the reference. 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/mutex-inl.h b/runtime/base/mutex-inl.h index 44a84c834f..08b370ec4e 100644 --- a/runtime/base/mutex-inl.h +++ b/runtime/base/mutex-inl.h @@ -89,13 +89,14 @@ inline void BaseMutex::RegisterAsLocked(Thread* self) { // Check if a bad Mutex of this level or lower is held. bool bad_mutexes_held = false; for (int i = level_; i >= 0; --i) { - BaseMutex* held_mutex = self->GetHeldMutex(static_cast<LockLevel>(i)); - if (UNLIKELY(held_mutex != nullptr)) { + LockLevel lock_level_i = static_cast<LockLevel>(i); + BaseMutex* held_mutex = self->GetHeldMutex(lock_level_i); + if (UNLIKELY(held_mutex != nullptr) && lock_level_i != kAbortLock) { LOG(ERROR) << "Lock level violation: holding \"" << held_mutex->name_ << "\" " - << "(level " << LockLevel(i) << " - " << i + << "(level " << lock_level_i << " - " << i << ") while locking \"" << name_ << "\" " << "(level " << level_ << " - " << static_cast<int>(level_) << ")"; - if (i > kAbortLock) { + if (lock_level_i > kAbortLock) { // Only abort in the check below if this is more than abort level lock. bad_mutexes_held = true; } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 039b60a26d..63794bff6f 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -2425,7 +2425,9 @@ JDWP::JdwpError Dbg::SuspendThread(JDWP::ObjectId thread_id, bool request_suspen // Suspend thread to build stack trace. bool timed_out; ThreadList* thread_list = Runtime::Current()->GetThreadList(); - Thread* thread = thread_list->SuspendThreadByPeer(peer.get(), request_suspension, true, + Thread* thread = thread_list->SuspendThreadByPeer(peer.get(), + request_suspension, + /* debug_suspension */ true, &timed_out); if (thread != nullptr) { return JDWP::ERR_NONE; @@ -3669,7 +3671,10 @@ class ScopedDebuggerThreadSuspension { jobject thread_peer = Dbg::GetObjectRegistry()->GetJObject(thread_id); bool timed_out; ThreadList* const thread_list = Runtime::Current()->GetThreadList(); - suspended_thread = thread_list->SuspendThreadByPeer(thread_peer, true, true, &timed_out); + suspended_thread = thread_list->SuspendThreadByPeer(thread_peer, + /* request_suspension */ true, + /* debug_suspension */ true, + &timed_out); } if (suspended_thread == nullptr) { // Thread terminated from under us while suspending. 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/gc/gc_cause.cc b/runtime/gc/gc_cause.cc index c1c1cad861..c35ec7c341 100644 --- a/runtime/gc/gc_cause.cc +++ b/runtime/gc/gc_cause.cc @@ -29,6 +29,7 @@ const char* PrettyCause(GcCause cause) { case kGcCauseBackground: return "Background"; case kGcCauseExplicit: return "Explicit"; case kGcCauseForNativeAlloc: return "NativeAlloc"; + case kGcCauseForNativeAllocBackground: return "NativeAllocBackground"; case kGcCauseCollectorTransition: return "CollectorTransition"; case kGcCauseDisableMovingGc: return "DisableMovingGc"; case kGcCauseHomogeneousSpaceCompact: return "HomogeneousSpaceCompact"; diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h index eb27547768..41c894340c 100644 --- a/runtime/gc/gc_cause.h +++ b/runtime/gc/gc_cause.h @@ -33,6 +33,8 @@ enum GcCause { kGcCauseExplicit, // GC triggered for a native allocation. kGcCauseForNativeAlloc, + // Background GC triggered for a native allocation. + kGcCauseForNativeAllocBackground, // GC triggered for a collector transition. kGcCauseCollectorTransition, // Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical). diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index a853b98bbe..4a25610bf4 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -3686,20 +3686,21 @@ void Heap::RequestConcurrentGCAndSaveObject(Thread* self, ObjPtr<mirror::Object>* obj) { StackHandleScope<1> hs(self); HandleWrapperObjPtr<mirror::Object> wrapper(hs.NewHandleWrapper(obj)); - RequestConcurrentGC(self, force_full); + RequestConcurrentGC(self, kGcCauseBackground, force_full); } class Heap::ConcurrentGCTask : public HeapTask { public: - ConcurrentGCTask(uint64_t target_time, bool force_full) - : HeapTask(target_time), force_full_(force_full) { } + ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full) + : HeapTask(target_time), cause_(cause), force_full_(force_full) {} virtual void Run(Thread* self) OVERRIDE { gc::Heap* heap = Runtime::Current()->GetHeap(); - heap->ConcurrentGC(self, force_full_); + heap->ConcurrentGC(self, cause_, force_full_); heap->ClearConcurrentGCRequest(); } private: + const GcCause cause_; const bool force_full_; // If true, force full (or partial) collection. }; @@ -3713,18 +3714,19 @@ void Heap::ClearConcurrentGCRequest() { concurrent_gc_pending_.StoreRelaxed(false); } -void Heap::RequestConcurrentGC(Thread* self, bool force_full) { +void Heap::RequestConcurrentGC(Thread* self, GcCause cause, bool force_full) { if (CanAddHeapTask(self) && concurrent_gc_pending_.CompareExchangeStrongSequentiallyConsistent(false, true)) { task_processor_->AddTask(self, new ConcurrentGCTask(NanoTime(), // Start straight away. + cause, force_full)); } } -void Heap::ConcurrentGC(Thread* self, bool force_full) { +void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full) { if (!Runtime::Current()->IsShuttingDown(self)) { // Wait for any GCs currently running to finish. - if (WaitForGcToComplete(kGcCauseBackground, self) == collector::kGcTypeNone) { + if (WaitForGcToComplete(cause, self) == collector::kGcTypeNone) { // If the we can't run the GC type we wanted to run, find the next appropriate one and try that // instead. E.g. can't do partial, so do full instead. collector::GcType next_gc_type = next_gc_type_; @@ -3732,13 +3734,11 @@ void Heap::ConcurrentGC(Thread* self, bool force_full) { if (force_full && next_gc_type == collector::kGcTypeSticky) { next_gc_type = NonStickyGcType(); } - if (CollectGarbageInternal(next_gc_type, kGcCauseBackground, false) == - collector::kGcTypeNone) { + if (CollectGarbageInternal(next_gc_type, cause, false) == collector::kGcTypeNone) { for (collector::GcType gc_type : gc_plan_) { // Attempt to run the collector, if we succeed, we are done. if (gc_type > next_gc_type && - CollectGarbageInternal(gc_type, kGcCauseBackground, false) != - collector::kGcTypeNone) { + CollectGarbageInternal(gc_type, cause, false) != collector::kGcTypeNone) { break; } } @@ -3940,7 +3940,7 @@ void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) { // Trigger another GC because there have been enough native bytes // allocated since the last GC. if (IsGcConcurrent()) { - RequestConcurrentGC(ThreadForEnv(env), /*force_full*/true); + RequestConcurrentGC(ThreadForEnv(env), kGcCauseForNativeAllocBackground, /*force_full*/true); } else { CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false); } diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 1a782b409b..241d84ce22 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -330,7 +330,7 @@ class Heap { // Does a concurrent GC, should only be called by the GC daemon thread // through runtime. - void ConcurrentGC(Thread* self, bool force_full) + void ConcurrentGC(Thread* self, GcCause cause, bool force_full) REQUIRES(!Locks::runtime_shutdown_lock_, !*gc_complete_lock_, !*pending_task_lock_); // Implements VMDebug.countInstancesOfClass and JDWP VM_InstanceCount. @@ -743,7 +743,8 @@ class Heap { void RequestTrim(Thread* self) REQUIRES(!*pending_task_lock_); // Request asynchronous GC. - void RequestConcurrentGC(Thread* self, bool force_full) REQUIRES(!*pending_task_lock_); + void RequestConcurrentGC(Thread* self, GcCause cause, bool force_full) + REQUIRES(!*pending_task_lock_); // Whether or not we may use a garbage collector, used so that we only create collectors we need. bool MayUseCollector(CollectorType type) const; 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/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc index 11f850524d..34bbf32a9a 100644 --- a/runtime/native/dalvik_system_VMRuntime.cc +++ b/runtime/native/dalvik_system_VMRuntime.cc @@ -252,7 +252,7 @@ static void VMRuntime_trimHeap(JNIEnv* env, jobject) { } static void VMRuntime_concurrentGC(JNIEnv* env, jobject) { - Runtime::Current()->GetHeap()->ConcurrentGC(ThreadForEnv(env), true); + Runtime::Current()->GetHeap()->ConcurrentGC(ThreadForEnv(env), gc::kGcCauseBackground, true); } static void VMRuntime_requestHeapTrim(JNIEnv* env, jobject) { @@ -260,7 +260,9 @@ static void VMRuntime_requestHeapTrim(JNIEnv* env, jobject) { } static void VMRuntime_requestConcurrentGC(JNIEnv* env, jobject) { - Runtime::Current()->GetHeap()->RequestConcurrentGC(ThreadForEnv(env), true); + Runtime::Current()->GetHeap()->RequestConcurrentGC(ThreadForEnv(env), + gc::kGcCauseBackground, + true); } static void VMRuntime_startHeapTaskProcessor(JNIEnv* env, jobject) { 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/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 06550791c1..358bb0f70e 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -1418,14 +1418,18 @@ void Redefiner::ClassRedefinition::RestoreObsoleteMethodMapsIfUnneeded( art::mirror::Class* klass = GetMirrorClass(); art::mirror::ClassExt* ext = klass->GetExtData(); art::mirror::PointerArray* methods = ext->GetObsoleteMethods(); - int32_t old_length = - cur_data->GetOldDexCaches() == nullptr ? 0 : cur_data->GetOldDexCaches()->GetLength(); + art::mirror::PointerArray* old_methods = cur_data->GetOldObsoleteMethods(); + int32_t old_length = old_methods == nullptr ? 0 : old_methods->GetLength(); int32_t expected_length = old_length + klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods(); // Check to make sure we are only undoing this one. if (expected_length == methods->GetLength()) { - for (int32_t i = old_length; i < expected_length; i++) { - if (methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize) != nullptr) { + for (int32_t i = 0; i < expected_length; i++) { + art::ArtMethod* expected = nullptr; + if (i < old_length) { + expected = old_methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize); + } + if (methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize) != expected) { // We actually have some new obsolete methods. Just abort since we cannot safely shrink the // obsolete methods array. return; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index e563027243..a48a58d235 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1722,6 +1722,7 @@ void Runtime::VisitConstantRoots(RootVisitor* visitor) { mirror::MethodHandlesLookup::VisitRoots(visitor); mirror::EmulatedStackFrame::VisitRoots(visitor); mirror::ClassExt::VisitRoots(visitor); + mirror::CallSite::VisitRoots(visitor); // Visit all the primitive array types classes. mirror::PrimitiveArray<uint8_t>::VisitRoots(visitor); // BooleanArray mirror::PrimitiveArray<int8_t>::VisitRoots(visitor); // ByteArray 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/thread.cc b/runtime/thread.cc index abe65c1de1..a8a03c7e86 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -1451,7 +1451,8 @@ void Thread::RequestSynchronousCheckpoint(Closure* function) { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); DCHECK_NE(GetState(), ThreadState::kRunnable); - CHECK(ModifySuspendCount(self, -1, nullptr, false)); + bool updated = ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); } return; // We're done, break out of the loop. diff --git a/runtime/thread.h b/runtime/thread.h index de0b892f5f..4d5d644f1c 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -244,6 +244,7 @@ class Thread { int delta, AtomicInteger* suspend_barrier, bool for_debugger) + WARN_UNUSED REQUIRES(Locks::thread_suspend_count_lock_); bool RequestCheckpoint(Closure* function) @@ -1276,6 +1277,7 @@ class Thread { int delta, AtomicInteger* suspend_barrier, bool for_debugger) + WARN_UNUSED REQUIRES(Locks::thread_suspend_count_lock_); void RunCheckpointFunction(); diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index 8d72fe80c2..2e0d866c21 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -323,7 +323,8 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function, Closure* callback // Spurious fail, try again. continue; } - thread->ModifySuspendCount(self, +1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, false); + DCHECK(updated); suspended_count_modified_threads.push_back(thread); break; } @@ -365,7 +366,8 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function, Closure* callback checkpoint_function->Run(thread); { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); - thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); } } @@ -565,7 +567,8 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, if ((state == kWaitingForGcThreadFlip || thread->IsTransitioningToRunnable()) && thread->GetSuspendCount() == 1) { // The thread will resume right after the broadcast. - thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); ++runnable_thread_count; } else { other_threads.push_back(thread); @@ -598,7 +601,8 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, TimingLogger::ScopedTiming split4("ResumeOtherThreads", collector->GetTimings()); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (const auto& thread : other_threads) { - thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); } Thread::resume_cond_->Broadcast(self); } @@ -708,7 +712,8 @@ void ThreadList::SuspendAllInternal(Thread* self, continue; } VLOG(threads) << "requesting thread suspend: " << *thread; - thread->ModifySuspendCount(self, +1, &pending_threads, debug_suspend); + bool updated = thread->ModifySuspendCount(self, +1, &pending_threads, debug_suspend); + DCHECK(updated); // Must install the pending_threads counter first, then check thread->IsSuspend() and clear // the counter. Otherwise there's a race with Thread::TransitionFromRunnableToSuspended() @@ -786,7 +791,8 @@ void ThreadList::ResumeAll() { if (thread == self) { continue; } - thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); } // Broadcast a notification to all suspended threads, some or all of @@ -828,7 +834,8 @@ void ThreadList::Resume(Thread* thread, bool for_debugger) { << ") thread not within thread list"; return; } - thread->ModifySuspendCount(self, -1, nullptr, for_debugger); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, for_debugger); + DCHECK(updated); } { @@ -884,7 +891,11 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, // If we incremented the suspend count but the thread reset its peer, we need to // re-decrement it since it is shutting down and may deadlock the runtime in // ThreadList::WaitForOtherNonDaemonThreadsToExit. - suspended_thread->ModifySuspendCount(soa.Self(), -1, nullptr, debug_suspension); + bool updated = suspended_thread->ModifySuspendCount(soa.Self(), + -1, + nullptr, + debug_suspension); + DCHECK(updated); } ThreadSuspendByPeerWarning(self, ::android::base::WARNING, @@ -910,7 +921,8 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, } CHECK(suspended_thread == nullptr); suspended_thread = thread; - suspended_thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + bool updated = suspended_thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + DCHECK(updated); request_suspension = false; } else { // If the caller isn't requesting suspension, a suspension should have already occurred. @@ -942,7 +954,11 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, peer); if (suspended_thread != nullptr) { CHECK_EQ(suspended_thread, thread); - suspended_thread->ModifySuspendCount(soa.Self(), -1, nullptr, debug_suspension); + bool updated = suspended_thread->ModifySuspendCount(soa.Self(), + -1, + nullptr, + debug_suspension); + DCHECK(updated); } *timed_out = true; return nullptr; @@ -1015,7 +1031,8 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, // which will allow this thread to be suspended. continue; } - thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, debug_suspension); + DCHECK(updated); suspended_thread = thread; } else { CHECK_EQ(suspended_thread, thread); @@ -1046,7 +1063,8 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, "Thread suspension timed out", thread_id); if (suspended_thread != nullptr) { - thread->ModifySuspendCount(soa.Self(), -1, nullptr, debug_suspension); + bool updated = thread->ModifySuspendCount(soa.Self(), -1, nullptr, debug_suspension); + DCHECK(updated); } *timed_out = true; return nullptr; @@ -1123,7 +1141,8 @@ void ThreadList::SuspendSelfForDebugger() { // to ensure that we're the only one fiddling with the suspend count // though. MutexLock mu(self, *Locks::thread_suspend_count_lock_); - self->ModifySuspendCount(self, +1, nullptr, true); + bool updated = self->ModifySuspendCount(self, +1, nullptr, true); + DCHECK(updated); CHECK_GT(self->GetSuspendCount(), 0); VLOG(threads) << *self << " self-suspending (debugger)"; @@ -1207,7 +1226,8 @@ void ThreadList::ResumeAllForDebugger() { continue; } VLOG(threads) << "requesting thread resume: " << *thread; - thread->ModifySuspendCount(self, -1, nullptr, true); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, true); + DCHECK(updated); } } } @@ -1236,7 +1256,11 @@ void ThreadList::UndoDebuggerSuspensions() { if (thread == self || thread->GetDebugSuspendCount() == 0) { continue; } - thread->ModifySuspendCount(self, -thread->GetDebugSuspendCount(), nullptr, true); + bool suspended = thread->ModifySuspendCount(self, + -thread->GetDebugSuspendCount(), + nullptr, + true); + DCHECK(suspended); } } @@ -1293,7 +1317,8 @@ void ThreadList::SuspendAllDaemonThreadsForShutdown() { // daemons. CHECK(thread->IsDaemon()) << *thread; if (thread != self) { - thread->ModifySuspendCount(self, +1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, +1, nullptr, false); + DCHECK(updated); ++daemons_left; } // We are shutting down the runtime, set the JNI functions of all the JNIEnvs to be @@ -1352,10 +1377,12 @@ void ThreadList::Register(Thread* self) { // Modify suspend count in increments of 1 to maintain invariants in ModifySuspendCount. While // this isn't particularly efficient the suspend counts are most commonly 0 or 1. for (int delta = debug_suspend_all_count_; delta > 0; delta--) { - self->ModifySuspendCount(self, +1, nullptr, true); + bool updated = self->ModifySuspendCount(self, +1, nullptr, true); + DCHECK(updated); } for (int delta = suspend_all_count_ - debug_suspend_all_count_; delta > 0; delta--) { - self->ModifySuspendCount(self, +1, nullptr, false); + bool updated = self->ModifySuspendCount(self, +1, nullptr, false); + DCHECK(updated); } CHECK(!Contains(self)); list_.push_back(self); @@ -1450,11 +1477,13 @@ void ThreadList::VisitRootsForSuspendedThreads(RootVisitor* visitor) { MutexLock mu(self, *Locks::thread_list_lock_); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (Thread* thread : list_) { - thread->ModifySuspendCount(self, +1, nullptr, false); + bool suspended = thread->ModifySuspendCount(self, +1, nullptr, false); + DCHECK(suspended); if (thread == self || thread->IsSuspended()) { threads_to_visit.push_back(thread); } else { - thread->ModifySuspendCount(self, -1, nullptr, false); + bool resumed = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(resumed); } } } @@ -1469,7 +1498,8 @@ void ThreadList::VisitRootsForSuspendedThreads(RootVisitor* visitor) { { MutexLock mu2(self, *Locks::thread_suspend_count_lock_); for (Thread* thread : threads_to_visit) { - thread->ModifySuspendCount(self, -1, nullptr, false); + bool updated = thread->ModifySuspendCount(self, -1, nullptr, false); + DCHECK(updated); } } } 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/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/985-re-obsolete/expected.txt b/test/985-re-obsolete/expected.txt new file mode 100644 index 0000000000..5159a00f43 --- /dev/null +++ b/test/985-re-obsolete/expected.txt @@ -0,0 +1,35 @@ +Pre Start private method call +hello - private +Post Start private method call +Not doing anything here +Pre Finish private method call +goodbye - private +Post Finish private method call +Pre Start private method call +hello - private +Post Start private method call +transforming calling function +Pre Finish private method call +Goodbye - private - Transformed +Post Finish private method call +Pre Start private method call - Transformed +Hello - private - Transformed +Post Start private method call - Transformed +Not doing anything here +Pre Finish private method call - Transformed +Goodbye - private - Transformed +Post Finish private method call - Transformed +Pre Start private method call - Transformed +Hello - private - Transformed +Post Start private method call - Transformed +transforming calling function +Pre Finish private method call - Transformed +second - Goodbye - private - Transformed +Post Finish private method call - Transformed +second - Pre Start private method call - Transformed +second - Hello - private - Transformed +second - Post Start private method call - Transformed +Not doing anything here +second - Pre Finish private method call - Transformed +second - Goodbye - private - Transformed +second - Post Finish private method call - Transformed diff --git a/test/985-re-obsolete/info.txt b/test/985-re-obsolete/info.txt new file mode 100644 index 0000000000..c8eafdc723 --- /dev/null +++ b/test/985-re-obsolete/info.txt @@ -0,0 +1,4 @@ +Tests basic obsolete method support + +Regression test for b/37475600 which was caused by incorrectly checking for +differences in the obsolete methods map. diff --git a/test/985-re-obsolete/run b/test/985-re-obsolete/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/985-re-obsolete/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/985-re-obsolete/src/Main.java b/test/985-re-obsolete/src/Main.java new file mode 100644 index 0000000000..d78d591f47 --- /dev/null +++ b/test/985-re-obsolete/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test985.run(); + } +} diff --git a/test/985-re-obsolete/src/art/Main.java b/test/985-re-obsolete/src/art/Main.java new file mode 100644 index 0000000000..8b01920638 --- /dev/null +++ b/test/985-re-obsolete/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/985-re-obsolete/src/art/Redefinition.java b/test/985-re-obsolete/src/art/Redefinition.java new file mode 100644 index 0000000000..0350ab42ad --- /dev/null +++ b/test/985-re-obsolete/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/985-re-obsolete/src/art/Test985.java b/test/985-re-obsolete/src/art/Test985.java new file mode 100644 index 0000000000..405abd5d14 --- /dev/null +++ b/test/985-re-obsolete/src/art/Test985.java @@ -0,0 +1,197 @@ +/* + * 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.Base64; + +public class Test985 { + + static class Transform { + private void Start() { + System.out.println("hello - private"); + } + + private void Finish() { + System.out.println("goodbye - private"); + } + + public void sayHi(Runnable r) { + System.out.println("Pre Start private method call"); + Start(); + System.out.println("Post Start private method call"); + r.run(); + System.out.println("Pre Finish private method call"); + Finish(); + System.out.println("Post Finish private method call"); + } + } + + // static class Transform { + // private void Start() { + // System.out.println("Hello - private - Transformed"); + // } + // + // private void Finish() { + // System.out.println("Goodbye - private - Transformed"); + // } + // + // public void sayHi(Runnable r) { + // System.out.println("Pre Start private method call - Transformed"); + // Start(); + // System.out.println("Post Start private method call - Transformed"); + // r.run(); + // System.out.println("Pre Finish private method call - Transformed"); + // Finish(); + // System.out.println("Post Finish private method call - Transformed"); + // } + // } + private static final byte[] CLASS_BYTES_1 = Base64.getDecoder().decode( + "yv66vgAAADQANgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" + + "JwcAKQcALAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" + + "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAM" + + "VGVzdDk4NS5qYXZhDAAPABAHAC0MAC4ALwEAHUhlbGxvIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVk" + + "BwAwDAAxADIBAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkAQArUHJlIFN0YXJ0IHBy" + + "aXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwAEwAQAQAsUG9zdCBTdGFydCBwcml2YXRl" + + "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHADMMADQAEAEALFByZSBGaW5pc2ggcHJpdmF0ZSBt" + + "ZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAUABABAC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" + + "ZCBjYWxsIC0gVHJhbnNmb3JtZWQHADUBABVhcnQvVGVzdDk4NSRUcmFuc2Zvcm0BAAlUcmFuc2Zv" + + "cm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEA" + + "A291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmlu" + + "dGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuAQAL" + + "YXJ0L1Rlc3Q5ODUAIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAABABIA" + + "AAAGAAEAAAAEAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAAAAYA" + + "CAAHAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAkACAAKAAEA" + + "FQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQqtwAL" + + "sgACEgy2AASxAAAAAQASAAAAIgAIAAAADAAIAA0ADAAOABQADwAaABAAIgARACYAEgAuABMAAgAX" + + "AAAAAgAYACsAAAAKAAEADQAoACoACA=="); + private static final byte[] DEX_BYTES_1 = Base64.getDecoder().decode( + "ZGV4CjAzNQAh+CJbAAAAAAAAAAAAAAAAAAAAAAAAAADUBQAAcAAAAHhWNBIAAAAAAAAAABAFAAAd" + + "AAAAcAAAAAoAAADkAAAAAwAAAAwBAAABAAAAMAEAAAcAAAA4AQAAAQAAAHABAABEBAAAkAEAAJAB" + + "AACYAQAAoAEAAMEBAADgAQAA+QEAAAgCAAAsAgAATAIAAGMCAAB3AgAAjQIAAKECAAC1AgAA5AIA" + + "ABIDAABAAwAAbQMAAHQDAACCAwAAjQMAAJADAACUAwAAoQMAAKcDAACsAwAAtQMAALoDAADBAwAA" + + "BAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAAFAAAABQAAAAJAAAAAAAAABUAAAAJ" + + "AAAA0AMAABUAAAAJAAAAyAMAAAgABAAYAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAARAAAAAAABABsA" + + "AAAEAAIAGQAAAAUAAAAAAAAABgAAABoAAAAAAAAAAAAAAAUAAAAAAAAAEgAAAAAFAADMBAAAAAAA" + + "AAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVsbG8g" + + "LSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAF0xhcnQvVGVzdDk4NSRUcmFuc2Zvcm07AA1MYXJ0L1Rl" + + "c3Q5ODU7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90" + + "YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl" + + "Y3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5n" + + "L1N5c3RlbTsALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAs" + + "UG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALFByZSBGaW5pc2gg" + + "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACtQcmUgU3RhcnQgcHJpdmF0ZSBtZXRo" + + "b2QgY2FsbCAtIFRyYW5zZm9ybWVkAAVTdGFydAAMVGVzdDk4NS5qYXZhAAlUcmFuc2Zvcm0AAVYA" + + "AlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxuAANydW4ABXNheUhpAAV2YWx1ZQAB" + + "AAAABwAAAAEAAAAGAAAABAAHDgAJAAcOAQgPAAYABw4BCA8ADAEABw4BCA8BAw8BCA8BAw8BCA8B" + + "Aw8BCA8AAQABAAEAAADYAwAABAAAAHAQBQAAAA4AAwABAAIAAADdAwAACQAAAGIAAAAbAQIAAABu" + + "IAQAEAAOAAAAAwABAAIAAADlAwAACQAAAGIAAAAbAQMAAABuIAQAEAAOAAAABAACAAIAAADtAwAA" + + "KgAAAGIAAAAbARAAAABuIAQAEABwEAIAAgBiAAAAGwEOAAAAbiAEABAAchAGAAMAYgAAABsBDwAA" + + "AG4gBAAQAHAQAQACAGIAAAAbAQ0AAABuIAQAEAAOAAAAAwEAgIAEiAgBAqAIAQLECAMB6AgAAAIC" + + "ARwYAQIDAhYECBcXEwACAAAA5AQAAOoEAAD0BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAA" + + "AAEAAAAdAAAAcAAAAAIAAAAKAAAA5AAAAAMAAAADAAAADAEAAAQAAAABAAAAMAEAAAUAAAAHAAAA" + + "OAEAAAYAAAABAAAAcAEAAAIgAAAdAAAAkAEAAAEQAAACAAAAyAMAAAMgAAAEAAAA2AMAAAEgAAAE" + + "AAAACAQAAAAgAAABAAAAzAQAAAQgAAACAAAA5AQAAAMQAAABAAAA9AQAAAYgAAABAAAAAAUAAAAQ" + + "AAABAAAAEAUAAA=="); + + // static class Transform { + // private void Start() { + // System.out.println("second - Hello - private - Transformed"); + // } + // + // private void Finish() { + // System.out.println("second - Goodbye - private - Transformed"); + // } + // + // public void sayHi(Runnable r) { + // System.out.println("second - Pre Start private method call - Transformed"); + // Start(); + // System.out.println("second - Post Start private method call - Transformed"); + // r.run(); + // System.out.println("second - Pre Finish private method call - Transformed"); + // Finish(); + // System.out.println("second - Post Finish private method call - Transformed"); + // } + // } + private static final byte[] CLASS_BYTES_2 = Base64.getDecoder().decode( + "yv66vgAAADQANgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" + + "JwcAKQcALAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" + + "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAM" + + "VGVzdDk4NS5qYXZhDAAPABAHAC0MAC4ALwEAJnNlY29uZCAtIEhlbGxvIC0gcHJpdmF0ZSAtIFRy" + + "YW5zZm9ybWVkBwAwDAAxADIBAChzZWNvbmQgLSBHb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9y" + + "bWVkAQA0c2Vjb25kIC0gUHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1l" + + "ZAwAEwAQAQA1c2Vjb25kIC0gUG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNm" + + "b3JtZWQHADMMADQAEAEANXNlY29uZCAtIFByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAt" + + "IFRyYW5zZm9ybWVkDAAUABABADZzZWNvbmQgLSBQb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBj" + + "YWxsIC0gVHJhbnNmb3JtZWQHADUBABVhcnQvVGVzdDk4NSRUcmFuc2Zvcm0BAAlUcmFuc2Zvcm0B" + + "AAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291" + + "dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxu" + + "AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuAQALYXJ0" + + "L1Rlc3Q5ODUAIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAABABIAAAAG" + + "AAEAAAAEAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAAAAYACAAH" + + "AAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAkACAAKAAEAFQAW" + + "AAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQqtwALsgAC" + + "Egy2AASxAAAAAQASAAAAIgAIAAAADAAIAA0ADAAOABQADwAaABAAIgARACYAEgAuABMAAgAXAAAA" + + "AgAYACsAAAAKAAEADQAoACoACA=="); + private static final byte[] DEX_BYTES_2 = Base64.getDecoder().decode( + "ZGV4CjAzNQBw/x+UAAAAAAAAAAAAAAAAAAAAAAAAAAAMBgAAcAAAAHhWNBIAAAAAAAAAAEgFAAAd" + + "AAAAcAAAAAoAAADkAAAAAwAAAAwBAAABAAAAMAEAAAcAAAA4AQAAAQAAAHABAAB8BAAAkAEAAJAB" + + "AACYAQAAoAEAALkBAADIAQAA7AEAAAwCAAAjAgAANwIAAE0CAABhAgAAdQIAAHwCAACKAgAAlQIA" + + "AJgCAACcAgAAqQIAAK8CAAC0AgAAvQIAAMICAADJAgAA8wIAABsDAABTAwAAigMAAMEDAAD3AwAA" + + "AgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAADgAAAA4AAAAJAAAAAAAAAA8AAAAJ" + + "AAAACAQAAA8AAAAJAAAAAAQAAAgABAASAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAALAAAAAAABABUA" + + "AAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAAAAAAAUAAAAAAAAADAAAADgFAAAEBQAAAAAA" + + "AAY8aW5pdD4ABkZpbmlzaAAXTGFydC9UZXN0OTg1JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk4NTsA" + + "IkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9J" + + "bm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExq" + + "YXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" + + "OwAFU3RhcnQADFRlc3Q5ODUuamF2YQAJVHJhbnNmb3JtAAFWAAJWTAALYWNjZXNzRmxhZ3MABG5h" + + "bWUAA291dAAHcHJpbnRsbgADcnVuAAVzYXlIaQAoc2Vjb25kIC0gR29vZGJ5ZSAtIHByaXZhdGUg" + + "LSBUcmFuc2Zvcm1lZAAmc2Vjb25kIC0gSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQANnNl" + + "Y29uZCAtIFBvc3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAA1c2Vj" + + "b25kIC0gUG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQANXNlY29u" + + "ZCAtIFByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkADRzZWNvbmQg" + + "LSBQcmUgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkAAV2YWx1ZQAAAAEA" + + "AAAHAAAAAQAAAAYAAAAEAAcOAAkABw4BCA8ABgAHDgEIDwAMAQAHDgEIDwEDDwEIDwEDDwEIDwED" + + "DwEIDwABAAEAAQAAABAEAAAEAAAAcBAFAAAADgADAAEAAgAAABUEAAAJAAAAYgAAABsBFgAAAG4g" + + "BAAQAA4AAAADAAEAAgAAAB0EAAAJAAAAYgAAABsBFwAAAG4gBAAQAA4AAAAEAAIAAgAAACUEAAAq" + + "AAAAYgAAABsBGwAAAG4gBAAQAHAQAgACAGIAAAAbARkAAABuIAQAEAByEAYAAwBiAAAAGwEaAAAA" + + "biAEABAAcBABAAIAYgAAABsBGAAAAG4gBAAQAA4AAAADAQCAgATACAEC2AgBAvwIAwGgCQAAAgIB" + + "HBgBAgMCEAQIERcNAAIAAAAcBQAAIgUAACwFAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAA" + + "AQAAAB0AAABwAAAAAgAAAAoAAADkAAAAAwAAAAMAAAAMAQAABAAAAAEAAAAwAQAABQAAAAcAAAA4" + + "AQAABgAAAAEAAABwAQAAAiAAAB0AAACQAQAAARAAAAIAAAAABAAAAyAAAAQAAAAQBAAAASAAAAQA" + + "AABABAAAACAAAAEAAAAEBQAABCAAAAIAAAAcBQAAAxAAAAEAAAAsBQAABiAAAAEAAAA4BQAAABAA" + + "AAEAAABIBQAA"); + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + t.sayHi(() -> { + System.out.println("transforming calling function"); + Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES_1, DEX_BYTES_1); + }); + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + t.sayHi(() -> { + System.out.println("transforming calling function"); + Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES_2, DEX_BYTES_2); + }); + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + } +} 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/986-native-method-bind/src/Main.java b/test/986-native-method-bind/src/Main.java new file mode 100644 index 0000000000..fac9d8e2a9 --- /dev/null +++ b/test/986-native-method-bind/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.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 c5d96da20c..15b3f8a438 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -275,6 +275,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", 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" }, { diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py index 95ab2e7875..6e47c5eb7a 100644 --- a/test/testrunner/target_config.py +++ b/test/testrunner/target_config.py @@ -26,7 +26,7 @@ target_config = { 'make' : 'test-art-host-gtest', 'run-test' : [], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, @@ -45,19 +45,19 @@ target_config = { 'art-interpreter' : { 'run-test' : ['--interpreter'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, 'art-interpreter-access-checks' : { 'run-test' : ['--interp-ac'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, 'art-jit' : { 'run-test' : ['--jit'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, 'art-gcstress-gcverify': { @@ -167,51 +167,51 @@ target_config = { 'art-tracing' : { 'run-test' : ['--trace'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, 'art-interpreter-tracing' : { 'run-test' : ['--interpreter', '--trace'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-forcecopy' : { 'run-test' : ['--forcecopy'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-no-prebuild' : { 'run-test' : ['--no-prebuild'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-no-image' : { 'run-test' : ['--no-image'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-interpreter-no-image' : { 'run-test' : ['--interpreter', '--no-image'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-relocate-no-patchoat' : { 'run-test' : ['--relocate-npatchoat'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-no-dex2oat' : { 'run-test' : ['--no-dex2oat'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false', + 'ART_USE_READ_BARRIER' : 'true', } }, 'art-heap-poisoning' : { @@ -231,7 +231,7 @@ target_config = { '--relocate', '--jit'], 'env' : { - 'ART_USE_READ_BARRIER' : 'false' + 'ART_USE_READ_BARRIER' : 'true' } }, diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index 6d5c74b82a..07c300e7a7 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -51,6 +51,12 @@ host="no" # Use JIT compiling by default. use_jit=true variant_cmdline_parameter="--variant=X32" +# Timeout of JDWP test in ms. +# +# Note: some tests expect a timeout to check that *no* reply/event is received for a specific case. +# A lower timeout can save up several minutes when running the whole test suite, especially for +# continuous testing. This value can be adjusted to fit the configuration of the host machine(s). +jdwp_test_timeout=10000 while true; do if [[ "$1" == "--mode=host" ]]; then @@ -150,6 +156,8 @@ vogar $vm_command \ $image_compiler_option \ --timeout 800 \ --vm-arg -Djpda.settings.verbose=true \ + --vm-arg -Djpda.settings.timeout=$jdwp_test_timeout \ + --vm-arg -Djpda.settings.waitingTime=$jdwp_test_timeout \ --vm-arg -Djpda.settings.transportAddress=127.0.0.1:55107 \ --vm-arg -Djpda.settings.debuggeeJavaPath="$art_debugee $image $debuggee_args" \ --classpath $test_jack \ |