diff options
| -rw-r--r-- | build/Android.oat.mk | 4 | ||||
| -rw-r--r-- | compiler/driver/compiler_driver.cc | 92 | ||||
| -rw-r--r-- | compiler/oat_test.cc | 23 | ||||
| -rw-r--r-- | compiler/oat_writer.cc | 41 | ||||
| -rw-r--r-- | compiler/optimizing/induction_var_range.cc | 83 | ||||
| -rw-r--r-- | compiler/optimizing/induction_var_range.h | 16 | ||||
| -rw-r--r-- | compiler/optimizing/induction_var_range_test.cc | 112 | ||||
| -rw-r--r-- | dex2oat/dex2oat.cc | 3 | ||||
| -rw-r--r-- | dex2oat/dex2oat_test.cc | 113 | ||||
| -rw-r--r-- | dexlayout/dex_ir.cc | 2 | ||||
| -rw-r--r-- | dexlayout/dexlayout.cc | 24 | ||||
| -rw-r--r-- | dexlayout/dexlayout_test.cc | 71 | ||||
| -rw-r--r-- | runtime/base/unix_file/fd_file.cc | 2 | ||||
| -rw-r--r-- | runtime/class_linker.cc | 33 | ||||
| -rw-r--r-- | runtime/dex_file.cc | 8 | ||||
| -rw-r--r-- | runtime/gc/collector/concurrent_copying.cc | 27 | ||||
| -rw-r--r-- | runtime/gc/space/region_space-inl.h | 14 | ||||
| -rw-r--r-- | runtime/gc/space/region_space.cc | 33 | ||||
| -rw-r--r-- | runtime/gc/space/region_space.h | 2 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.cc | 110 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.h | 9 | ||||
| -rw-r--r-- | test/154-gc-loop/src/Main.java | 2 | ||||
| -rwxr-xr-x | test/etc/run-test-jar | 5 | ||||
| -rwxr-xr-x | tools/setup-buildbot-device.sh | 24 |
24 files changed, 645 insertions, 208 deletions
diff --git a/build/Android.oat.mk b/build/Android.oat.mk index f53740e6e0..c733febd06 100644 --- a/build/Android.oat.mk +++ b/build/Android.oat.mk @@ -109,7 +109,7 @@ $$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency) --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \ --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(2)ART_HOST_ARCH) \ $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \ - --host --android-root=$$(HOST_OUT) --include-patch-information \ + --host --android-root=$$(HOST_OUT) \ --generate-debug-info --generate-build-id --compile-pic \ $$(PRIVATE_CORE_MULTI_PARAM) $$(PRIVATE_CORE_COMPILE_OPTIONS) @@ -212,7 +212,7 @@ $$(core_image_name): $$(TARGET_CORE_DEX_FILES) $$(core_dex2oat_dependency) --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(2)TARGET_ARCH) \ --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \ --instruction-set-features=$$($(2)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ - --android-root=$$(PRODUCT_OUT)/system --include-patch-information \ + --android-root=$$(PRODUCT_OUT)/system \ --generate-debug-info --generate-build-id --compile-pic \ $$(PRIVATE_CORE_COMPILE_OPTIONS) || (rm $$(PRIVATE_CORE_OAT_NAME); exit 1) diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 7e91453741..a5e4cb0877 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -2283,7 +2283,7 @@ class InitializeClassVisitor : public CompilationVisitor { public: explicit InitializeClassVisitor(const ParallelCompilationManager* manager) : manager_(manager) {} - virtual void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE { + void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE { ATRACE_CALL(); jobject jclass_loader = manager_->GetClassLoader(); const DexFile& dex_file = *manager_->GetDexFile(); @@ -2343,23 +2343,32 @@ class InitializeClassVisitor : public CompilationVisitor { // mode which prevents the GC from visiting objects modified during the transaction. // Ensure GC is not run so don't access freed objects when aborting transaction. - ScopedAssertNoThreadSuspension ants("Transaction end"); - runtime->ExitTransactionMode(); + { + ScopedAssertNoThreadSuspension ants("Transaction end"); + runtime->ExitTransactionMode(); + + if (!success) { + CHECK(soa.Self()->IsExceptionPending()); + mirror::Throwable* exception = soa.Self()->GetException(); + VLOG(compiler) << "Initialization of " << descriptor << " aborted because of " + << exception->Dump(); + std::ostream* file_log = manager_->GetCompiler()-> + GetCompilerOptions().GetInitFailureOutput(); + if (file_log != nullptr) { + *file_log << descriptor << "\n"; + *file_log << exception->Dump() << "\n"; + } + soa.Self()->ClearException(); + transaction.Rollback(); + CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored"; + } + } if (!success) { - CHECK(soa.Self()->IsExceptionPending()); - mirror::Throwable* exception = soa.Self()->GetException(); - VLOG(compiler) << "Initialization of " << descriptor << " aborted because of " - << exception->Dump(); - std::ostream* file_log = manager_->GetCompiler()-> - GetCompilerOptions().GetInitFailureOutput(); - if (file_log != nullptr) { - *file_log << descriptor << "\n"; - *file_log << exception->Dump() << "\n"; - } - soa.Self()->ClearException(); - transaction.Rollback(); - CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored"; + // On failure, still intern strings of static fields and seen in <clinit>, as these + // will be created in the zygote. This is separated from the transaction code just + // above as we will allocate strings, so must be allowed to suspend. + InternStrings(klass, class_loader); } } } @@ -2375,6 +2384,57 @@ class InitializeClassVisitor : public CompilationVisitor { } private: + void InternStrings(Handle<mirror::Class> klass, Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(manager_->GetCompiler()->GetCompilerOptions().IsBootImage()); + DCHECK(klass->IsVerified()); + DCHECK(!klass->IsInitialized()); + + StackHandleScope<1> hs(Thread::Current()); + Handle<mirror::DexCache> h_dex_cache = hs.NewHandle(klass->GetDexCache()); + const DexFile* dex_file = manager_->GetDexFile(); + const DexFile::ClassDef* class_def = klass->GetClassDef(); + ClassLinker* class_linker = manager_->GetClassLinker(); + + // Check encoded final field values for strings and intern. + annotations::RuntimeEncodedStaticFieldValueIterator value_it(*dex_file, + &h_dex_cache, + &class_loader, + manager_->GetClassLinker(), + *class_def); + for ( ; value_it.HasNext(); value_it.Next()) { + if (value_it.GetValueType() == annotations::RuntimeEncodedStaticFieldValueIterator::kString) { + // Resolve the string. This will intern the string. + art::ObjPtr<mirror::String> resolved = class_linker->ResolveString( + *dex_file, dex::StringIndex(value_it.GetJavaValue().i), h_dex_cache); + CHECK(resolved != nullptr); + } + } + + // Intern strings seen in <clinit>. + ArtMethod* clinit = klass->FindClassInitializer(class_linker->GetImagePointerSize()); + if (clinit != nullptr) { + const DexFile::CodeItem* code_item = clinit->GetCodeItem(); + DCHECK(code_item != nullptr); + const Instruction* inst = Instruction::At(code_item->insns_); + + const uint32_t insns_size = code_item->insns_size_in_code_units_; + for (uint32_t dex_pc = 0; dex_pc < insns_size;) { + if (inst->Opcode() == Instruction::CONST_STRING) { + ObjPtr<mirror::String> s = class_linker->ResolveString( + *dex_file, dex::StringIndex(inst->VRegB_21c()), h_dex_cache); + CHECK(s != nullptr); + } else if (inst->Opcode() == Instruction::CONST_STRING_JUMBO) { + ObjPtr<mirror::String> s = class_linker->ResolveString( + *dex_file, dex::StringIndex(inst->VRegB_31c()), h_dex_cache); + CHECK(s != nullptr); + } + dex_pc += inst->SizeInCodeUnits(); + inst = inst->Next(); + } + } + } + const ParallelCompilationManager* const manager_; }; diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 66111f6e23..e2233e4bbd 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -265,6 +265,7 @@ class OatTest : public CommonCompilerTest { void TestDexFileInput(bool verify, bool low_4gb, bool use_profile); void TestZipFileInput(bool verify); + void TestZipFileInputWithEmptyDex(); std::unique_ptr<const InstructionSetFeatures> insn_features_; std::unique_ptr<QuickCompilerCallbacks> callbacks_; @@ -821,6 +822,28 @@ TEST_F(OatTest, ZipFileInputCheckVerifier) { TestZipFileInput(true); } +void OatTest::TestZipFileInputWithEmptyDex() { + ScratchFile zip_file; + ZipBuilder zip_builder(zip_file.GetFile()); + bool success = zip_builder.AddFile("classes.dex", nullptr, 0); + ASSERT_TRUE(success); + success = zip_builder.Finish(); + ASSERT_TRUE(success) << strerror(errno); + + SafeMap<std::string, std::string> key_value_store; + key_value_store.Put(OatHeader::kImageLocationKey, "test.art"); + std::vector<const char*> input_filenames { zip_file.GetFilename().c_str() }; // NOLINT [readability/braces] [4] + ScratchFile oat_file, vdex_file(oat_file, ".vdex"); + std::unique_ptr<ProfileCompilationInfo> profile_compilation_info(new ProfileCompilationInfo()); + success = WriteElf(vdex_file.GetFile(), oat_file.GetFile(), input_filenames, + key_value_store, /*verify*/false, profile_compilation_info.get()); + ASSERT_FALSE(success); +} + +TEST_F(OatTest, ZipFileInputWithEmptyDex) { + TestZipFileInputWithEmptyDex(); +} + TEST_F(OatTest, UpdateChecksum) { InstructionSet insn_set = kX86; std::string error_msg; diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 0ea11255a8..8ab44d2c19 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -104,6 +104,13 @@ inline uint32_t CodeAlignmentSize(uint32_t header_offset, const CompiledMethod& // Defines the location of the raw dex file to write. class OatWriter::DexFileSource { public: + enum Type { + kNone, + kZipEntry, + kRawFile, + kRawData, + }; + explicit DexFileSource(ZipEntry* zip_entry) : type_(kZipEntry), source_(zip_entry) { DCHECK(source_ != nullptr); @@ -119,6 +126,7 @@ class OatWriter::DexFileSource { DCHECK(source_ != nullptr); } + Type GetType() const { return type_; } bool IsZipEntry() const { return type_ == kZipEntry; } bool IsRawFile() const { return type_ == kRawFile; } bool IsRawData() const { return type_ == kRawData; } @@ -147,13 +155,6 @@ class OatWriter::DexFileSource { } private: - enum Type { - kNone, - kZipEntry, - kRawFile, - kRawData, - }; - Type type_; const void* source_; }; @@ -2259,16 +2260,38 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil ZipEntry* zip_entry = oat_dex_file->source_.GetZipEntry(); std::unique_ptr<MemMap> mem_map( zip_entry->ExtractToMemMap(location.c_str(), "classes.dex", &error_msg)); + if (mem_map == nullptr) { + LOG(ERROR) << "Failed to extract dex file to mem map for layout: " << error_msg; + return false; + } dex_file = DexFile::Open(location, zip_entry->GetCrc32(), std::move(mem_map), /* verify */ true, /* verify_checksum */ true, &error_msg); - } else { - DCHECK(oat_dex_file->source_.IsRawFile()); + } else if (oat_dex_file->source_.IsRawFile()) { File* raw_file = oat_dex_file->source_.GetRawFile(); dex_file = DexFile::OpenDex(raw_file->Fd(), location, /* verify_checksum */ true, &error_msg); + } else { + // The source data is a vdex file. + CHECK(oat_dex_file->source_.IsRawData()) + << static_cast<size_t>(oat_dex_file->source_.GetType()); + const uint8_t* raw_dex_file = oat_dex_file->source_.GetRawData(); + // Note: The raw data has already been checked to contain the header + // and all the data that the header specifies as the file size. + DCHECK(raw_dex_file != nullptr); + DCHECK(ValidateDexFileHeader(raw_dex_file, oat_dex_file->GetLocation())); + const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(raw_dex_file); + // Since the source may have had its layout changed, don't verify the checksum. + dex_file = DexFile::Open(raw_dex_file, + header->file_size_, + location, + oat_dex_file->dex_file_location_checksum_, + nullptr, + /* verify */ true, + /* verify_checksum */ false, + &error_msg); } if (dex_file == nullptr) { LOG(ERROR) << "Failed to open dex file for layout: " << error_msg; diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index 5539413aad..1cd65c1c66 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -377,6 +377,53 @@ bool InductionVarRange::IsFinite(HLoopInformation* loop, /*out*/ int64_t* tc) co return false; } +bool InductionVarRange::IsUnitStride(HInstruction* instruction, + /*out*/ HInstruction** offset) const { + HLoopInformation* loop = nullptr; + HInductionVarAnalysis::InductionInfo* info = nullptr; + HInductionVarAnalysis::InductionInfo* trip = nullptr; + if (HasInductionInfo(instruction, instruction, &loop, &info, &trip)) { + if (info->induction_class == HInductionVarAnalysis::kLinear && + info->op_b->operation == HInductionVarAnalysis::kFetch) { + int64_t stride_value = 0; + if (IsConstant(info->op_a, kExact, &stride_value) && stride_value == 1) { + int64_t off_value = 0; + if (IsConstant(info->op_b, kExact, &off_value) && off_value == 0) { + *offset = nullptr; + } else { + *offset = info->op_b->fetch; + } + return true; + } + } + } + return false; +} + +HInstruction* InductionVarRange::GenerateTripCount(HLoopInformation* loop, + HGraph* graph, + HBasicBlock* block) { + HInductionVarAnalysis::InductionInfo *trip = + induction_analysis_->LookupInfo(loop, GetLoopControl(loop)); + if (trip != nullptr && !IsUnsafeTripCount(trip)) { + HInstruction* taken_test = nullptr; + HInstruction* trip_expr = nullptr; + if (IsBodyTripCount(trip)) { + if (!GenerateCode(trip->op_b, nullptr, graph, block, &taken_test, false, false)) { + return nullptr; + } + } + if (GenerateCode(trip->op_a, nullptr, graph, block, &trip_expr, false, false)) { + if (taken_test != nullptr) { + HInstruction* zero = graph->GetConstant(trip->type, 0); + trip_expr = Insert(block, new (graph->GetArena()) HSelect(taken_test, trip_expr, zero, kNoDexPc)); + } + return trip_expr; + } + } + return nullptr; +} + // // Private class methods. // @@ -1157,12 +1204,15 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, HInstruction* opb = nullptr; switch (info->induction_class) { case HInductionVarAnalysis::kInvariant: - // Invariants (note that even though is_min does not impact code generation for - // invariants, some effort is made to keep this parameter consistent). + // Invariants (note that since invariants only have other invariants as + // sub expressions, viz. no induction, there is no need to adjust is_min). switch (info->operation) { case HInductionVarAnalysis::kAdd: - case HInductionVarAnalysis::kRem: // no proper is_min for second arg - case HInductionVarAnalysis::kXor: // no proper is_min for second arg + case HInductionVarAnalysis::kSub: + case HInductionVarAnalysis::kMul: + case HInductionVarAnalysis::kDiv: + case HInductionVarAnalysis::kRem: + case HInductionVarAnalysis::kXor: case HInductionVarAnalysis::kLT: case HInductionVarAnalysis::kLE: case HInductionVarAnalysis::kGT: @@ -1174,6 +1224,12 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, switch (info->operation) { case HInductionVarAnalysis::kAdd: operation = new (graph->GetArena()) HAdd(type, opa, opb); break; + case HInductionVarAnalysis::kSub: + operation = new (graph->GetArena()) HSub(type, opa, opb); break; + case HInductionVarAnalysis::kMul: + operation = new (graph->GetArena()) HMul(type, opa, opb, kNoDexPc); break; + case HInductionVarAnalysis::kDiv: + operation = new (graph->GetArena()) HDiv(type, opa, opb, kNoDexPc); break; case HInductionVarAnalysis::kRem: operation = new (graph->GetArena()) HRem(type, opa, opb, kNoDexPc); break; case HInductionVarAnalysis::kXor: @@ -1194,16 +1250,7 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, return true; } break; - case HInductionVarAnalysis::kSub: // second reversed! - if (GenerateCode(info->op_a, trip, graph, block, &opa, in_body, is_min) && - GenerateCode(info->op_b, trip, graph, block, &opb, in_body, !is_min)) { - if (graph != nullptr) { - *result = Insert(block, new (graph->GetArena()) HSub(type, opa, opb)); - } - return true; - } - break; - case HInductionVarAnalysis::kNeg: // reversed! + case HInductionVarAnalysis::kNeg: if (GenerateCode(info->op_b, trip, graph, block, &opb, in_body, !is_min)) { if (graph != nullptr) { *result = Insert(block, new (graph->GetArena()) HNeg(type, opb)); @@ -1240,9 +1287,9 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, } } break; - default: - break; - } + case HInductionVarAnalysis::kNop: + LOG(FATAL) << "unexpected invariant nop"; + } // switch invariant operation break; case HInductionVarAnalysis::kLinear: { // Linear induction a * i + b, for normalized 0 <= i < TC. For ranges, this should @@ -1293,7 +1340,7 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, } break; } - } + } // switch induction class } return false; } diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h index 6c424b78b9..0858d73982 100644 --- a/compiler/optimizing/induction_var_range.h +++ b/compiler/optimizing/induction_var_range.h @@ -24,7 +24,8 @@ namespace art { /** * This class implements range analysis on expressions within loops. It takes the results * of induction variable analysis in the constructor and provides a public API to obtain - * a conservative lower and upper bound value on each instruction in the HIR. + * a conservative lower and upper bound value or last value on each instruction in the HIR. + * The public API also provides a few general-purpose utility methods related to induction. * * The range analysis is done with a combination of symbolic and partial integral evaluation * of expressions. The analysis avoids complications with wrap-around arithmetic on the integral @@ -154,6 +155,19 @@ class InductionVarRange { */ bool IsFinite(HLoopInformation* loop, /*out*/ int64_t* tc) const; + /** + * Checks if instruction is a unit stride induction inside the closest enveloping loop. + * Returns invariant offset on success. + */ + bool IsUnitStride(HInstruction* instruction, /*out*/ HInstruction** offset) const; + + /** + * Generates the trip count expression for the given loop. Code is generated in given block + * and graph. The expression is guarded by a taken test if needed. Returns the trip count + * expression on success or null otherwise. + */ + HInstruction* GenerateTripCount(HLoopInformation* loop, HGraph* graph, HBasicBlock* block); + private: /* * Enum used in IsConstant() request. diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc index d81817fb09..fcdf8eb7dc 100644 --- a/compiler/optimizing/induction_var_range_test.cc +++ b/compiler/optimizing/induction_var_range_test.cc @@ -48,6 +48,11 @@ class InductionVarRangeTest : public CommonCompilerTest { EXPECT_EQ(v1.is_known, v2.is_known); } + void ExpectInt(int32_t value, HInstruction* i) { + ASSERT_TRUE(i->IsIntConstant()); + EXPECT_EQ(value, i->AsIntConstant()->GetValue()); + } + // // Construction methods. // @@ -757,10 +762,20 @@ TEST_F(InductionVarRangeTest, ConstantTripCountUp) { // Last value (unsimplified). HInstruction* last = range_.GenerateLastValue(phi, graph_, loop_preheader_); ASSERT_TRUE(last->IsAdd()); - ASSERT_TRUE(last->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, last->InputAt(0)->AsIntConstant()->GetValue()); - ASSERT_TRUE(last->InputAt(1)->IsIntConstant()); - EXPECT_EQ(0, last->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(1000, last->InputAt(0)); + ExpectInt(0, last->InputAt(1)); + + // Loop logic. + int64_t tc = 0; + EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); + EXPECT_EQ(1000, tc); + HInstruction* offset = nullptr; + EXPECT_TRUE(range_.IsUnitStride(phi, &offset)); + EXPECT_TRUE(offset == nullptr); + HInstruction* tce = range_.GenerateTripCount( + loop_header_->GetLoopInformation(), graph_, loop_preheader_); + ASSERT_TRUE(tce != nullptr); + ExpectInt(1000, tce); } TEST_F(InductionVarRangeTest, ConstantTripCountDown) { @@ -799,15 +814,27 @@ TEST_F(InductionVarRangeTest, ConstantTripCountDown) { // Last value (unsimplified). HInstruction* last = range_.GenerateLastValue(phi, graph_, loop_preheader_); ASSERT_TRUE(last->IsSub()); - ASSERT_TRUE(last->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, last->InputAt(0)->AsIntConstant()->GetValue()); + ExpectInt(1000, last->InputAt(0)); ASSERT_TRUE(last->InputAt(1)->IsNeg()); last = last->InputAt(1)->InputAt(0); ASSERT_TRUE(last->IsSub()); - ASSERT_TRUE(last->InputAt(0)->IsIntConstant()); - EXPECT_EQ(0, last->InputAt(0)->AsIntConstant()->GetValue()); - ASSERT_TRUE(last->InputAt(1)->IsIntConstant()); - EXPECT_EQ(1000, last->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(0, last->InputAt(0)); + ExpectInt(1000, last->InputAt(1)); + + // Loop logic. + int64_t tc = 0; + EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); + EXPECT_EQ(1000, tc); + HInstruction* offset = nullptr; + EXPECT_FALSE(range_.IsUnitStride(phi, &offset)); + HInstruction* tce = range_.GenerateTripCount( + loop_header_->GetLoopInformation(), graph_, loop_preheader_); + ASSERT_TRUE(tce != nullptr); + ASSERT_TRUE(tce->IsNeg()); + last = tce->InputAt(0); + EXPECT_TRUE(last->IsSub()); + ExpectInt(0, last->InputAt(0)); + ExpectInt(1000, last->InputAt(1)); } TEST_F(InductionVarRangeTest, SymbolicTripCountUp) { @@ -851,27 +878,22 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountUp) { // Verify lower is 0+0. ASSERT_TRUE(lower != nullptr); ASSERT_TRUE(lower->IsAdd()); - ASSERT_TRUE(lower->InputAt(0)->IsIntConstant()); - EXPECT_EQ(0, lower->InputAt(0)->AsIntConstant()->GetValue()); - ASSERT_TRUE(lower->InputAt(1)->IsIntConstant()); - EXPECT_EQ(0, lower->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(0, lower->InputAt(0)); + ExpectInt(0, lower->InputAt(1)); // Verify upper is (V-1)+0. ASSERT_TRUE(upper != nullptr); ASSERT_TRUE(upper->IsAdd()); ASSERT_TRUE(upper->InputAt(0)->IsSub()); EXPECT_TRUE(upper->InputAt(0)->InputAt(0)->IsParameterValue()); - ASSERT_TRUE(upper->InputAt(0)->InputAt(1)->IsIntConstant()); - EXPECT_EQ(1, upper->InputAt(0)->InputAt(1)->AsIntConstant()->GetValue()); - ASSERT_TRUE(upper->InputAt(1)->IsIntConstant()); - EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(1, upper->InputAt(0)->InputAt(1)); + ExpectInt(0, upper->InputAt(1)); // Verify taken-test is 0<V. HInstruction* taken = range_.GenerateTakenTest(increment_, graph_, loop_preheader_); ASSERT_TRUE(taken != nullptr); ASSERT_TRUE(taken->IsLessThan()); - ASSERT_TRUE(taken->InputAt(0)->IsIntConstant()); - EXPECT_EQ(0, taken->InputAt(0)->AsIntConstant()->GetValue()); + ExpectInt(0, taken->InputAt(0)); EXPECT_TRUE(taken->InputAt(1)->IsParameterValue()); // Replacement. @@ -880,6 +902,21 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountUp) { EXPECT_FALSE(needs_finite_test); ExpectEqual(Value(1), v1); ExpectEqual(Value(y_, 1, 0), v2); + + // Loop logic. + int64_t tc = 0; + EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); + EXPECT_EQ(0, tc); // unknown + HInstruction* offset = nullptr; + EXPECT_TRUE(range_.IsUnitStride(phi, &offset)); + EXPECT_TRUE(offset == nullptr); + HInstruction* tce = range_.GenerateTripCount( + loop_header_->GetLoopInformation(), graph_, loop_preheader_); + ASSERT_TRUE(tce != nullptr); + EXPECT_TRUE(tce->IsSelect()); // guarded by taken-test + ExpectInt(0, tce->InputAt(0)); + EXPECT_TRUE(tce->InputAt(1)->IsParameterValue()); + EXPECT_TRUE(tce->InputAt(2)->IsLessThan()); } TEST_F(InductionVarRangeTest, SymbolicTripCountDown) { @@ -923,32 +960,26 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountDown) { // Verify lower is 1000-((1000-V)-1). ASSERT_TRUE(lower != nullptr); ASSERT_TRUE(lower->IsSub()); - ASSERT_TRUE(lower->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, lower->InputAt(0)->AsIntConstant()->GetValue()); + ExpectInt(1000, lower->InputAt(0)); lower = lower->InputAt(1); ASSERT_TRUE(lower->IsSub()); - ASSERT_TRUE(lower->InputAt(1)->IsIntConstant()); - EXPECT_EQ(1, lower->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(1, lower->InputAt(1)); lower = lower->InputAt(0); ASSERT_TRUE(lower->IsSub()); - ASSERT_TRUE(lower->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, lower->InputAt(0)->AsIntConstant()->GetValue()); + ExpectInt(1000, lower->InputAt(0)); EXPECT_TRUE(lower->InputAt(1)->IsParameterValue()); // Verify upper is 1000-0. ASSERT_TRUE(upper != nullptr); ASSERT_TRUE(upper->IsSub()); - ASSERT_TRUE(upper->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, upper->InputAt(0)->AsIntConstant()->GetValue()); - ASSERT_TRUE(upper->InputAt(1)->IsIntConstant()); - EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue()); + ExpectInt(1000, upper->InputAt(0)); + ExpectInt(0, upper->InputAt(1)); // Verify taken-test is 1000>V. HInstruction* taken = range_.GenerateTakenTest(increment_, graph_, loop_preheader_); ASSERT_TRUE(taken != nullptr); ASSERT_TRUE(taken->IsGreaterThan()); - ASSERT_TRUE(taken->InputAt(0)->IsIntConstant()); - EXPECT_EQ(1000, taken->InputAt(0)->AsIntConstant()->GetValue()); + ExpectInt(1000, taken->InputAt(0)); EXPECT_TRUE(taken->InputAt(1)->IsParameterValue()); // Replacement. @@ -957,6 +988,23 @@ TEST_F(InductionVarRangeTest, SymbolicTripCountDown) { EXPECT_FALSE(needs_finite_test); ExpectEqual(Value(y_, 1, 0), v1); ExpectEqual(Value(999), v2); + + // Loop logic. + int64_t tc = 0; + EXPECT_TRUE(range_.IsFinite(loop_header_->GetLoopInformation(), &tc)); + EXPECT_EQ(0, tc); // unknown + HInstruction* offset = nullptr; + EXPECT_FALSE(range_.IsUnitStride(phi, &offset)); + HInstruction* tce = range_.GenerateTripCount( + loop_header_->GetLoopInformation(), graph_, loop_preheader_); + ASSERT_TRUE(tce != nullptr); + EXPECT_TRUE(tce->IsSelect()); // guarded by taken-test + ExpectInt(0, tce->InputAt(0)); + EXPECT_TRUE(tce->InputAt(1)->IsSub()); + EXPECT_TRUE(tce->InputAt(2)->IsGreaterThan()); + tce = tce->InputAt(1); + ExpectInt(1000, taken->InputAt(0)); + EXPECT_TRUE(taken->InputAt(1)->IsParameterValue()); } } // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index be756286fc..dbae70ec14 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -2821,6 +2821,9 @@ static int CompileImage(Dex2Oat& dex2oat) { // When given --host, finish early without stripping. if (dex2oat.IsHost()) { + if (!dex2oat.FlushCloseOutputFiles()) { + return EXIT_FAILURE; + } dex2oat.DumpTiming(); return EXIT_SUCCESS; } diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index 2c0b125fb7..b79050e9d0 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -37,6 +37,8 @@ namespace art { +using android::base::StringPrintf; + class Dex2oatTest : public Dex2oatEnvironmentTest { public: virtual void TearDown() OVERRIDE { @@ -52,10 +54,18 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { const std::string& odex_location, CompilerFilter::Filter filter, const std::vector<std::string>& extra_args = {}, - bool expect_success = true) { + bool expect_success = true, + bool use_fd = false) { + std::unique_ptr<File> oat_file; std::vector<std::string> args; args.push_back("--dex-file=" + dex_location); - args.push_back("--oat-file=" + odex_location); + if (use_fd) { + oat_file.reset(OS::CreateEmptyFile(odex_location.c_str())); + CHECK(oat_file != nullptr) << odex_location; + args.push_back("--oat-fd=" + std::to_string(oat_file->Fd())); + } else { + args.push_back("--oat-file=" + odex_location); + } args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); args.push_back("--runtime-arg"); args.push_back("-Xnorelocate"); @@ -64,6 +74,9 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { std::string error_msg; bool success = Dex2Oat(args, &error_msg); + if (oat_file != nullptr) { + ASSERT_EQ(oat_file->FlushClose(), 0) << "Could not flush and close oat file"; + } if (expect_success) { ASSERT_TRUE(success) << error_msg << std::endl << output_; @@ -582,12 +595,11 @@ class Dex2oatLayoutTest : public Dex2oatTest { ASSERT_TRUE(result); } - void RunTest() { - std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; - std::string profile_location = GetScratchDir() + "/primary.prof"; - std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex"; - - Copy(GetDexSrc2(), dex_location); + void CompileProfileOdex(const std::string& dex_location, + const std::string& odex_location, + bool use_fd, + const std::vector<std::string>& extra_args = {}) { + const std::string profile_location = GetScratchDir() + "/primary.prof"; const char* location = dex_location.c_str(); std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; @@ -595,14 +607,61 @@ class Dex2oatLayoutTest : public Dex2oatTest { EXPECT_EQ(dex_files.size(), 1U); std::unique_ptr<const DexFile>& dex_file = dex_files[0]; GenerateProfile(profile_location, dex_location, dex_file->GetLocationChecksum()); + std::vector<std::string> copy(extra_args); + copy.push_back("--profile-file=" + profile_location); + GenerateOdexForTest(dex_location, + odex_location, + CompilerFilter::kSpeedProfile, + copy, + /* expect_success */ true, + use_fd); + } - const std::vector<std::string>& extra_args = { "--profile-file=" + profile_location }; - GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile, extra_args); + void RunTest() { + std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex"; + Copy(GetDexSrc2(), dex_location); + + CompileProfileOdex(dex_location, odex_location, /* use_fd */ false); CheckValidity(); ASSERT_TRUE(success_); CheckResult(dex_location, odex_location); } + + void RunTestVDex() { + std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex"; + std::string vdex_location = GetOdexDir() + "/DexOdexNoOat.vdex"; + Copy(GetDexSrc2(), dex_location); + + std::unique_ptr<File> vdex_file1(OS::CreateEmptyFile(vdex_location.c_str())); + CHECK(vdex_file1 != nullptr) << vdex_location; + ScratchFile vdex_file2; + { + std::string input_vdex = "--input-vdex-fd=-1"; + std::string output_vdex = StringPrintf("--output-vdex-fd=%d", vdex_file1->Fd()); + CompileProfileOdex(dex_location, + odex_location, + /* use_fd */ true, + { input_vdex, output_vdex }); + EXPECT_GT(vdex_file1->GetLength(), 0u); + } + { + std::string input_vdex = StringPrintf("--input-vdex-fd=%d", vdex_file1->Fd()); + std::string output_vdex = StringPrintf("--output-vdex-fd=%d", vdex_file2.GetFd()); + CompileProfileOdex(dex_location, + odex_location, + /* use_fd */ true, + { input_vdex, output_vdex }); + EXPECT_GT(vdex_file2.GetFile()->GetLength(), 0u); + } + ASSERT_EQ(vdex_file1->FlushCloseOrErase(), 0) << "Could not flush and close vdex file"; + CheckValidity(); + ASSERT_TRUE(success_); + CheckResult(dex_location, odex_location); + } + void CheckResult(const std::string& dex_location, const std::string& odex_location) { // Host/target independent checks. std::string error_msg; @@ -641,29 +700,33 @@ class Dex2oatLayoutTest : public Dex2oatTest { EXPECT_EQ(odex_file->GetCompilerFilter(), CompilerFilter::kSpeedProfile); } - // Check whether the dex2oat run was really successful. - void CheckValidity() { - if (kIsTargetBuild) { - CheckTargetValidity(); - } else { - CheckHostValidity(); - } + // Check whether the dex2oat run was really successful. + void CheckValidity() { + if (kIsTargetBuild) { + CheckTargetValidity(); + } else { + CheckHostValidity(); } + } - void CheckTargetValidity() { - // TODO: Ignore for now. - } + void CheckTargetValidity() { + // TODO: Ignore for now. + } - // On the host, we can get the dex2oat output. Here, look for "dex2oat took." - void CheckHostValidity() { - EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_; - } - }; + // On the host, we can get the dex2oat output. Here, look for "dex2oat took." + void CheckHostValidity() { + EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_; + } +}; TEST_F(Dex2oatLayoutTest, TestLayout) { RunTest(); } +TEST_F(Dex2oatLayoutTest, TestVdexLayout) { + RunTestVDex(); +} + class Dex2oatWatchdogTest : public Dex2oatTest { protected: void RunTest(bool expect_success, const std::vector<std::string>& extra_args = {}) { diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc index 131f4b9f63..a69409947e 100644 --- a/dexlayout/dex_ir.cc +++ b/dexlayout/dex_ir.cc @@ -649,7 +649,7 @@ CodeItem* Collections::CreateCodeItem(const DexFile& dex_file, } } int32_t size = DecodeSignedLeb128(&handlers_data); - bool has_catch_all = size < 0; + bool has_catch_all = size <= 0; if (has_catch_all) { size = -size; } diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 22619b9e8d..4aa8b82ec7 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1529,10 +1529,18 @@ std::vector<dex_ir::ClassData*> DexLayout::LayoutClassDefsAndClassData(const Dex // NOTE: If the section following the code items is byte aligned, the last code item is left in // place to preserve alignment. Layout needs an overhaul to handle movement of other sections. int32_t DexLayout::LayoutCodeItems(std::vector<dex_ir::ClassData*> new_class_data_order) { + // Do not move code items if class data section precedes code item section. + // ULEB encoding is variable length, causing problems determining the offset of the code items. + // TODO: We should swap the order of these sections in the future to avoid this issue. + uint32_t class_data_offset = header_->GetCollections().ClassDatasOffset(); + uint32_t code_item_offset = header_->GetCollections().CodeItemsOffset(); + if (class_data_offset < code_item_offset) { + return 0; + } + // Find the last code item so we can leave it in place if the next section is not 4 byte aligned. std::unordered_set<dex_ir::CodeItem*> visited_code_items; - uint32_t offset = header_->GetCollections().CodeItemsOffset(); - bool is_code_item_aligned = IsNextSectionCodeItemAligned(offset); + bool is_code_item_aligned = IsNextSectionCodeItemAligned(code_item_offset); if (!is_code_item_aligned) { dex_ir::CodeItem* last_code_item = nullptr; for (auto& code_item_pair : header_->GetCollections().CodeItems()) { @@ -1552,18 +1560,18 @@ int32_t DexLayout::LayoutCodeItems(std::vector<dex_ir::ClassData*> new_class_dat dex_ir::CodeItem* code_item = method->GetCodeItem(); if (code_item != nullptr && visited_code_items.find(code_item) == visited_code_items.end()) { visited_code_items.insert(code_item); - diff += UnsignedLeb128Size(offset) - UnsignedLeb128Size(code_item->GetOffset()); - code_item->SetOffset(offset); - offset += RoundUp(code_item->GetSize(), kDexCodeItemAlignment); + diff += UnsignedLeb128Size(code_item_offset) - UnsignedLeb128Size(code_item->GetOffset()); + code_item->SetOffset(code_item_offset); + code_item_offset += RoundUp(code_item->GetSize(), kDexCodeItemAlignment); } } for (auto& method : *class_data->VirtualMethods()) { dex_ir::CodeItem* code_item = method->GetCodeItem(); if (code_item != nullptr && visited_code_items.find(code_item) == visited_code_items.end()) { visited_code_items.insert(code_item); - diff += UnsignedLeb128Size(offset) - UnsignedLeb128Size(code_item->GetOffset()); - code_item->SetOffset(offset); - offset += RoundUp(code_item->GetSize(), kDexCodeItemAlignment); + diff += UnsignedLeb128Size(code_item_offset) - UnsignedLeb128Size(code_item->GetOffset()); + code_item->SetOffset(code_item_offset); + code_item_offset += RoundUp(code_item->GetSize(), kDexCodeItemAlignment); } } } diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc index 9f0593a5cd..2d084c1990 100644 --- a/dexlayout/dexlayout_test.cc +++ b/dexlayout/dexlayout_test.cc @@ -75,6 +75,26 @@ static const char kUnreferencedCatchHandlerInputDex[] = "AAAEAQAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAARAAAAIAAADIAQAAAiAAABIAAADWAQAAAyAA" "AAIAAAC1AgAAACAAAAEAAADIAgAAABAAAAEAAADYAgAA"; +// Dex file with 0-size (catch all only) catch handler unreferenced by try blocks. +// Constructed by building a dex file with try/catch blocks and hex editing. +static const char kUnreferenced0SizeCatchHandlerInputDex[] = + "ZGV4CjAzNQCEbEEvMstSNpQpjPdfMEfUBS48cis2QRJoAwAAcAAAAHhWNBIAAAAAAAAAAMgCAAAR" + "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAQAAAD8AAAAAQAAABwBAAAsAgAAPAEAAOoB" + "AADyAQAABAIAABMCAAAqAgAAPgIAAFICAABmAgAAaQIAAG0CAACCAgAAhgIAAIoCAACQAgAAlQIA" + "AJ4CAACiAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACQAAAAcAAAAFAAAAAAAAAAgAAAAFAAAA" + "3AEAAAgAAAAFAAAA5AEAAAQAAQANAAAAAAAAAAAAAAAAAAIADAAAAAEAAQAOAAAAAgAAAAAAAAAA" + "AAAAAQAAAAIAAAAAAAAAAQAAAAAAAAC5AgAAAAAAAAEAAQABAAAApgIAAAQAAABwEAMAAAAOAAQA" + "AQACAAIAqwIAAC8AAABiAAAAGgEPAG4gAgAQAGIAAAAaAQoAbiACABAAYgAAABoBEABuIAIAEABi" + "AAAAGgELAG4gAgAQAA4ADQBiAQAAGgIKAG4gAgAhACcADQBiAQAAGgILAG4gAgAhACcAAAAAAAAA" + "BwABAA4AAAAHAAEAAgAdACYAAAABAAAAAwAAAAEAAAAGAAY8aW5pdD4AEEhhbmRsZXJUZXN0Lmph" + "dmEADUxIYW5kbGVyVGVzdDsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl" + "Y3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwABVgACVkwAE1tMamF2" + "YS9sYW5nL1N0cmluZzsAAmYxAAJmMgAEbWFpbgADb3V0AAdwcmludGxuAAJ0MQACdDIAAQAHDgAE" + "AQAHDnl7eXkCeB2bAAAAAgAAgYAEvAIBCdQCAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAAC" + "AAAABwAAALQAAAADAAAAAwAAANAAAAAEAAAAAQAAAPQAAAAFAAAABAAAAPwAAAAGAAAAAQAAABwB" + "AAABIAAAAgAAADwBAAABEAAAAgAAANwBAAACIAAAEQAAAOoBAAADIAAAAgAAAKYCAAAAIAAAAQAA" + "ALkCAAAAEAAAAQAAAMgCAAA="; + // Dex file with multiple code items that have the same debug_info_off_. Constructed by a modified // dexlayout on XandY. static const char kDexFileDuplicateOffset[] = @@ -145,6 +165,21 @@ static const char kUnalignedCodeInfoInputDex[] = "AAEAAAC4AAAAASAAAAIAAADYAAAAAiAAAAYAAAACAQAAAyAAAAIAAAAxAQAAACAAAAEAAAA7AQAA" "ABAAAAEAAABMAQAA"; +// Dex file with class data section preceding code items. +// Constructed by passing dex file through dexmerger tool and hex editing. +static const char kClassDataBeforeCodeInputDex[] = + "ZGV4CjAzNQCZKmCu3XXn4zvxCh5VH0gZNNobEAcsc49EAgAAcAAAAHhWNBIAAAAAAAAAAAQBAAAJ" + "AAAAcAAAAAQAAACUAAAAAgAAAKQAAAAAAAAAAAAAAAUAAAC8AAAAAQAAAOQAAABAAQAABAEAAPgB" + "AAAAAgAACAIAAAsCAAAQAgAAJAIAACcCAAAqAgAALQIAAAIAAAADAAAABAAAAAUAAAACAAAAAAAA" + "AAAAAAAFAAAAAwAAAAAAAAABAAEAAAAAAAEAAAAGAAAAAQAAAAcAAAABAAAACAAAAAIAAQAAAAAA" + "AQAAAAEAAAACAAAAAAAAAAEAAAAAAAAAjAEAAAAAAAALAAAAAAAAAAEAAAAAAAAAAQAAAAkAAABw" + "AAAAAgAAAAQAAACUAAAAAwAAAAIAAACkAAAABQAAAAUAAAC8AAAABgAAAAEAAADkAAAAABAAAAEA" + "AAAEAQAAACAAAAEAAACMAQAAASAAAAQAAACkAQAAAiAAAAkAAAD4AQAAAyAAAAQAAAAwAgAAAAAB" + "AwCBgASkAwEBvAMBAdADAQHkAwAAAQABAAEAAAAwAgAABAAAAHAQBAAAAA4AAgABAAAAAAA1AgAA" + "AgAAABIQDwACAAEAAAAAADoCAAACAAAAEiAPAAIAAQAAAAAAPwIAAAIAAAASMA8ABjxpbml0PgAG" + "QS5qYXZhAAFJAANMQTsAEkxqYXZhL2xhbmcvT2JqZWN0OwABVgABYQABYgABYwABAAcOAAMABw4A" + "BgAHDgAJAAcOAA=="; + static void WriteBase64ToFile(const char* base64, File* file) { // Decode base64. CHECK(base64 != nullptr); @@ -282,8 +317,8 @@ class DexLayoutTest : public CommonRuntimeTest { return true; } - // Runs UnreferencedCatchHandlerTest. - bool UnreferencedCatchHandlerExec(std::string* error_msg) { + // Runs UnreferencedCatchHandlerTest & Unreferenced0SizeCatchHandlerTest. + bool UnreferencedCatchHandlerExec(std::string* error_msg, const char* filename) { ScratchFile tmp_file; std::string tmp_name = tmp_file.GetFilename(); size_t tmp_last_slash = tmp_name.rfind("/"); @@ -291,7 +326,7 @@ class DexLayoutTest : public CommonRuntimeTest { // Write inputs and expected outputs. std::string input_dex = tmp_dir + "classes.dex"; - WriteFileBase64(kUnreferencedCatchHandlerInputDex, input_dex.c_str()); + WriteFileBase64(filename, input_dex.c_str()); std::string output_dex = tmp_dir + "classes.dex.new"; std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout"; @@ -343,8 +378,18 @@ TEST_F(DexLayoutTest, UnreferencedCatchHandler) { // Disable test on target. TEST_DISABLED_FOR_TARGET(); std::string error_msg; - ASSERT_TRUE(UnreferencedCatchHandlerExec(&error_msg)) << error_msg; + ASSERT_TRUE(UnreferencedCatchHandlerExec(&error_msg, + kUnreferencedCatchHandlerInputDex)) << error_msg; } + +TEST_F(DexLayoutTest, Unreferenced0SizeCatchHandler) { + // Disable test on target. + TEST_DISABLED_FOR_TARGET(); + std::string error_msg; + ASSERT_TRUE(UnreferencedCatchHandlerExec(&error_msg, + kUnreferenced0SizeCatchHandlerInputDex)) << error_msg; +} + TEST_F(DexLayoutTest, DuplicateOffset) { ScratchFile temp; WriteBase64ToFile(kDexFileDuplicateOffset, temp.GetFile()); @@ -418,4 +463,22 @@ TEST_F(DexLayoutTest, UnalignedCodeInfo) { } } +TEST_F(DexLayoutTest, ClassDataBeforeCode) { + ScratchFile temp; + WriteBase64ToFile(kClassDataBeforeCodeInputDex, temp.GetFile()); + ScratchFile temp2; + WriteBase64ToFile(kDexFileLayoutInputProfile, temp2.GetFile()); + EXPECT_EQ(temp.GetFile()->Flush(), 0); + std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout"; + EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path"; + std::vector<std::string> dexlayout_exec_argv = + { dexlayout, "-p", temp2.GetFilename(), "-o", "/dev/null", temp.GetFilename() }; + std::string error_msg; + const bool result = ::art::Exec(dexlayout_exec_argv, &error_msg); + EXPECT_TRUE(result); + if (!result) { + LOG(ERROR) << "Error " << error_msg; + } +} + } // namespace art diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc index ff2dd1b399..03fc959f6b 100644 --- a/runtime/base/unix_file/fd_file.cc +++ b/runtime/base/unix_file/fd_file.cc @@ -73,7 +73,7 @@ void FdFile::Destroy() { } if (auto_close_ && fd_ != -1) { if (Close() != 0) { - PLOG(WARNING) << "Failed to close file " << file_path_; + PLOG(WARNING) << "Failed to close file with fd=" << fd_ << " path=" << file_path_; } } } diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index eaa35fe12d..cac5449c4c 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1025,7 +1025,8 @@ bool ClassLinker::IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa, class_loader->GetClass(); } -static mirror::String* GetDexPathListElementName(ObjPtr<mirror::Object> element) +static bool GetDexPathListElementName(ObjPtr<mirror::Object> element, + ObjPtr<mirror::String>* out_name) REQUIRES_SHARED(Locks::mutator_lock_) { ArtField* const dex_file_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); @@ -1037,17 +1038,20 @@ static mirror::String* GetDexPathListElementName(ObjPtr<mirror::Object> element) CHECK_EQ(dex_file_field->GetDeclaringClass(), element->GetClass()) << element->PrettyTypeOf(); ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element); if (dex_file == nullptr) { - return nullptr; + // Null dex file means it was probably a jar with no dex files, return a null string. + *out_name = nullptr; + return true; } ObjPtr<mirror::Object> name_object = dex_file_name_field->GetObject(dex_file); if (name_object != nullptr) { - return name_object->AsString(); + *out_name = name_object->AsString(); + return true; } - return nullptr; + return false; } static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, - std::list<mirror::String*>* out_dex_file_names, + std::list<ObjPtr<mirror::String>>* out_dex_file_names, std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(out_dex_file_names != nullptr); @@ -1083,12 +1087,14 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, *error_msg = StringPrintf("Null dex element at index %d", i); return false; } - ObjPtr<mirror::String> const name = GetDexPathListElementName(element); - if (name == nullptr) { - *error_msg = StringPrintf("Null name for dex element at index %d", i); + ObjPtr<mirror::String> name; + if (!GetDexPathListElementName(element, &name)) { + *error_msg = StringPrintf("Invalid dex path list element at index %d", i); return false; } - out_dex_file_names->push_front(name.Ptr()); + if (name != nullptr) { + out_dex_file_names->push_front(name.Ptr()); + } } } } @@ -1769,14 +1775,14 @@ bool ClassLinker::AddImageSpace( *error_msg = "Unexpected BootClassLoader in app image"; return false; } - std::list<mirror::String*> image_dex_file_names; + std::list<ObjPtr<mirror::String>> image_dex_file_names; std::string temp_error_msg; if (!FlattenPathClassLoader(image_class_loader.Get(), &image_dex_file_names, &temp_error_msg)) { *error_msg = StringPrintf("Failed to flatten image class loader hierarchy '%s'", temp_error_msg.c_str()); return false; } - std::list<mirror::String*> loader_dex_file_names; + std::list<ObjPtr<mirror::String>> loader_dex_file_names; if (!FlattenPathClassLoader(class_loader.Get(), &loader_dex_file_names, &temp_error_msg)) { *error_msg = StringPrintf("Failed to flatten class loader hierarchy '%s'", temp_error_msg.c_str()); @@ -1788,7 +1794,10 @@ bool ClassLinker::AddImageSpace( ObjPtr<mirror::Object> element = elements->GetWithoutChecks(i); if (element != nullptr) { // If we are somewhere in the middle of the array, there may be nulls at the end. - loader_dex_file_names.push_back(GetDexPathListElementName(element)); + ObjPtr<mirror::String> name; + if (GetDexPathListElementName(element, &name) && name != nullptr) { + loader_dex_file_names.push_back(name); + } } } // Ignore the number of image dex files since we are adding those to the class loader anyways. diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index b6a2e09719..35e9d5db29 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -179,6 +179,14 @@ std::unique_ptr<const DexFile> DexFile::Open(const std::string& location, std::string* error_msg) { ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location); CHECK(map.get() != nullptr); + + if (map->Size() < sizeof(DexFile::Header)) { + *error_msg = StringPrintf( + "DexFile: failed to open dex file '%s' that is too short to have a header", + location.c_str()); + return nullptr; + } + std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), map->Size(), location, diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 8f9c187e1d..aea9708ddc 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -1644,10 +1644,10 @@ void ConcurrentCopying::ReclaimPhase() { // Record freed objects. TimingLogger::ScopedTiming split2("RecordFree", GetTimings()); // Don't include thread-locals that are in the to-space. - uint64_t from_bytes = region_space_->GetBytesAllocatedInFromSpace(); - uint64_t from_objects = region_space_->GetObjectsAllocatedInFromSpace(); - uint64_t unevac_from_bytes = region_space_->GetBytesAllocatedInUnevacFromSpace(); - uint64_t unevac_from_objects = region_space_->GetObjectsAllocatedInUnevacFromSpace(); + const uint64_t from_bytes = region_space_->GetBytesAllocatedInFromSpace(); + const uint64_t from_objects = region_space_->GetObjectsAllocatedInFromSpace(); + const uint64_t unevac_from_bytes = region_space_->GetBytesAllocatedInUnevacFromSpace(); + const uint64_t unevac_from_objects = region_space_->GetObjectsAllocatedInUnevacFromSpace(); uint64_t to_bytes = bytes_moved_.LoadSequentiallyConsistent(); cumulative_bytes_moved_.FetchAndAddRelaxed(to_bytes); uint64_t to_objects = objects_moved_.LoadSequentiallyConsistent(); @@ -1658,8 +1658,18 @@ void ConcurrentCopying::ReclaimPhase() { } CHECK_LE(to_objects, from_objects); CHECK_LE(to_bytes, from_bytes); - int64_t freed_bytes = from_bytes - to_bytes; - int64_t freed_objects = from_objects - to_objects; + // cleared_bytes and cleared_objects may be greater than the from space equivalents since + // ClearFromSpace may clear empty unevac regions. + uint64_t cleared_bytes; + uint64_t cleared_objects; + { + TimingLogger::ScopedTiming split4("ClearFromSpace", GetTimings()); + region_space_->ClearFromSpace(&cleared_bytes, &cleared_objects); + CHECK_GE(cleared_bytes, from_bytes); + CHECK_GE(cleared_objects, from_objects); + } + int64_t freed_bytes = cleared_bytes - to_bytes; + int64_t freed_objects = cleared_objects - to_objects; if (kVerboseMode) { LOG(INFO) << "RecordFree:" << " from_bytes=" << from_bytes << " from_objects=" << from_objects @@ -1678,11 +1688,6 @@ void ConcurrentCopying::ReclaimPhase() { } { - TimingLogger::ScopedTiming split4("ClearFromSpace", GetTimings()); - region_space_->ClearFromSpace(); - } - - { WriterMutexLock mu(self, *Locks::heap_bitmap_lock_); Sweep(false); SwapBitmaps(); diff --git a/runtime/gc/space/region_space-inl.h b/runtime/gc/space/region_space-inl.h index fffcee64ad..5809027235 100644 --- a/runtime/gc/space/region_space-inl.h +++ b/runtime/gc/space/region_space-inl.h @@ -233,14 +233,12 @@ void RegionSpace::WalkInternal(ObjectCallback* callback, void* arg) { continue; } if (r->IsLarge()) { - if (r->LiveBytes() > 0) { - // Avoid visiting dead large objects since they may contain dangling pointers to the - // from-space. - DCHECK_GT(r->LiveBytes(), 0u) << "Visiting dead large object"; - mirror::Object* obj = reinterpret_cast<mirror::Object*>(r->Begin()); - DCHECK(obj->GetClass() != nullptr); - callback(obj, arg); - } + // Avoid visiting dead large objects since they may contain dangling pointers to the + // from-space. + DCHECK_GT(r->LiveBytes(), 0u) << "Visiting dead large object"; + mirror::Object* obj = reinterpret_cast<mirror::Object*>(r->Begin()); + DCHECK(obj->GetClass() != nullptr); + callback(obj, arg); } else if (r->IsLargeTail()) { // Do nothing. } else { diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc index 560abe121a..1ad48438ba 100644 --- a/runtime/gc/space/region_space.cc +++ b/runtime/gc/space/region_space.cc @@ -246,16 +246,45 @@ void RegionSpace::SetFromSpace(accounting::ReadBarrierTable* rb_table, bool forc evac_region_ = &full_region_; } -void RegionSpace::ClearFromSpace() { +void RegionSpace::ClearFromSpace(uint64_t* cleared_bytes, uint64_t* cleared_objects) { + DCHECK(cleared_bytes != nullptr); + DCHECK(cleared_objects != nullptr); + *cleared_bytes = 0; + *cleared_objects = 0; MutexLock mu(Thread::Current(), region_lock_); VerifyNonFreeRegionLimit(); size_t new_non_free_region_index_limit = 0; for (size_t i = 0; i < std::min(num_regions_, non_free_region_index_limit_); ++i) { Region* r = ®ions_[i]; if (r->IsInFromSpace()) { - r->Clear(); + *cleared_bytes += r->BytesAllocated(); + *cleared_objects += r->ObjectsAllocated(); --num_non_free_regions_; + r->Clear(); } else if (r->IsInUnevacFromSpace()) { + if (r->LiveBytes() == 0) { + // Special case for 0 live bytes, this means all of the objects in the region are dead and + // we can clear it. This is important for large objects since we must not visit dead ones in + // RegionSpace::Walk because they may contain dangling references to invalid objects. + // It is also better to clear these regions now instead of at the end of the next GC to + // save RAM. If we don't clear the regions here, they will be cleared next GC by the normal + // live percent evacuation logic. + size_t free_regions = 1; + // Also release RAM for large tails. + while (i + free_regions < num_regions_ && regions_[i + free_regions].IsLargeTail()) { + DCHECK(r->IsLarge()); + regions_[i + free_regions].Clear(); + ++free_regions; + } + *cleared_bytes += r->BytesAllocated(); + *cleared_objects += r->ObjectsAllocated(); + num_non_free_regions_ -= free_regions; + r->Clear(); + GetLiveBitmap()->ClearRange( + reinterpret_cast<mirror::Object*>(r->Begin()), + reinterpret_cast<mirror::Object*>(r->Begin() + free_regions * kRegionSize)); + continue; + } size_t full_count = 0; while (r->IsInUnevacFromSpace()) { Region* const cur = ®ions_[i + full_count]; diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h index 95f293bc12..253792993b 100644 --- a/runtime/gc/space/region_space.h +++ b/runtime/gc/space/region_space.h @@ -215,7 +215,7 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { size_t FromSpaceSize() REQUIRES(!region_lock_); size_t UnevacFromSpaceSize() REQUIRES(!region_lock_); size_t ToSpaceSize() REQUIRES(!region_lock_); - void ClearFromSpace() REQUIRES(!region_lock_); + void ClearFromSpace(uint64_t* cleared_bytes, uint64_t* cleared_objects) REQUIRES(!region_lock_); void AddLiveBytes(mirror::Object* ref, size_t alloc_size) { Region* reg = RefToRegionUnlocked(ref); diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 5ae2fc51b7..48bf1e72a4 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -430,8 +430,7 @@ OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& // starts up. LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. " << "Allow oat file use. This is potentially dangerous."; - } else if (file.GetOatHeader().GetImageFileLocationOatChecksum() - != GetCombinedImageChecksum()) { + } else if (file.GetOatHeader().GetImageFileLocationOatChecksum() != image_info->oat_checksum) { VLOG(oat) << "Oat image checksum does not match image checksum."; return kOatBootImageOutOfDate; } @@ -726,68 +725,81 @@ const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { return required_dex_checksums_found_ ? &cached_required_dex_checksums_ : nullptr; } -const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() { - if (!image_info_load_attempted_) { - image_info_load_attempted_ = true; - - Runtime* runtime = Runtime::Current(); - std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces(); - if (!image_spaces.empty()) { - cached_image_info_.location = image_spaces[0]->GetImageLocation(); - - if (isa_ == kRuntimeISA) { - const ImageHeader& image_header = image_spaces[0]->GetImageHeader(); - cached_image_info_.oat_checksum = image_header.GetOatChecksum(); - cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>( - image_header.GetOatDataBegin()); - cached_image_info_.patch_delta = image_header.GetPatchDelta(); - } else { - std::string error_msg; - std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeader(cached_image_info_.location.c_str(), - isa_, - &error_msg)); - CHECK(image_header != nullptr) << error_msg; - cached_image_info_.oat_checksum = image_header->GetOatChecksum(); - cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>( - image_header->GetOatDataBegin()); - cached_image_info_.patch_delta = image_header->GetPatchDelta(); - } - } - image_info_load_succeeded_ = (!image_spaces.empty()); +// TODO: Use something better than xor for the combined image checksum. +std::unique_ptr<OatFileAssistant::ImageInfo> +OatFileAssistant::ImageInfo::GetRuntimeImageInfo(InstructionSet isa, std::string* error_msg) { + CHECK(error_msg != nullptr); - combined_image_checksum_ = CalculateCombinedImageChecksum(isa_); + // Use the currently loaded image to determine the image locations for all + // the image spaces, regardless of the isa requested. Otherwise we would + // need to read from the boot image's oat file to determine the rest of the + // image locations in the case of multi-image. + Runtime* runtime = Runtime::Current(); + std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces(); + if (image_spaces.empty()) { + *error_msg = "There are no boot image spaces"; + return nullptr; } - return image_info_load_succeeded_ ? &cached_image_info_ : nullptr; -} -// TODO: Use something better than xor. -uint32_t OatFileAssistant::CalculateCombinedImageChecksum(InstructionSet isa) { - uint32_t checksum = 0; - std::vector<gc::space::ImageSpace*> image_spaces = - Runtime::Current()->GetHeap()->GetBootImageSpaces(); + std::unique_ptr<ImageInfo> info(new ImageInfo()); + info->location = image_spaces[0]->GetImageLocation(); + + // TODO: Special casing on isa == kRuntimeISA is presumably motivated by + // performance: 'it's faster to use an already loaded image header than read + // the image header from disk'. But the loaded image is not necessarily the + // same as kRuntimeISA, so this behavior is suspect (b/35659889). if (isa == kRuntimeISA) { + const ImageHeader& image_header = image_spaces[0]->GetImageHeader(); + info->oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()); + info->patch_delta = image_header.GetPatchDelta(); + + info->oat_checksum = 0; for (gc::space::ImageSpace* image_space : image_spaces) { - checksum ^= image_space->GetImageHeader().GetOatChecksum(); + info->oat_checksum ^= image_space->GetImageHeader().GetOatChecksum(); } } else { + std::unique_ptr<ImageHeader> image_header( + gc::space::ImageSpace::ReadImageHeader(info->location.c_str(), isa, error_msg)); + if (image_header == nullptr) { + return nullptr; + } + info->oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()); + info->patch_delta = image_header->GetPatchDelta(); + + info->oat_checksum = 0; for (gc::space::ImageSpace* image_space : image_spaces) { std::string location = image_space->GetImageLocation(); - std::string error_msg; - std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, &error_msg)); - CHECK(image_header != nullptr) << error_msg; - checksum ^= image_header->GetOatChecksum(); + image_header.reset( + gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, error_msg)); + if (image_header == nullptr) { + return nullptr; + } + info->oat_checksum ^= image_header->GetOatChecksum(); } } - return checksum; + return info; } -uint32_t OatFileAssistant::GetCombinedImageChecksum() { +const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() { if (!image_info_load_attempted_) { - GetImageInfo(); + image_info_load_attempted_ = true; + std::string error_msg; + cached_image_info_ = ImageInfo::GetRuntimeImageInfo(isa_, &error_msg); + if (cached_image_info_ == nullptr) { + LOG(WARNING) << "Unable to get runtime image info: " << error_msg; + } + } + return cached_image_info_.get(); +} + +uint32_t OatFileAssistant::CalculateCombinedImageChecksum(InstructionSet isa) { + std::string error_msg; + std::unique_ptr<ImageInfo> info = ImageInfo::GetRuntimeImageInfo(isa, &error_msg); + if (info == nullptr) { + LOG(WARNING) << "Unable to get runtime image info for checksum: " << error_msg; + return 0; } - return combined_image_checksum_; + return info->oat_checksum; } OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 3ede29f5e0..eec87f0768 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -284,6 +284,9 @@ class OatFileAssistant { uintptr_t oat_data_begin = 0; int32_t patch_delta = 0; std::string location; + + static std::unique_ptr<ImageInfo> GetRuntimeImageInfo(InstructionSet isa, + std::string* error_msg); }; class OatFileInfo { @@ -414,8 +417,6 @@ class OatFileAssistant { // The caller shouldn't clean up or free the returned pointer. const ImageInfo* GetImageInfo(); - uint32_t GetCombinedImageChecksum(); - // To implement Lock(), we lock a dummy file where the oat file would go // (adding ".flock" to the target file name) and retain the lock for the // remaining lifetime of the OatFileAssistant object. @@ -445,9 +446,7 @@ class OatFileAssistant { // TODO: The image info should probably be moved out of the oat file // assistant to an image file manager. bool image_info_load_attempted_ = false; - bool image_info_load_succeeded_ = false; - ImageInfo cached_image_info_; - uint32_t combined_image_checksum_ = 0; + std::unique_ptr<ImageInfo> cached_image_info_; DISALLOW_COPY_AND_ASSIGN(OatFileAssistant); }; diff --git a/test/154-gc-loop/src/Main.java b/test/154-gc-loop/src/Main.java index 2228ca2783..69015b65aa 100644 --- a/test/154-gc-loop/src/Main.java +++ b/test/154-gc-loop/src/Main.java @@ -38,7 +38,7 @@ public class Main { } } catch (Exception e) {} System.out.println("Finalize count too large: " + - ((finalizeCounter >= 12) ? Integer.toString(finalizeCounter) : "false")); + ((finalizeCounter >= 15) ? Integer.toString(finalizeCounter) : "false")); } private static native void backgroundProcessState(); diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 161aa2340d..0ac5481f6d 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -456,8 +456,9 @@ else FLAGS="$FLAGS -Xnorelocate" COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xnorelocate" if [ "$HOST" = "y" ]; then - # Increase ulimit to 64MB in case we are running hprof test. - ulimit -S 64000 || exit 1 + # Increase ulimit to 128MB in case we are running hprof test, + # or string append test with art-debug-gc. + ulimit -S 128000 || exit 1 fi fi diff --git a/tools/setup-buildbot-device.sh b/tools/setup-buildbot-device.sh index 1e9c763534..7eaaaf9cbd 100755 --- a/tools/setup-buildbot-device.sh +++ b/tools/setup-buildbot-device.sh @@ -17,9 +17,33 @@ green='\033[0;32m' nc='\033[0m' +# Setup as root, as the next buildbot step (device cleanup) requires it. +# This is also required to set the date, if needed. +adb root +adb wait-for-device + +echo -e "${green}Date on host${nc}" +date + echo -e "${green}Date on device${nc}" adb shell date +host_seconds_since_epoch=$(date -u +%s) +device_seconds_since_epoch=$(adb shell date -u +%s) + +abs_time_difference_in_seconds=$(expr $host_seconds_since_epoch - $device_seconds_since_epoch) +if [ $abs_time_difference_in_seconds -lt 0 ]; then + abs_time_difference_in_seconds=$(expr 0 - $abs_time_difference_in_seconds) +fi + +seconds_per_hour=3600 + +# Update date on device if the difference with host is more than one hour. +if [ $abs_time_difference_in_seconds -gt $seconds_per_hour ]; then + echo -e "${green}Update date on device${nc}" + adb shell date -u @$host_seconds_since_epoch +fi + echo -e "${green}Turn off selinux${nc}" adb shell setenforce 0 adb shell getenforce |