diff options
110 files changed, 6336 insertions, 1450 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index e0f0ae5815..7d76795714 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -191,6 +191,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/dex/mir_graph_test.cc \ compiler/dex/mir_optimization_test.cc \ compiler/dex/quick/quick_cfi_test.cc \ + compiler/dex/type_inference_test.cc \ compiler/dwarf/dwarf_test.cc \ compiler/driver/compiler_driver_test.cc \ compiler/elf_writer_test.cc \ @@ -227,6 +228,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/utils/arena_allocator_test.cc \ compiler/utils/dedupe_set_test.cc \ compiler/utils/swap_space_test.cc \ + compiler/utils/test_dex_file_builder_test.cc \ compiler/utils/arm/managed_register_arm_test.cc \ compiler/utils/arm64/managed_register_arm64_test.cc \ compiler/utils/x86/managed_register_x86_test.cc \ diff --git a/compiler/Android.mk b/compiler/Android.mk index ac95abdd8d..3f5271d31f 100644 --- a/compiler/Android.mk +++ b/compiler/Android.mk @@ -23,6 +23,7 @@ LIBART_COMPILER_SRC_FILES := \ dex/global_value_numbering.cc \ dex/gvn_dead_code_elimination.cc \ dex/local_value_numbering.cc \ + dex/type_inference.cc \ dex/quick/arm/assemble_arm.cc \ dex/quick/arm/call_arm.cc \ dex/quick/arm/fp_arm.cc \ @@ -124,13 +125,14 @@ LIBART_COMPILER_SRC_FILES := \ optimizing/optimizing_compiler.cc \ optimizing/parallel_move_resolver.cc \ optimizing/prepare_for_register_allocation.cc \ + optimizing/primitive_type_propagation.cc \ + optimizing/reference_type_propagation.cc \ optimizing/register_allocator.cc \ optimizing/side_effects_analysis.cc \ optimizing/ssa_builder.cc \ optimizing/ssa_liveness_analysis.cc \ optimizing/ssa_phi_elimination.cc \ - optimizing/primitive_type_propagation.cc \ - optimizing/reference_type_propagation.cc \ + optimizing/stack_map_stream.cc \ trampolines/trampoline_compiler.cc \ utils/arena_bit_vector.cc \ utils/arm/assembler_arm.cc \ diff --git a/compiler/dex/bb_optimizations.h b/compiler/dex/bb_optimizations.h index 0850f42a9a..eb87c29f9d 100644 --- a/compiler/dex/bb_optimizations.h +++ b/compiler/dex/bb_optimizations.h @@ -270,7 +270,25 @@ class DeadCodeEliminationPass : public PassME { CompilationUnit* c_unit = down_cast<PassMEDataHolder*>(data)->c_unit; DCHECK(c_unit != nullptr); c_unit->mir_graph->EliminateDeadCodeEnd(); - down_cast<PassMEDataHolder*>(data)->dirty = !c_unit->mir_graph->MirSsaRepUpToDate(); + } +}; + +/** + * @class GlobalValueNumberingCleanupPass + * @brief Performs the cleanup after global value numbering pass and the dependent + * dead code elimination pass that needs the GVN data. + */ +class GlobalValueNumberingCleanupPass : public PassME { + public: + GlobalValueNumberingCleanupPass() + : PassME("GVNCleanup", kNoNodes, "") { + } + + void Start(PassDataHolder* data) const OVERRIDE { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast<const PassMEDataHolder*>(data)->c_unit; + DCHECK(c_unit != nullptr); + return c_unit->mir_graph->GlobalValueNumberingCleanup(); } }; diff --git a/compiler/dex/global_value_numbering_test.cc b/compiler/dex/global_value_numbering_test.cc index b4559ef375..c538d0beee 100644 --- a/compiler/dex/global_value_numbering_test.cc +++ b/compiler/dex/global_value_numbering_test.cc @@ -15,7 +15,6 @@ */ #include "base/logging.h" -#include "dataflow_iterator.h" #include "dataflow_iterator-inl.h" #include "dex/mir_field_info.h" #include "global_value_numbering.h" @@ -260,10 +259,8 @@ class GlobalValueNumberingTest : public testing::Test { mir->ssa_rep = &ssa_reps_[i]; mir->ssa_rep->num_uses = def->num_uses; mir->ssa_rep->uses = const_cast<int32_t*>(def->uses); // Not modified by LVN. - mir->ssa_rep->fp_use = nullptr; // Not used by LVN. mir->ssa_rep->num_defs = def->num_defs; mir->ssa_rep->defs = const_cast<int32_t*>(def->defs); // Not modified by LVN. - mir->ssa_rep->fp_def = nullptr; // Not used by LVN. mir->dalvikInsn.opcode = def->opcode; mir->offset = i; // LVN uses offset only for debug output mir->optimization_flags = 0u; diff --git a/compiler/dex/gvn_dead_code_elimination.cc b/compiler/dex/gvn_dead_code_elimination.cc index ec12221f3c..bd7bd71e73 100644 --- a/compiler/dex/gvn_dead_code_elimination.cc +++ b/compiler/dex/gvn_dead_code_elimination.cc @@ -347,6 +347,21 @@ bool GvnDeadCodeElimination::VRegChains::IsSRegUsed(uint16_t first_change, uint1 return false; } +bool GvnDeadCodeElimination::VRegChains::IsVRegUsed(uint16_t first_change, uint16_t last_change, + int v_reg, MIRGraph* mir_graph) const { + DCHECK_LE(first_change, last_change); + DCHECK_LE(last_change, mir_data_.size()); + for (size_t c = first_change; c != last_change; ++c) { + SSARepresentation* ssa_rep = mir_data_[c].mir->ssa_rep; + for (int i = 0; i != ssa_rep->num_uses; ++i) { + if (mir_graph->SRegToVReg(ssa_rep->uses[i]) == v_reg) { + return true; + } + } + } + return false; +} + void GvnDeadCodeElimination::VRegChains::RenameSRegUses(uint16_t first_change, uint16_t last_change, int old_s_reg, int new_s_reg, bool wide) { for (size_t c = first_change; c != last_change; ++c) { @@ -478,7 +493,7 @@ void GvnDeadCodeElimination::ChangeBinOp2AddrToPlainBinOp(MIR* mir) { mir->dalvikInsn.opcode - Instruction::ADD_INT_2ADDR + Instruction::ADD_INT); } -MIR* GvnDeadCodeElimination::CreatePhi(int s_reg, bool fp) { +MIR* GvnDeadCodeElimination::CreatePhi(int s_reg) { int v_reg = mir_graph_->SRegToVReg(s_reg); MIR* phi = mir_graph_->NewMIR(); phi->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpPhi); @@ -491,11 +506,9 @@ MIR* GvnDeadCodeElimination::CreatePhi(int s_reg, bool fp) { mir_graph_->AllocateSSADefData(phi, 1); phi->ssa_rep->defs[0] = s_reg; - phi->ssa_rep->fp_def[0] = fp; size_t num_uses = bb_->predecessors.size(); mir_graph_->AllocateSSAUseData(phi, num_uses); - std::fill_n(phi->ssa_rep->fp_use, num_uses, fp); size_t idx = 0u; for (BasicBlockId pred_id : bb_->predecessors) { BasicBlock* pred_bb = mir_graph_->GetBasicBlock(pred_id); @@ -523,14 +536,12 @@ MIR* GvnDeadCodeElimination::RenameSRegDefOrCreatePhi(uint16_t def_change, uint1 // defining MIR for that dalvik reg, the preserved valus must come from its predecessors // and we need to create a new Phi (a degenerate Phi if there's only a single predecessor). if (def_change == kNPos) { - bool fp = mir_to_kill->ssa_rep->fp_def[0]; if (wide) { DCHECK_EQ(new_s_reg + 1, mir_to_kill->ssa_rep->defs[1]); - DCHECK_EQ(fp, mir_to_kill->ssa_rep->fp_def[1]); DCHECK_EQ(mir_graph_->SRegToVReg(new_s_reg) + 1, mir_graph_->SRegToVReg(new_s_reg + 1)); - CreatePhi(new_s_reg + 1, fp); // High word Phi. + CreatePhi(new_s_reg + 1); // High word Phi. } - return CreatePhi(new_s_reg, fp); + return CreatePhi(new_s_reg); } else { DCHECK_LT(def_change, last_change); DCHECK_LE(last_change, vreg_chains_.NumMIRs()); @@ -676,8 +687,14 @@ void GvnDeadCodeElimination::RecordPassTryToKillOverwrittenMoveOrMoveSrc(uint16_ uint16_t src_name = (d->wide_def ? lvn_->GetSregValueWide(src_s_reg) : lvn_->GetSregValue(src_s_reg)); if (value_name == src_name) { - RecordPassKillMoveByRenamingSrcDef(check_change, c); - return; + // Check if the move's destination vreg is unused between check_change and the move. + uint32_t new_dest_v_reg = mir_graph_->SRegToVReg(d->mir->ssa_rep->defs[0]); + if (!vreg_chains_.IsVRegUsed(check_change + 1u, c, new_dest_v_reg, mir_graph_) && + (!d->wide_def || + !vreg_chains_.IsVRegUsed(check_change + 1u, c, new_dest_v_reg + 1, mir_graph_))) { + RecordPassKillMoveByRenamingSrcDef(check_change, c); + return; + } } } } diff --git a/compiler/dex/gvn_dead_code_elimination.h b/compiler/dex/gvn_dead_code_elimination.h index 9a19f29970..bc75a01778 100644 --- a/compiler/dex/gvn_dead_code_elimination.h +++ b/compiler/dex/gvn_dead_code_elimination.h @@ -111,6 +111,8 @@ class GvnDeadCodeElimination : public DeletableArenaObject<kArenaAllocMisc> { void RemoveChange(uint16_t change); bool IsTopChange(uint16_t change) const; bool IsSRegUsed(uint16_t first_change, uint16_t last_change, int s_reg) const; + bool IsVRegUsed(uint16_t first_change, uint16_t last_change, int v_reg, + MIRGraph* mir_graph) const; void RenameSRegUses(uint16_t first_change, uint16_t last_change, int old_s_reg, int new_s_reg, bool wide); void RenameVRegUses(uint16_t first_change, uint16_t last_change, @@ -128,7 +130,7 @@ class GvnDeadCodeElimination : public DeletableArenaObject<kArenaAllocMisc> { void KillMIR(MIRData* data); static void KillMIR(MIR* mir); static void ChangeBinOp2AddrToPlainBinOp(MIR* mir); - MIR* CreatePhi(int s_reg, bool fp); + MIR* CreatePhi(int s_reg); MIR* RenameSRegDefOrCreatePhi(uint16_t def_change, uint16_t last_change, MIR* mir_to_kill); // Update state variables going backwards through a MIR. diff --git a/compiler/dex/gvn_dead_code_elimination_test.cc b/compiler/dex/gvn_dead_code_elimination_test.cc index 4d2b8b319f..3eb372ce05 100644 --- a/compiler/dex/gvn_dead_code_elimination_test.cc +++ b/compiler/dex/gvn_dead_code_elimination_test.cc @@ -1030,6 +1030,40 @@ TEST_F(GvnDeadCodeEliminationTestSimple, NoRename3) { } } +TEST_F(GvnDeadCodeEliminationTestSimple, NoRename4) { + static const MIRDef mirs[] = { + DEF_CONST(3, Instruction::CONST, 0u, 1000u), + DEF_UNIQUE_REF(3, Instruction::NEW_INSTANCE, 1u), + DEF_CONST(3, Instruction::CONST, 2u, 100u), + DEF_CONST(3, Instruction::CONST, 3u, 200u), + DEF_BINOP(3, Instruction::OR_INT_2ADDR, 4u, 2u, 3u), // 3. Find definition of the move src. + DEF_MOVE(3, Instruction::MOVE, 5u, 0u), // 4. Uses move dest vreg. + DEF_MOVE(3, Instruction::MOVE, 6u, 4u), // 2. Find overwritten move src. + DEF_CONST(3, Instruction::CONST, 7u, 2000u), // 1. Overwrites 4u, look for moves. + }; + + static const int32_t sreg_to_vreg_map[] = { 0, 1, 2, 3, 2, 4, 0, 2 }; + PrepareSRegToVRegMap(sreg_to_vreg_map); + + PrepareMIRs(mirs); + PerformGVN_DCE(); + + ASSERT_EQ(arraysize(mirs), value_names_.size()); + static const size_t diff_indexes[] = { 0, 1, 2, 3, 4, 7 }; + ExpectValueNamesNE(diff_indexes); + EXPECT_EQ(value_names_[0], value_names_[5]); + EXPECT_EQ(value_names_[4], value_names_[6]); + + static const bool eliminated[] = { + false, false, false, false, false, false, false, false + }; + static_assert(arraysize(eliminated) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(eliminated); ++i) { + bool actually_eliminated = (static_cast<int>(mirs_[i].dalvikInsn.opcode) == kMirOpNop); + EXPECT_EQ(eliminated[i], actually_eliminated) << i; + } +} + TEST_F(GvnDeadCodeEliminationTestSimple, Simple1) { static const IFieldDef ifields[] = { { 0u, 1u, 0u, false, kDexMemAccessObject }, diff --git a/compiler/dex/local_value_numbering_test.cc b/compiler/dex/local_value_numbering_test.cc index 566527ad42..0393410867 100644 --- a/compiler/dex/local_value_numbering_test.cc +++ b/compiler/dex/local_value_numbering_test.cc @@ -158,10 +158,8 @@ class LocalValueNumberingTest : public testing::Test { mir->ssa_rep = &ssa_reps_[i]; mir->ssa_rep->num_uses = def->num_uses; mir->ssa_rep->uses = const_cast<int32_t*>(def->uses); // Not modified by LVN. - mir->ssa_rep->fp_use = nullptr; // Not used by LVN. mir->ssa_rep->num_defs = def->num_defs; mir->ssa_rep->defs = const_cast<int32_t*>(def->defs); // Not modified by LVN. - mir->ssa_rep->fp_def = nullptr; // Not used by LVN. mir->dalvikInsn.opcode = def->opcode; mir->offset = i; // LVN uses offset only for debug output mir->optimization_flags = 0u; diff --git a/compiler/dex/mir_dataflow.cc b/compiler/dex/mir_dataflow.cc index 12e67cdb74..b4aec98e01 100644 --- a/compiler/dex/mir_dataflow.cc +++ b/compiler/dex/mir_dataflow.cc @@ -123,7 +123,7 @@ const uint64_t MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { DF_UA | DF_NULL_CHK_A | DF_REF_A, // 1F CHK_CAST vAA, type@BBBB - DF_UA | DF_REF_A | DF_UMS, + DF_UA | DF_REF_A | DF_CHK_CAST | DF_UMS, // 20 INSTANCE_OF vA, vB, type@CCCC DF_DA | DF_UB | DF_CORE_A | DF_REF_B | DF_UMS, @@ -159,10 +159,10 @@ const uint64_t MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { DF_NOP, // 2B PACKED_SWITCH vAA, +BBBBBBBB - DF_UA, + DF_UA | DF_CORE_A, // 2C SPARSE_SWITCH vAA, +BBBBBBBB - DF_UA, + DF_UA | DF_CORE_A, // 2D CMPL_FLOAT vAA, vBB, vCC DF_DA | DF_UB | DF_UC | DF_FP_B | DF_FP_C | DF_CORE_A, @@ -180,22 +180,22 @@ const uint64_t MIRGraph::oat_data_flow_attributes_[kMirOpLast] = { DF_DA | DF_UB | DF_B_WIDE | DF_UC | DF_C_WIDE | DF_CORE_A | DF_CORE_B | DF_CORE_C, // 32 IF_EQ vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 33 IF_NE vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 34 IF_LT vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 35 IF_GE vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 36 IF_GT vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 37 IF_LE vA, vB, +CCCC - DF_UA | DF_UB, + DF_UA | DF_UB | DF_SAME_TYPE_AB, // 38 IF_EQZ vAA, +BBBB DF_UA, @@ -1080,8 +1080,6 @@ void MIRGraph::AllocateSSAUseData(MIR *mir, int num_uses) { if (mir->ssa_rep->num_uses_allocated < num_uses) { mir->ssa_rep->uses = arena_->AllocArray<int32_t>(num_uses, kArenaAllocDFInfo); - // NOTE: will be filled in during type & size inference pass - mir->ssa_rep->fp_use = arena_->AllocArray<bool>(num_uses, kArenaAllocDFInfo); } } @@ -1090,7 +1088,6 @@ void MIRGraph::AllocateSSADefData(MIR *mir, int num_defs) { if (mir->ssa_rep->num_defs_allocated < num_defs) { mir->ssa_rep->defs = arena_->AllocArray<int32_t>(num_defs, kArenaAllocDFInfo); - mir->ssa_rep->fp_def = arena_->AllocArray<bool>(num_defs, kArenaAllocDFInfo); } } @@ -1287,35 +1284,27 @@ bool MIRGraph::DoSSAConversion(BasicBlock* bb) { if (df_attributes & DF_HAS_USES) { num_uses = 0; if (df_attributes & DF_UA) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_A; HandleSSAUse(mir->ssa_rep->uses, d_insn->vA, num_uses++); if (df_attributes & DF_A_WIDE) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_A; HandleSSAUse(mir->ssa_rep->uses, d_insn->vA+1, num_uses++); } } if (df_attributes & DF_UB) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_B; HandleSSAUse(mir->ssa_rep->uses, d_insn->vB, num_uses++); if (df_attributes & DF_B_WIDE) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_B; HandleSSAUse(mir->ssa_rep->uses, d_insn->vB+1, num_uses++); } } if (df_attributes & DF_UC) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_C; HandleSSAUse(mir->ssa_rep->uses, d_insn->vC, num_uses++); if (df_attributes & DF_C_WIDE) { - mir->ssa_rep->fp_use[num_uses] = df_attributes & DF_FP_C; HandleSSAUse(mir->ssa_rep->uses, d_insn->vC+1, num_uses++); } } } if (df_attributes & DF_HAS_DEFS) { - mir->ssa_rep->fp_def[0] = df_attributes & DF_FP_A; HandleSSADef(mir->ssa_rep->defs, d_insn->vA, 0); if (df_attributes & DF_A_WIDE) { - mir->ssa_rep->fp_def[1] = df_attributes & DF_FP_A; HandleSSADef(mir->ssa_rep->defs, d_insn->vA+1, 1); } } diff --git a/compiler/dex/mir_field_info.h b/compiler/dex/mir_field_info.h index 0d131fb3e5..e4570fd8d3 100644 --- a/compiler/dex/mir_field_info.h +++ b/compiler/dex/mir_field_info.h @@ -179,6 +179,7 @@ class MirIFieldLoweringInfo : public MirFieldInfo { friend class GlobalValueNumberingTest; friend class GvnDeadCodeEliminationTest; friend class LocalValueNumberingTest; + friend class TypeInferenceTest; }; class MirSFieldLoweringInfo : public MirFieldInfo { @@ -254,6 +255,7 @@ class MirSFieldLoweringInfo : public MirFieldInfo { friend class GlobalValueNumberingTest; friend class GvnDeadCodeEliminationTest; friend class LocalValueNumberingTest; + friend class TypeInferenceTest; }; } // namespace art diff --git a/compiler/dex/mir_graph.cc b/compiler/dex/mir_graph.cc index 0c1cddecb0..b5c42f11ac 100644 --- a/compiler/dex/mir_graph.cc +++ b/compiler/dex/mir_graph.cc @@ -695,9 +695,10 @@ void MIRGraph::InlineMethod(const DexFile::CodeItem* code_item, uint32_t access_ current_method_ = m_units_.size(); current_offset_ = 0; // TODO: will need to snapshot stack image and use that as the mir context identification. - m_units_.push_back(new DexCompilationUnit(cu_, class_loader, Runtime::Current()->GetClassLinker(), - dex_file, current_code_item_, class_def_idx, method_idx, access_flags, - cu_->compiler_driver->GetVerifiedMethod(&dex_file, method_idx))); + m_units_.push_back(new (arena_) DexCompilationUnit( + cu_, class_loader, Runtime::Current()->GetClassLinker(), dex_file, + current_code_item_, class_def_idx, method_idx, access_flags, + cu_->compiler_driver->GetVerifiedMethod(&dex_file, method_idx))); const uint16_t* code_ptr = current_code_item_->insns_; const uint16_t* code_end = current_code_item_->insns_ + current_code_item_->insns_size_in_code_units_; diff --git a/compiler/dex/mir_graph.h b/compiler/dex/mir_graph.h index d6dc566b49..7f9698bbee 100644 --- a/compiler/dex/mir_graph.h +++ b/compiler/dex/mir_graph.h @@ -39,6 +39,7 @@ class DexFileMethodInliner; class GlobalValueNumbering; class GvnDeadCodeElimination; class PassManager; +class TypeInference; // Forward declaration. class MIRGraph; @@ -64,6 +65,7 @@ enum DataFlowAttributePos { kNullTransferSrc0, // Object copy src[0] -> dst. kNullTransferSrcN, // Phi null check state transfer. kRangeCheckC, // Range check of C. + kCheckCastA, // Check cast of A. kFPA, kFPB, kFPC, @@ -73,6 +75,7 @@ enum DataFlowAttributePos { kRefA, kRefB, kRefC, + kSameTypeAB, // A and B have the same type but it can be core/ref/fp (IF_cc). kUsesMethodStar, // Implicit use of Method*. kUsesIField, // Accesses an instance field (IGET/IPUT). kUsesSField, // Accesses a static field (SGET/SPUT). @@ -101,6 +104,7 @@ enum DataFlowAttributePos { #define DF_NULL_TRANSFER_0 (UINT64_C(1) << kNullTransferSrc0) #define DF_NULL_TRANSFER_N (UINT64_C(1) << kNullTransferSrcN) #define DF_RANGE_CHK_C (UINT64_C(1) << kRangeCheckC) +#define DF_CHK_CAST (UINT64_C(1) << kCheckCastA) #define DF_FP_A (UINT64_C(1) << kFPA) #define DF_FP_B (UINT64_C(1) << kFPB) #define DF_FP_C (UINT64_C(1) << kFPC) @@ -110,6 +114,7 @@ enum DataFlowAttributePos { #define DF_REF_A (UINT64_C(1) << kRefA) #define DF_REF_B (UINT64_C(1) << kRefB) #define DF_REF_C (UINT64_C(1) << kRefC) +#define DF_SAME_TYPE_AB (UINT64_C(1) << kSameTypeAB) #define DF_UMS (UINT64_C(1) << kUsesMethodStar) #define DF_IFIELD (UINT64_C(1) << kUsesIField) #define DF_SFIELD (UINT64_C(1) << kUsesSField) @@ -217,13 +222,11 @@ struct BasicBlockDataFlow { */ struct SSARepresentation { int32_t* uses; - bool* fp_use; int32_t* defs; - bool* fp_def; - int16_t num_uses_allocated; - int16_t num_defs_allocated; - int16_t num_uses; - int16_t num_defs; + uint16_t num_uses_allocated; + uint16_t num_defs_allocated; + uint16_t num_uses; + uint16_t num_defs; static uint32_t GetStartUseIndex(Instruction::Code opcode); }; @@ -334,7 +337,8 @@ class MIR : public ArenaObject<kArenaAllocMIR> { // SGET/SPUT lowering info index, points to MIRGraph::sfield_lowering_infos_. Due to limit on // the number of code points (64K) and size of SGET/SPUT insn (2), this will never exceed 32K. uint32_t sfield_lowering_info; - // INVOKE data index, points to MIRGraph::method_lowering_infos_. + // INVOKE data index, points to MIRGraph::method_lowering_infos_. Also used for inlined + // CONST and MOVE insn (with MIR_CALLEE) to remember the invoke for type inference. uint32_t method_lowering_info; } meta; @@ -647,6 +651,10 @@ class MIRGraph { */ void DumpCFG(const char* dir_prefix, bool all_blocks, const char* suffix = nullptr); + bool HasCheckCast() const { + return (merged_df_flags_ & DF_CHK_CAST) != 0u; + } + bool HasFieldAccess() const { return (merged_df_flags_ & (DF_IFIELD | DF_SFIELD)) != 0u; } @@ -691,8 +699,16 @@ class MIRGraph { void DoCacheMethodLoweringInfo(); const MirMethodLoweringInfo& GetMethodLoweringInfo(MIR* mir) const { - DCHECK_LT(mir->meta.method_lowering_info, method_lowering_infos_.size()); - return method_lowering_infos_[mir->meta.method_lowering_info]; + return GetMethodLoweringInfo(mir->meta.method_lowering_info); + } + + const MirMethodLoweringInfo& GetMethodLoweringInfo(uint32_t lowering_info) const { + DCHECK_LT(lowering_info, method_lowering_infos_.size()); + return method_lowering_infos_[lowering_info]; + } + + size_t GetMethodLoweringInfoCount() const { + return method_lowering_infos_.size(); } void ComputeInlineIFieldLoweringInfo(uint16_t field_idx, MIR* invoke, MIR* iget_or_iput); @@ -1073,7 +1089,9 @@ class MIRGraph { bool EliminateNullChecksGate(); bool EliminateNullChecks(BasicBlock* bb); void EliminateNullChecksEnd(); + void InferTypesStart(); bool InferTypes(BasicBlock* bb); + void InferTypesEnd(); bool EliminateClassInitChecksGate(); bool EliminateClassInitChecks(BasicBlock* bb); void EliminateClassInitChecksEnd(); @@ -1083,6 +1101,7 @@ class MIRGraph { bool EliminateDeadCodeGate(); bool EliminateDeadCode(BasicBlock* bb); void EliminateDeadCodeEnd(); + void GlobalValueNumberingCleanup(); bool EliminateSuspendChecksGate(); bool EliminateSuspendChecks(BasicBlock* bb); @@ -1100,34 +1119,6 @@ class MIRGraph { return temp_.gvn.sfield_ids[mir->meta.sfield_lowering_info]; } - /* - * Type inference handling helpers. Because Dalvik's bytecode is not fully typed, - * we have to do some work to figure out the sreg type. For some operations it is - * clear based on the opcode (i.e. ADD_FLOAT v0, v1, v2), but for others (MOVE), we - * may never know the "real" type. - * - * We perform the type inference operation by using an iterative walk over - * the graph, propagating types "defined" by typed opcodes to uses and defs in - * non-typed opcodes (such as MOVE). The Setxx(index) helpers are used to set defined - * types on typed opcodes (such as ADD_INT). The Setxx(index, is_xx) form is used to - * propagate types through non-typed opcodes such as PHI and MOVE. The is_xx flag - * tells whether our guess of the type is based on a previously typed definition. - * If so, the defined type takes precedence. Note that it's possible to have the same sreg - * show multiple defined types because dx treats constants as untyped bit patterns. - * The return value of the Setxx() helpers says whether or not the Setxx() action changed - * the current guess, and is used to know when to terminate the iterative walk. - */ - bool SetFp(int index, bool is_fp); - bool SetFp(int index); - bool SetCore(int index, bool is_core); - bool SetCore(int index); - bool SetRef(int index, bool is_ref); - bool SetRef(int index); - bool SetWide(int index, bool is_wide); - bool SetWide(int index); - bool SetHigh(int index, bool is_high); - bool SetHigh(int index); - bool PuntToInterpreter() { return punt_to_interpreter_; } @@ -1252,7 +1243,6 @@ class MIRGraph { static const char* extended_mir_op_names_[kMirOpLast - kMirOpFirst]; void HandleSSADef(int* defs, int dalvik_reg, int reg_index); - bool InferTypeAndSize(BasicBlock* bb, MIR* mir, bool changed); protected: int FindCommonParent(int block1, int block2); @@ -1399,6 +1389,7 @@ class MIRGraph { ArenaBitVector* work_live_vregs; ArenaBitVector** def_block_matrix; // num_vregs x num_blocks_. ArenaBitVector** phi_node_blocks; // num_vregs x num_blocks_. + TypeInference* ti; } ssa; // Global value numbering. struct { @@ -1458,6 +1449,7 @@ class MIRGraph { friend class GvnDeadCodeEliminationTest; friend class LocalValueNumberingTest; friend class TopologicalSortOrderTest; + friend class TypeInferenceTest; friend class QuickCFITest; }; diff --git a/compiler/dex/mir_method_info.h b/compiler/dex/mir_method_info.h index 000144f9d1..946c74becf 100644 --- a/compiler/dex/mir_method_info.h +++ b/compiler/dex/mir_method_info.h @@ -232,6 +232,7 @@ class MirMethodLoweringInfo : public MirMethodInfo { int stats_flags_; friend class MirOptimizationTest; + friend class TypeInferenceTest; }; } // namespace art diff --git a/compiler/dex/mir_optimization.cc b/compiler/dex/mir_optimization.cc index ac7963d5c0..0d5da32d6d 100644 --- a/compiler/dex/mir_optimization.cc +++ b/compiler/dex/mir_optimization.cc @@ -25,6 +25,7 @@ #include "gvn_dead_code_elimination.h" #include "local_value_numbering.h" #include "mir_field_info.h" +#include "type_inference.h" #include "quick/dex_file_method_inliner.h" #include "quick/dex_file_to_method_inliner_map.h" #include "stack.h" @@ -576,7 +577,6 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { // Copy the SSA information that is relevant. mir_next->ssa_rep->num_uses = mir->ssa_rep->num_uses; mir_next->ssa_rep->uses = mir->ssa_rep->uses; - mir_next->ssa_rep->fp_use = mir->ssa_rep->fp_use; mir_next->ssa_rep->num_defs = 0; mir->ssa_rep->num_uses = 0; mir->ssa_rep->num_defs = 0; @@ -670,16 +670,7 @@ bool MIRGraph::BasicBlockOpt(BasicBlock* bb) { mir->ssa_rep->uses = src_ssa; mir->ssa_rep->num_uses = 3; } - mir->ssa_rep->num_defs = 1; - mir->ssa_rep->defs = arena_->AllocArray<int32_t>(1, kArenaAllocDFInfo); - mir->ssa_rep->fp_def = arena_->AllocArray<bool>(1, kArenaAllocDFInfo); - mir->ssa_rep->fp_def[0] = if_true->ssa_rep->fp_def[0]; - // Match type of uses to def. - mir->ssa_rep->fp_use = arena_->AllocArray<bool>(mir->ssa_rep->num_uses, - kArenaAllocDFInfo); - for (int i = 0; i < mir->ssa_rep->num_uses; i++) { - mir->ssa_rep->fp_use[i] = mir->ssa_rep->fp_def[0]; - } + AllocateSSADefData(mir, 1); /* * There is usually a Phi node in the join block for our two cases. If the * Phi node only contains our two cases as input, we will use the result @@ -1134,23 +1125,26 @@ void MIRGraph::EliminateNullChecksEnd() { } } +void MIRGraph::InferTypesStart() { + DCHECK(temp_scoped_alloc_ != nullptr); + temp_.ssa.ti = new (temp_scoped_alloc_.get()) TypeInference(this, temp_scoped_alloc_.get()); +} + /* * Perform type and size inference for a basic block. */ bool MIRGraph::InferTypes(BasicBlock* bb) { if (bb->data_flow_info == nullptr) return false; - bool infer_changed = false; - for (MIR* mir = bb->first_mir_insn; mir != NULL; mir = mir->next) { - if (mir->ssa_rep == NULL) { - continue; - } - - // Propagate type info. - infer_changed = InferTypeAndSize(bb, mir, infer_changed); - } + DCHECK(temp_.ssa.ti != nullptr); + return temp_.ssa.ti->Apply(bb); +} - return infer_changed; +void MIRGraph::InferTypesEnd() { + DCHECK(temp_.ssa.ti != nullptr); + temp_.ssa.ti->Finish(); + delete temp_.ssa.ti; + temp_.ssa.ti = nullptr; } bool MIRGraph::EliminateClassInitChecksGate() { @@ -1415,14 +1409,10 @@ void MIRGraph::ApplyGlobalValueNumberingEnd() { LOG(WARNING) << "GVN failed for " << PrettyMethod(cu_->method_idx, *cu_->dex_file); cu_->disable_opt |= (1u << kGvnDeadCodeElimination); } - - if ((cu_->disable_opt & (1 << kGvnDeadCodeElimination)) != 0) { - EliminateDeadCodeEnd(); - } // else preserve GVN data for CSE. } bool MIRGraph::EliminateDeadCodeGate() { - if ((cu_->disable_opt & (1 << kGvnDeadCodeElimination)) != 0) { + if ((cu_->disable_opt & (1 << kGvnDeadCodeElimination)) != 0 || temp_.gvn.gvn == nullptr) { return false; } DCHECK(temp_scoped_alloc_ != nullptr); @@ -1443,11 +1433,21 @@ bool MIRGraph::EliminateDeadCode(BasicBlock* bb) { } void MIRGraph::EliminateDeadCodeEnd() { - DCHECK_EQ(temp_.gvn.dce != nullptr, (cu_->disable_opt & (1 << kGvnDeadCodeElimination)) == 0); - if (temp_.gvn.dce != nullptr) { - delete temp_.gvn.dce; - temp_.gvn.dce = nullptr; + if (kIsDebugBuild) { + // DCE can make some previously dead vregs alive again. Make sure the obsolete + // live-in information is not used anymore. + AllNodesIterator iter(this); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->data_flow_info != nullptr) { + bb->data_flow_info->live_in_v = nullptr; + } + } } +} + +void MIRGraph::GlobalValueNumberingCleanup() { + delete temp_.gvn.dce; + temp_.gvn.dce = nullptr; delete temp_.gvn.gvn; temp_.gvn.gvn = nullptr; temp_.gvn.ifield_ids = nullptr; diff --git a/compiler/dex/pass_driver_me_opts.cc b/compiler/dex/pass_driver_me_opts.cc index 2e871dafef..3e193b471b 100644 --- a/compiler/dex/pass_driver_me_opts.cc +++ b/compiler/dex/pass_driver_me_opts.cc @@ -46,6 +46,7 @@ void PassDriverMEOpts::SetupPasses(PassManager* pass_manager) { pass_manager->AddPass(new CodeLayout); pass_manager->AddPass(new GlobalValueNumberingPass); pass_manager->AddPass(new DeadCodeEliminationPass); + pass_manager->AddPass(new GlobalValueNumberingCleanupPass); pass_manager->AddPass(new ConstantPropagation); pass_manager->AddPass(new MethodUseCount); pass_manager->AddPass(new BBOptimizations); diff --git a/compiler/dex/pass_driver_me_post_opt.cc b/compiler/dex/pass_driver_me_post_opt.cc index a8b8a54033..b35bc3d7d3 100644 --- a/compiler/dex/pass_driver_me_post_opt.cc +++ b/compiler/dex/pass_driver_me_post_opt.cc @@ -41,7 +41,7 @@ void PassDriverMEPostOpt::SetupPasses(PassManager* pass_manager) { pass_manager->AddPass(new SSAConversion); pass_manager->AddPass(new PhiNodeOperands); pass_manager->AddPass(new PerformInitRegLocations); - pass_manager->AddPass(new TypeInference); + pass_manager->AddPass(new TypeInferencePass); pass_manager->AddPass(new FinishSSATransformation); } diff --git a/compiler/dex/post_opt_passes.h b/compiler/dex/post_opt_passes.h index 1ab862503b..e9fa0eb578 100644 --- a/compiler/dex/post_opt_passes.h +++ b/compiler/dex/post_opt_passes.h @@ -263,12 +263,19 @@ class PerformInitRegLocations : public PassMEMirSsaRep { }; /** - * @class TypeInference + * @class TypeInferencePass * @brief Type inference pass. */ -class TypeInference : public PassMEMirSsaRep { +class TypeInferencePass : public PassMEMirSsaRep { public: - TypeInference() : PassMEMirSsaRep("TypeInference", kRepeatingPreOrderDFSTraversal) { + TypeInferencePass() : PassMEMirSsaRep("TypeInference", kRepeatingPreOrderDFSTraversal) { + } + + void Start(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast<PassMEDataHolder*>(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph->InferTypesStart(); } bool Worker(PassDataHolder* data) const { @@ -280,6 +287,13 @@ class TypeInference : public PassMEMirSsaRep { DCHECK(bb != nullptr); return c_unit->mir_graph->InferTypes(bb); } + + void End(PassDataHolder* data) const { + DCHECK(data != nullptr); + CompilationUnit* c_unit = down_cast<PassMEDataHolder*>(data)->c_unit; + DCHECK(c_unit != nullptr); + c_unit->mir_graph.get()->InferTypesEnd(); + } }; /** diff --git a/compiler/dex/quick/arm/int_arm.cc b/compiler/dex/quick/arm/int_arm.cc index 8d20f1b37e..7598e50977 100644 --- a/compiler/dex/quick/arm/int_arm.cc +++ b/compiler/dex/quick/arm/int_arm.cc @@ -1326,11 +1326,6 @@ void ArmMir2Lir::GenMulLong(Instruction::Code opcode, RegLocation rl_dest, } } - // Now, restore lr to its non-temp status. - FreeTemp(tmp1); - Clobber(rs_rARM_LR); - UnmarkTemp(rs_rARM_LR); - if (reg_status != 0) { // We had manually allocated registers for rl_result. // Now construct a RegLocation. @@ -1338,7 +1333,14 @@ void ArmMir2Lir::GenMulLong(Instruction::Code opcode, RegLocation rl_dest, rl_result.reg = RegStorage::MakeRegPair(res_lo, res_hi); } + // Free tmp1 but keep LR as temp for StoreValueWide() if needed. + FreeTemp(tmp1); + StoreValueWide(rl_dest, rl_result); + + // Now, restore lr to its non-temp status. + Clobber(rs_rARM_LR); + UnmarkTemp(rs_rARM_LR); } void ArmMir2Lir::GenArithOpLong(Instruction::Code opcode, RegLocation rl_dest, RegLocation rl_src1, diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc index 8f7eb599d5..f5e6c09dba 100644 --- a/compiler/dex/quick/dex_file_method_inliner.cc +++ b/compiler/dex/quick/dex_file_method_inliner.cc @@ -753,6 +753,7 @@ bool DexFileMethodInliner::GenInlineConst(MIRGraph* mir_graph, BasicBlock* bb, M insn->dalvikInsn.opcode = Instruction::CONST; insn->dalvikInsn.vA = move_result->dalvikInsn.vA; insn->dalvikInsn.vB = method.d.data; + insn->meta.method_lowering_info = invoke->meta.method_lowering_info; // Preserve type info. bb->InsertMIRAfter(move_result, insn); return true; } @@ -791,6 +792,7 @@ bool DexFileMethodInliner::GenInlineReturnArg(MIRGraph* mir_graph, BasicBlock* b insn->dalvikInsn.opcode = opcode; insn->dalvikInsn.vA = move_result->dalvikInsn.vA; insn->dalvikInsn.vB = arg; + insn->meta.method_lowering_info = invoke->meta.method_lowering_info; // Preserve type info. bb->InsertMIRAfter(move_result, insn); return true; } @@ -913,6 +915,7 @@ bool DexFileMethodInliner::GenInlineIPut(MIRGraph* mir_graph, BasicBlock* bb, MI } move->dalvikInsn.vA = move_result->dalvikInsn.vA; move->dalvikInsn.vB = return_reg; + move->meta.method_lowering_info = invoke->meta.method_lowering_info; // Preserve type info. bb->InsertMIRAfter(insn, move); } return true; diff --git a/compiler/dex/quick/gen_common.cc b/compiler/dex/quick/gen_common.cc index de5e0410fb..0592c74181 100644 --- a/compiler/dex/quick/gen_common.cc +++ b/compiler/dex/quick/gen_common.cc @@ -58,24 +58,19 @@ ALWAYS_INLINE static inline bool ForceSlowTypePath(CompilationUnit* cu) { return (cu->enable_debug & (1 << kDebugSlowTypePath)) != 0; } -void Mir2Lir::GenIfNullUseHelperImmMethod( - RegStorage r_result, QuickEntrypointEnum trampoline, int imm, RegStorage r_method) { +void Mir2Lir::GenIfNullUseHelperImm(RegStorage r_result, QuickEntrypointEnum trampoline, int imm) { class CallHelperImmMethodSlowPath : public LIRSlowPath { public: CallHelperImmMethodSlowPath(Mir2Lir* m2l, LIR* fromfast, LIR* cont, QuickEntrypointEnum trampoline_in, int imm_in, - RegStorage r_method_in, RegStorage r_result_in) + RegStorage r_result_in) : LIRSlowPath(m2l, fromfast, cont), trampoline_(trampoline_in), - imm_(imm_in), r_method_(r_method_in), r_result_(r_result_in) { + imm_(imm_in), r_result_(r_result_in) { } void Compile() { GenerateTargetLabel(); - if (r_method_.Valid()) { - m2l_->CallRuntimeHelperImmReg(trampoline_, imm_, r_method_, true); - } else { - m2l_->CallRuntimeHelperImmMethod(trampoline_, imm_, true); - } + m2l_->CallRuntimeHelperImm(trampoline_, imm_, true); m2l_->OpRegCopy(r_result_, m2l_->TargetReg(kRet0, kRef)); m2l_->OpUnconditionalBranch(cont_); } @@ -83,7 +78,6 @@ void Mir2Lir::GenIfNullUseHelperImmMethod( private: QuickEntrypointEnum trampoline_; const int imm_; - const RegStorage r_method_; const RegStorage r_result_; }; @@ -91,7 +85,7 @@ void Mir2Lir::GenIfNullUseHelperImmMethod( LIR* cont = NewLIR0(kPseudoTargetLabel); AddSlowPath(new (arena_) CallHelperImmMethodSlowPath(this, branch, cont, trampoline, imm, - r_method, r_result)); + r_result)); } RegStorage Mir2Lir::GenGetOtherTypeForSgetSput(const MirSFieldLoweringInfo& field_info, @@ -101,13 +95,12 @@ RegStorage Mir2Lir::GenGetOtherTypeForSgetSput(const MirSFieldLoweringInfo& fiel FlushAllRegs(); RegStorage r_base = TargetReg(kArg0, kRef); LockTemp(r_base); - RegStorage r_method = RegStorage::InvalidReg(); // Loaded lazily, maybe in the slow-path. if (CanUseOpPcRelDexCacheArrayLoad()) { uint32_t offset = dex_cache_arrays_layout_.TypeOffset(field_info.StorageIndex()); OpPcRelDexCacheArrayLoad(cu_->dex_file, offset, r_base); } else { // Using fixed register to sync with possible call to runtime support. - r_method = LoadCurrMethodWithHint(TargetReg(kArg1, kRef)); + RegStorage r_method = LoadCurrMethodWithHint(r_base); LoadRefDisp(r_method, mirror::ArtMethod::DexCacheResolvedTypesOffset().Int32Value(), r_base, kNotVolatile); int32_t offset_of_field = ObjArray::OffsetOfElement(field_info.StorageIndex()).Int32Value(); @@ -139,10 +132,10 @@ RegStorage Mir2Lir::GenGetOtherTypeForSgetSput(const MirSFieldLoweringInfo& fiel // entry in the dex cache is null, and the "uninit" when the class is not yet initialized. // At least one will be non-null here, otherwise we wouldn't generate the slow path. StaticFieldSlowPath(Mir2Lir* m2l, LIR* unresolved, LIR* uninit, LIR* cont, int storage_index, - RegStorage r_base_in, RegStorage r_method_in) + RegStorage r_base_in) : LIRSlowPath(m2l, unresolved != nullptr ? unresolved : uninit, cont), second_branch_(unresolved != nullptr ? uninit : nullptr), - storage_index_(storage_index), r_base_(r_base_in), r_method_(r_method_in) { + storage_index_(storage_index), r_base_(r_base_in) { } void Compile() { @@ -150,14 +143,7 @@ RegStorage Mir2Lir::GenGetOtherTypeForSgetSput(const MirSFieldLoweringInfo& fiel if (second_branch_ != nullptr) { second_branch_->target = target; } - if (r_method_.Valid()) { - // ArtMethod* was loaded in normal path - use it. - m2l_->CallRuntimeHelperImmReg(kQuickInitializeStaticStorage, storage_index_, r_method_, - true); - } else { - // ArtMethod* wasn't loaded in normal path - use a helper that loads it. - m2l_->CallRuntimeHelperImmMethod(kQuickInitializeStaticStorage, storage_index_, true); - } + m2l_->CallRuntimeHelperImm(kQuickInitializeStaticStorage, storage_index_, true); // Copy helper's result into r_base, a no-op on all but MIPS. m2l_->OpRegCopy(r_base_, m2l_->TargetReg(kRet0, kRef)); @@ -170,17 +156,13 @@ RegStorage Mir2Lir::GenGetOtherTypeForSgetSput(const MirSFieldLoweringInfo& fiel const int storage_index_; const RegStorage r_base_; - RegStorage r_method_; }; // The slow path is invoked if the r_base is null or the class pointed // to by it is not initialized. LIR* cont = NewLIR0(kPseudoTargetLabel); AddSlowPath(new (arena_) StaticFieldSlowPath(this, unresolved_branch, uninit_branch, cont, - field_info.StorageIndex(), r_base, r_method)); - } - if (IsTemp(r_method)) { - FreeTemp(r_method); + field_info.StorageIndex(), r_base)); } return r_base; } @@ -1042,22 +1024,19 @@ void Mir2Lir::GenConstClass(uint32_t type_idx, RegLocation rl_dest) { type_idx)) { // Call out to helper which resolves type and verifies access. // Resolved type returned in kRet0. - CallRuntimeHelperImmMethod(kQuickInitializeTypeAndVerifyAccess, type_idx, true); + CallRuntimeHelperImm(kQuickInitializeTypeAndVerifyAccess, type_idx, true); rl_result = GetReturn(kRefReg); } else { rl_result = EvalLoc(rl_dest, kRefReg, true); // We don't need access checks, load type from dex cache - RegStorage r_method = RegStorage::InvalidReg(); if (CanUseOpPcRelDexCacheArrayLoad()) { size_t offset = dex_cache_arrays_layout_.TypeOffset(type_idx); OpPcRelDexCacheArrayLoad(cu_->dex_file, offset, rl_result.reg); } else { - RegLocation rl_method = LoadCurrMethod(); - CheckRegLocation(rl_method); - r_method = rl_method.reg; int32_t dex_cache_offset = mirror::ArtMethod::DexCacheResolvedTypesOffset().Int32Value(); RegStorage res_reg = AllocTempRef(); + RegStorage r_method = LoadCurrMethodWithHint(res_reg); LoadRefDisp(r_method, dex_cache_offset, res_reg, kNotVolatile); int32_t offset_of_type = ClassArray::OffsetOfElement(type_idx).Int32Value(); LoadRefDisp(res_reg, offset_of_type, rl_result.reg, kNotVolatile); @@ -1067,7 +1046,7 @@ void Mir2Lir::GenConstClass(uint32_t type_idx, RegLocation rl_dest) { type_idx) || ForceSlowTypePath(cu_)) { // Slow path, at runtime test if type is null and if so initialize FlushAllRegs(); - GenIfNullUseHelperImmMethod(rl_result.reg, kQuickInitializeType, type_idx, r_method); + GenIfNullUseHelperImm(rl_result.reg, kQuickInitializeType, type_idx); } } StoreValue(rl_dest, rl_result); @@ -1085,14 +1064,13 @@ void Mir2Lir::GenConstString(uint32_t string_idx, RegLocation rl_dest) { // Might call out to helper, which will return resolved string in kRet0 RegStorage ret0 = TargetReg(kRet0, kRef); - RegStorage r_method = RegStorage::InvalidReg(); if (CanUseOpPcRelDexCacheArrayLoad()) { size_t offset = dex_cache_arrays_layout_.StringOffset(string_idx); OpPcRelDexCacheArrayLoad(cu_->dex_file, offset, ret0); } else { - r_method = LoadCurrMethodWithHint(TargetReg(kArg1, kRef)); // Method to declaring class. RegStorage arg0 = TargetReg(kArg0, kRef); + RegStorage r_method = LoadCurrMethodWithHint(arg0); LoadRefDisp(r_method, mirror::ArtMethod::DeclaringClassOffset().Int32Value(), arg0, kNotVolatile); // Declaring class to dex cache strings. @@ -1100,7 +1078,7 @@ void Mir2Lir::GenConstString(uint32_t string_idx, RegLocation rl_dest) { LoadRefDisp(arg0, offset_of_string, ret0, kNotVolatile); } - GenIfNullUseHelperImmMethod(ret0, kQuickResolveString, string_idx, r_method); + GenIfNullUseHelperImm(ret0, kQuickResolveString, string_idx); GenBarrier(); StoreValue(rl_dest, GetReturn(kRefReg)); @@ -1262,12 +1240,11 @@ void Mir2Lir::GenInstanceofCallingHelper(bool needs_access_check, bool type_know LoadValueDirectFixed(rl_src, ref_reg); // kArg0 <= ref } - RegStorage r_method = RegStorage::InvalidReg(); if (CanUseOpPcRelDexCacheArrayLoad()) { size_t offset = dex_cache_arrays_layout_.TypeOffset(type_idx); OpPcRelDexCacheArrayLoad(cu_->dex_file, offset, class_reg); } else { - r_method = LoadCurrMethodWithHint(TargetReg(kArg1, kRef)); + RegStorage r_method = LoadCurrMethodWithHint(class_reg); // Load dex cache entry into class_reg (kArg2) LoadRefDisp(r_method, mirror::ArtMethod::DexCacheResolvedTypesOffset().Int32Value(), class_reg, kNotVolatile); @@ -1275,7 +1252,7 @@ void Mir2Lir::GenInstanceofCallingHelper(bool needs_access_check, bool type_know LoadRefDisp(class_reg, offset_of_type, class_reg, kNotVolatile); } if (!can_assume_type_is_in_dex_cache) { - GenIfNullUseHelperImmMethod(class_reg, kQuickInitializeType, type_idx, r_method); + GenIfNullUseHelperImm(class_reg, kQuickInitializeType, type_idx); // Should load value here. LoadValueDirectFixed(rl_src, ref_reg); // kArg0 <= ref @@ -1394,12 +1371,11 @@ void Mir2Lir::GenCheckCast(int opt_flags, uint32_t insn_idx, uint32_t type_idx, class_reg, kNotVolatile); } else { // Load dex cache entry into class_reg (kArg2) - RegStorage r_method = RegStorage::InvalidReg(); if (CanUseOpPcRelDexCacheArrayLoad()) { size_t offset = dex_cache_arrays_layout_.TypeOffset(type_idx); OpPcRelDexCacheArrayLoad(cu_->dex_file, offset, class_reg); } else { - r_method = LoadCurrMethodWithHint(TargetReg(kArg1, kRef)); + RegStorage r_method = LoadCurrMethodWithHint(class_reg); LoadRefDisp(r_method, mirror::ArtMethod::DexCacheResolvedTypesOffset().Int32Value(), class_reg, kNotVolatile); @@ -1408,7 +1384,7 @@ void Mir2Lir::GenCheckCast(int opt_flags, uint32_t insn_idx, uint32_t type_idx, } if (!cu_->compiler_driver->CanAssumeTypeIsPresentInDexCache(*cu_->dex_file, type_idx)) { // Need to test presence of type in dex cache at runtime - GenIfNullUseHelperImmMethod(class_reg, kQuickInitializeType, type_idx, r_method); + GenIfNullUseHelperImm(class_reg, kQuickInitializeType, type_idx); } } // At this point, class_reg (kArg2) has class diff --git a/compiler/dex/quick/mir_to_lir.h b/compiler/dex/quick/mir_to_lir.h index 8f08a51e95..6f227fcee3 100644 --- a/compiler/dex/quick/mir_to_lir.h +++ b/compiler/dex/quick/mir_to_lir.h @@ -1692,10 +1692,8 @@ class Mir2Lir { * @param r_result the result register. * @param trampoline the helper to call in slow path. * @param imm the immediate passed to the helper. - * @param r_method the register with ArtMethod* if available, otherwise RegStorage::Invalid(). */ - void GenIfNullUseHelperImmMethod( - RegStorage r_result, QuickEntrypointEnum trampoline, int imm, RegStorage r_method); + void GenIfNullUseHelperImm(RegStorage r_result, QuickEntrypointEnum trampoline, int imm); /** * @brief Generate code to retrieve Class* for another type to be used by SGET/SPUT. diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc index 39eb117e9c..73cfe92c45 100644 --- a/compiler/dex/quick/quick_compiler.cc +++ b/compiler/dex/quick/quick_compiler.cc @@ -575,7 +575,7 @@ static uint32_t kCompilerOptimizerDisableFlags = 0 | // Disable specific optimi // (1 << kNullCheckElimination) | // (1 << kClassInitCheckElimination) | // (1 << kGlobalValueNumbering) | - (1 << kGvnDeadCodeElimination) | + // (1 << kGvnDeadCodeElimination) | // (1 << kLocalValueNumbering) | // (1 << kPromoteRegs) | // (1 << kTrackLiveTemps) | diff --git a/compiler/dex/type_inference.cc b/compiler/dex/type_inference.cc new file mode 100644 index 0000000000..19d591ba41 --- /dev/null +++ b/compiler/dex/type_inference.cc @@ -0,0 +1,1067 @@ +/* + * Copyright (C) 2015 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 "type_inference.h" + +#include "base/bit_vector-inl.h" +#include "compiler_ir.h" +#include "dataflow_iterator-inl.h" +#include "dex_flags.h" +#include "dex_file-inl.h" +#include "driver/dex_compilation_unit.h" +#include "mir_field_info.h" +#include "mir_graph.h" +#include "mir_method_info.h" + +namespace art { + +inline TypeInference::Type TypeInference::Type::ArrayType(uint32_t array_depth, Type nested_type) { + DCHECK_NE(array_depth, 0u); + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | (array_depth << kBitArrayDepthStart) | + ((nested_type.raw_bits_ & kMaskWideAndType) << kArrayTypeShift)); +} + +inline TypeInference::Type TypeInference::Type::ArrayTypeFromComponent(Type component_type) { + if (component_type.ArrayDepth() == 0u) { + return ArrayType(1u, component_type); + } + if (UNLIKELY(component_type.ArrayDepth() == kMaxArrayDepth)) { + return component_type; + } + return Type(component_type.raw_bits_ + (1u << kBitArrayDepthStart)); // array_depth + 1u; +} + +TypeInference::Type TypeInference::Type::ShortyType(char shorty) { + switch (shorty) { + case 'L': + return Type(kFlagLowWord | kFlagNarrow | kFlagRef); + case 'D': + return Type(kFlagLowWord | kFlagWide | kFlagFp); + case 'J': + return Type(kFlagLowWord | kFlagWide | kFlagCore); + case 'F': + return Type(kFlagLowWord | kFlagNarrow | kFlagFp); + default: + DCHECK(shorty == 'I' || shorty == 'S' || shorty == 'C' || shorty == 'B' || shorty == 'Z'); + return Type(kFlagLowWord | kFlagNarrow | kFlagCore); + } +} + +TypeInference::Type TypeInference::Type::DexType(const DexFile* dex_file, uint32_t type_idx) { + const char* desc = dex_file->GetTypeDescriptor(dex_file->GetTypeId(type_idx)); + if (UNLIKELY(desc[0] == 'V')) { + return Unknown(); + } else if (UNLIKELY(desc[0] == '[')) { + size_t array_depth = 0u; + while (*desc == '[') { + ++array_depth; + ++desc; + } + if (UNLIKELY(array_depth > kMaxArrayDepth)) { + LOG(WARNING) << "Array depth exceeds " << kMaxArrayDepth << ": " << array_depth + << " in dex file " << dex_file->GetLocation() << " type index " << type_idx; + array_depth = kMaxArrayDepth; + } + Type shorty_result = Type::ShortyType(desc[0]); + return ArrayType(array_depth, shorty_result); + } else { + return ShortyType(desc[0]); + } +} + +bool TypeInference::Type::MergeArrayConflict(Type src_type) { + DCHECK(Ref()); + DCHECK_NE(ArrayDepth(), src_type.ArrayDepth()); + DCHECK_GE(std::min(ArrayDepth(), src_type.ArrayDepth()), 1u); + bool size_conflict = + (ArrayDepth() == 1u && (raw_bits_ & kFlagArrayWide) != 0u) || + (src_type.ArrayDepth() == 1u && (src_type.raw_bits_ & kFlagArrayWide) != 0u); + // Mark all three array type bits so that merging any other type bits will not change this type. + return Copy(Type((raw_bits_ & kMaskNonArray) | + (1u << kBitArrayDepthStart) | kFlagArrayCore | kFlagArrayRef | kFlagArrayFp | + kFlagArrayNarrow | (size_conflict ? kFlagArrayWide : 0u))); +} + +bool TypeInference::Type::MergeStrong(Type src_type) { + bool changed = MergeNonArrayFlags(src_type); + if (src_type.ArrayDepth() != 0u) { + if (ArrayDepth() == 0u) { + DCHECK_EQ(raw_bits_ & ~kMaskNonArray, 0u); + DCHECK_NE(src_type.raw_bits_ & kFlagRef, 0u); + raw_bits_ |= src_type.raw_bits_ & (~kMaskNonArray | kFlagRef); + changed = true; + } else if (ArrayDepth() == src_type.ArrayDepth()) { + changed |= MergeBits(src_type, kMaskArrayWideAndType); + } else if (src_type.ArrayDepth() == 1u && + (((src_type.raw_bits_ ^ UnknownArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u || + ((src_type.raw_bits_ ^ ObjectArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u)) { + // Source type is [L or [? but current type is at least [[, preserve it. + } else if (ArrayDepth() == 1u && + (((raw_bits_ ^ UnknownArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u || + ((raw_bits_ ^ ObjectArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u)) { + // Overwrite [? or [L with the source array type which is at least [[. + raw_bits_ = (raw_bits_ & kMaskNonArray) | (src_type.raw_bits_ & ~kMaskNonArray); + changed = true; + } else { + // Mark the array value type with conflict - both ref and fp. + changed |= MergeArrayConflict(src_type); + } + } + return changed; +} + +bool TypeInference::Type::MergeWeak(Type src_type) { + bool changed = MergeNonArrayFlags(src_type); + if (src_type.ArrayDepth() != 0u && src_type.NonNull()) { + DCHECK_NE(src_type.ArrayDepth(), 0u); + if (ArrayDepth() == 0u) { + DCHECK_EQ(raw_bits_ & ~kMaskNonArray, 0u); + // Preserve current type. + } else if (ArrayDepth() == src_type.ArrayDepth()) { + changed |= MergeBits(src_type, kMaskArrayWideAndType); + } else if (src_type.ArrayDepth() == 1u && + (((src_type.raw_bits_ ^ UnknownArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u || + ((src_type.raw_bits_ ^ ObjectArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u)) { + // Source type is [L or [? but current type is at least [[, preserve it. + } else if (ArrayDepth() == 1u && + (((raw_bits_ ^ UnknownArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u || + ((raw_bits_ ^ ObjectArrayType().raw_bits_) & kMaskArrayWideAndType) == 0u)) { + // We have [? or [L. If it's [?, upgrade to [L as the source array type is at least [[. + changed |= MergeBits(ObjectArrayType(), kMaskArrayWideAndType); + } else { + // Mark the array value type with conflict - both ref and fp. + changed |= MergeArrayConflict(src_type); + } + } + return changed; +} + +TypeInference::CheckCastData::CheckCastData(MIRGraph* mir_graph, ScopedArenaAllocator* alloc) + : mir_graph_(mir_graph), + alloc_(alloc), + num_blocks_(mir_graph->GetNumBlocks()), + num_sregs_(mir_graph->GetNumSSARegs()), + check_cast_map_(std::less<MIR*>(), alloc->Adapter()), + split_sreg_data_(std::less<int32_t>(), alloc->Adapter()) { +} + +void TypeInference::CheckCastData::AddCheckCast(MIR* check_cast, Type type) { + DCHECK_EQ(check_cast->dalvikInsn.opcode, Instruction::CHECK_CAST); + type.CheckPureRef(); + int32_t extra_s_reg = static_cast<int32_t>(num_sregs_); + num_sregs_ += 1; + check_cast_map_.Put(check_cast, CheckCastMapValue{extra_s_reg, type}); // NOLINT + int32_t s_reg = check_cast->ssa_rep->uses[0]; + auto lb = split_sreg_data_.lower_bound(s_reg); + if (lb == split_sreg_data_.end() || split_sreg_data_.key_comp()(s_reg, lb->first)) { + SplitSRegData split_s_reg_data = { + 0, + alloc_->AllocArray<int32_t>(num_blocks_, kArenaAllocMisc), + alloc_->AllocArray<int32_t>(num_blocks_, kArenaAllocMisc), + new (alloc_) ArenaBitVector(alloc_, num_blocks_, false) + }; + std::fill_n(split_s_reg_data.starting_mod_s_reg, num_blocks_, INVALID_SREG); + std::fill_n(split_s_reg_data.ending_mod_s_reg, num_blocks_, INVALID_SREG); + split_s_reg_data.def_phi_blocks_->ClearAllBits(); + BasicBlock* def_bb = FindDefBlock(check_cast); + split_s_reg_data.ending_mod_s_reg[def_bb->id] = s_reg; + split_s_reg_data.def_phi_blocks_->SetBit(def_bb->id); + lb = split_sreg_data_.PutBefore(lb, s_reg, split_s_reg_data); + } + lb->second.ending_mod_s_reg[check_cast->bb] = extra_s_reg; + lb->second.def_phi_blocks_->SetBit(check_cast->bb); +} + +void TypeInference::CheckCastData::AddPseudoPhis() { + // Look for pseudo-phis where a split SSA reg merges with a differently typed version + // and initialize all starting_mod_s_reg. + DCHECK(!split_sreg_data_.empty()); + ArenaBitVector* phi_blocks = new (alloc_) ArenaBitVector(alloc_, num_blocks_, false); + + for (auto& entry : split_sreg_data_) { + SplitSRegData& data = entry.second; + + // Find pseudo-phi nodes. + phi_blocks->ClearAllBits(); + ArenaBitVector* input_blocks = data.def_phi_blocks_; + do { + for (uint32_t idx : input_blocks->Indexes()) { + BasicBlock* def_bb = mir_graph_->GetBasicBlock(idx); + if (def_bb->dom_frontier != nullptr) { + phi_blocks->Union(def_bb->dom_frontier); + } + } + } while (input_blocks->Union(phi_blocks)); + + // Find live pseudo-phis. Make sure they're merging the same SSA reg. + data.def_phi_blocks_->ClearAllBits(); + int32_t s_reg = entry.first; + int v_reg = mir_graph_->SRegToVReg(s_reg); + for (uint32_t phi_bb_id : phi_blocks->Indexes()) { + BasicBlock* phi_bb = mir_graph_->GetBasicBlock(phi_bb_id); + DCHECK(phi_bb != nullptr); + DCHECK(phi_bb->data_flow_info != nullptr); + DCHECK(phi_bb->data_flow_info->live_in_v != nullptr); + if (IsSRegLiveAtStart(phi_bb, v_reg, s_reg)) { + int32_t extra_s_reg = static_cast<int32_t>(num_sregs_); + num_sregs_ += 1; + data.starting_mod_s_reg[phi_bb_id] = extra_s_reg; + data.def_phi_blocks_->SetBit(phi_bb_id); + } + } + + // SSA rename for s_reg. + TopologicalSortIterator iter(mir_graph_); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + if (bb->data_flow_info == nullptr || bb->block_type == kEntryBlock) { + continue; + } + BasicBlockId bb_id = bb->id; + if (data.def_phi_blocks_->IsBitSet(bb_id)) { + DCHECK_NE(data.starting_mod_s_reg[bb_id], INVALID_SREG); + } else { + DCHECK_EQ(data.starting_mod_s_reg[bb_id], INVALID_SREG); + if (IsSRegLiveAtStart(bb, v_reg, s_reg)) { + // The earliest predecessor must have been processed already. + BasicBlock* pred_bb = FindTopologicallyEarliestPredecessor(bb); + int32_t mod_s_reg = data.ending_mod_s_reg[pred_bb->id]; + data.starting_mod_s_reg[bb_id] = (mod_s_reg != INVALID_SREG) ? mod_s_reg : s_reg; + } else if (data.ending_mod_s_reg[bb_id] != INVALID_SREG) { + // Start the original defining block with s_reg. + data.starting_mod_s_reg[bb_id] = s_reg; + } + } + if (data.ending_mod_s_reg[bb_id] == INVALID_SREG) { + // If the block doesn't define the modified SSA reg, it propagates the starting type. + data.ending_mod_s_reg[bb_id] = data.starting_mod_s_reg[bb_id]; + } + } + } +} + +void TypeInference::CheckCastData::InitializeCheckCastSRegs(Type* sregs) const { + for (const auto& entry : check_cast_map_) { + DCHECK_LT(static_cast<size_t>(entry.second.modified_s_reg), num_sregs_); + sregs[entry.second.modified_s_reg] = entry.second.type.AsNonNull(); + } +} + +void TypeInference::CheckCastData::MergeCheckCastConflicts(Type* sregs) const { + for (const auto& entry : check_cast_map_) { + DCHECK_LT(static_cast<size_t>(entry.second.modified_s_reg), num_sregs_); + sregs[entry.first->ssa_rep->uses[0]].MergeNonArrayFlags( + sregs[entry.second.modified_s_reg].AsNull()); + } +} + +void TypeInference::CheckCastData::MarkPseudoPhiBlocks(uint64_t* bb_df_attrs) const { + for (auto& entry : split_sreg_data_) { + for (uint32_t bb_id : entry.second.def_phi_blocks_->Indexes()) { + bb_df_attrs[bb_id] |= DF_NULL_TRANSFER_N; + } + } +} + +void TypeInference::CheckCastData::Start(BasicBlock* bb) { + for (auto& entry : split_sreg_data_) { + entry.second.current_mod_s_reg = entry.second.starting_mod_s_reg[bb->id]; + } +} + +bool TypeInference::CheckCastData::ProcessPseudoPhis(BasicBlock* bb, Type* sregs) { + bool changed = false; + for (auto& entry : split_sreg_data_) { + DCHECK_EQ(entry.second.current_mod_s_reg, entry.second.starting_mod_s_reg[bb->id]); + if (entry.second.def_phi_blocks_->IsBitSet(bb->id)) { + int32_t* ending_mod_s_reg = entry.second.ending_mod_s_reg; + Type merged_type = sregs[entry.second.current_mod_s_reg]; + for (BasicBlockId pred_id : bb->predecessors) { + DCHECK_LT(static_cast<size_t>(ending_mod_s_reg[pred_id]), num_sregs_); + merged_type.MergeWeak(sregs[ending_mod_s_reg[pred_id]]); + } + if (UNLIKELY(!merged_type.IsDefined())) { + // This can happen during an initial merge of a loop head if the original def is + // actually an untyped null. (All other definitions are typed using the check-cast.) + } else if (merged_type.Wide()) { + // Ignore the pseudo-phi, just remember that there's a size mismatch. + sregs[entry.second.current_mod_s_reg].MarkSizeConflict(); + } else { + DCHECK(merged_type.Narrow() && merged_type.LowWord() && !merged_type.HighWord()); + // Propagate both down (fully) and up (without the "non-null" flag). + changed |= sregs[entry.second.current_mod_s_reg].Copy(merged_type); + merged_type = merged_type.AsNull(); + for (BasicBlockId pred_id : bb->predecessors) { + DCHECK_LT(static_cast<size_t>(ending_mod_s_reg[pred_id]), num_sregs_); + sregs[ending_mod_s_reg[pred_id]].MergeStrong(merged_type); + } + } + } + } + return changed; +} + +void TypeInference::CheckCastData::ProcessCheckCast(MIR* mir) { + auto mir_it = check_cast_map_.find(mir); + DCHECK(mir_it != check_cast_map_.end()); + auto sreg_it = split_sreg_data_.find(mir->ssa_rep->uses[0]); + DCHECK(sreg_it != split_sreg_data_.end()); + sreg_it->second.current_mod_s_reg = mir_it->second.modified_s_reg; +} + +TypeInference::SplitSRegData* TypeInference::CheckCastData::GetSplitSRegData(int32_t s_reg) { + auto it = split_sreg_data_.find(s_reg); + return (it == split_sreg_data_.end()) ? nullptr : &it->second; +} + +BasicBlock* TypeInference::CheckCastData::FindDefBlock(MIR* check_cast) { + // Find the initial definition of the SSA reg used by the check-cast. + DCHECK_EQ(check_cast->dalvikInsn.opcode, Instruction::CHECK_CAST); + int32_t s_reg = check_cast->ssa_rep->uses[0]; + if (mir_graph_->IsInVReg(s_reg)) { + return mir_graph_->GetEntryBlock(); + } + int v_reg = mir_graph_->SRegToVReg(s_reg); + BasicBlock* bb = mir_graph_->GetBasicBlock(check_cast->bb); + DCHECK(bb != nullptr); + while (true) { + // Find the earliest predecessor in the topological sort order to ensure we don't + // go in a loop. + BasicBlock* pred_bb = FindTopologicallyEarliestPredecessor(bb); + DCHECK(pred_bb != nullptr); + DCHECK(pred_bb->data_flow_info != nullptr); + DCHECK(pred_bb->data_flow_info->vreg_to_ssa_map_exit != nullptr); + if (pred_bb->data_flow_info->vreg_to_ssa_map_exit[v_reg] != s_reg) { + // The s_reg was not valid at the end of pred_bb, so it must have been defined in bb. + return bb; + } + bb = pred_bb; + } +} + +BasicBlock* TypeInference::CheckCastData::FindTopologicallyEarliestPredecessor(BasicBlock* bb) { + DCHECK(!bb->predecessors.empty()); + const auto& indexes = mir_graph_->GetTopologicalSortOrderIndexes(); + DCHECK_LT(bb->id, indexes.size()); + size_t best_idx = indexes[bb->id]; + BasicBlockId best_id = NullBasicBlockId; + for (BasicBlockId pred_id : bb->predecessors) { + DCHECK_LT(pred_id, indexes.size()); + if (best_idx > indexes[pred_id]) { + best_idx = indexes[pred_id]; + best_id = pred_id; + } + } + // There must be at least one predecessor earlier than the bb. + DCHECK_LT(best_idx, indexes[bb->id]); + return mir_graph_->GetBasicBlock(best_id); +} + +bool TypeInference::CheckCastData::IsSRegLiveAtStart(BasicBlock* bb, int v_reg, int32_t s_reg) { + DCHECK_EQ(v_reg, mir_graph_->SRegToVReg(s_reg)); + DCHECK(bb != nullptr); + DCHECK(bb->data_flow_info != nullptr); + DCHECK(bb->data_flow_info->live_in_v != nullptr); + if (!bb->data_flow_info->live_in_v->IsBitSet(v_reg)) { + return false; + } + for (BasicBlockId pred_id : bb->predecessors) { + BasicBlock* pred_bb = mir_graph_->GetBasicBlock(pred_id); + DCHECK(pred_bb != nullptr); + DCHECK(pred_bb->data_flow_info != nullptr); + DCHECK(pred_bb->data_flow_info->vreg_to_ssa_map_exit != nullptr); + if (pred_bb->data_flow_info->vreg_to_ssa_map_exit[v_reg] != s_reg) { + return false; + } + } + return true; +} + +TypeInference::TypeInference(MIRGraph* mir_graph, ScopedArenaAllocator* alloc) + : mir_graph_(mir_graph), + cu_(mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit()), + check_cast_data_(!mir_graph->HasCheckCast() ? nullptr : + InitializeCheckCastData(mir_graph, alloc)), + num_sregs_( + check_cast_data_ != nullptr ? check_cast_data_->NumSRegs() : mir_graph->GetNumSSARegs()), + ifields_(mir_graph->GetIFieldLoweringInfoCount() == 0u ? nullptr : + PrepareIFieldTypes(cu_->dex_file, mir_graph, alloc)), + sfields_(mir_graph->GetSFieldLoweringInfoCount() == 0u ? nullptr : + PrepareSFieldTypes(cu_->dex_file, mir_graph, alloc)), + signatures_(mir_graph->GetMethodLoweringInfoCount() == 0u ? nullptr : + PrepareSignatures(cu_->dex_file, mir_graph, alloc)), + current_method_signature_( + Signature(cu_->dex_file, cu_->method_idx, (cu_->access_flags & kAccStatic) != 0, alloc)), + sregs_(alloc->AllocArray<Type>(num_sregs_, kArenaAllocMisc)), + bb_df_attrs_(alloc->AllocArray<uint64_t>(mir_graph->GetNumBlocks(), kArenaAllocDFInfo)) { + InitializeSRegs(); +} + +bool TypeInference::Apply(BasicBlock* bb) { + bool changed = false; + uint64_t bb_df_attrs = bb_df_attrs_[bb->id]; + if (bb_df_attrs != 0u) { + if (UNLIKELY(check_cast_data_ != nullptr)) { + check_cast_data_->Start(bb); + if (bb_df_attrs & DF_NULL_TRANSFER_N) { + changed |= check_cast_data_->ProcessPseudoPhis(bb, sregs_); + } + } + MIR* mir = bb->first_mir_insn; + MIR* main_mirs_end = ((bb_df_attrs & DF_SAME_TYPE_AB) != 0u) ? bb->last_mir_insn : nullptr; + for (; mir != main_mirs_end && static_cast<int>(mir->dalvikInsn.opcode) == kMirOpPhi; + mir = mir->next) { + // Special-case handling for Phi comes first because we have 2 Phis instead of a wide one. + // At least one input must have been previously processed. Look for the first + // occurrence of a high_word or low_word flag to determine the type. + size_t num_uses = mir->ssa_rep->num_uses; + const int32_t* uses = mir->ssa_rep->uses; + const int32_t* defs = mir->ssa_rep->defs; + DCHECK_EQ(bb->predecessors.size(), num_uses); + Type merged_type = sregs_[defs[0]]; + for (size_t pred_idx = 0; pred_idx != num_uses; ++pred_idx) { + int32_t input_mod_s_reg = PhiInputModifiedSReg(uses[pred_idx], bb, pred_idx); + merged_type.MergeWeak(sregs_[input_mod_s_reg]); + } + if (UNLIKELY(!merged_type.IsDefined())) { + // No change + } else if (merged_type.HighWord()) { + // Ignore the high word phi, just remember if there's a size mismatch. + if (UNLIKELY(merged_type.LowWord())) { + sregs_[defs[0]].MarkSizeConflict(); + } + } else { + // Propagate both down (fully) and up (without the "non-null" flag). + changed |= sregs_[defs[0]].Copy(merged_type); + merged_type = merged_type.AsNull(); + for (size_t pred_idx = 0; pred_idx != num_uses; ++pred_idx) { + int32_t input_mod_s_reg = PhiInputModifiedSReg(uses[pred_idx], bb, pred_idx); + changed |= UpdateSRegFromLowWordType(input_mod_s_reg, merged_type); + } + } + } + + // Propagate types with MOVEs and AGETs, process CHECK_CASTs for modified SSA reg tracking. + for (; mir != main_mirs_end; mir = mir->next) { + uint64_t attrs = MIRGraph::GetDataFlowAttributes(mir); + size_t num_uses = mir->ssa_rep->num_uses; + const int32_t* uses = mir->ssa_rep->uses; + const int32_t* defs = mir->ssa_rep->defs; + + // Special handling for moves. Propagate type both ways. + if ((attrs & DF_IS_MOVE) != 0) { + int32_t used_mod_s_reg = ModifiedSReg(uses[0]); + int32_t defd_mod_s_reg = defs[0]; + + // The "non-null" flag is propagated only downwards from actual definitions and it's + // not initially marked for moves, so used sreg must be marked before defined sreg. + // The only exception is an inlined move where we know the type from the original invoke. + DCHECK(sregs_[used_mod_s_reg].NonNull() || !sregs_[defd_mod_s_reg].NonNull() || + (mir->optimization_flags & MIR_CALLEE) != 0); + changed |= UpdateSRegFromLowWordType(used_mod_s_reg, sregs_[defd_mod_s_reg].AsNull()); + + // The value is the same, so either both registers are null or no register is. + // In any case we can safely propagate the array type down. + changed |= UpdateSRegFromLowWordType(defd_mod_s_reg, sregs_[used_mod_s_reg]); + if (UNLIKELY((attrs & DF_REF_A) == 0 && sregs_[used_mod_s_reg].Ref())) { + // Mark type conflict: move instead of move-object. + sregs_[used_mod_s_reg].MarkTypeConflict(); + } + continue; + } + + // Handle AGET/APUT. + if ((attrs & DF_HAS_RANGE_CHKS) != 0) { + int32_t base_mod_s_reg = ModifiedSReg(uses[num_uses - 2u]); + int32_t mod_s_reg = (attrs & DF_DA) != 0 ? defs[0] : ModifiedSReg(uses[0]); + DCHECK_NE(sregs_[base_mod_s_reg].ArrayDepth(), 0u); + if (!sregs_[base_mod_s_reg].NonNull()) { + // If the base is null, don't propagate anything. All that we could determine + // has already been merged in the previous stage. + } else { + changed |= UpdateSRegFromLowWordType(mod_s_reg, sregs_[base_mod_s_reg].ComponentType()); + Type array_type = Type::ArrayTypeFromComponent(sregs_[mod_s_reg]); + if ((attrs & DF_DA) != 0) { + changed |= sregs_[base_mod_s_reg].MergeStrong(array_type); + } else { + changed |= sregs_[base_mod_s_reg].MergeWeak(array_type); + } + } + if (UNLIKELY((attrs & DF_REF_A) == 0 && sregs_[mod_s_reg].Ref())) { + // Mark type conflict: aget/aput instead of aget/aput-object. + sregs_[mod_s_reg].MarkTypeConflict(); + } + continue; + } + + // Special-case handling for check-cast to advance modified SSA reg. + if (UNLIKELY((attrs & DF_CHK_CAST) != 0)) { + DCHECK(check_cast_data_ != nullptr); + check_cast_data_->ProcessCheckCast(mir); + } + } + + // Propagate types for IF_cc if present. + if (mir != nullptr) { + DCHECK(mir == bb->last_mir_insn); + DCHECK(mir->next == nullptr); + DCHECK_NE(MIRGraph::GetDataFlowAttributes(mir) & DF_SAME_TYPE_AB, 0u); + DCHECK_EQ(mir->ssa_rep->num_uses, 2u); + const int32_t* uses = mir->ssa_rep->uses; + int32_t mod_s_reg0 = ModifiedSReg(uses[0]); + int32_t mod_s_reg1 = ModifiedSReg(uses[1]); + changed |= sregs_[mod_s_reg0].MergeWeak(sregs_[mod_s_reg1].AsNull()); + changed |= sregs_[mod_s_reg1].MergeWeak(sregs_[mod_s_reg0].AsNull()); + } + } + return changed; +} + +void TypeInference::Finish() { + if (UNLIKELY(check_cast_data_ != nullptr)) { + check_cast_data_->MergeCheckCastConflicts(sregs_); + } + + size_t num_sregs = mir_graph_->GetNumSSARegs(); // Without the extra SSA regs. + for (size_t s_reg = 0; s_reg != num_sregs; ++s_reg) { + if (sregs_[s_reg].SizeConflict()) { + /* + * The dex bytecode definition does not explicitly outlaw the definition of the same + * virtual register to be used in both a 32-bit and 64-bit pair context. However, dx + * does not generate this pattern (at least recently). Further, in the next revision of + * dex, we will forbid this. To support the few cases in the wild, detect this pattern + * and punt to the interpreter. + */ + LOG(WARNING) << PrettyMethod(cu_->method_idx, *cu_->dex_file) + << " has size conflict block for sreg " << s_reg + << ", punting to interpreter."; + mir_graph_->SetPuntToInterpreter(true); + return; + } + } + + size_t conflict_s_reg = 0; + bool type_conflict = false; + for (size_t s_reg = 0; s_reg != num_sregs; ++s_reg) { + Type type = sregs_[s_reg]; + RegLocation* loc = &mir_graph_->reg_location_[s_reg]; + loc->wide = type.Wide(); + loc->defined = type.IsDefined(); + loc->fp = type.Fp(); + loc->core = type.Core(); + loc->ref = type.Ref(); + loc->high_word = type.HighWord(); + if (UNLIKELY(type.TypeConflict())) { + type_conflict = true; + conflict_s_reg = s_reg; + } + } + + if (type_conflict) { + /* + * We don't normally expect to see a Dalvik register definition used both as a + * floating point and core value, though technically it could happen with constants. + * Until we have proper typing, detect this situation and disable register promotion + * (which relies on the distinction between core a fp usages). + */ + LOG(WARNING) << PrettyMethod(cu_->method_idx, *cu_->dex_file) + << " has type conflict block for sreg " << conflict_s_reg + << ", disabling register promotion."; + cu_->disable_opt |= (1 << kPromoteRegs); + } +} + +TypeInference::Type TypeInference::FieldType(const DexFile* dex_file, uint32_t field_idx) { + uint32_t type_idx = dex_file->GetFieldId(field_idx).type_idx_; + Type result = Type::DexType(dex_file, type_idx); + return result; +} + +TypeInference::Type* TypeInference::PrepareIFieldTypes(const DexFile* dex_file, + MIRGraph* mir_graph, + ScopedArenaAllocator* alloc) { + size_t count = mir_graph->GetIFieldLoweringInfoCount(); + Type* ifields = alloc->AllocArray<Type>(count, kArenaAllocDFInfo); + for (uint32_t i = 0u; i != count; ++i) { + // NOTE: Quickened field accesses have invalid FieldIndex() but they are always resolved. + const MirFieldInfo& info = mir_graph->GetIFieldLoweringInfo(i); + const DexFile* current_dex_file = info.IsResolved() ? info.DeclaringDexFile() : dex_file; + uint32_t field_idx = info.IsResolved() ? info.DeclaringFieldIndex() : info.FieldIndex(); + ifields[i] = FieldType(current_dex_file, field_idx); + DCHECK_EQ(info.MemAccessType() == kDexMemAccessWide, ifields[i].Wide()); + DCHECK_EQ(info.MemAccessType() == kDexMemAccessObject, ifields[i].Ref()); + } + return ifields; +} + +TypeInference::Type* TypeInference::PrepareSFieldTypes(const DexFile* dex_file, + MIRGraph* mir_graph, + ScopedArenaAllocator* alloc) { + size_t count = mir_graph->GetSFieldLoweringInfoCount(); + Type* sfields = alloc->AllocArray<Type>(count, kArenaAllocDFInfo); + for (uint32_t i = 0u; i != count; ++i) { + // FieldIndex() is always valid for static fields (no quickened instructions). + sfields[i] = FieldType(dex_file, mir_graph->GetSFieldLoweringInfo(i).FieldIndex()); + } + return sfields; +} + +TypeInference::MethodSignature TypeInference::Signature(const DexFile* dex_file, + uint32_t method_idx, + bool is_static, + ScopedArenaAllocator* alloc) { + const DexFile::MethodId& method_id = dex_file->GetMethodId(method_idx); + const DexFile::ProtoId& proto_id = dex_file->GetMethodPrototype(method_id); + Type return_type = Type::DexType(dex_file, proto_id.return_type_idx_); + const DexFile::TypeList* type_list = dex_file->GetProtoParameters(proto_id); + size_t this_size = (is_static ? 0u : 1u); + size_t param_size = ((type_list != nullptr) ? type_list->Size() : 0u); + size_t size = this_size + param_size; + Type* param_types = (size != 0u) ? alloc->AllocArray<Type>(size, kArenaAllocDFInfo) : nullptr; + if (!is_static) { + param_types[0] = Type::DexType(dex_file, method_id.class_idx_); + } + for (size_t i = 0; i != param_size; ++i) { + uint32_t type_idx = type_list->GetTypeItem(i).type_idx_; + param_types[this_size + i] = Type::DexType(dex_file, type_idx); + } + return MethodSignature{ return_type, size, param_types }; // NOLINT +} + +TypeInference::MethodSignature* TypeInference::PrepareSignatures(const DexFile* dex_file, + MIRGraph* mir_graph, + ScopedArenaAllocator* alloc) { + size_t count = mir_graph->GetMethodLoweringInfoCount(); + MethodSignature* signatures = alloc->AllocArray<MethodSignature>(count, kArenaAllocDFInfo); + for (uint32_t i = 0u; i != count; ++i) { + // NOTE: Quickened invokes have invalid MethodIndex() but they are always resolved. + const MirMethodInfo& info = mir_graph->GetMethodLoweringInfo(i); + uint32_t method_idx = info.IsResolved() ? info.DeclaringMethodIndex() : info.MethodIndex(); + const DexFile* current_dex_file = info.IsResolved() ? info.DeclaringDexFile() : dex_file; + signatures[i] = Signature(current_dex_file, method_idx, info.IsStatic(), alloc); + } + return signatures; +} + +TypeInference::CheckCastData* TypeInference::InitializeCheckCastData(MIRGraph* mir_graph, + ScopedArenaAllocator* alloc) { + if (!mir_graph->HasCheckCast()) { + return nullptr; + } + + CheckCastData* data = nullptr; + const DexFile* dex_file = nullptr; + PreOrderDfsIterator iter(mir_graph); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + if (mir->dalvikInsn.opcode == Instruction::CHECK_CAST) { + if (data == nullptr) { + data = new (alloc) CheckCastData(mir_graph, alloc); + dex_file = mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit()->dex_file; + } + Type type = Type::DexType(dex_file, mir->dalvikInsn.vB); + data->AddCheckCast(mir, type); + } + } + } + if (data != nullptr) { + data->AddPseudoPhis(); + } + return data; +} + +void TypeInference::InitializeSRegs() { + std::fill_n(sregs_, num_sregs_, Type::Unknown()); + + /* Treat ArtMethod* as a normal reference */ + sregs_[mir_graph_->GetMethodSReg()] = Type::NonArrayRefType(); + + // Initialize parameter SSA regs at method entry. + int32_t entry_param_s_reg = mir_graph_->GetFirstInVR(); + for (size_t i = 0, size = current_method_signature_.num_params; i != size; ++i) { + Type param_type = current_method_signature_.param_types[i].AsNonNull(); + sregs_[entry_param_s_reg] = param_type; + entry_param_s_reg += param_type.Wide() ? 2 : 1; + } + DCHECK_EQ(static_cast<uint32_t>(entry_param_s_reg), + mir_graph_->GetFirstInVR() + mir_graph_->GetNumOfInVRs()); + + // Initialize check-cast types. + if (UNLIKELY(check_cast_data_ != nullptr)) { + check_cast_data_->InitializeCheckCastSRegs(sregs_); + } + + // Initialize well-known SSA register definition types. Merge inferred types + // upwards where a single merge is enough (INVOKE arguments and return type, + // RETURN type, IPUT/SPUT source type). + // NOTE: Using topological sort order to make sure the definition comes before + // any upward merging. This allows simple assignment of the defined types + // instead of MergeStrong(). + TopologicalSortIterator iter(mir_graph_); + for (BasicBlock* bb = iter.Next(); bb != nullptr; bb = iter.Next()) { + uint64_t bb_df_attrs = 0u; + if (UNLIKELY(check_cast_data_ != nullptr)) { + check_cast_data_->Start(bb); + } + // Ignore pseudo-phis, we're not setting types for SSA regs that depend on them in this pass. + for (MIR* mir = bb->first_mir_insn; mir != nullptr; mir = mir->next) { + uint64_t attrs = MIRGraph::GetDataFlowAttributes(mir); + bb_df_attrs |= attrs; + + const uint32_t num_uses = mir->ssa_rep->num_uses; + const int32_t* uses = mir->ssa_rep->uses; + const int32_t* defs = mir->ssa_rep->defs; + + uint16_t opcode = mir->dalvikInsn.opcode; + switch (opcode) { + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST: + case Instruction::CONST_HIGH16: + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + case Instruction::CONST_WIDE: + case Instruction::CONST_WIDE_HIGH16: + case Instruction::MOVE: + case Instruction::MOVE_FROM16: + case Instruction::MOVE_16: + case Instruction::MOVE_WIDE: + case Instruction::MOVE_WIDE_FROM16: + case Instruction::MOVE_WIDE_16: + case Instruction::MOVE_OBJECT: + case Instruction::MOVE_OBJECT_FROM16: + case Instruction::MOVE_OBJECT_16: + if ((mir->optimization_flags & MIR_CALLEE) != 0) { + // Inlined const/move keeps method_lowering_info for type inference. + DCHECK_LT(mir->meta.method_lowering_info, mir_graph_->GetMethodLoweringInfoCount()); + Type return_type = signatures_[mir->meta.method_lowering_info].return_type; + DCHECK(return_type.IsDefined()); // Method return type can't be void. + sregs_[defs[0]] = return_type.AsNonNull(); + if (return_type.Wide()) { + DCHECK_EQ(defs[0] + 1, defs[1]); + sregs_[defs[1]] = return_type.ToHighWord(); + } + break; + } + FALLTHROUGH_INTENDED; + case kMirOpPhi: + // These cannot be determined in this simple pass and will be processed later. + break; + + case Instruction::MOVE_RESULT: + case Instruction::MOVE_RESULT_WIDE: + case Instruction::MOVE_RESULT_OBJECT: + // Nothing to do, handled with invoke-* or filled-new-array/-range. + break; + case Instruction::MOVE_EXCEPTION: + // NOTE: We can never catch an array. + sregs_[defs[0]] = Type::NonArrayRefType().AsNonNull(); + break; + case Instruction::CONST_STRING: + case Instruction::CONST_STRING_JUMBO: + sregs_[defs[0]] = Type::NonArrayRefType().AsNonNull(); + break; + case Instruction::CONST_CLASS: + sregs_[defs[0]] = Type::NonArrayRefType().AsNonNull(); + break; + case Instruction::CHECK_CAST: + DCHECK(check_cast_data_ != nullptr); + check_cast_data_->ProcessCheckCast(mir); + break; + case Instruction::ARRAY_LENGTH: + sregs_[ModifiedSReg(uses[0])].MergeStrong(Type::UnknownArrayType()); + break; + case Instruction::NEW_INSTANCE: + sregs_[defs[0]] = Type::DexType(cu_->dex_file, mir->dalvikInsn.vB).AsNonNull(); + DCHECK(sregs_[defs[0]].Ref()); + DCHECK_EQ(sregs_[defs[0]].ArrayDepth(), 0u); + break; + case Instruction::NEW_ARRAY: + sregs_[defs[0]] = Type::DexType(cu_->dex_file, mir->dalvikInsn.vC).AsNonNull(); + DCHECK(sregs_[defs[0]].Ref()); + DCHECK_NE(sregs_[defs[0]].ArrayDepth(), 0u); + break; + case Instruction::FILLED_NEW_ARRAY: + case Instruction::FILLED_NEW_ARRAY_RANGE: { + Type array_type = Type::DexType(cu_->dex_file, mir->dalvikInsn.vB); + array_type.CheckPureRef(); // Previously checked by the method verifier. + DCHECK_NE(array_type.ArrayDepth(), 0u); + Type component_type = array_type.ComponentType(); + DCHECK(!component_type.Wide()); + MIR* move_result_mir = mir_graph_->FindMoveResult(bb, mir); + if (move_result_mir != nullptr) { + DCHECK_EQ(move_result_mir->dalvikInsn.opcode, Instruction::MOVE_RESULT_OBJECT); + sregs_[move_result_mir->ssa_rep->defs[0]] = array_type.AsNonNull(); + } + DCHECK_EQ(num_uses, mir->dalvikInsn.vA); + for (size_t next = 0u; next != num_uses; ++next) { + int32_t input_mod_s_reg = ModifiedSReg(uses[next]); + sregs_[input_mod_s_reg].MergeStrong(component_type); + } + break; + } + case Instruction::INVOKE_VIRTUAL: + case Instruction::INVOKE_SUPER: + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_INTERFACE: + case Instruction::INVOKE_VIRTUAL_RANGE: + case Instruction::INVOKE_SUPER_RANGE: + case Instruction::INVOKE_DIRECT_RANGE: + case Instruction::INVOKE_STATIC_RANGE: + case Instruction::INVOKE_INTERFACE_RANGE: + case Instruction::INVOKE_VIRTUAL_QUICK: + case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: { + const MethodSignature* signature = &signatures_[mir->meta.method_lowering_info]; + MIR* move_result_mir = mir_graph_->FindMoveResult(bb, mir); + if (move_result_mir != nullptr) { + Type return_type = signature->return_type; + sregs_[move_result_mir->ssa_rep->defs[0]] = return_type.AsNonNull(); + if (return_type.Wide()) { + DCHECK_EQ(move_result_mir->ssa_rep->defs[0] + 1, move_result_mir->ssa_rep->defs[1]); + sregs_[move_result_mir->ssa_rep->defs[1]] = return_type.ToHighWord(); + } + } + size_t next = 0u; + for (size_t i = 0, size = signature->num_params; i != size; ++i) { + Type param_type = signature->param_types[i]; + int32_t param_s_reg = ModifiedSReg(uses[next]); + DCHECK(!param_type.Wide() || uses[next] + 1 == uses[next + 1]); + UpdateSRegFromLowWordType(param_s_reg, param_type); + next += param_type.Wide() ? 2 : 1; + } + DCHECK_EQ(next, num_uses); + DCHECK_EQ(next, mir->dalvikInsn.vA); + break; + } + + case Instruction::RETURN_WIDE: + DCHECK(current_method_signature_.return_type.Wide()); + DCHECK_EQ(uses[0] + 1, uses[1]); + DCHECK_EQ(ModifiedSReg(uses[0]), uses[0]); + FALLTHROUGH_INTENDED; + case Instruction::RETURN: + case Instruction::RETURN_OBJECT: { + int32_t mod_s_reg = ModifiedSReg(uses[0]); + UpdateSRegFromLowWordType(mod_s_reg, current_method_signature_.return_type); + break; + } + + // NOTE: For AGET/APUT we set only the array type. The operand type is set + // below based on the data flow attributes. + case Instruction::AGET: + case Instruction::APUT: + sregs_[ModifiedSReg(uses[num_uses - 2u])].MergeStrong(Type::NarrowArrayType()); + break; + case Instruction::AGET_WIDE: + case Instruction::APUT_WIDE: + sregs_[ModifiedSReg(uses[num_uses - 2u])].MergeStrong(Type::WideArrayType()); + break; + case Instruction::AGET_OBJECT: + sregs_[defs[0]] = sregs_[defs[0]].AsNonNull(); + FALLTHROUGH_INTENDED; + case Instruction::APUT_OBJECT: + sregs_[ModifiedSReg(uses[num_uses - 2u])].MergeStrong(Type::ObjectArrayType()); + break; + case Instruction::AGET_BOOLEAN: + case Instruction::APUT_BOOLEAN: + case Instruction::AGET_BYTE: + case Instruction::APUT_BYTE: + case Instruction::AGET_CHAR: + case Instruction::APUT_CHAR: + case Instruction::AGET_SHORT: + case Instruction::APUT_SHORT: + sregs_[ModifiedSReg(uses[num_uses - 2u])].MergeStrong(Type::NarrowCoreArrayType()); + break; + + case Instruction::IGET_WIDE: + case Instruction::IGET_WIDE_QUICK: + DCHECK_EQ(defs[0] + 1, defs[1]); + DCHECK_LT(mir->meta.ifield_lowering_info, mir_graph_->GetIFieldLoweringInfoCount()); + sregs_[defs[1]] = ifields_[mir->meta.ifield_lowering_info].ToHighWord(); + FALLTHROUGH_INTENDED; + case Instruction::IGET: + case Instruction::IGET_OBJECT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: + case Instruction::IGET_QUICK: + case Instruction::IGET_OBJECT_QUICK: + case Instruction::IGET_BOOLEAN_QUICK: + case Instruction::IGET_BYTE_QUICK: + case Instruction::IGET_CHAR_QUICK: + case Instruction::IGET_SHORT_QUICK: + DCHECK_LT(mir->meta.ifield_lowering_info, mir_graph_->GetIFieldLoweringInfoCount()); + sregs_[defs[0]] = ifields_[mir->meta.ifield_lowering_info].AsNonNull(); + break; + case Instruction::IPUT_WIDE: + case Instruction::IPUT_WIDE_QUICK: + DCHECK_EQ(uses[0] + 1, uses[1]); + FALLTHROUGH_INTENDED; + case Instruction::IPUT: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: + case Instruction::IPUT_QUICK: + case Instruction::IPUT_OBJECT_QUICK: + case Instruction::IPUT_BOOLEAN_QUICK: + case Instruction::IPUT_BYTE_QUICK: + case Instruction::IPUT_CHAR_QUICK: + case Instruction::IPUT_SHORT_QUICK: + DCHECK_LT(mir->meta.ifield_lowering_info, mir_graph_->GetIFieldLoweringInfoCount()); + UpdateSRegFromLowWordType(ModifiedSReg(uses[0]), + ifields_[mir->meta.ifield_lowering_info]); + break; + case Instruction::SGET_WIDE: + DCHECK_EQ(defs[0] + 1, defs[1]); + DCHECK_LT(mir->meta.sfield_lowering_info, mir_graph_->GetSFieldLoweringInfoCount()); + sregs_[defs[1]] = sfields_[mir->meta.sfield_lowering_info].ToHighWord(); + FALLTHROUGH_INTENDED; + case Instruction::SGET: + case Instruction::SGET_OBJECT: + case Instruction::SGET_BOOLEAN: + case Instruction::SGET_BYTE: + case Instruction::SGET_CHAR: + case Instruction::SGET_SHORT: + DCHECK_LT(mir->meta.sfield_lowering_info, mir_graph_->GetSFieldLoweringInfoCount()); + sregs_[defs[0]] = sfields_[mir->meta.sfield_lowering_info].AsNonNull(); + break; + case Instruction::SPUT_WIDE: + DCHECK_EQ(uses[0] + 1, uses[1]); + FALLTHROUGH_INTENDED; + case Instruction::SPUT: + case Instruction::SPUT_OBJECT: + case Instruction::SPUT_BOOLEAN: + case Instruction::SPUT_BYTE: + case Instruction::SPUT_CHAR: + case Instruction::SPUT_SHORT: + DCHECK_LT(mir->meta.sfield_lowering_info, mir_graph_->GetSFieldLoweringInfoCount()); + UpdateSRegFromLowWordType(ModifiedSReg(uses[0]), + sfields_[mir->meta.sfield_lowering_info]); + break; + + default: + // No invokes or reference definitions here. + DCHECK_EQ(attrs & (DF_FORMAT_35C | DF_FORMAT_3RC), 0u); + DCHECK_NE(attrs & (DF_DA | DF_REF_A), (DF_DA | DF_REF_A)); + break; + } + + if ((attrs & DF_NULL_TRANSFER_N) != 0) { + // Don't process Phis at this stage. + continue; + } + + // Handle defs + if (attrs & DF_DA) { + int32_t s_reg = defs[0]; + sregs_[s_reg].SetLowWord(); + if (attrs & DF_FP_A) { + sregs_[s_reg].SetFp(); + } + if (attrs & DF_CORE_A) { + sregs_[s_reg].SetCore(); + } + if (attrs & DF_REF_A) { + sregs_[s_reg].SetRef(); + } + if (attrs & DF_A_WIDE) { + sregs_[s_reg].SetWide(); + DCHECK_EQ(s_reg + 1, ModifiedSReg(defs[1])); + sregs_[s_reg + 1].MergeHighWord(sregs_[s_reg]); + } else { + sregs_[s_reg].SetNarrow(); + } + } + + // Handles uses + size_t next = 0; + #define PROCESS(REG) \ + if (attrs & DF_U##REG) { \ + int32_t mod_s_reg = ModifiedSReg(uses[next]); \ + sregs_[mod_s_reg].SetLowWord(); \ + if (attrs & DF_FP_##REG) { \ + sregs_[mod_s_reg].SetFp(); \ + } \ + if (attrs & DF_CORE_##REG) { \ + sregs_[mod_s_reg].SetCore(); \ + } \ + if (attrs & DF_REF_##REG) { \ + sregs_[mod_s_reg].SetRef(); \ + } \ + if (attrs & DF_##REG##_WIDE) { \ + sregs_[mod_s_reg].SetWide(); \ + DCHECK_EQ(mod_s_reg + 1, ModifiedSReg(uses[next + 1])); \ + sregs_[mod_s_reg + 1].SetWide(); \ + sregs_[mod_s_reg + 1].MergeHighWord(sregs_[mod_s_reg]); \ + next += 2; \ + } else { \ + sregs_[mod_s_reg].SetNarrow(); \ + next++; \ + } \ + } + PROCESS(A) + PROCESS(B) + PROCESS(C) + #undef PROCESS + DCHECK(next == mir->ssa_rep->num_uses || (attrs & (DF_FORMAT_35C | DF_FORMAT_3RC)) != 0); + } + // Record relevant attributes. + bb_df_attrs_[bb->id] = bb_df_attrs & + (DF_NULL_TRANSFER_N | DF_CHK_CAST | DF_IS_MOVE | DF_HAS_RANGE_CHKS | DF_SAME_TYPE_AB); + } + + if (UNLIKELY(check_cast_data_ != nullptr)) { + check_cast_data_->MarkPseudoPhiBlocks(bb_df_attrs_); + } +} + +int32_t TypeInference::ModifiedSReg(int32_t s_reg) { + if (UNLIKELY(check_cast_data_ != nullptr)) { + SplitSRegData* split_data = check_cast_data_->GetSplitSRegData(s_reg); + if (UNLIKELY(split_data != nullptr)) { + DCHECK_NE(split_data->current_mod_s_reg, INVALID_SREG); + return split_data->current_mod_s_reg; + } + } + return s_reg; +} + +int32_t TypeInference::PhiInputModifiedSReg(int32_t s_reg, BasicBlock* bb, size_t pred_idx) { + DCHECK_LT(pred_idx, bb->predecessors.size()); + if (UNLIKELY(check_cast_data_ != nullptr)) { + SplitSRegData* split_data = check_cast_data_->GetSplitSRegData(s_reg); + if (UNLIKELY(split_data != nullptr)) { + return split_data->ending_mod_s_reg[bb->predecessors[pred_idx]]; + } + } + return s_reg; +} + +bool TypeInference::UpdateSRegFromLowWordType(int32_t mod_s_reg, Type low_word_type) { + DCHECK(low_word_type.LowWord()); + bool changed = sregs_[mod_s_reg].MergeStrong(low_word_type); + if (!sregs_[mod_s_reg].Narrow()) { // Wide without conflict with narrow. + DCHECK(!low_word_type.Narrow()); + DCHECK_LT(mod_s_reg, mir_graph_->GetNumSSARegs()); // Original SSA reg. + changed |= sregs_[mod_s_reg + 1].MergeHighWord(sregs_[mod_s_reg]); + } + return changed; +} + +} // namespace art diff --git a/compiler/dex/type_inference.h b/compiler/dex/type_inference.h new file mode 100644 index 0000000000..c9b29bf7aa --- /dev/null +++ b/compiler/dex/type_inference.h @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ART_COMPILER_DEX_TYPE_INFERENCE_H_ +#define ART_COMPILER_DEX_TYPE_INFERENCE_H_ + +#include "base/logging.h" +#include "base/arena_object.h" +#include "base/scoped_arena_containers.h" + +namespace art { + +class ArenaBitVector; +class BasicBlock; +struct CompilationUnit; +class DexFile; +class MirFieldInfo; +class MirMethodInfo; +class MIR; +class MIRGraph; + +/** + * @brief Determine the type of SSA registers. + * + * @details + * Because Dalvik's bytecode is not fully typed, we have to do some work to figure + * out the sreg type. For some operations it is clear based on the opcode (i.e. + * ADD_FLOAT v0, v1, v2), but for others (MOVE), we may never know the "real" type. + * + * We perform the type inference operation in two phases: + * 1. First, we make one pass over all insns in the topological sort order and + * extract known type information from all insns for their defs and uses. + * 2. Then we repeatedly go through the graph to process insns that can propagate + * types from inputs to outputs and vice versa. These insns are just the MOVEs, + * AGET/APUTs, IF_ccs and Phis (including pseudo-Phis, see below). + * + * Since the main purpose is to determine the basic FP/core/reference type, we don't + * need to record the precise reference type, we only record the array type to determine + * the result types of agets and source type of aputs. + * + * One complication is the check-cast instruction that effectively defines a new + * virtual register that has a different type than the original sreg. We need to + * track these virtual sregs and insert pseudo-phis where they merge. + * + * Another problems is with null references. The same zero constant can be used + * as differently typed null and moved around with move-object which would normally + * be an ill-formed assignment. So we need to keep track of values that can be null + * and values that cannot. + * + * Note that it's possible to have the same sreg show multiple defined types because dx + * treats constants as untyped bit patterns. We disable register promotion in that case. + */ +class TypeInference : public DeletableArenaObject<kArenaAllocMisc> { + public: + TypeInference(MIRGraph* mir_graph, ScopedArenaAllocator* alloc); + + bool Apply(BasicBlock* bb); + void Finish(); + + private: + struct Type { + static Type Unknown() { + return Type(0u); + } + + static Type NonArrayRefType() { + return Type(kFlagLowWord | kFlagNarrow | kFlagRef); + } + + static Type ObjectArrayType() { + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | + (1u << kBitArrayDepthStart) | kFlagArrayNarrow | kFlagArrayRef); + } + + static Type WideArrayType() { + // Core or FP unknown. + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | + (1u << kBitArrayDepthStart) | kFlagArrayWide); + } + + static Type NarrowArrayType() { + // Core or FP unknown. + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | + (1u << kBitArrayDepthStart) | kFlagArrayNarrow); + } + + static Type NarrowCoreArrayType() { + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | + (1u << kBitArrayDepthStart) | kFlagArrayNarrow | kFlagArrayCore); + } + + static Type UnknownArrayType() { + return Type(kFlagNarrow | kFlagRef | kFlagLowWord | (1u << kBitArrayDepthStart)); + } + + static Type ArrayType(uint32_t array_depth, Type nested_type); + static Type ArrayTypeFromComponent(Type component_type); + static Type ShortyType(char shorty); + static Type DexType(const DexFile* dex_file, uint32_t type_idx); + + bool IsDefined() { + return raw_bits_ != 0u; + } + + bool SizeConflict() const { + // NOTE: Ignore array element conflicts that don't propagate to direct conflicts. + return (Wide() && Narrow()) || (HighWord() && LowWord()); + } + + bool TypeConflict() const { + // NOTE: Ignore array element conflicts that don't propagate to direct conflicts. + return (raw_bits_ & kMaskType) != 0u && !IsPowerOfTwo(raw_bits_ & kMaskType); // 2+ bits. + } + + void MarkSizeConflict() { + SetBits(kFlagLowWord | kFlagHighWord); + } + + void MarkTypeConflict() { + // Mark all three type bits so that merging any other type bits will not change this type. + SetBits(kFlagFp | kFlagCore | kFlagRef); + } + + void CheckPureRef() const { + DCHECK_EQ(raw_bits_ & (kMaskWideAndType | kMaskWord), kFlagNarrow | kFlagRef | kFlagLowWord); + } + + // If reference, don't treat as possible null and require precise type. + // + // References without this flag are allowed to have a type conflict and their + // type will not be propagated down. However, for simplicity we allow propagation + // of other flags up as it will affect only other null references; should those + // references be marked non-null later, we would have to do it anyway. + // NOTE: This is a negative "non-null" flag rather then a positive "is-null" + // to simplify merging together with other non-array flags. + bool NonNull() const { + return IsBitSet(kFlagNonNull); + } + + bool Wide() const { + return IsBitSet(kFlagWide); + } + + bool Narrow() const { + return IsBitSet(kFlagNarrow); + } + + bool Fp() const { + return IsBitSet(kFlagFp); + } + + bool Core() const { + return IsBitSet(kFlagCore); + } + + bool Ref() const { + return IsBitSet(kFlagRef); + } + + bool LowWord() const { + return IsBitSet(kFlagLowWord); + } + + bool HighWord() const { + return IsBitSet(kFlagHighWord); + } + + uint32_t ArrayDepth() const { + return raw_bits_ >> kBitArrayDepthStart; + } + + Type NestedType() const { + DCHECK_NE(ArrayDepth(), 0u); + return Type(kFlagLowWord | ((raw_bits_ & kMaskArrayWideAndType) >> kArrayTypeShift)); + } + + Type ComponentType() const { + DCHECK_NE(ArrayDepth(), 0u); + Type temp(raw_bits_ - (1u << kBitArrayDepthStart)); // array_depth - 1u; + return (temp.ArrayDepth() != 0u) ? temp.AsNull() : NestedType(); + } + + void SetWide() { + SetBits(kFlagWide); + } + + void SetNarrow() { + SetBits(kFlagNarrow); + } + + void SetFp() { + SetBits(kFlagFp); + } + + void SetCore() { + SetBits(kFlagCore); + } + + void SetRef() { + SetBits(kFlagRef); + } + + void SetLowWord() { + SetBits(kFlagLowWord); + } + + void SetHighWord() { + SetBits(kFlagHighWord); + } + + Type ToHighWord() const { + DCHECK_EQ(raw_bits_ & (kMaskWide | kMaskWord), kFlagWide | kFlagLowWord); + return Type(raw_bits_ ^ (kFlagLowWord | kFlagHighWord)); + } + + bool MergeHighWord(Type low_word_type) { + // NOTE: low_word_type may be also Narrow() or HighWord(). + DCHECK(low_word_type.Wide() && low_word_type.LowWord()); + return MergeBits(Type(low_word_type.raw_bits_ | kFlagHighWord), + kMaskWideAndType | kFlagHighWord); + } + + bool Copy(Type type) { + if (raw_bits_ != type.raw_bits_) { + raw_bits_ = type.raw_bits_; + return true; + } + return false; + } + + // Merge non-array flags. + bool MergeNonArrayFlags(Type src_type) { + return MergeBits(src_type, kMaskNonArray); + } + + // Merge array flags for conflict. + bool MergeArrayConflict(Type src_type); + + // Merge all flags. + bool MergeStrong(Type src_type); + + // Merge all flags. + bool MergeWeak(Type src_type); + + // Get the same type but mark that it should not be treated as null. + Type AsNonNull() const { + return Type(raw_bits_ | kFlagNonNull); + } + + // Get the same type but mark that it can be treated as null. + Type AsNull() const { + return Type(raw_bits_ & ~kFlagNonNull); + } + + private: + enum FlagBits { + kBitNonNull = 0, + kBitWide, + kBitNarrow, + kBitFp, + kBitCore, + kBitRef, + kBitLowWord, + kBitHighWord, + kBitArrayWide, + kBitArrayNarrow, + kBitArrayFp, + kBitArrayCore, + kBitArrayRef, + kBitArrayDepthStart, + }; + static constexpr size_t kArrayDepthBits = sizeof(uint32_t) * 8u - kBitArrayDepthStart; + + static constexpr uint32_t kFlagNonNull = 1u << kBitNonNull; + static constexpr uint32_t kFlagWide = 1u << kBitWide; + static constexpr uint32_t kFlagNarrow = 1u << kBitNarrow; + static constexpr uint32_t kFlagFp = 1u << kBitFp; + static constexpr uint32_t kFlagCore = 1u << kBitCore; + static constexpr uint32_t kFlagRef = 1u << kBitRef; + static constexpr uint32_t kFlagLowWord = 1u << kBitLowWord; + static constexpr uint32_t kFlagHighWord = 1u << kBitHighWord; + static constexpr uint32_t kFlagArrayWide = 1u << kBitArrayWide; + static constexpr uint32_t kFlagArrayNarrow = 1u << kBitArrayNarrow; + static constexpr uint32_t kFlagArrayFp = 1u << kBitArrayFp; + static constexpr uint32_t kFlagArrayCore = 1u << kBitArrayCore; + static constexpr uint32_t kFlagArrayRef = 1u << kBitArrayRef; + + static constexpr uint32_t kMaskWide = kFlagWide | kFlagNarrow; + static constexpr uint32_t kMaskType = kFlagFp | kFlagCore | kFlagRef; + static constexpr uint32_t kMaskWord = kFlagLowWord | kFlagHighWord; + static constexpr uint32_t kMaskArrayWide = kFlagArrayWide | kFlagArrayNarrow; + static constexpr uint32_t kMaskArrayType = kFlagArrayFp | kFlagArrayCore | kFlagArrayRef; + static constexpr uint32_t kMaskWideAndType = kMaskWide | kMaskType; + static constexpr uint32_t kMaskArrayWideAndType = kMaskArrayWide | kMaskArrayType; + + static constexpr size_t kArrayTypeShift = kBitArrayWide - kBitWide; + static_assert(kArrayTypeShift == kBitArrayNarrow - kBitNarrow, "shift mismatch"); + static_assert(kArrayTypeShift == kBitArrayFp - kBitFp, "shift mismatch"); + static_assert(kArrayTypeShift == kBitArrayCore - kBitCore, "shift mismatch"); + static_assert(kArrayTypeShift == kBitArrayRef - kBitRef, "shift mismatch"); + static_assert((kMaskWide << kArrayTypeShift) == kMaskArrayWide, "shift mismatch"); + static_assert((kMaskType << kArrayTypeShift) == kMaskArrayType, "shift mismatch"); + static_assert((kMaskWideAndType << kArrayTypeShift) == kMaskArrayWideAndType, "shift mismatch"); + + static constexpr uint32_t kMaskArrayDepth = static_cast<uint32_t>(-1) << kBitArrayDepthStart; + static constexpr uint32_t kMaskNonArray = ~(kMaskArrayWideAndType | kMaskArrayDepth); + + // The maximum representable array depth. If we exceed the maximum (which can happen + // only with an absurd nested array type in a dex file which would presumably cause + // OOM while being resolved), we can report false conflicts. + static constexpr uint32_t kMaxArrayDepth = static_cast<uint32_t>(-1) >> kBitArrayDepthStart; + + explicit Type(uint32_t raw_bits) : raw_bits_(raw_bits) { } + + bool IsBitSet(uint32_t flag) const { + return (raw_bits_ & flag) != 0u; + } + + void SetBits(uint32_t flags) { + raw_bits_ |= flags; + } + + bool MergeBits(Type src_type, uint32_t mask) { + uint32_t new_bits = raw_bits_ | (src_type.raw_bits_ & mask); + if (new_bits != raw_bits_) { + raw_bits_ = new_bits; + return true; + } + return false; + } + + uint32_t raw_bits_; + }; + + struct MethodSignature { + Type return_type; + size_t num_params; + Type* param_types; + }; + + struct SplitSRegData { + int32_t current_mod_s_reg; + int32_t* starting_mod_s_reg; // Indexed by BasicBlock::id. + int32_t* ending_mod_s_reg; // Indexed by BasicBlock::id. + + // NOTE: Before AddPseudoPhis(), def_phi_blocks_ marks the blocks + // with check-casts and the block with the original SSA reg. + // After AddPseudoPhis(), it marks blocks with pseudo-phis. + ArenaBitVector* def_phi_blocks_; // Indexed by BasicBlock::id. + }; + + class CheckCastData : public DeletableArenaObject<kArenaAllocMisc> { + public: + CheckCastData(MIRGraph* mir_graph, ScopedArenaAllocator* alloc); + + size_t NumSRegs() const { + return num_sregs_; + } + + void AddCheckCast(MIR* check_cast, Type type); + void AddPseudoPhis(); + void InitializeCheckCastSRegs(Type* sregs) const; + void MergeCheckCastConflicts(Type* sregs) const; + void MarkPseudoPhiBlocks(uint64_t* bb_df_attrs) const; + + void Start(BasicBlock* bb); + bool ProcessPseudoPhis(BasicBlock* bb, Type* sregs); + void ProcessCheckCast(MIR* mir); + + SplitSRegData* GetSplitSRegData(int32_t s_reg); + + private: + BasicBlock* FindDefBlock(MIR* check_cast); + BasicBlock* FindTopologicallyEarliestPredecessor(BasicBlock* bb); + bool IsSRegLiveAtStart(BasicBlock* bb, int v_reg, int32_t s_reg); + + MIRGraph* const mir_graph_; + ScopedArenaAllocator* const alloc_; + const size_t num_blocks_; + size_t num_sregs_; + + // Map check-cast mir to special sreg and type. + struct CheckCastMapValue { + int32_t modified_s_reg; + Type type; + }; + ScopedArenaSafeMap<MIR*, CheckCastMapValue> check_cast_map_; + ScopedArenaSafeMap<int32_t, SplitSRegData> split_sreg_data_; + }; + + static Type FieldType(const DexFile* dex_file, uint32_t field_idx); + static Type* PrepareIFieldTypes(const DexFile* dex_file, MIRGraph* mir_graph, + ScopedArenaAllocator* alloc); + static Type* PrepareSFieldTypes(const DexFile* dex_file, MIRGraph* mir_graph, + ScopedArenaAllocator* alloc); + static MethodSignature Signature(const DexFile* dex_file, uint32_t method_idx, bool is_static, + ScopedArenaAllocator* alloc); + static MethodSignature* PrepareSignatures(const DexFile* dex_file, MIRGraph* mir_graph, + ScopedArenaAllocator* alloc); + static CheckCastData* InitializeCheckCastData(MIRGraph* mir_graph, ScopedArenaAllocator* alloc); + + void InitializeSRegs(); + + int32_t ModifiedSReg(int32_t s_reg); + int32_t PhiInputModifiedSReg(int32_t s_reg, BasicBlock* bb, size_t pred_idx); + + bool UpdateSRegFromLowWordType(int32_t mod_s_reg, Type low_word_type); + + MIRGraph* const mir_graph_; + CompilationUnit* const cu_; + + // The type inference propagates types also backwards but this must not happen across + // check-cast. So we need to effectively split an SSA reg into two at check-cast and + // keep track of the types separately. + std::unique_ptr<CheckCastData> check_cast_data_; + + size_t num_sregs_; // Number of SSA regs or modified SSA regs, see check-cast. + const Type* const ifields_; // Indexed by MIR::meta::ifield_lowering_info. + const Type* const sfields_; // Indexed by MIR::meta::sfield_lowering_info. + const MethodSignature* const signatures_; // Indexed by MIR::meta::method_lowering_info. + const MethodSignature current_method_signature_; + Type* const sregs_; // Indexed by SSA reg or modified SSA reg, see check-cast. + uint64_t* const bb_df_attrs_; // Indexed by BasicBlock::id. + + friend class TypeInferenceTest; +}; + +} // namespace art + +#endif // ART_COMPILER_DEX_TYPE_INFERENCE_H_ diff --git a/compiler/dex/type_inference_test.cc b/compiler/dex/type_inference_test.cc new file mode 100644 index 0000000000..eaa2bfac93 --- /dev/null +++ b/compiler/dex/type_inference_test.cc @@ -0,0 +1,2044 @@ +/* + * Copyright (C) 2015 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 "base/logging.h" +#include "compiler_ir.h" +#include "dataflow_iterator-inl.h" +#include "dex_flags.h" +#include "dex/mir_field_info.h" +#include "dex/mir_graph.h" +#include "driver/dex_compilation_unit.h" +#include "gtest/gtest.h" +#include "type_inference.h" +#include "utils/test_dex_file_builder.h" + +namespace art { + +class TypeInferenceTest : public testing::Test { + protected: + struct TypeDef { + const char* descriptor; + }; + + struct FieldDef { + const char* class_descriptor; + const char* type; + const char* name; + }; + + struct MethodDef { + const char* class_descriptor; + const char* signature; + const char* name; + InvokeType type; + }; + + struct BBDef { + static constexpr size_t kMaxSuccessors = 4; + static constexpr size_t kMaxPredecessors = 4; + + BBType type; + size_t num_successors; + BasicBlockId successors[kMaxPredecessors]; + size_t num_predecessors; + BasicBlockId predecessors[kMaxPredecessors]; + }; + + struct MIRDef { + static constexpr size_t kMaxSsaDefs = 2; + static constexpr size_t kMaxSsaUses = 4; + + BasicBlockId bbid; + Instruction::Code opcode; + int64_t value; + uint32_t metadata; + size_t num_uses; + int32_t uses[kMaxSsaUses]; + size_t num_defs; + int32_t defs[kMaxSsaDefs]; + }; + +#define DEF_SUCC0() \ + 0u, { } +#define DEF_SUCC1(s1) \ + 1u, { s1 } +#define DEF_SUCC2(s1, s2) \ + 2u, { s1, s2 } +#define DEF_SUCC3(s1, s2, s3) \ + 3u, { s1, s2, s3 } +#define DEF_SUCC4(s1, s2, s3, s4) \ + 4u, { s1, s2, s3, s4 } +#define DEF_PRED0() \ + 0u, { } +#define DEF_PRED1(p1) \ + 1u, { p1 } +#define DEF_PRED2(p1, p2) \ + 2u, { p1, p2 } +#define DEF_PRED3(p1, p2, p3) \ + 3u, { p1, p2, p3 } +#define DEF_PRED4(p1, p2, p3, p4) \ + 4u, { p1, p2, p3, p4 } +#define DEF_BB(type, succ, pred) \ + { type, succ, pred } + +#define DEF_CONST(bb, opcode, reg, value) \ + { bb, opcode, value, 0u, 0, { }, 1, { reg } } +#define DEF_CONST_WIDE(bb, opcode, reg, value) \ + { bb, opcode, value, 0u, 0, { }, 2, { reg, reg + 1 } } +#define DEF_CONST_STRING(bb, opcode, reg, index) \ + { bb, opcode, index, 0u, 0, { }, 1, { reg } } +#define DEF_IGET(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 1, { obj }, 1, { reg } } +#define DEF_IGET_WIDE(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 1, { obj }, 2, { reg, reg + 1 } } +#define DEF_IPUT(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 2, { reg, obj }, 0, { } } +#define DEF_IPUT_WIDE(bb, opcode, reg, obj, field_info) \ + { bb, opcode, 0u, field_info, 3, { reg, reg + 1, obj }, 0, { } } +#define DEF_SGET(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 0, { }, 1, { reg } } +#define DEF_SGET_WIDE(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 0, { }, 2, { reg, reg + 1 } } +#define DEF_SPUT(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 1, { reg }, 0, { } } +#define DEF_SPUT_WIDE(bb, opcode, reg, field_info) \ + { bb, opcode, 0u, field_info, 2, { reg, reg + 1 }, 0, { } } +#define DEF_AGET(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 2, { obj, idx }, 1, { reg } } +#define DEF_AGET_WIDE(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 2, { obj, idx }, 2, { reg, reg + 1 } } +#define DEF_APUT(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 3, { reg, obj, idx }, 0, { } } +#define DEF_APUT_WIDE(bb, opcode, reg, obj, idx) \ + { bb, opcode, 0u, 0u, 4, { reg, reg + 1, obj, idx }, 0, { } } +#define DEF_INVOKE0(bb, opcode, method_idx) \ + { bb, opcode, 0u, method_idx, 0, { }, 0, { } } +#define DEF_INVOKE1(bb, opcode, reg, method_idx) \ + { bb, opcode, 0u, method_idx, 1, { reg }, 0, { } } +#define DEF_INVOKE2(bb, opcode, reg1, reg2, method_idx) \ + { bb, opcode, 0u, method_idx, 2, { reg1, reg2 }, 0, { } } +#define DEF_IFZ(bb, opcode, reg) \ + { bb, opcode, 0u, 0u, 1, { reg }, 0, { } } +#define DEF_MOVE(bb, opcode, reg, src) \ + { bb, opcode, 0u, 0u, 1, { src }, 1, { reg } } +#define DEF_MOVE_WIDE(bb, opcode, reg, src) \ + { bb, opcode, 0u, 0u, 2, { src, src + 1 }, 2, { reg, reg + 1 } } +#define DEF_PHI2(bb, reg, src1, src2) \ + { bb, static_cast<Instruction::Code>(kMirOpPhi), 0, 0u, 2u, { src1, src2 }, 1, { reg } } +#define DEF_BINOP(bb, opcode, result, src1, src2) \ + { bb, opcode, 0u, 0u, 2, { src1, src2 }, 1, { result } } +#define DEF_UNOP(bb, opcode, result, src) DEF_MOVE(bb, opcode, result, src) +#define DEF_NULOP(bb, opcode, result) DEF_CONST(bb, opcode, result, 0) +#define DEF_NULOP_WIDE(bb, opcode, result) DEF_CONST_WIDE(bb, opcode, result, 0) +#define DEF_CHECK_CAST(bb, opcode, reg, type) \ + { bb, opcode, 0, type, 1, { reg }, 0, { } } +#define DEF_NEW_ARRAY(bb, opcode, reg, length, type) \ + { bb, opcode, 0, type, 1, { length }, 1, { reg } } + + void AddTypes(const TypeDef* defs, size_t count) { + for (size_t i = 0; i != count; ++i) { + const TypeDef* def = &defs[i]; + dex_file_builder_.AddType(def->descriptor); + } + } + + template <size_t count> + void PrepareTypes(const TypeDef (&defs)[count]) { + type_defs_ = defs; + type_count_ = count; + AddTypes(defs, count); + } + + void AddFields(const FieldDef* defs, size_t count) { + for (size_t i = 0; i != count; ++i) { + const FieldDef* def = &defs[i]; + dex_file_builder_.AddField(def->class_descriptor, def->type, def->name); + } + } + + template <size_t count> + void PrepareIFields(const FieldDef (&defs)[count]) { + ifield_defs_ = defs; + ifield_count_ = count; + AddFields(defs, count); + } + + template <size_t count> + void PrepareSFields(const FieldDef (&defs)[count]) { + sfield_defs_ = defs; + sfield_count_ = count; + AddFields(defs, count); + } + + void AddMethods(const MethodDef* defs, size_t count) { + for (size_t i = 0; i != count; ++i) { + const MethodDef* def = &defs[i]; + dex_file_builder_.AddMethod(def->class_descriptor, def->signature, def->name); + } + } + + template <size_t count> + void PrepareMethods(const MethodDef (&defs)[count]) { + method_defs_ = defs; + method_count_ = count; + AddMethods(defs, count); + } + + DexMemAccessType AccessTypeForDescriptor(const char* descriptor) { + switch (descriptor[0]) { + case 'I': + case 'F': + return kDexMemAccessWord; + case 'J': + case 'D': + return kDexMemAccessWide; + case '[': + case 'L': + return kDexMemAccessObject; + case 'Z': + return kDexMemAccessBoolean; + case 'B': + return kDexMemAccessByte; + case 'C': + return kDexMemAccessChar; + case 'S': + return kDexMemAccessShort; + default: + LOG(FATAL) << "Bad descriptor: " << descriptor; + UNREACHABLE(); + } + } + + size_t CountIns(const std::string& test_method_signature, bool is_static) { + const char* sig = test_method_signature.c_str(); + CHECK_EQ(sig[0], '('); + ++sig; + size_t result = is_static ? 0u : 1u; + while (*sig != ')') { + result += (AccessTypeForDescriptor(sig) == kDexMemAccessWide) ? 2u : 1u; + while (*sig == '[') { + ++sig; + } + if (*sig == 'L') { + do { + ++sig; + CHECK(*sig != '\0' && *sig != ')'); + } while (*sig != ';'); + } + ++sig; + } + return result; + } + + void BuildDexFile(const std::string& test_method_signature, bool is_static) { + dex_file_builder_.AddMethod(kClassName, test_method_signature, kMethodName); + dex_file_ = dex_file_builder_.Build(kDexLocation); + cu_.dex_file = dex_file_.get(); + cu_.method_idx = dex_file_builder_.GetMethodIdx(kClassName, test_method_signature, kMethodName); + cu_.access_flags = is_static ? kAccStatic : 0u; + cu_.mir_graph->m_units_.push_back(new (cu_.mir_graph->arena_) DexCompilationUnit( + &cu_, cu_.class_loader, cu_.class_linker, *cu_.dex_file, nullptr /* code_item not used */, + 0u /* class_def_idx not used */, 0u /* method_index not used */, + cu_.access_flags, nullptr /* verified_method not used */)); + cu_.mir_graph->current_method_ = 0u; + code_item_ = static_cast<DexFile::CodeItem*>( + cu_.arena.Alloc(sizeof(DexFile::CodeItem), kArenaAllocMisc)); + + code_item_->ins_size_ = CountIns(test_method_signature, is_static); + code_item_->registers_size_ = kLocalVRs + code_item_->ins_size_; + cu_.mir_graph->current_code_item_ = code_item_; + cu_.mir_graph->num_ssa_regs_ = kMaxSsaRegs; + + cu_.mir_graph->ifield_lowering_infos_.clear(); + cu_.mir_graph->ifield_lowering_infos_.reserve(ifield_count_); + for (size_t i = 0u; i != ifield_count_; ++i) { + const FieldDef* def = &ifield_defs_[i]; + uint32_t field_idx = + dex_file_builder_.GetFieldIdx(def->class_descriptor, def->type, def->name); + MirIFieldLoweringInfo field_info(field_idx, AccessTypeForDescriptor(def->type), false); + field_info.declaring_dex_file_ = cu_.dex_file; + field_info.declaring_field_idx_ = field_idx; + cu_.mir_graph->ifield_lowering_infos_.push_back(field_info); + } + + cu_.mir_graph->sfield_lowering_infos_.clear(); + cu_.mir_graph->sfield_lowering_infos_.reserve(sfield_count_); + for (size_t i = 0u; i != sfield_count_; ++i) { + const FieldDef* def = &sfield_defs_[i]; + uint32_t field_idx = + dex_file_builder_.GetFieldIdx(def->class_descriptor, def->type, def->name); + MirSFieldLoweringInfo field_info(field_idx, AccessTypeForDescriptor(def->type)); + field_info.declaring_dex_file_ = cu_.dex_file; + field_info.declaring_field_idx_ = field_idx; + cu_.mir_graph->sfield_lowering_infos_.push_back(field_info); + } + + cu_.mir_graph->method_lowering_infos_.clear(); + cu_.mir_graph->method_lowering_infos_.reserve(ifield_count_); + for (size_t i = 0u; i != method_count_; ++i) { + const MethodDef* def = &method_defs_[i]; + uint32_t method_idx = + dex_file_builder_.GetMethodIdx(def->class_descriptor, def->signature, def->name); + MirMethodLoweringInfo method_info(method_idx, def->type, false); + method_info.declaring_dex_file_ = cu_.dex_file; + method_info.declaring_method_idx_ = method_idx; + cu_.mir_graph->method_lowering_infos_.push_back(method_info); + } + } + + void DoPrepareBasicBlocks(const BBDef* defs, size_t count) { + cu_.mir_graph->block_id_map_.clear(); + cu_.mir_graph->block_list_.clear(); + ASSERT_LT(3u, count); // null, entry, exit and at least one bytecode block. + ASSERT_EQ(kNullBlock, defs[0].type); + ASSERT_EQ(kEntryBlock, defs[1].type); + ASSERT_EQ(kExitBlock, defs[2].type); + for (size_t i = 0u; i != count; ++i) { + const BBDef* def = &defs[i]; + BasicBlock* bb = cu_.mir_graph->CreateNewBB(def->type); + if (def->num_successors <= 2) { + bb->successor_block_list_type = kNotUsed; + bb->fall_through = (def->num_successors >= 1) ? def->successors[0] : 0u; + bb->taken = (def->num_successors >= 2) ? def->successors[1] : 0u; + } else { + bb->successor_block_list_type = kPackedSwitch; + bb->fall_through = 0u; + bb->taken = 0u; + bb->successor_blocks.reserve(def->num_successors); + for (size_t j = 0u; j != def->num_successors; ++j) { + SuccessorBlockInfo* successor_block_info = + static_cast<SuccessorBlockInfo*>(cu_.arena.Alloc(sizeof(SuccessorBlockInfo), + kArenaAllocSuccessor)); + successor_block_info->block = j; + successor_block_info->key = 0u; // Not used by class init check elimination. + bb->successor_blocks.push_back(successor_block_info); + } + } + bb->predecessors.assign(def->predecessors, def->predecessors + def->num_predecessors); + if (def->type == kDalvikByteCode || def->type == kEntryBlock || def->type == kExitBlock) { + bb->data_flow_info = static_cast<BasicBlockDataFlow*>( + cu_.arena.Alloc(sizeof(BasicBlockDataFlow), kArenaAllocDFInfo)); + bb->data_flow_info->live_in_v = live_in_v_; + } + } + ASSERT_EQ(count, cu_.mir_graph->block_list_.size()); + cu_.mir_graph->entry_block_ = cu_.mir_graph->block_list_[1]; + ASSERT_EQ(kEntryBlock, cu_.mir_graph->entry_block_->block_type); + cu_.mir_graph->exit_block_ = cu_.mir_graph->block_list_[2]; + ASSERT_EQ(kExitBlock, cu_.mir_graph->exit_block_->block_type); + } + + template <size_t count> + void PrepareBasicBlocks(const BBDef (&defs)[count]) { + DoPrepareBasicBlocks(defs, count); + } + + void PrepareSingleBlock() { + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(1)), + }; + PrepareBasicBlocks(bbs); + } + + void PrepareDiamond() { + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(6)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(4, 5), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(6), DEF_PRED1(3)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED2(4, 5)), + }; + PrepareBasicBlocks(bbs); + } + + void PrepareLoop() { + static const BBDef bbs[] = { + DEF_BB(kNullBlock, DEF_SUCC0(), DEF_PRED0()), + DEF_BB(kEntryBlock, DEF_SUCC1(3), DEF_PRED0()), + DEF_BB(kExitBlock, DEF_SUCC0(), DEF_PRED1(5)), + DEF_BB(kDalvikByteCode, DEF_SUCC1(4), DEF_PRED1(1)), + DEF_BB(kDalvikByteCode, DEF_SUCC2(5, 4), DEF_PRED2(3, 4)), // "taken" loops to self. + DEF_BB(kDalvikByteCode, DEF_SUCC1(2), DEF_PRED1(4)), + }; + PrepareBasicBlocks(bbs); + } + + void DoPrepareMIRs(const MIRDef* defs, size_t count) { + mir_count_ = count; + mirs_ = cu_.arena.AllocArray<MIR>(count, kArenaAllocMIR); + ssa_reps_.resize(count); + for (size_t i = 0u; i != count; ++i) { + const MIRDef* def = &defs[i]; + MIR* mir = &mirs_[i]; + ASSERT_LT(def->bbid, cu_.mir_graph->block_list_.size()); + BasicBlock* bb = cu_.mir_graph->block_list_[def->bbid]; + bb->AppendMIR(mir); + mir->dalvikInsn.opcode = def->opcode; + mir->dalvikInsn.vB = static_cast<int32_t>(def->value); + mir->dalvikInsn.vB_wide = def->value; + if (IsInstructionIGetOrIPut(def->opcode)) { + ASSERT_LT(def->metadata, cu_.mir_graph->ifield_lowering_infos_.size()); + mir->meta.ifield_lowering_info = def->metadata; + ASSERT_EQ(cu_.mir_graph->ifield_lowering_infos_[def->metadata].MemAccessType(), + IGetOrIPutMemAccessType(def->opcode)); + cu_.mir_graph->merged_df_flags_ |= DF_IFIELD; + } else if (IsInstructionSGetOrSPut(def->opcode)) { + ASSERT_LT(def->metadata, cu_.mir_graph->sfield_lowering_infos_.size()); + mir->meta.sfield_lowering_info = def->metadata; + ASSERT_EQ(cu_.mir_graph->sfield_lowering_infos_[def->metadata].MemAccessType(), + SGetOrSPutMemAccessType(def->opcode)); + cu_.mir_graph->merged_df_flags_ |= DF_SFIELD; + } else if (IsInstructionInvoke(def->opcode)) { + ASSERT_LT(def->metadata, cu_.mir_graph->method_lowering_infos_.size()); + mir->meta.method_lowering_info = def->metadata; + mir->dalvikInsn.vA = def->num_uses; + cu_.mir_graph->merged_df_flags_ |= DF_FORMAT_35C; + } else if (def->opcode == static_cast<Instruction::Code>(kMirOpPhi)) { + mir->meta.phi_incoming = + allocator_->AllocArray<BasicBlockId>(def->num_uses, kArenaAllocDFInfo); + ASSERT_EQ(def->num_uses, bb->predecessors.size()); + std::copy(bb->predecessors.begin(), bb->predecessors.end(), mir->meta.phi_incoming); + } else if (def->opcode == Instruction::CHECK_CAST) { + ASSERT_LT(def->metadata, type_count_); + mir->dalvikInsn.vB = dex_file_builder_.GetTypeIdx(type_defs_[def->metadata].descriptor); + cu_.mir_graph->merged_df_flags_ |= DF_CHK_CAST; + } else if (def->opcode == Instruction::NEW_ARRAY) { + ASSERT_LT(def->metadata, type_count_); + mir->dalvikInsn.vC = dex_file_builder_.GetTypeIdx(type_defs_[def->metadata].descriptor); + } + mir->ssa_rep = &ssa_reps_[i]; + mir->ssa_rep->num_uses = def->num_uses; + mir->ssa_rep->uses = const_cast<int32_t*>(def->uses); // Not modified by LVN. + mir->ssa_rep->num_defs = def->num_defs; + mir->ssa_rep->defs = const_cast<int32_t*>(def->defs); // Not modified by LVN. + mir->dalvikInsn.opcode = def->opcode; + mir->offset = i; // LVN uses offset only for debug output + mir->optimization_flags = 0u; + } + code_item_->insns_size_in_code_units_ = 2u * count; + } + + template <size_t count> + void PrepareMIRs(const MIRDef (&defs)[count]) { + DoPrepareMIRs(defs, count); + } + + // BasicBlockDataFlow::vreg_to_ssa_map_exit is used only for check-casts. + void AllocEndingVRegToSRegMaps() { + AllNodesIterator iterator(cu_.mir_graph.get()); + for (BasicBlock* bb = iterator.Next(); bb != nullptr; bb = iterator.Next()) { + if (bb->data_flow_info != nullptr) { + if (bb->data_flow_info->vreg_to_ssa_map_exit == nullptr) { + size_t num_vregs = code_item_->registers_size_; + bb->data_flow_info->vreg_to_ssa_map_exit = static_cast<int32_t*>( + cu_.arena.AllocArray<int32_t>(num_vregs, kArenaAllocDFInfo)); + std::fill_n(bb->data_flow_info->vreg_to_ssa_map_exit, num_vregs, INVALID_SREG); + } + } + } + } + + template <size_t count> + void MapVRegToSReg(int vreg, int32_t sreg, const BasicBlockId (&bb_ids)[count]) { + AllocEndingVRegToSRegMaps(); + for (BasicBlockId bb_id : bb_ids) { + BasicBlock* bb = cu_.mir_graph->GetBasicBlock(bb_id); + CHECK(bb != nullptr); + CHECK(bb->data_flow_info != nullptr); + CHECK(bb->data_flow_info->vreg_to_ssa_map_exit != nullptr); + bb->data_flow_info->vreg_to_ssa_map_exit[vreg] = sreg; + } + } + + void PerformTypeInference() { + cu_.mir_graph->SSATransformationStart(); + cu_.mir_graph->ComputeDFSOrders(); + cu_.mir_graph->ComputeDominators(); + cu_.mir_graph->ComputeTopologicalSortOrder(); + cu_.mir_graph->SSATransformationEnd(); + ASSERT_TRUE(type_inference_ == nullptr); + type_inference_.reset(new (allocator_.get()) TypeInference(cu_.mir_graph.get(), + allocator_.get())); + RepeatingPreOrderDfsIterator iter(cu_.mir_graph.get()); + bool changed = false; + for (BasicBlock* bb = iter.Next(changed); bb != nullptr; bb = iter.Next(changed)) { + changed = type_inference_->Apply(bb); + } + type_inference_->Finish(); + } + + TypeInferenceTest() + : pool_(), + cu_(&pool_, kRuntimeISA, nullptr, nullptr), + mir_count_(0u), + mirs_(nullptr), + code_item_(nullptr), + ssa_reps_(), + allocator_(), + live_in_v_(new (&cu_.arena) ArenaBitVector(&cu_.arena, kMaxSsaRegs, false, kBitMapMisc)), + type_defs_(nullptr), + type_count_(0u), + ifield_defs_(nullptr), + ifield_count_(0u), + sfield_defs_(nullptr), + sfield_count_(0u), + method_defs_(nullptr), + method_count_(0u), + dex_file_builder_(), + dex_file_(nullptr) { + cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena)); + allocator_.reset(ScopedArenaAllocator::Create(&cu_.arena_stack)); + // Bind all possible sregs to live vregs for test purposes. + live_in_v_->SetInitialBits(kMaxSsaRegs); + cu_.mir_graph->reg_location_ = static_cast<RegLocation*>(cu_.arena.Alloc( + kMaxSsaRegs * sizeof(cu_.mir_graph->reg_location_[0]), kArenaAllocRegAlloc)); + cu_.mir_graph->method_sreg_ = kMaxSsaRegs - 1u; + cu_.mir_graph->reg_location_[cu_.mir_graph->GetMethodSReg()].location = kLocCompilerTemp; + // Bind all possible sregs to live vregs for test purposes. + live_in_v_->SetInitialBits(kMaxSsaRegs); + cu_.mir_graph->ssa_base_vregs_.reserve(kMaxSsaRegs); + cu_.mir_graph->ssa_subscripts_.reserve(kMaxSsaRegs); + for (unsigned int i = 0; i < kMaxSsaRegs; i++) { + cu_.mir_graph->ssa_base_vregs_.push_back(i); + cu_.mir_graph->ssa_subscripts_.push_back(0); + } + } + + enum ExpectFlags : uint32_t { + kExpectWide = 0x0001u, + kExpectNarrow = 0x0002u, + kExpectFp = 0x0004u, + kExpectCore = 0x0008u, + kExpectRef = 0x0010u, + kExpectArrayWide = 0x0020u, + kExpectArrayNarrow = 0x0040u, + kExpectArrayFp = 0x0080u, + kExpectArrayCore = 0x0100u, + kExpectArrayRef = 0x0200u, + kExpectNull = 0x0400u, + kExpectHigh = 0x0800u, // Reserved for ExpectSRegType(). + }; + + struct SRegExpectation { + uint32_t array_depth; + uint32_t flags; + }; + + void ExpectSRegType(int s_reg, const SRegExpectation& expectation, bool check_loc = true) { + uint32_t flags = expectation.flags; + uint32_t array_depth = expectation.array_depth; + TypeInference::Type type = type_inference_->sregs_[s_reg]; + + if (check_loc) { + RegLocation loc = cu_.mir_graph->reg_location_[s_reg]; + EXPECT_EQ((flags & kExpectWide) != 0u, loc.wide) << s_reg; + EXPECT_EQ((flags & kExpectFp) != 0u, loc.fp) << s_reg; + EXPECT_EQ((flags & kExpectCore) != 0u, loc.core) << s_reg; + EXPECT_EQ((flags & kExpectRef) != 0u, loc.ref) << s_reg; + EXPECT_EQ((flags & kExpectHigh) != 0u, loc.high_word) << s_reg; + } + + EXPECT_EQ((flags & kExpectWide) != 0u, type.Wide()) << s_reg; + EXPECT_EQ((flags & kExpectNarrow) != 0u, type.Narrow()) << s_reg; + EXPECT_EQ((flags & kExpectFp) != 0u, type.Fp()) << s_reg; + EXPECT_EQ((flags & kExpectCore) != 0u, type.Core()) << s_reg; + EXPECT_EQ((flags & kExpectRef) != 0u, type.Ref()) << s_reg; + EXPECT_EQ((flags & kExpectHigh) == 0u, type.LowWord()) << s_reg; + EXPECT_EQ((flags & kExpectHigh) != 0u, type.HighWord()) << s_reg; + + if ((flags & kExpectRef) != 0u) { + EXPECT_EQ((flags & kExpectNull) != 0u, !type.NonNull()) << s_reg; + } else { + // Null should be checked only for references. + ASSERT_EQ((flags & kExpectNull), 0u); + } + + ASSERT_EQ(array_depth, type.ArrayDepth()) << s_reg; + if (array_depth != 0u) { + ASSERT_NE((flags & kExpectRef), 0u); + TypeInference::Type nested_type = type.NestedType(); + EXPECT_EQ((flags & kExpectArrayWide) != 0u, nested_type.Wide()) << s_reg; + EXPECT_EQ((flags & kExpectArrayNarrow) != 0u, nested_type.Narrow()) << s_reg; + EXPECT_EQ((flags & kExpectArrayFp) != 0u, nested_type.Fp()) << s_reg; + EXPECT_EQ((flags & kExpectArrayCore) != 0u, nested_type.Core()) << s_reg; + EXPECT_EQ((flags & kExpectArrayRef) != 0u, nested_type.Ref()) << s_reg; + } + if (!type.Narrow() && type.LowWord() && + (expectation.flags & (kExpectWide | kExpectNarrow | kExpectHigh)) == kExpectWide) { + SRegExpectation high_expectation = { array_depth, flags | kExpectHigh }; + ExpectSRegType(s_reg + 1, high_expectation); + } + } + + void ExpectCore(int s_reg, bool core) { + EXPECT_EQ(core, type_inference_->sregs_[s_reg].Core()); + } + + void ExpectRef(int s_reg, bool ref) { + EXPECT_EQ(ref, type_inference_->sregs_[s_reg].Ref()); + } + + void ExpectArrayDepth(int s_reg, uint32_t array_depth) { + EXPECT_EQ(array_depth, type_inference_->sregs_[s_reg].ArrayDepth()); + } + + static constexpr size_t kMaxSsaRegs = 16384u; + static constexpr uint16_t kLocalVRs = 1000u; + + static constexpr const char* kDexLocation = "TypeInferenceDexFile;"; + static constexpr const char* kClassName = "LTypeInferenceTest;"; + static constexpr const char* kMethodName = "test"; + + ArenaPool pool_; + CompilationUnit cu_; + size_t mir_count_; + MIR* mirs_; + DexFile::CodeItem* code_item_; + std::vector<SSARepresentation> ssa_reps_; + std::unique_ptr<ScopedArenaAllocator> allocator_; + std::unique_ptr<TypeInference> type_inference_; + ArenaBitVector* live_in_v_; + + const TypeDef* type_defs_; + size_t type_count_; + const FieldDef* ifield_defs_; + size_t ifield_count_; + const FieldDef* sfield_defs_; + size_t sfield_count_; + const MethodDef* method_defs_; + size_t method_count_; + + TestDexFileBuilder dex_file_builder_; + std::unique_ptr<const DexFile> dex_file_; +}; + +TEST_F(TypeInferenceTest, IGet) { + static const FieldDef ifields[] = { + { kClassName, "B", "byteField" }, + { kClassName, "C", "charField" }, + { kClassName, "D", "doubleField" }, + { kClassName, "F", "floatField" }, + { kClassName, "I", "intField" }, + { kClassName, "J", "longField" }, + { kClassName, "S", "shortField" }, + { kClassName, "Z", "booleanField" }, + { kClassName, "Ljava/lang/Object;", "objectField" }, + { kClassName, "[Ljava/lang/Object;", "objectArrayField" }, + }; + constexpr uint32_t thiz = kLocalVRs; + static const MIRDef mirs[] = { + DEF_IGET(3u, Instruction::IGET_BYTE, 0u, thiz, 0u), + DEF_IGET(3u, Instruction::IGET_CHAR, 1u, thiz, 1u), + DEF_IGET_WIDE(3u, Instruction::IGET_WIDE, 2u, thiz, 2u), + DEF_IGET(3u, Instruction::IGET, 4u, thiz, 3u), + DEF_IGET(3u, Instruction::IGET, 5u, thiz, 4u), + DEF_IGET_WIDE(3u, Instruction::IGET_WIDE, 6u, thiz, 5u), + DEF_IGET(3u, Instruction::IGET_SHORT, 8u, thiz, 6u), + DEF_IGET(3u, Instruction::IGET_BOOLEAN, 9u, thiz, 7u), + DEF_IGET(3u, Instruction::IGET_OBJECT, 10u, thiz, 8u), + DEF_IGET(3u, Instruction::IGET_OBJECT, 11u, thiz, 9u), + }; + + PrepareIFields(ifields); + BuildDexFile("()V", false); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[i].opcode, mirs_[i].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[i].ssa_rep->num_defs); + ExpectSRegType(mirs_[i].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, SGet) { + static const FieldDef sfields[] = { + { kClassName, "B", "staticByteField" }, + { kClassName, "C", "staticCharField" }, + { kClassName, "D", "staticDoubleField" }, + { kClassName, "F", "staticFloatField" }, + { kClassName, "I", "staticIntField" }, + { kClassName, "J", "staticLongField" }, + { kClassName, "S", "staticShortField" }, + { kClassName, "Z", "staticBooleanField" }, + { kClassName, "Ljava/lang/Object;", "staticObjectField" }, + { kClassName, "[Ljava/lang/Object;", "staticObjectArrayField" }, + }; + static const MIRDef mirs[] = { + DEF_SGET(3u, Instruction::SGET_BYTE, 0u, 0u), + DEF_SGET(3u, Instruction::SGET_CHAR, 1u, 1u), + DEF_SGET_WIDE(3u, Instruction::SGET_WIDE, 2u, 2u), + DEF_SGET(3u, Instruction::SGET, 4u, 3u), + DEF_SGET(3u, Instruction::SGET, 5u, 4u), + DEF_SGET_WIDE(3u, Instruction::SGET_WIDE, 6u, 5u), + DEF_SGET(3u, Instruction::SGET_SHORT, 8u, 6u), + DEF_SGET(3u, Instruction::SGET_BOOLEAN, 9u, 7u), + DEF_SGET(3u, Instruction::SGET_OBJECT, 10u, 8u), + DEF_SGET(3u, Instruction::SGET_OBJECT, 11u, 9u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[i].opcode, mirs_[i].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[i].ssa_rep->num_defs); + ExpectSRegType(mirs_[i].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, IPut) { + static const FieldDef ifields[] = { + { kClassName, "B", "byteField" }, + { kClassName, "C", "charField" }, + { kClassName, "D", "doubleField" }, + { kClassName, "F", "floatField" }, + { kClassName, "I", "intField" }, + { kClassName, "J", "longField" }, + { kClassName, "S", "shortField" }, + { kClassName, "Z", "booleanField" }, + { kClassName, "Ljava/lang/Object;", "objectField" }, + { kClassName, "[Ljava/lang/Object;", "objectArrayField" }, + }; + constexpr uint32_t thiz = kLocalVRs; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_IPUT(3u, Instruction::IPUT_BYTE, 0u, thiz, 0u), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_IPUT(3u, Instruction::IPUT_CHAR, 1u, thiz, 1u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 2u, 0), + DEF_IPUT_WIDE(3u, Instruction::IPUT_WIDE, 2u, thiz, 2u), + DEF_CONST(3u, Instruction::CONST, 4u, 0), + DEF_IPUT(3u, Instruction::IPUT, 4u, thiz, 3u), + DEF_CONST(3u, Instruction::CONST, 5u, 0), + DEF_IPUT(3u, Instruction::IPUT, 5u, thiz, 4u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 6u, 0), + DEF_IPUT_WIDE(3u, Instruction::IPUT_WIDE, 6u, thiz, 5u), + DEF_CONST(3u, Instruction::CONST, 8u, 0), + DEF_IPUT(3u, Instruction::IPUT_SHORT, 8u, thiz, 6u), + DEF_CONST(3u, Instruction::CONST, 9u, 0), + DEF_IPUT(3u, Instruction::IPUT_BOOLEAN, 9u, thiz, 7u), + DEF_CONST(3u, Instruction::CONST, 10u, 0), + DEF_IPUT(3u, Instruction::IPUT_OBJECT, 10u, thiz, 8u), + DEF_CONST(3u, Instruction::CONST, 11u, 0), + DEF_IPUT(3u, Instruction::IPUT_OBJECT, 11u, thiz, 9u), + }; + + PrepareIFields(ifields); + BuildDexFile("()V", false); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + // One expectation for every 2 MIRs. + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(2 * arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[2 * i].opcode, mirs_[2 * i].dalvikInsn.opcode); + EXPECT_EQ(mirs[2 * i + 1].opcode, mirs_[2 * i + 1].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[2 * i].ssa_rep->num_defs); + ExpectSRegType(mirs_[2 * i].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, SPut) { + static const FieldDef sfields[] = { + { kClassName, "B", "staticByteField" }, + { kClassName, "C", "staticCharField" }, + { kClassName, "D", "staticDoubleField" }, + { kClassName, "F", "staticFloatField" }, + { kClassName, "I", "staticIntField" }, + { kClassName, "J", "staticLongField" }, + { kClassName, "S", "staticShortField" }, + { kClassName, "Z", "staticBooleanField" }, + { kClassName, "Ljava/lang/Object;", "staticObjectField" }, + { kClassName, "[Ljava/lang/Object;", "staticObjectArrayField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_SPUT(3u, Instruction::SPUT_BYTE, 0u, 0u), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_SPUT(3u, Instruction::SPUT_CHAR, 1u, 1u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 2u, 0), + DEF_SPUT_WIDE(3u, Instruction::SPUT_WIDE, 2u, 2u), + DEF_CONST(3u, Instruction::CONST, 4u, 0), + DEF_SPUT(3u, Instruction::SPUT, 4u, 3u), + DEF_CONST(3u, Instruction::CONST, 5u, 0), + DEF_SPUT(3u, Instruction::SPUT, 5u, 4u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 6u, 0), + DEF_SPUT_WIDE(3u, Instruction::SPUT_WIDE, 6u, 5u), + DEF_CONST(3u, Instruction::CONST, 8u, 0), + DEF_SPUT(3u, Instruction::SPUT_SHORT, 8u, 6u), + DEF_CONST(3u, Instruction::CONST, 9u, 0), + DEF_SPUT(3u, Instruction::SPUT_BOOLEAN, 9u, 7u), + DEF_CONST(3u, Instruction::CONST, 10u, 0), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 10u, 8u), + DEF_CONST(3u, Instruction::CONST, 11u, 0), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 11u, 9u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + // One expectation for every 2 MIRs. + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(2 * arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[2 * i].opcode, mirs_[2 * i].dalvikInsn.opcode); + EXPECT_EQ(mirs[2 * i + 1].opcode, mirs_[2 * i + 1].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[2 * i].ssa_rep->num_defs); + ExpectSRegType(mirs_[2 * i].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, MethodReturnType) { + static const MethodDef methods[] = { + { kClassName, "()B", "byteFoo", kStatic }, + { kClassName, "()C", "charFoo", kStatic }, + { kClassName, "()D", "doubleFoo", kStatic }, + { kClassName, "()F", "floatFoo", kStatic }, + { kClassName, "()I", "intFoo", kStatic }, + { kClassName, "()J", "longFoo", kStatic }, + { kClassName, "()S", "shortFoo", kStatic }, + { kClassName, "()Z", "booleanFoo", kStatic }, + { kClassName, "()Ljava/lang/Object;", "objectFoo", kStatic }, + { kClassName, "()[Ljava/lang/Object;", "objectArrayFoo", kStatic }, + }; + static const MIRDef mirs[] = { + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 0u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 0u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 1u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 1u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 2u), + DEF_NULOP_WIDE(3u, Instruction::MOVE_RESULT_WIDE, 2u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 3u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 4u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 4u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 5u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 5u), + DEF_NULOP_WIDE(3u, Instruction::MOVE_RESULT_WIDE, 6u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 6u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 8u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 7u), + DEF_NULOP(3u, Instruction::MOVE_RESULT, 9u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 8u), + DEF_NULOP(3u, Instruction::MOVE_RESULT_OBJECT, 10u), + DEF_INVOKE0(3u, Instruction::INVOKE_STATIC, 9u), + DEF_NULOP(3u, Instruction::MOVE_RESULT_OBJECT, 11u), + }; + + PrepareMethods(methods); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + // One expectation for every 2 MIRs. + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(2 * arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[2 * i].opcode, mirs_[2 * i].dalvikInsn.opcode); + EXPECT_EQ(mirs[2 * i + 1].opcode, mirs_[2 * i + 1].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[2 * i + 1].ssa_rep->num_defs); + ExpectSRegType(mirs_[2 * i + 1].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, MethodArgType) { + static const MethodDef methods[] = { + { kClassName, "(B)V", "fooByte", kStatic }, + { kClassName, "(C)V", "fooChar", kStatic }, + { kClassName, "(D)V", "fooDouble", kStatic }, + { kClassName, "(F)V", "fooFloat", kStatic }, + { kClassName, "(I)V", "fooInt", kStatic }, + { kClassName, "(J)V", "fooLong", kStatic }, + { kClassName, "(S)V", "fooShort", kStatic }, + { kClassName, "(Z)V", "fooBoolean", kStatic }, + { kClassName, "(Ljava/lang/Object;)V", "fooObject", kStatic }, + { kClassName, "([Ljava/lang/Object;)V", "fooObjectArray", kStatic }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 0u, 0u), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 1u, 1u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 2u, 0), + DEF_INVOKE2(3u, Instruction::INVOKE_STATIC, 2u, 3u, 2u), + DEF_CONST(3u, Instruction::CONST, 4u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 4u, 3u), + DEF_CONST(3u, Instruction::CONST, 5u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 5u, 4u), + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 6u, 0), + DEF_INVOKE2(3u, Instruction::INVOKE_STATIC, 6u, 7u, 5u), + DEF_CONST(3u, Instruction::CONST, 8u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 8u, 6u), + DEF_CONST(3u, Instruction::CONST, 9u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 9u, 7u), + DEF_CONST(3u, Instruction::CONST, 10u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 10u, 8u), + DEF_CONST(3u, Instruction::CONST, 11u, 0), + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 11u, 9u), + }; + + PrepareMethods(methods); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + // One expectation for every 2 MIRs. + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectFp | kExpectWide }, + { 0u, kExpectFp | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + }; + static_assert(2 * arraysize(expectations) == arraysize(mirs), "array size mismatch"); + for (size_t i = 0; i != arraysize(expectations); ++i) { + EXPECT_EQ(mirs[2 * i].opcode, mirs_[2 * i].dalvikInsn.opcode); + EXPECT_EQ(mirs[2 * i + 1].opcode, mirs_[2 * i + 1].dalvikInsn.opcode); + ASSERT_LE(1u, mirs_[2 * i].ssa_rep->num_defs); + ExpectSRegType(mirs_[2 * i].ssa_rep->defs[0], expectations[i]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut1) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), // Object[] array + DEF_CONST(3u, Instruction::CONST, 1u, 0), // value; can't even determine whether core or fp. + DEF_CONST(3u, Instruction::CONST, 2u, 0), // index + DEF_APUT(3u, Instruction::APUT, 1u, 0u, 2u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayNarrow }, + { 0u, kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut2) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), // Object[] array + DEF_CONST(3u, Instruction::CONST, 1u, 0), // Object[] value + DEF_CONST(3u, Instruction::CONST, 2u, 0), // index + DEF_APUT(3u, Instruction::APUT_OBJECT, 1u, 0u, 2u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut3) { + static const MIRDef mirs[] = { + // Either array1 or array2 could be Object[][] but there is no way to tell from the bytecode. + DEF_CONST(3u, Instruction::CONST, 0u, 0), // Object[] array1 + DEF_CONST(3u, Instruction::CONST, 1u, 0), // Object[] array2 + DEF_CONST(3u, Instruction::CONST, 2u, 0), // index + DEF_APUT(3u, Instruction::APUT_OBJECT, 0u, 1u, 2u), + DEF_APUT(3u, Instruction::APUT_OBJECT, 1u, 0u, 2u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut4) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), // index + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), // Object[] array + DEF_CONST(3u, Instruction::CONST, 3u, 0), // value; can't even determine whether core or fp. + DEF_APUT(3u, Instruction::APUT, 3u, 2u, 1u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayNarrow }, + { 0u, kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut5) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), // index + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), // Object[] array + DEF_CONST(3u, Instruction::CONST, 3u, 0), // Object[] value + DEF_APUT(3u, Instruction::APUT_OBJECT, 3u, 2u, 1u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, APut6) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), // index + // Either array1 or array2 could be Object[][] but there is no way to tell from the bytecode. + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), // Object[] array1 + DEF_AGET(3u, Instruction::AGET_OBJECT, 3u, 0u, 1u), // Object[] array2 + DEF_APUT(3u, Instruction::APUT_OBJECT, 2u, 3u, 1u), + DEF_APUT(3u, Instruction::APUT_OBJECT, 3u, 2u, 1u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, TwoNullObjectArraysInLoop) { + static const MIRDef mirs[] = { + // void foo() { + // Object[] array1 = ((Object[])null)[0]; + // Object[] array2 = ((Object[])null)[0]; + // for (int i = 0; i != 3; ++i) { + // Object[] a1 = null; // One of these could be Object[][] but not both. + // Object[] a2 = null; // But they will be deduced as Object[]. + // try { a1[0] = a2; } catch (Throwable ignored) { } + // try { a2[0] = a1; } catch (Throwable ignored) { } + // array1 = a1; + // array2 = a2; + // } + // } + // + // Omitting the try-catch: + DEF_CONST(3u, Instruction::CONST, 0u, 0), // null + DEF_CONST(3u, Instruction::CONST, 1u, 0), // index + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), // array1 + DEF_AGET(3u, Instruction::AGET_OBJECT, 3u, 0u, 1u), // array2 + DEF_PHI2(4u, 4u, 2u, 8u), // ? + [L -> [? gives [L (see array-length below) + DEF_PHI2(4u, 5u, 3u, 9u), // ? + [L -> ? gives ? + DEF_AGET(4u, Instruction::AGET_OBJECT, 6u, 0u, 1u), // a1 + DEF_AGET(4u, Instruction::AGET_OBJECT, 7u, 0u, 1u), // a2 + DEF_APUT(4u, Instruction::APUT_OBJECT, 6u, 7u, 1u), + DEF_APUT(4u, Instruction::APUT_OBJECT, 7u, 6u, 1u), + DEF_MOVE(4u, Instruction::MOVE_OBJECT, 8u, 6u), + DEF_MOVE(4u, Instruction::MOVE_OBJECT, 9u, 7u), + DEF_UNOP(5u, Instruction::ARRAY_LENGTH, 10u, 4u), + }; + + BuildDexFile("()V", true); + PrepareLoop(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ArrayArrayFloat) { + static const MethodDef methods[] = { + { kClassName, "(F)V", "fooFloat", kStatic }, + }; + static const MIRDef mirs[] = { + // void foo() { + // try { + // float[][][] aaaf = null; + // float[][] array = aaaf[0]; // Make sure array is treated as properly typed. + // array[0][0] = 0.0f; // const + aget-object[1] + aput + // fooFloat(array[0][0]); // aget-object[2] + aget + invoke + // // invoke: signature => input is F. + // // aget: output is F => base is [F (precise) + // // aget-object[2]: output is [F => base is [[F (precise) + // // aput: unknown input type => base is [? + // // aget-object[1]: base is [[F => result is L or [F, merge with [? => result is [F + // // aput (again): base is [F => result is F + // // const: F determined by the aput reprocessing. + // } catch (Throwable ignored) { + // } + // } + // + // Omitting the try-catch: + DEF_CONST(3u, Instruction::CONST, 0u, 0), // 0 + DEF_CONST(3u, Instruction::CONST, 1u, 0), // aaaf + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 1u, 0u), // array = aaaf[0] + DEF_CONST(3u, Instruction::CONST, 3u, 0), // 0.0f + DEF_AGET(3u, Instruction::AGET_OBJECT, 4u, 2u, 0u), // array[0] + DEF_APUT(3u, Instruction::APUT, 3u, 4u, 0u), // array[0][0] = 0.0f + DEF_AGET(3u, Instruction::AGET_OBJECT, 5u, 2u, 0u), // array[0] + DEF_AGET(3u, Instruction::AGET, 6u, 5u, 0u), // array[0][0] + DEF_INVOKE1(3u, Instruction::INVOKE_STATIC, 6u, 0u), // fooFloat(array[0][0]) + }; + + PrepareMethods(methods); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 2u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectFp | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectFp | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, CheckCast1) { + static const TypeDef types[] = { + { "[I" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), + DEF_CHECK_CAST(4u, Instruction::CHECK_CAST, 2u, 0u), + DEF_CHECK_CAST(5u, Instruction::CHECK_CAST, 2u, 0u), + // Pseudo-phi from [I and [I into L infers only L but not [. + DEF_MOVE(6u, Instruction::MOVE_OBJECT, 3u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + static const BasicBlockId v0_def_blocks[] = { 3u, 4u, 5u, 6u }; + MapVRegToSReg(2, 2, v0_def_blocks); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, CheckCast2) { + static const TypeDef types[] = { + { "[I" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), + DEF_CHECK_CAST(4u, Instruction::CHECK_CAST, 2u, 0u), + DEF_CHECK_CAST(5u, Instruction::CHECK_CAST, 2u, 0u), + // Pseudo-phi from [I and [I into [? infers [I. + DEF_MOVE(6u, Instruction::MOVE_OBJECT, 3u, 2u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 4u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + static const BasicBlockId v0_def_blocks[] = { 3u, 4u, 5u, 6u }; + MapVRegToSReg(2, 2, v0_def_blocks); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, CheckCast3) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), + DEF_CHECK_CAST(4u, Instruction::CHECK_CAST, 2u, 0u), + DEF_CHECK_CAST(5u, Instruction::CHECK_CAST, 2u, 1u), + // Pseudo-phi from [I and [F into L correctly leaves it as L. + DEF_MOVE(6u, Instruction::MOVE_OBJECT, 3u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + static const BasicBlockId v0_def_blocks[] = { 3u, 4u, 5u, 6u }; + MapVRegToSReg(2, 2, v0_def_blocks); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, CheckCastConflict1) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), + DEF_CHECK_CAST(4u, Instruction::CHECK_CAST, 2u, 0u), + DEF_CHECK_CAST(5u, Instruction::CHECK_CAST, 2u, 1u), + // Pseudo-phi from [I and [F into [? infers conflict [I/[F. + DEF_MOVE(6u, Instruction::MOVE_OBJECT, 3u, 2u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 4u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + static const BasicBlockId v0_def_blocks[] = { 3u, 4u, 5u, 6u }; + MapVRegToSReg(2, 2, v0_def_blocks); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg], false); + } + // The type conflict in array element wasn't propagated to an SSA reg. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, CheckCastConflict2) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), + DEF_CHECK_CAST(4u, Instruction::CHECK_CAST, 2u, 0u), + DEF_CHECK_CAST(5u, Instruction::CHECK_CAST, 2u, 1u), + // Pseudo-phi from [I and [F into [? infers conflict [I/[F. + DEF_MOVE(6u, Instruction::MOVE_OBJECT, 3u, 2u), + DEF_AGET(6u, Instruction::AGET, 4u, 2u, 1u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + static const BasicBlockId v0_def_blocks[] = { 3u, 4u, 5u, 6u }; + MapVRegToSReg(2, 2, v0_def_blocks); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectFp | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg], false); + } + // Type conflict in an SSA reg, register promotion disabled. + EXPECT_NE(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, Phi1) { + static const TypeDef types[] = { + { "[I" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_NEW_ARRAY(5u, Instruction::NEW_ARRAY, 2u, 0u, 0u), + // Phi from [I and [I infers only L but not [. + DEF_PHI2(6u, 3u, 1u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, Phi2) { + static const TypeDef types[] = { + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_NEW_ARRAY(5u, Instruction::NEW_ARRAY, 2u, 0u, 0u), + // Phi from [F and [F into [? infers [F. + DEF_PHI2(6u, 3u, 1u, 2u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 4u, 3u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, Phi3) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_NEW_ARRAY(5u, Instruction::NEW_ARRAY, 2u, 0u, 1u), + // Phi from [I and [F infers L. + DEF_PHI2(6u, 3u, 1u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, Phi4) { + static const TypeDef types[] = { + { "[I" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_CONST(5u, Instruction::CONST, 2u, 0), + // Pseudo-phi from [I and null infers L. + DEF_PHI2(6u, 3u, 1u, 2u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 0u, kExpectRef | kExpectNarrow | kExpectNull }, + { 0u, kExpectRef | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, PhiConflict1) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_NEW_ARRAY(5u, Instruction::NEW_ARRAY, 2u, 0u, 1u), + // Pseudo-phi from [I and [F into [? infers conflict [I/[F (then propagated upwards). + DEF_PHI2(6u, 3u, 1u, 2u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 4u, 3u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg], false); + } + // The type conflict in array element wasn't propagated to an SSA reg. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, PhiConflict2) { + static const TypeDef types[] = { + { "[I" }, + { "[F" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 100), + DEF_NEW_ARRAY(4u, Instruction::NEW_ARRAY, 1u, 0u, 0u), + DEF_NEW_ARRAY(5u, Instruction::NEW_ARRAY, 2u, 0u, 1u), + // Pseudo-phi from [I and [F into [? infers conflict [I/[F (then propagated upwards). + DEF_PHI2(6u, 3u, 1u, 2u), + DEF_AGET(6u, Instruction::AGET, 4u, 3u, 0u), + }; + PrepareTypes(types); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectFp | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg], false); + } + // Type conflict in an SSA reg, register promotion disabled. + EXPECT_NE(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, Wide1) { + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_CONST(3u, Instruction::CONST, 1u, 0), // index + DEF_AGET(3u, Instruction::AGET_OBJECT, 2u, 0u, 1u), // long[] + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 3u, 0), // long + DEF_APUT_WIDE(3u, Instruction::APUT_WIDE, 3u, 2u, 1u), + { 3u, Instruction::RETURN_OBJECT, 0, 0u, 1u, { 2u }, 0u, { } }, + }; + + BuildDexFile("()[J", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayWide }, + { 0u, kExpectCore | kExpectWide }, + // NOTE: High word checked implicitly for sreg = 3. + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg], false); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, WideSizeConflict1) { + static const MIRDef mirs[] = { + DEF_CONST_WIDE(3u, Instruction::CONST_WIDE, 0u, 0), + DEF_MOVE(3u, Instruction::MOVE, 2u, 0u), + }; + + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectNarrow | kExpectWide }, + { 0u, kExpectNarrow | kExpectWide }, + }; + ExpectSRegType(0u, expectations[0], false); + ExpectSRegType(2u, expectations[1], false); + EXPECT_TRUE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ArrayLongLength) { + static const FieldDef sfields[] = { + { kClassName, "[J", "arrayLongField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(4u, Instruction::CONST, 0u, 0), + DEF_SGET(5u, Instruction::SGET_OBJECT, 1u, 0u), + DEF_PHI2(6u, 2u, 0u, 1u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 3u, 2u), + DEF_SGET(6u, Instruction::SGET_OBJECT, 4u, 0u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 5u, 4u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayCore | kExpectArrayWide }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayWide }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayWide }, + { 0u, kExpectCore | kExpectNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayWide }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ArrayArrayObjectLength) { + static const FieldDef sfields[] = { + { kClassName, "[[Ljava/lang/Object;", "arrayLongField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(4u, Instruction::CONST, 0u, 0), + DEF_SGET(5u, Instruction::SGET_OBJECT, 1u, 0u), + DEF_PHI2(6u, 2u, 0u, 1u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 3u, 2u), + DEF_SGET(6u, Instruction::SGET_OBJECT, 4u, 0u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 5u, 4u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | kExpectArrayRef | kExpectArrayNarrow }, + { 2u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 2u, kExpectRef | kExpectNarrow | kExpectArrayRef | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, SGetAdd0SPut) { + static const FieldDef sfields[] = { + { kClassName, "I", "staticIntField" }, + }; + static const MIRDef mirs[] = { + DEF_SGET(3u, Instruction::SGET, 0u, 0u), + DEF_UNOP(3u, Instruction::ADD_INT_LIT8, 1u, 0u), // +0 + DEF_SPUT(3u, Instruction::SPUT, 1u, 0u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, MoveObjectNull) { + static const MethodDef methods[] = { + { kClassName, "([I[D)V", "foo", kStatic }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_MOVE(3u, Instruction::MOVE_OBJECT, 1u, 0u), + DEF_INVOKE2(3u, Instruction::INVOKE_STATIC, 0u, 1u, 0u), + }; + + PrepareMethods(methods); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectation = { + 1u, + kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow | kExpectArrayWide + }; + ExpectSRegType(0u, expectation); + ExpectSRegType(1u, expectation); + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, MoveNull1) { + static const MethodDef methods[] = { + { kClassName, "([I[D)V", "foo", kStatic }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_MOVE(3u, Instruction::MOVE, 1u, 0u), + DEF_INVOKE2(3u, Instruction::INVOKE_STATIC, 0u, 1u, 0u), + }; + + PrepareMethods(methods); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectation = { + 1u, + kExpectCore | kExpectRef | kExpectFp | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayFp | kExpectArrayNarrow | kExpectArrayWide + }; + ExpectSRegType(0u, expectation); + ExpectSRegType(1u, expectation); + // Type conflict using move instead of move-object for null, register promotion disabled. + EXPECT_NE(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, MoveNull2) { + static const FieldDef sfields[] = { + { kClassName, "[F", "staticArrayArrayFloatField" }, + { kClassName, "[I", "staticArrayIntField" }, + { kClassName, "[[I", "staticArrayArrayIntField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(4u, Instruction::CONST, 0u, 0), + DEF_MOVE(4u, Instruction::MOVE_OBJECT, 1u, 0u), + DEF_MOVE(4u, Instruction::MOVE_OBJECT, 2u, 1u), + DEF_SGET(5u, Instruction::SGET_OBJECT, 3u, 0u), + DEF_SGET(5u, Instruction::SGET_OBJECT, 4u, 1u), + DEF_SGET(5u, Instruction::SGET_OBJECT, 5u, 2u), + DEF_PHI2(6u, 6u, 0u, 3u), + DEF_PHI2(6u, 7u, 1u, 4u), + DEF_PHI2(6u, 8u, 2u, 5u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 9u, 6u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 10u, 7u), + DEF_UNOP(6u, Instruction::ARRAY_LENGTH, 11u, 8u), + { 6u, Instruction::RETURN_OBJECT, 0, 0u, 1u, { 8u }, 0u, { } }, + }; + + PrepareSFields(sfields); + BuildDexFile("()[[I", true); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 1u, kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayFp | kExpectArrayRef | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayFp | kExpectArrayRef | kExpectArrayNarrow}, + { 1u, kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayFp | kExpectArrayRef | kExpectArrayNarrow}, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 2u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayFp | kExpectArrayNarrow }, + { 1u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 2u, kExpectRef | kExpectNarrow | kExpectArrayCore | kExpectArrayNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + // Type conflict in array type not propagated to actual register. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ReuseNull1) { + static const FieldDef sfields[] = { + { kClassName, "[I", "staticArrayLongField" }, + { kClassName, "[[F", "staticArrayArrayFloatField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 0u, 0u), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 0u, 1u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectation = { + 1u, + kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayRef | kExpectArrayFp | kExpectArrayNarrow + }; + ExpectSRegType(0u, expectation); + // Type conflict in array type not propagated to actual register. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ReuseNull2) { + static const FieldDef sfields[] = { + { kClassName, "[J", "staticArrayLongField" }, + { kClassName, "[[F", "staticArrayArrayFloatField" }, + }; + static const MIRDef mirs[] = { + DEF_CONST(3u, Instruction::CONST, 0u, 0), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 0u, 0u), + DEF_SPUT(3u, Instruction::SPUT_OBJECT, 0u, 1u), + }; + + PrepareSFields(sfields); + BuildDexFile("()V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectation = { + 1u, + kExpectRef | kExpectNarrow | kExpectNull | + kExpectArrayCore | kExpectArrayRef | kExpectArrayFp | kExpectArrayNarrow | kExpectArrayWide + }; + ExpectSRegType(0u, expectation); + // Type conflict in array type not propagated to actual register. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, ArgIsNonNull) { + constexpr uint32_t thiz = kLocalVRs; + static const MIRDef mirs[] = { + DEF_MOVE(3u, Instruction::MOVE_OBJECT, 0u, thiz), + }; + + BuildDexFile("(Ljava/lang/Object;)V", true); + PrepareSingleBlock(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectation = { + 0u, + kExpectRef | kExpectNarrow + }; + ExpectSRegType(0u, expectation); + // Type conflict in array type not propagated to actual register. + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +TEST_F(TypeInferenceTest, IfCc) { + static const FieldDef sfields[] = { + { kClassName, "I", "intField" }, + }; + static const MIRDef mirs[] = { + DEF_SGET(3u, Instruction::SGET, 0u, 0u), + DEF_CONST(3u, Instruction::CONST, 1u, 0u), + { 3u, Instruction::IF_EQ, 0, 0u, 2, { 0u, 1u }, 0, { } }, + }; + + PrepareSFields(sfields); + BuildDexFile("()V", false); + PrepareDiamond(); + PrepareMIRs(mirs); + PerformTypeInference(); + + ASSERT_EQ(arraysize(mirs), mir_count_); + static const SRegExpectation expectations[] = { + { 0u, kExpectCore | kExpectNarrow }, + { 0u, kExpectCore | kExpectNarrow }, + }; + for (int32_t sreg = 0; sreg != arraysize(expectations); ++sreg) { + ExpectSRegType(sreg, expectations[sreg]); + } + EXPECT_EQ(cu_.disable_opt & (1u << kPromoteRegs), 0u); + EXPECT_FALSE(cu_.mir_graph->PuntToInterpreter()); +} + +} // namespace art diff --git a/compiler/dex/vreg_analysis.cc b/compiler/dex/vreg_analysis.cc index 2b78e38f5a..948ba7b273 100644 --- a/compiler/dex/vreg_analysis.cc +++ b/compiler/dex/vreg_analysis.cc @@ -23,400 +23,6 @@ namespace art { -bool MIRGraph::SetFp(int index, bool is_fp) { - bool change = false; - if (is_fp && !reg_location_[index].fp) { - reg_location_[index].fp = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetFp(int index) { - bool change = false; - if (!reg_location_[index].fp) { - reg_location_[index].fp = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetCore(int index, bool is_core) { - bool change = false; - if (is_core && !reg_location_[index].defined) { - reg_location_[index].core = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetCore(int index) { - bool change = false; - if (!reg_location_[index].defined) { - reg_location_[index].core = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetRef(int index, bool is_ref) { - bool change = false; - if (is_ref && !reg_location_[index].defined) { - reg_location_[index].ref = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetRef(int index) { - bool change = false; - if (!reg_location_[index].defined) { - reg_location_[index].ref = true; - reg_location_[index].defined = true; - change = true; - } - return change; -} - -bool MIRGraph::SetWide(int index, bool is_wide) { - bool change = false; - if (is_wide && !reg_location_[index].wide) { - reg_location_[index].wide = true; - change = true; - } - return change; -} - -bool MIRGraph::SetWide(int index) { - bool change = false; - if (!reg_location_[index].wide) { - reg_location_[index].wide = true; - change = true; - } - return change; -} - -bool MIRGraph::SetHigh(int index, bool is_high) { - bool change = false; - if (is_high && !reg_location_[index].high_word) { - reg_location_[index].high_word = true; - change = true; - } - return change; -} - -bool MIRGraph::SetHigh(int index) { - bool change = false; - if (!reg_location_[index].high_word) { - reg_location_[index].high_word = true; - change = true; - } - return change; -} - - -/* - * Infer types and sizes. We don't need to track change on sizes, - * as it doesn't propagate. We're guaranteed at least one pass through - * the cfg. - */ -bool MIRGraph::InferTypeAndSize(BasicBlock* bb, MIR* mir, bool changed) { - SSARepresentation *ssa_rep = mir->ssa_rep; - - /* - * The dex bytecode definition does not explicitly outlaw the definition of the same - * virtual register to be used in both a 32-bit and 64-bit pair context. However, dx - * does not generate this pattern (at least recently). Further, in the next revision of - * dex, we will forbid this. To support the few cases in the wild, detect this pattern - * and punt to the interpreter. - */ - bool type_mismatch = false; - - if (ssa_rep) { - uint64_t attrs = GetDataFlowAttributes(mir); - const int* uses = ssa_rep->uses; - const int* defs = ssa_rep->defs; - - // Handle defs - if (attrs & DF_DA) { - if (attrs & DF_CORE_A) { - changed |= SetCore(defs[0]); - } - if (attrs & DF_REF_A) { - changed |= SetRef(defs[0]); - } - if (attrs & DF_A_WIDE) { - reg_location_[defs[0]].wide = true; - reg_location_[defs[1]].wide = true; - reg_location_[defs[1]].high_word = true; - DCHECK_EQ(SRegToVReg(defs[0])+1, - SRegToVReg(defs[1])); - } - } - - - // Handles uses - int next = 0; - if (attrs & DF_UA) { - if (attrs & DF_CORE_A) { - changed |= SetCore(uses[next]); - } - if (attrs & DF_REF_A) { - changed |= SetRef(uses[next]); - } - if (attrs & DF_A_WIDE) { - reg_location_[uses[next]].wide = true; - reg_location_[uses[next + 1]].wide = true; - reg_location_[uses[next + 1]].high_word = true; - DCHECK_EQ(SRegToVReg(uses[next])+1, - SRegToVReg(uses[next + 1])); - next += 2; - } else { - type_mismatch |= reg_location_[uses[next]].wide; - next++; - } - } - if (attrs & DF_UB) { - if (attrs & DF_CORE_B) { - changed |= SetCore(uses[next]); - } - if (attrs & DF_REF_B) { - changed |= SetRef(uses[next]); - } - if (attrs & DF_B_WIDE) { - reg_location_[uses[next]].wide = true; - reg_location_[uses[next + 1]].wide = true; - reg_location_[uses[next + 1]].high_word = true; - DCHECK_EQ(SRegToVReg(uses[next])+1, - SRegToVReg(uses[next + 1])); - next += 2; - } else { - type_mismatch |= reg_location_[uses[next]].wide; - next++; - } - } - if (attrs & DF_UC) { - if (attrs & DF_CORE_C) { - changed |= SetCore(uses[next]); - } - if (attrs & DF_REF_C) { - changed |= SetRef(uses[next]); - } - if (attrs & DF_C_WIDE) { - reg_location_[uses[next]].wide = true; - reg_location_[uses[next + 1]].wide = true; - reg_location_[uses[next + 1]].high_word = true; - DCHECK_EQ(SRegToVReg(uses[next])+1, - SRegToVReg(uses[next + 1])); - } else { - type_mismatch |= reg_location_[uses[next]].wide; - } - } - - // Special-case return handling - if ((mir->dalvikInsn.opcode == Instruction::RETURN) || - (mir->dalvikInsn.opcode == Instruction::RETURN_WIDE) || - (mir->dalvikInsn.opcode == Instruction::RETURN_OBJECT)) { - switch (cu_->shorty[0]) { - case 'I': - type_mismatch |= reg_location_[uses[0]].wide; - changed |= SetCore(uses[0]); - break; - case 'J': - changed |= SetCore(uses[0]); - changed |= SetCore(uses[1]); - reg_location_[uses[0]].wide = true; - reg_location_[uses[1]].wide = true; - reg_location_[uses[1]].high_word = true; - break; - case 'F': - type_mismatch |= reg_location_[uses[0]].wide; - changed |= SetFp(uses[0]); - break; - case 'D': - changed |= SetFp(uses[0]); - changed |= SetFp(uses[1]); - reg_location_[uses[0]].wide = true; - reg_location_[uses[1]].wide = true; - reg_location_[uses[1]].high_word = true; - break; - case 'L': - type_mismatch |= reg_location_[uses[0]].wide; - changed |= SetRef(uses[0]); - break; - default: break; - } - } - - // Special-case handling for format 35c/3rc invokes - Instruction::Code opcode = mir->dalvikInsn.opcode; - int flags = MIR::DecodedInstruction::IsPseudoMirOp(opcode) ? - 0 : mir->dalvikInsn.FlagsOf(); - if ((flags & Instruction::kInvoke) && - (attrs & (DF_FORMAT_35C | DF_FORMAT_3RC))) { - DCHECK_EQ(next, 0); - const auto& lowering_info = GetMethodLoweringInfo(mir); - const char* shorty = GetShortyFromMethodReference(lowering_info.GetTargetMethod()); - // Handle result type if floating point - if ((shorty[0] == 'F') || (shorty[0] == 'D')) { - MIR* move_result_mir = FindMoveResult(bb, mir); - // Result might not be used at all, so no move-result - if (move_result_mir && (move_result_mir->dalvikInsn.opcode != - Instruction::MOVE_RESULT_OBJECT)) { - SSARepresentation* tgt_rep = move_result_mir->ssa_rep; - DCHECK(tgt_rep != NULL); - tgt_rep->fp_def[0] = true; - changed |= SetFp(tgt_rep->defs[0]); - if (shorty[0] == 'D') { - tgt_rep->fp_def[1] = true; - changed |= SetFp(tgt_rep->defs[1]); - } - } - } - int num_uses = mir->dalvikInsn.vA; - // If this is a non-static invoke, mark implicit "this" - if (!IsInstructionInvokeStatic(mir->dalvikInsn.opcode)) { - reg_location_[uses[next]].defined = true; - reg_location_[uses[next]].ref = true; - type_mismatch |= reg_location_[uses[next]].wide; - next++; - } - uint32_t cpos = 1; - if (strlen(shorty) > 1) { - for (int i = next; i < num_uses;) { - DCHECK_LT(cpos, strlen(shorty)); - switch (shorty[cpos++]) { - case 'D': - ssa_rep->fp_use[i] = true; - ssa_rep->fp_use[i+1] = true; - reg_location_[uses[i]].wide = true; - reg_location_[uses[i+1]].wide = true; - reg_location_[uses[i+1]].high_word = true; - DCHECK_EQ(SRegToVReg(uses[i])+1, SRegToVReg(uses[i+1])); - i++; - break; - case 'J': - reg_location_[uses[i]].wide = true; - reg_location_[uses[i+1]].wide = true; - reg_location_[uses[i+1]].high_word = true; - DCHECK_EQ(SRegToVReg(uses[i])+1, SRegToVReg(uses[i+1])); - changed |= SetCore(uses[i]); - i++; - break; - case 'F': - type_mismatch |= reg_location_[uses[i]].wide; - ssa_rep->fp_use[i] = true; - break; - case 'L': - type_mismatch |= reg_location_[uses[i]].wide; - changed |= SetRef(uses[i]); - break; - default: - type_mismatch |= reg_location_[uses[i]].wide; - changed |= SetCore(uses[i]); - break; - } - i++; - } - } - } - - for (int i = 0; ssa_rep->fp_use && i< ssa_rep->num_uses; i++) { - if (ssa_rep->fp_use[i]) { - changed |= SetFp(uses[i]); - } - } - for (int i = 0; ssa_rep->fp_def && i< ssa_rep->num_defs; i++) { - if (ssa_rep->fp_def[i]) { - changed |= SetFp(defs[i]); - } - } - // Special-case handling for moves & Phi - if (attrs & (DF_IS_MOVE | DF_NULL_TRANSFER_N)) { - /* - * If any of our inputs or outputs is defined, set all. - * Some ugliness related to Phi nodes and wide values. - * The Phi set will include all low words or all high - * words, so we have to treat them specially. - */ - bool is_phi = (static_cast<int>(mir->dalvikInsn.opcode) == kMirOpPhi); - RegLocation rl_temp = reg_location_[defs[0]]; - bool defined_fp = rl_temp.defined && rl_temp.fp; - bool defined_core = rl_temp.defined && rl_temp.core; - bool defined_ref = rl_temp.defined && rl_temp.ref; - bool is_wide = rl_temp.wide || ((attrs & DF_A_WIDE) != 0); - bool is_high = is_phi && rl_temp.wide && rl_temp.high_word; - for (int i = 0; i < ssa_rep->num_uses; i++) { - rl_temp = reg_location_[uses[i]]; - defined_fp |= rl_temp.defined && rl_temp.fp; - defined_core |= rl_temp.defined && rl_temp.core; - defined_ref |= rl_temp.defined && rl_temp.ref; - is_wide |= rl_temp.wide; - is_high |= is_phi && rl_temp.wide && rl_temp.high_word; - } - /* - * We don't normally expect to see a Dalvik register definition used both as a - * floating point and core value, though technically it could happen with constants. - * Until we have proper typing, detect this situation and disable register promotion - * (which relies on the distinction between core a fp usages). - */ - if ((defined_fp && (defined_core | defined_ref)) && - ((cu_->disable_opt & (1 << kPromoteRegs)) == 0)) { - LOG(WARNING) << PrettyMethod(cu_->method_idx, *cu_->dex_file) - << " op at block " << bb->id - << " has both fp and core/ref uses for same def."; - cu_->disable_opt |= (1 << kPromoteRegs); - } - changed |= SetFp(defs[0], defined_fp); - changed |= SetCore(defs[0], defined_core); - changed |= SetRef(defs[0], defined_ref); - changed |= SetWide(defs[0], is_wide); - changed |= SetHigh(defs[0], is_high); - if (attrs & DF_A_WIDE) { - changed |= SetWide(defs[1]); - changed |= SetHigh(defs[1]); - } - - bool has_ins = (GetNumOfInVRs() > 0); - - for (int i = 0; i < ssa_rep->num_uses; i++) { - if (has_ins && IsInVReg(uses[i])) { - // NB: The SSA name for the first def of an in-reg will be the same as - // the reg's actual name. - if (!reg_location_[uses[i]].fp && defined_fp) { - // If we were about to infer that this first def of an in-reg is a float - // when it wasn't previously (because float/int is set during SSA initialization), - // do not allow this to happen. - continue; - } - } - changed |= SetFp(uses[i], defined_fp); - changed |= SetCore(uses[i], defined_core); - changed |= SetRef(uses[i], defined_ref); - changed |= SetWide(uses[i], is_wide); - changed |= SetHigh(uses[i], is_high); - } - if (attrs & DF_A_WIDE) { - DCHECK_EQ(ssa_rep->num_uses, 2); - changed |= SetWide(uses[1]); - changed |= SetHigh(uses[1]); - } - } - } - if (type_mismatch) { - LOG(WARNING) << "Deprecated dex type mismatch, interpreting " - << PrettyMethod(cu_->method_idx, *cu_->dex_file); - LOG(INFO) << "@ 0x" << std::hex << mir->offset; - SetPuntToInterpreter(true); - } - return changed; -} - static const char* storage_name[] = {" Frame ", "PhysReg", " CompilerTemp "}; void MIRGraph::DumpRegLocTable(RegLocation* table, int count) { @@ -446,66 +52,12 @@ void MIRGraph::InitRegLocations() { loc[i] = fresh_loc; loc[i].s_reg_low = i; loc[i].is_const = false; // Constants will be marked by constant propagation pass later. - loc[i].wide = false; } - /* Treat Method* as a normal reference */ - int method_sreg = GetMethodSReg(); - loc[method_sreg].ref = true; - loc[method_sreg].location = kLocCompilerTemp; - loc[method_sreg].defined = true; + /* Mark the location of ArtMethod* as temporary */ + loc[GetMethodSReg()].location = kLocCompilerTemp; reg_location_ = loc; - - int num_regs = GetNumOfCodeVRs(); - - /* Add types of incoming arguments based on signature */ - int num_ins = GetNumOfInVRs(); - if (num_ins > 0) { - int s_reg = num_regs - num_ins; - if ((cu_->access_flags & kAccStatic) == 0) { - // For non-static, skip past "this" - reg_location_[s_reg].defined = true; - reg_location_[s_reg].ref = true; - s_reg++; - } - const char* shorty = cu_->shorty; - int shorty_len = strlen(shorty); - for (int i = 1; i < shorty_len; i++) { - switch (shorty[i]) { - case 'D': - reg_location_[s_reg].wide = true; - reg_location_[s_reg+1].high_word = true; - reg_location_[s_reg+1].fp = true; - DCHECK_EQ(SRegToVReg(s_reg)+1, SRegToVReg(s_reg+1)); - reg_location_[s_reg].fp = true; - reg_location_[s_reg].defined = true; - s_reg++; - break; - case 'J': - reg_location_[s_reg].wide = true; - reg_location_[s_reg+1].high_word = true; - DCHECK_EQ(SRegToVReg(s_reg)+1, SRegToVReg(s_reg+1)); - reg_location_[s_reg].core = true; - reg_location_[s_reg].defined = true; - s_reg++; - break; - case 'F': - reg_location_[s_reg].fp = true; - reg_location_[s_reg].defined = true; - break; - case 'L': - reg_location_[s_reg].ref = true; - reg_location_[s_reg].defined = true; - break; - default: - reg_location_[s_reg].core = true; - reg_location_[s_reg].defined = true; - break; - } - s_reg++; - } - } } /* diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index bad83359d7..e54cbf6fb6 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -127,34 +127,67 @@ inline std::pair<bool, bool> CompilerDriver::IsFastInstanceField( return std::make_pair(fast_get, fast_put); } -inline std::pair<bool, bool> CompilerDriver::IsFastStaticField( - mirror::DexCache* dex_cache, mirror::Class* referrer_class, - ArtField* resolved_field, uint16_t field_idx, uint32_t* storage_index) { - DCHECK(resolved_field->IsStatic()); +template <typename ArtMember> +inline bool CompilerDriver::CanAccessResolvedMember(mirror::Class* referrer_class ATTRIBUTE_UNUSED, + mirror::Class* access_to ATTRIBUTE_UNUSED, + ArtMember* member ATTRIBUTE_UNUSED, + mirror::DexCache* dex_cache ATTRIBUTE_UNUSED, + uint32_t field_idx ATTRIBUTE_UNUSED) { + // Not defined for ArtMember values other than ArtField or mirror::ArtMethod. + UNREACHABLE(); +} + +template <> +inline bool CompilerDriver::CanAccessResolvedMember<ArtField>(mirror::Class* referrer_class, + mirror::Class* access_to, + ArtField* field, + mirror::DexCache* dex_cache, + uint32_t field_idx) { + return referrer_class->CanAccessResolvedField(access_to, field, dex_cache, field_idx); +} + +template <> +inline bool CompilerDriver::CanAccessResolvedMember<mirror::ArtMethod>( + mirror::Class* referrer_class, + mirror::Class* access_to, + mirror::ArtMethod* method, + mirror::DexCache* dex_cache, + uint32_t field_idx) { + return referrer_class->CanAccessResolvedMethod(access_to, method, dex_cache, field_idx); +} + +template <typename ArtMember> +inline std::pair<bool, bool> CompilerDriver::IsClassOfStaticMemberAvailableToReferrer( + mirror::DexCache* dex_cache, + mirror::Class* referrer_class, + ArtMember* resolved_member, + uint16_t member_idx, + uint32_t* storage_index) { + DCHECK(resolved_member->IsStatic()); if (LIKELY(referrer_class != nullptr)) { - mirror::Class* fields_class = resolved_field->GetDeclaringClass(); - if (fields_class == referrer_class) { - *storage_index = fields_class->GetDexTypeIndex(); + mirror::Class* members_class = resolved_member->GetDeclaringClass(); + if (members_class == referrer_class) { + *storage_index = members_class->GetDexTypeIndex(); return std::make_pair(true, true); } - if (referrer_class->CanAccessResolvedField(fields_class, resolved_field, - dex_cache, field_idx)) { - // We have the resolved field, we must make it into a index for the referrer + if (CanAccessResolvedMember<ArtMember>( + referrer_class, members_class, resolved_member, dex_cache, member_idx)) { + // We have the resolved member, we must make it into a index for the referrer // in its static storage (which may fail if it doesn't have a slot for it) // TODO: for images we can elide the static storage base null check // if we know there's a non-null entry in the image const DexFile* dex_file = dex_cache->GetDexFile(); uint32_t storage_idx = DexFile::kDexNoIndex; - if (LIKELY(fields_class->GetDexCache() == dex_cache)) { - // common case where the dex cache of both the referrer and the field are the same, + if (LIKELY(members_class->GetDexCache() == dex_cache)) { + // common case where the dex cache of both the referrer and the member are the same, // no need to search the dex file - storage_idx = fields_class->GetDexTypeIndex(); + storage_idx = members_class->GetDexTypeIndex(); } else { - // Search dex file for localized ssb index, may fail if field's class is a parent + // Search dex file for localized ssb index, may fail if member's class is a parent // of the class mentioned in the dex file and there is no dex cache entry. std::string temp; const DexFile::StringId* string_id = - dex_file->FindStringId(resolved_field->GetDeclaringClass()->GetDescriptor(&temp)); + dex_file->FindStringId(resolved_member->GetDeclaringClass()->GetDescriptor(&temp)); if (string_id != nullptr) { const DexFile::TypeId* type_id = dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id)); @@ -166,7 +199,7 @@ inline std::pair<bool, bool> CompilerDriver::IsFastStaticField( } if (storage_idx != DexFile::kDexNoIndex) { *storage_index = storage_idx; - return std::make_pair(true, !resolved_field->IsFinal()); + return std::make_pair(true, !resolved_member->IsFinal()); } } } @@ -175,6 +208,23 @@ inline std::pair<bool, bool> CompilerDriver::IsFastStaticField( return std::make_pair(false, false); } +inline std::pair<bool, bool> CompilerDriver::IsFastStaticField( + mirror::DexCache* dex_cache, mirror::Class* referrer_class, + ArtField* resolved_field, uint16_t field_idx, uint32_t* storage_index) { + return IsClassOfStaticMemberAvailableToReferrer( + dex_cache, referrer_class, resolved_field, field_idx, storage_index); +} + +inline bool CompilerDriver::IsClassOfStaticMethodAvailableToReferrer( + mirror::DexCache* dex_cache, mirror::Class* referrer_class, + mirror::ArtMethod* resolved_method, uint16_t method_idx, uint32_t* storage_index) { + std::pair<bool, bool> result = IsClassOfStaticMemberAvailableToReferrer( + dex_cache, referrer_class, resolved_method, method_idx, storage_index); + // Only the first member of `result` is meaningful, as there is no + // "write access" to a method. + return result.first; +} + inline bool CompilerDriver::IsStaticFieldInReferrerClass(mirror::Class* referrer_class, ArtField* resolved_field) { DCHECK(resolved_field->IsStatic()); diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 03c5c5c352..02de11e960 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -281,6 +281,18 @@ class CompilerDriver { ArtField* resolved_field, uint16_t field_idx, uint32_t* storage_index) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // Return whether the declaring class of `resolved_method` is + // available to `referrer_class`. If this is true, compute the type + // index of the declaring class in the referrer's dex file and + // return it through the out argument `storage_index`; otherwise + // return DexFile::kDexNoIndex through `storage_index`. + bool IsClassOfStaticMethodAvailableToReferrer(mirror::DexCache* dex_cache, + mirror::Class* referrer_class, + mirror::ArtMethod* resolved_method, + uint16_t method_idx, + uint32_t* storage_index) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // Is static field's in referrer's class? bool IsStaticFieldInReferrerClass(mirror::Class* referrer_class, ArtField* resolved_field) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -459,6 +471,33 @@ class CompilerDriver { } private: + // Return whether the declaring class of `resolved_member` is + // available to `referrer_class` for read or write access using two + // Boolean values returned as a pair. If is true at least for read + // access, compute the type index of the declaring class in the + // referrer's dex file and return it through the out argument + // `storage_index`; otherwise return DexFile::kDexNoIndex through + // `storage_index`. + template <typename ArtMember> + std::pair<bool, bool> IsClassOfStaticMemberAvailableToReferrer(mirror::DexCache* dex_cache, + mirror::Class* referrer_class, + ArtMember* resolved_member, + uint16_t member_idx, + uint32_t* storage_index) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Can `referrer_class` access the resolved `member`? + // Dispatch call to mirror::Class::CanAccessResolvedField or + // mirror::Class::CanAccessResolvedMember depending on the value of + // ArtMember. + template <typename ArtMember> + static bool CanAccessResolvedMember(mirror::Class* referrer_class, + mirror::Class* access_to, + ArtMember* member, + mirror::DexCache* dex_cache, + uint32_t field_idx) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + // These flags are internal to CompilerDriver for collecting INVOKE resolution statistics. // The only external contract is that unresolved method has flags 0 and resolved non-0. enum { diff --git a/compiler/driver/dex_compilation_unit.h b/compiler/driver/dex_compilation_unit.h index 03ae489da1..398300699e 100644 --- a/compiler/driver/dex_compilation_unit.h +++ b/compiler/driver/dex_compilation_unit.h @@ -21,6 +21,7 @@ #include "dex_file.h" #include "jni.h" +#include "base/arena_object.h" namespace art { namespace mirror { @@ -31,7 +32,7 @@ class ClassLinker; struct CompilationUnit; class VerifiedMethod; -class DexCompilationUnit { +class DexCompilationUnit : public DeletableArenaObject<kArenaAllocMisc> { public: explicit DexCompilationUnit(CompilationUnit* cu); diff --git a/compiler/optimizing/boolean_simplifier.cc b/compiler/optimizing/boolean_simplifier.cc index 06328f2490..30c89f2d15 100644 --- a/compiler/optimizing/boolean_simplifier.cc +++ b/compiler/optimizing/boolean_simplifier.cc @@ -72,8 +72,8 @@ static HInstruction* GetOppositeCondition(HInstruction* cond) { return graph->GetIntConstant(0); } } else { - // General case when 'cond' is another instruction of type boolean. - DCHECK_EQ(cond->GetType(), Primitive::Type::kPrimBoolean); + // General case when 'cond' is another instruction of type boolean, + // as verified by SSAChecker. return new (allocator) HBooleanNot(cond); } } @@ -120,8 +120,11 @@ void HBooleanSimplifier::Run() { phi->ReplaceWith(replacement); merge_block->RemovePhi(phi); - // Link the start/end blocks and remove empty branches. - graph_->MergeEmptyBranches(block, merge_block); + // Delete the true branch and merge the resulting chain of blocks + // 'block->false_block->merge_block' into one. + true_block->DisconnectAndDelete(); + block->MergeWith(false_block); + block->MergeWith(merge_block); // Remove the original condition if it is now unused. if (!if_condition->HasUses()) { diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 818d671b5b..ebd8243c2b 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -21,6 +21,7 @@ #include "class_linker.h" #include "dex_file-inl.h" #include "dex_instruction-inl.h" +#include "dex/verified_method.h" #include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" #include "mirror/class_loader.h" @@ -587,7 +588,7 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, const char* descriptor = dex_file_->StringDataByIdx(proto_id.shorty_idx_); Primitive::Type return_type = Primitive::GetType(descriptor[0]); bool is_instance_call = invoke_type != kStatic; - const size_t number_of_arguments = strlen(descriptor) - (is_instance_call ? 0 : 1); + size_t number_of_arguments = strlen(descriptor) - (is_instance_call ? 0 : 1); MethodReference target_method(dex_file_, method_idx); uintptr_t direct_code; @@ -605,7 +606,15 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, } DCHECK(optimized_invoke_type != kSuper); + // By default, consider that the called method implicitly requires + // an initialization check of its declaring method. + HInvokeStaticOrDirect::ClinitCheckRequirement clinit_check_requirement = + HInvokeStaticOrDirect::ClinitCheckRequirement::kImplicit; + // Potential class initialization check, in the case of a static method call. + HClinitCheck* clinit_check = nullptr; + HInvoke* invoke = nullptr; + if (optimized_invoke_type == kVirtual) { invoke = new (arena_) HInvokeVirtual( arena_, number_of_arguments, return_type, dex_pc, method_idx, table_index); @@ -620,9 +629,76 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, bool is_recursive = (target_method.dex_method_index == dex_compilation_unit_->GetDexMethodIndex()); DCHECK(!is_recursive || (target_method.dex_file == dex_compilation_unit_->GetDexFile())); + + if (optimized_invoke_type == kStatic) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<4> hs(soa.Self()); + Handle<mirror::DexCache> dex_cache(hs.NewHandle( + dex_compilation_unit_->GetClassLinker()->FindDexCache( + *dex_compilation_unit_->GetDexFile()))); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle( + soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader()))); + mirror::ArtMethod* resolved_method = compiler_driver_->ResolveMethod( + soa, dex_cache, class_loader, dex_compilation_unit_, method_idx, + optimized_invoke_type); + + if (resolved_method == nullptr) { + MaybeRecordStat(MethodCompilationStat::kNotCompiledUnresolvedMethod); + return false; + } + + const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile(); + Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle( + outer_compilation_unit_->GetClassLinker()->FindDexCache(outer_dex_file))); + Handle<mirror::Class> referrer_class(hs.NewHandle(GetOutermostCompilingClass())); + + // The index at which the method's class is stored in the DexCache's type array. + uint32_t storage_index = DexFile::kDexNoIndex; + bool is_referrer_class = (resolved_method->GetDeclaringClass() == referrer_class.Get()); + if (is_referrer_class) { + storage_index = referrer_class->GetDexTypeIndex(); + } else if (outer_dex_cache.Get() == dex_cache.Get()) { + // Get `storage_index` from IsClassOfStaticMethodAvailableToReferrer. + compiler_driver_->IsClassOfStaticMethodAvailableToReferrer(outer_dex_cache.Get(), + referrer_class.Get(), + resolved_method, + method_idx, + &storage_index); + } + + if (referrer_class.Get()->IsSubClass(resolved_method->GetDeclaringClass())) { + // If the referrer class is the declaring class or a subclass + // of the declaring class, no class initialization is needed + // before the static method call. + clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kNone; + } else if (storage_index != DexFile::kDexNoIndex) { + // If the method's class type index is available, check + // whether we should add an explicit class initialization + // check for its declaring class before the static method call. + + // TODO: find out why this check is needed. + bool is_in_dex_cache = compiler_driver_->CanAssumeTypeIsPresentInDexCache( + *outer_compilation_unit_->GetDexFile(), storage_index); + bool is_initialized = + resolved_method->GetDeclaringClass()->IsInitialized() && is_in_dex_cache; + + if (is_initialized) { + clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kNone; + } else { + clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kExplicit; + HLoadClass* load_class = + new (arena_) HLoadClass(storage_index, is_referrer_class, dex_pc); + current_block_->AddInstruction(load_class); + clinit_check = new (arena_) HClinitCheck(load_class, dex_pc); + current_block_->AddInstruction(clinit_check); + ++number_of_arguments; + } + } + } + invoke = new (arena_) HInvokeStaticOrDirect( arena_, number_of_arguments, return_type, dex_pc, target_method.dex_method_index, - is_recursive, invoke_type, optimized_invoke_type); + is_recursive, invoke_type, optimized_invoke_type, clinit_check_requirement); } size_t start_index = 0; @@ -655,6 +731,12 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, } } + if (clinit_check_requirement == HInvokeStaticOrDirect::ClinitCheckRequirement::kExplicit) { + // Add the class initialization check as last input of `invoke`. + DCHECK(clinit_check != nullptr); + invoke->SetArgumentAt(argument_index++, clinit_check); + } + DCHECK_EQ(argument_index, number_of_arguments); current_block_->AddInstruction(invoke); latest_result_ = invoke; @@ -732,7 +814,6 @@ bool HGraphBuilder::IsOutermostCompilingClass(uint16_t type_index) const { return compiling_class.Get() == cls.Get(); } - bool HGraphBuilder::BuildStaticFieldAccess(const Instruction& instruction, uint32_t dex_pc, bool is_put) { @@ -764,7 +845,7 @@ bool HGraphBuilder::BuildStaticFieldAccess(const Instruction& instruction, if (is_referrer_class) { storage_index = referrer_class->GetDexTypeIndex(); } else if (outer_dex_cache.Get() != dex_cache.Get()) { - // The compiler driver cannot currently understand multple dex caches involved. Just bailout. + // The compiler driver cannot currently understand multiple dex caches involved. Just bailout. return false; } else { std::pair<bool, bool> pair = compiler_driver_->IsFastStaticField( @@ -984,6 +1065,7 @@ void HGraphBuilder::BuildFillArrayData(const Instruction& instruction, uint32_t default: LOG(FATAL) << "Unknown element width for " << payload->element_width; } + graph_->SetHasArrayAccesses(true); } void HGraphBuilder::BuildFillWideArrayData(HInstruction* object, diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index b14b69ba39..5163395cac 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -612,7 +612,7 @@ void CodeGenerator::BuildVMapTable(std::vector<uint8_t>* data) const { } void CodeGenerator::BuildStackMaps(std::vector<uint8_t>* data) { - uint32_t size = stack_map_stream_.ComputeNeededSize(); + uint32_t size = stack_map_stream_.PrepareForFillIn(); data->resize(size); MemoryRegion region(data->data(), size); stack_map_stream_.FillIn(region); @@ -654,7 +654,8 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, if (instruction == nullptr) { // For stack overflow checks. - stack_map_stream_.AddStackMapEntry(dex_pc, pc_info.native_pc, 0, 0, 0, inlining_depth); + stack_map_stream_.BeginStackMapEntry(dex_pc, pc_info.native_pc, 0, 0, 0, inlining_depth); + stack_map_stream_.EndStackMapEntry(); return; } LocationSummary* locations = instruction->GetLocations(); @@ -672,12 +673,12 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, } // The register mask must be a subset of callee-save registers. DCHECK_EQ(register_mask & core_callee_save_mask_, register_mask); - stack_map_stream_.AddStackMapEntry(dex_pc, - pc_info.native_pc, - register_mask, - locations->GetStackMask(), - environment_size, - inlining_depth); + stack_map_stream_.BeginStackMapEntry(dex_pc, + pc_info.native_pc, + register_mask, + locations->GetStackMask(), + environment_size, + inlining_depth); // Walk over the environment, and record the location of dex registers. for (size_t i = 0; i < environment_size; ++i) { @@ -823,6 +824,7 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, LOG(FATAL) << "Unexpected kind " << location.GetKind(); } } + stack_map_stream_.EndStackMapEntry(); } bool CodeGenerator::CanMoveNullCheckToUser(HNullCheck* null_check) { diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 38fa04315a..01748a9f5c 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -176,7 +176,6 @@ class LoadClassSlowPathARM : public SlowPathCodeARM { InvokeRuntimeCallingConvention calling_convention; __ LoadImmediate(calling_convention.GetRegisterAt(0), cls_->GetTypeIndex()); - arm_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1)); int32_t entry_point_offset = do_clinit_ ? QUICK_ENTRY_POINT(pInitializeStaticStorage) : QUICK_ENTRY_POINT(pInitializeType); @@ -222,7 +221,6 @@ class LoadStringSlowPathARM : public SlowPathCodeARM { SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; - arm_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1)); __ LoadImmediate(calling_convention.GetRegisterAt(0), instruction_->GetStringIndex()); arm_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pResolveString), instruction_, instruction_->GetDexPc(), this); @@ -1243,6 +1241,14 @@ void InstructionCodeGeneratorARM::VisitReturn(HReturn* ret) { } void LocationsBuilderARM::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation, but this step is not + // run in baseline. So we remove them manually here if we find them. + // TODO: Instead of this local workaround, address this properly. + if (invoke->IsStaticWithExplicitClinitCheck()) { + invoke->RemoveClinitCheckOrLoadClassAsLastInput(); + } + IntrinsicLocationsBuilderARM intrinsic(GetGraph()->GetArena(), codegen_->GetInstructionSetFeatures()); if (intrinsic.TryDispatch(invoke)) { @@ -1267,6 +1273,10 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorARM* codegen) } void InstructionCodeGeneratorARM::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); + if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; } @@ -3898,9 +3908,11 @@ void InstructionCodeGeneratorARM::VisitInstanceOf(HInstanceOf* instruction) { SlowPathCodeARM* slow_path = nullptr; // Return 0 if `obj` is null. - // TODO: avoid this check if we know obj is not null. - __ cmp(obj, ShifterOperand(0)); - __ b(&zero, EQ); + // avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ cmp(obj, ShifterOperand(0)); + __ b(&zero, EQ); + } // Compare the class of `obj` with `cls`. __ LoadFromOffset(kLoadWord, out, obj, class_offset); __ cmp(out, ShifterOperand(cls)); @@ -3919,8 +3931,12 @@ void InstructionCodeGeneratorARM::VisitInstanceOf(HInstanceOf* instruction) { __ LoadImmediate(out, 1); __ b(&done); } - __ Bind(&zero); - __ LoadImmediate(out, 0); + + if (instruction->MustDoNullCheck() || instruction->IsClassFinal()) { + __ Bind(&zero); + __ LoadImmediate(out, 0); + } + if (slow_path != nullptr) { __ Bind(slow_path->GetExitLabel()); } @@ -3946,9 +3962,11 @@ void InstructionCodeGeneratorARM::VisitCheckCast(HCheckCast* instruction) { instruction, locations->InAt(1), locations->GetTemp(0), instruction->GetDexPc()); codegen_->AddSlowPath(slow_path); - // TODO: avoid this check if we know obj is not null. - __ cmp(obj, ShifterOperand(0)); - __ b(slow_path->GetExitLabel(), EQ); + // avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ cmp(obj, ShifterOperand(0)); + __ b(slow_path->GetExitLabel(), EQ); + } // Compare the class of `obj` with `cls`. __ LoadFromOffset(kLoadWord, temp, obj, class_offset); __ cmp(temp, ShifterOperand(cls)); diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 23ba339a98..dada4ce5bd 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -173,14 +173,13 @@ class LoadClassSlowPathARM64 : public SlowPathCodeARM64 { InvokeRuntimeCallingConvention calling_convention; __ Mov(calling_convention.GetRegisterAt(0).W(), cls_->GetTypeIndex()); - arm64_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1).W()); int32_t entry_point_offset = do_clinit_ ? QUICK_ENTRY_POINT(pInitializeStaticStorage) : QUICK_ENTRY_POINT(pInitializeType); arm64_codegen->InvokeRuntime(entry_point_offset, at_, dex_pc_, this); if (do_clinit_) { - CheckEntrypointTypes<kQuickInitializeStaticStorage, void*, uint32_t, mirror::ArtMethod*>(); + CheckEntrypointTypes<kQuickInitializeStaticStorage, void*, uint32_t>(); } else { - CheckEntrypointTypes<kQuickInitializeType, void*, uint32_t, mirror::ArtMethod*>(); + CheckEntrypointTypes<kQuickInitializeType, void*, uint32_t>(); } // Move the class to the desired location. @@ -225,11 +224,10 @@ class LoadStringSlowPathARM64 : public SlowPathCodeARM64 { SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; - arm64_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1).W()); __ Mov(calling_convention.GetRegisterAt(0).W(), instruction_->GetStringIndex()); arm64_codegen->InvokeRuntime( QUICK_ENTRY_POINT(pResolveString), instruction_, instruction_->GetDexPc(), this); - CheckEntrypointTypes<kQuickResolveString, void*, uint32_t, mirror::ArtMethod*>(); + CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>(); Primitive::Type type = instruction_->GetType(); arm64_codegen->MoveLocation(locations->Out(), calling_convention.GetReturnLocation(type), type); @@ -1452,8 +1450,10 @@ void InstructionCodeGeneratorARM64::VisitCheckCast(HCheckCast* instruction) { instruction, locations->InAt(1), LocationFrom(obj_cls), instruction->GetDexPc()); codegen_->AddSlowPath(slow_path); - // TODO: avoid this check if we know obj is not null. - __ Cbz(obj, slow_path->GetExitLabel()); + // Avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ Cbz(obj, slow_path->GetExitLabel()); + } // Compare the class of `obj` with `cls`. __ Ldr(obj_cls, HeapOperand(obj, mirror::Object::ClassOffset())); __ Cmp(obj_cls, cls); @@ -1855,9 +1855,11 @@ void InstructionCodeGeneratorARM64::VisitInstanceOf(HInstanceOf* instruction) { vixl::Label done; // Return 0 if `obj` is null. - // TODO: Avoid this check if we know `obj` is not null. - __ Mov(out, 0); - __ Cbz(obj, &done); + // Avoid null check if we know `obj` is not null. + if (instruction->MustDoNullCheck()) { + __ Mov(out, 0); + __ Cbz(obj, &done); + } // Compare the class of `obj` with `cls`. __ Ldr(out, HeapOperand(obj, mirror::Object::ClassOffset())); @@ -1966,6 +1968,14 @@ void LocationsBuilderARM64::VisitInvokeVirtual(HInvokeVirtual* invoke) { } void LocationsBuilderARM64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation, but this step is not + // run in baseline. So we remove them manually here if we find them. + // TODO: Instead of this local workaround, address this properly. + if (invoke->IsStaticWithExplicitClinitCheck()) { + invoke->RemoveClinitCheckOrLoadClassAsLastInput(); + } + IntrinsicLocationsBuilderARM64 intrinsic(GetGraph()->GetArena()); if (intrinsic.TryDispatch(invoke)) { return; @@ -2016,6 +2026,10 @@ void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invok } void InstructionCodeGeneratorARM64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); + if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; } diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 3dcfca6a0c..f0c14f6700 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -174,7 +174,6 @@ class LoadStringSlowPathX86 : public SlowPathCodeX86 { SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; - x86_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1)); __ movl(calling_convention.GetRegisterAt(0), Immediate(instruction_->GetStringIndex())); __ fs()->call(Address::Absolute(QUICK_ENTRYPOINT_OFFSET(kX86WordSize, pResolveString))); RecordPcInfo(codegen, instruction_, instruction_->GetDexPc()); @@ -208,7 +207,6 @@ class LoadClassSlowPathX86 : public SlowPathCodeX86 { InvokeRuntimeCallingConvention calling_convention; __ movl(calling_convention.GetRegisterAt(0), Immediate(cls_->GetTypeIndex())); - x86_codegen->LoadCurrentMethod(calling_convention.GetRegisterAt(1)); __ fs()->call(Address::Absolute(do_clinit_ ? QUICK_ENTRYPOINT_OFFSET(kX86WordSize, pInitializeStaticStorage) : QUICK_ENTRYPOINT_OFFSET(kX86WordSize, pInitializeType))); @@ -1196,6 +1194,14 @@ void InstructionCodeGeneratorX86::VisitReturn(HReturn* ret) { } void LocationsBuilderX86::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation, but this step is not + // run in baseline. So we remove them manually here if we find them. + // TODO: Instead of this local workaround, address this properly. + if (invoke->IsStaticWithExplicitClinitCheck()) { + invoke->RemoveClinitCheckOrLoadClassAsLastInput(); + } + IntrinsicLocationsBuilderX86 intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { return; @@ -1214,6 +1220,10 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorX86* codegen) } void InstructionCodeGeneratorX86::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); + if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; } @@ -4250,9 +4260,11 @@ void InstructionCodeGeneratorX86::VisitInstanceOf(HInstanceOf* instruction) { SlowPathCodeX86* slow_path = nullptr; // Return 0 if `obj` is null. - // TODO: avoid this check if we know obj is not null. - __ testl(obj, obj); - __ j(kEqual, &zero); + // Avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ testl(obj, obj); + __ j(kEqual, &zero); + } __ movl(out, Address(obj, class_offset)); // Compare the class of `obj` with `cls`. if (cls.IsRegister()) { @@ -4277,8 +4289,12 @@ void InstructionCodeGeneratorX86::VisitInstanceOf(HInstanceOf* instruction) { __ movl(out, Immediate(1)); __ jmp(&done); } - __ Bind(&zero); - __ movl(out, Immediate(0)); + + if (instruction->MustDoNullCheck() || instruction->IsClassFinal()) { + __ Bind(&zero); + __ movl(out, Immediate(0)); + } + if (slow_path != nullptr) { __ Bind(slow_path->GetExitLabel()); } @@ -4303,11 +4319,13 @@ void InstructionCodeGeneratorX86::VisitCheckCast(HCheckCast* instruction) { instruction, locations->InAt(1), locations->GetTemp(0), instruction->GetDexPc()); codegen_->AddSlowPath(slow_path); - // TODO: avoid this check if we know obj is not null. - __ testl(obj, obj); - __ j(kEqual, slow_path->GetExitLabel()); - __ movl(temp, Address(obj, class_offset)); + // Avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ testl(obj, obj); + __ j(kEqual, slow_path->GetExitLabel()); + } + __ movl(temp, Address(obj, class_offset)); // Compare the class of `obj` with `cls`. if (cls.IsRegister()) { __ cmpl(temp, cls.AsRegister<Register>()); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index b404f8de3d..642900f461 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -197,7 +197,6 @@ class LoadClassSlowPathX86_64 : public SlowPathCodeX86_64 { InvokeRuntimeCallingConvention calling_convention; __ movl(CpuRegister(calling_convention.GetRegisterAt(0)), Immediate(cls_->GetTypeIndex())); - x64_codegen->LoadCurrentMethod(CpuRegister(calling_convention.GetRegisterAt(1))); __ gs()->call(Address::Absolute((do_clinit_ ? QUICK_ENTRYPOINT_OFFSET(kX86_64WordSize, pInitializeStaticStorage) : QUICK_ENTRYPOINT_OFFSET(kX86_64WordSize, pInitializeType)) , true)); @@ -244,7 +243,6 @@ class LoadStringSlowPathX86_64 : public SlowPathCodeX86_64 { SaveLiveRegisters(codegen, locations); InvokeRuntimeCallingConvention calling_convention; - x64_codegen->LoadCurrentMethod(CpuRegister(calling_convention.GetRegisterAt(1))); __ movl(CpuRegister(calling_convention.GetRegisterAt(0)), Immediate(instruction_->GetStringIndex())); __ gs()->call(Address::Absolute( @@ -1291,6 +1289,14 @@ Location InvokeDexCallingConventionVisitor::GetNextLocation(Primitive::Type type } void LocationsBuilderX86_64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation, but this step is not + // run in baseline. So we remove them manually here if we find them. + // TODO: Instead of this local workaround, address this properly. + if (invoke->IsStaticWithExplicitClinitCheck()) { + invoke->RemoveClinitCheckOrLoadClassAsLastInput(); + } + IntrinsicLocationsBuilderX86_64 intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { return; @@ -1309,6 +1315,10 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorX86_64* codeg } void InstructionCodeGeneratorX86_64::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + // Explicit clinit checks triggered by static invokes must have been + // pruned by art::PrepareForRegisterAllocation. + DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); + if (TryGenerateIntrinsicCode(invoke, codegen_)) { return; } @@ -4181,9 +4191,11 @@ void InstructionCodeGeneratorX86_64::VisitInstanceOf(HInstanceOf* instruction) { SlowPathCodeX86_64* slow_path = nullptr; // Return 0 if `obj` is null. - // TODO: avoid this check if we know obj is not null. - __ testl(obj, obj); - __ j(kEqual, &zero); + // Avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ testl(obj, obj); + __ j(kEqual, &zero); + } // Compare the class of `obj` with `cls`. __ movl(out, Address(obj, class_offset)); if (cls.IsRegister()) { @@ -4207,8 +4219,12 @@ void InstructionCodeGeneratorX86_64::VisitInstanceOf(HInstanceOf* instruction) { __ movl(out, Immediate(1)); __ jmp(&done); } - __ Bind(&zero); - __ movl(out, Immediate(0)); + + if (instruction->MustDoNullCheck() || instruction->IsClassFinal()) { + __ Bind(&zero); + __ movl(out, Immediate(0)); + } + if (slow_path != nullptr) { __ Bind(slow_path->GetExitLabel()); } @@ -4233,9 +4249,11 @@ void InstructionCodeGeneratorX86_64::VisitCheckCast(HCheckCast* instruction) { instruction, locations->InAt(1), locations->GetTemp(0), instruction->GetDexPc()); codegen_->AddSlowPath(slow_path); - // TODO: avoid this check if we know obj is not null. - __ testl(obj, obj); - __ j(kEqual, slow_path->GetExitLabel()); + // Avoid null check if we know obj is not null. + if (instruction->MustDoNullCheck()) { + __ testl(obj, obj); + __ j(kEqual, slow_path->GetExitLabel()); + } // Compare the class of `obj` with `cls`. __ movl(temp, Address(obj, class_offset)); if (cls.IsRegister()) { diff --git a/compiler/optimizing/constant_folding.h b/compiler/optimizing/constant_folding.h index ac00824e33..66ff57855e 100644 --- a/compiler/optimizing/constant_folding.h +++ b/compiler/optimizing/constant_folding.h @@ -32,8 +32,8 @@ namespace art { */ class HConstantFolding : public HOptimization { public: - explicit HConstantFolding(HGraph* graph) - : HOptimization(graph, true, kConstantFoldingPassName) {} + explicit HConstantFolding(HGraph* graph, const char* name = kConstantFoldingPassName) + : HOptimization(graph, true, name) {} void Run() OVERRIDE; diff --git a/compiler/optimizing/constant_folding_test.cc b/compiler/optimizing/constant_folding_test.cc index 02ad675dc3..422223f5e0 100644 --- a/compiler/optimizing/constant_folding_test.cc +++ b/compiler/optimizing/constant_folding_test.cc @@ -572,14 +572,19 @@ TEST(ConstantFolding, IntConstantFoldingAndJumps) { }; // Expected difference after dead code elimination. - diff_t expected_dce_diff = { - { " 3: IntConstant\n", removed }, - { " 13: IntConstant\n", removed }, - { " 18: IntConstant\n", removed }, - { " 24: IntConstant\n", removed }, - { " 34: IntConstant\n", removed }, - }; - std::string expected_after_dce = Patch(expected_after_cf, expected_dce_diff); + std::string expected_after_dce = + "BasicBlock 0, succ: 1\n" + " 5: IntConstant []\n" + " 30: SuspendCheck\n" + " 32: IntConstant []\n" + " 33: IntConstant []\n" + " 35: IntConstant [28]\n" + " 31: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 5\n" + " 21: SuspendCheck\n" + " 28: Return(35)\n" + "BasicBlock 5, pred: 1\n" + " 29: Exit\n"; TestCode(data, expected_before, @@ -647,13 +652,15 @@ TEST(ConstantFolding, ConstantCondition) { ASSERT_EQ(inst->AsIntConstant()->GetValue(), 1); }; - // Expected difference after dead code elimination. - diff_t expected_dce_diff = { - { " 3: IntConstant [9, 15, 22]\n", " 3: IntConstant [9, 22]\n" }, - { " 22: Phi(3, 5) [15]\n", " 22: Phi(3, 5)\n" }, - { " 15: Add(22, 3)\n", removed } - }; - std::string expected_after_dce = Patch(expected_after_cf, expected_dce_diff); + // Expected graph after dead code elimination. + std::string expected_after_dce = + "BasicBlock 0, succ: 1\n" + " 19: SuspendCheck\n" + " 20: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 4\n" + " 17: ReturnVoid\n" + "BasicBlock 4, pred: 1\n" + " 18: Exit\n"; TestCode(data, expected_before, diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 8045cc5027..91cd60acce 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -20,10 +20,78 @@ namespace art { -void HDeadCodeElimination::Run() { +static void MarkReachableBlocks(HBasicBlock* block, ArenaBitVector* visited) { + int block_id = block->GetBlockId(); + if (visited->IsBitSet(block_id)) { + return; + } + visited->SetBit(block_id); + + HInstruction* last_instruction = block->GetLastInstruction(); + if (last_instruction->IsIf()) { + HIf* if_instruction = last_instruction->AsIf(); + HInstruction* condition = if_instruction->InputAt(0); + if (!condition->IsIntConstant()) { + MarkReachableBlocks(if_instruction->IfTrueSuccessor(), visited); + MarkReachableBlocks(if_instruction->IfFalseSuccessor(), visited); + } else if (condition->AsIntConstant()->IsOne()) { + MarkReachableBlocks(if_instruction->IfTrueSuccessor(), visited); + } else { + DCHECK(condition->AsIntConstant()->IsZero()); + MarkReachableBlocks(if_instruction->IfFalseSuccessor(), visited); + } + } else { + for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) { + MarkReachableBlocks(block->GetSuccessors().Get(i), visited); + } + } +} + +void HDeadCodeElimination::MaybeRecordDeadBlock(HBasicBlock* block) { + if (stats_ != nullptr) { + stats_->RecordStat(MethodCompilationStat::kRemovedDeadInstruction, + block->GetPhis().CountSize() + block->GetInstructions().CountSize()); + } +} + +void HDeadCodeElimination::RemoveDeadBlocks() { + // Classify blocks as reachable/unreachable. + ArenaAllocator* allocator = graph_->GetArena(); + ArenaBitVector live_blocks(allocator, graph_->GetBlocks().Size(), false); + MarkReachableBlocks(graph_->GetEntryBlock(), &live_blocks); + + // Remove all dead blocks. Process blocks in post-order, because removal needs + // the block's chain of dominators. + for (HPostOrderIterator it(*graph_); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + if (live_blocks.IsBitSet(block->GetBlockId())) { + continue; + } + MaybeRecordDeadBlock(block); + block->DisconnectAndDelete(); + } + + // Connect successive blocks created by dead branches. Order does not matter. + for (HReversePostOrderIterator it(*graph_); !it.Done();) { + HBasicBlock* block = it.Current(); + if (block->IsEntryBlock() || block->GetSuccessors().Size() != 1u) { + it.Advance(); + continue; + } + HBasicBlock* successor = block->GetSuccessors().Get(0); + if (successor->IsExitBlock() || successor->GetPredecessors().Size() != 1u) { + it.Advance(); + continue; + } + block->MergeWith(successor); + + // Reiterate on this block in case it can be merged with its new successor. + } +} + +void HDeadCodeElimination::RemoveDeadInstructions() { // Process basic blocks in post-order in the dominator tree, so that - // a dead instruction depending on another dead instruction is - // removed. + // a dead instruction depending on another dead instruction is removed. for (HPostOrderIterator b(*graph_); !b.Done(); b.Advance()) { HBasicBlock* block = b.Current(); // Traverse this block's instructions in backward order and remove @@ -47,4 +115,9 @@ void HDeadCodeElimination::Run() { } } +void HDeadCodeElimination::Run() { + RemoveDeadBlocks(); + RemoveDeadInstructions(); +} + } // namespace art diff --git a/compiler/optimizing/dead_code_elimination.h b/compiler/optimizing/dead_code_elimination.h index cee9364c84..0bea0fc1c2 100644 --- a/compiler/optimizing/dead_code_elimination.h +++ b/compiler/optimizing/dead_code_elimination.h @@ -40,6 +40,10 @@ class HDeadCodeElimination : public HOptimization { "dead_code_elimination"; private: + void MaybeRecordDeadBlock(HBasicBlock* block); + void RemoveDeadBlocks(); + void RemoveDeadInstructions(); + DISALLOW_COPY_AND_ASSIGN(HDeadCodeElimination); }; diff --git a/compiler/optimizing/dead_code_elimination_test.cc b/compiler/optimizing/dead_code_elimination_test.cc index 98ae1ec5d3..3209d3eb18 100644 --- a/compiler/optimizing/dead_code_elimination_test.cc +++ b/compiler/optimizing/dead_code_elimination_test.cc @@ -169,20 +169,25 @@ TEST(DeadCodeElimination, AdditionsAndInconditionalJumps) { "BasicBlock 5, pred: 4\n" " 28: Exit\n"; - // Expected difference after dead code elimination. - diff_t expected_diff = { - { " 13: IntConstant [14]\n", removed }, - { " 24: IntConstant [25]\n", removed }, - { " 14: Add(19, 13) [25]\n", removed }, - // The SuspendCheck instruction following this Add instruction - // inserts the latter in an environment, thus making it "used" and - // therefore non removable. It ensues that some other Add and - // IntConstant instructions cannot be removed, as they are direct - // or indirect inputs of the initial Add instruction. - { " 19: Add(9, 18) [14]\n", " 19: Add(9, 18) []\n" }, - { " 25: Add(14, 24)\n", removed }, - }; - std::string expected_after = Patch(expected_before, expected_diff); + // The SuspendCheck instruction following this Add instruction + // inserts the latter in an environment, thus making it "used" and + // therefore non removable. It ensures that some other Add and + // IntConstant instructions cannot be removed, as they are direct + // or indirect inputs of the initial Add instruction. + std::string expected_after = + "BasicBlock 0, succ: 1\n" + " 3: IntConstant [9]\n" + " 5: IntConstant [9]\n" + " 18: IntConstant [19]\n" + " 29: SuspendCheck\n" + " 30: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 5\n" + " 9: Add(3, 5) [19]\n" + " 19: Add(9, 18) []\n" + " 21: SuspendCheck\n" + " 27: ReturnVoid\n" + "BasicBlock 5, pred: 1\n" + " 28: Exit\n"; TestCode(data, expected_before, expected_after); } diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 8950635d6a..890676467f 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -191,6 +191,30 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { } } +void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + VisitInstruction(invoke); + + if (invoke->IsStaticWithExplicitClinitCheck()) { + size_t last_input_index = invoke->InputCount() - 1; + HInstruction* last_input = invoke->InputAt(last_input_index); + if (last_input == nullptr) { + AddError(StringPrintf("Static invoke %s:%d marked as having an explicit clinit check " + "has a null pointer as last input.", + invoke->DebugName(), + invoke->GetId())); + } + if (!last_input->IsClinitCheck() && !last_input->IsLoadClass()) { + AddError(StringPrintf("Static invoke %s:%d marked as having an explicit clinit check " + "has a last instruction (%s:%d) which is neither a clinit check " + "nor a load class instruction.", + invoke->DebugName(), + invoke->GetId(), + last_input->DebugName(), + last_input->GetId())); + } + } +} + void SSAChecker::VisitBasicBlock(HBasicBlock* block) { super_type::VisitBasicBlock(block); @@ -264,6 +288,8 @@ void SSAChecker::CheckLoop(HBasicBlock* loop_header) { } } + const ArenaBitVector& loop_blocks = loop_header->GetLoopInformation()->GetBlocks(); + // Ensure there is only one back edge per loop. size_t num_back_edges = loop_header->GetLoopInformation()->GetBackEdges().Size(); @@ -276,16 +302,27 @@ void SSAChecker::CheckLoop(HBasicBlock* loop_header) { "Loop defined by header %d has several back edges: %zu.", id, num_back_edges)); + } else { + DCHECK_EQ(num_back_edges, 1u); + int back_edge_id = loop_header->GetLoopInformation()->GetBackEdges().Get(0)->GetBlockId(); + if (!loop_blocks.IsBitSet(back_edge_id)) { + AddError(StringPrintf( + "Loop defined by header %d has an invalid back edge %d.", + id, + back_edge_id)); + } } - // Ensure all blocks in the loop are dominated by the loop header. - const ArenaBitVector& loop_blocks = - loop_header->GetLoopInformation()->GetBlocks(); + // Ensure all blocks in the loop are live and dominated by the loop header. for (uint32_t i : loop_blocks.Indexes()) { HBasicBlock* loop_block = GetGraph()->GetBlocks().Get(i); - if (!loop_header->Dominates(loop_block)) { + if (loop_block == nullptr) { + AddError(StringPrintf("Loop defined by header %d contains a previously removed block %d.", + id, + i)); + } else if (!loop_header->Dominates(loop_block)) { AddError(StringPrintf("Loop block %d not dominated by loop header %d.", - loop_block->GetBlockId(), + i, id)); } } @@ -296,7 +333,7 @@ void SSAChecker::CheckLoop(HBasicBlock* loop_header) { if (!loop_blocks.IsSubsetOf(&outer_info->GetBlocks())) { AddError(StringPrintf("Blocks of loop defined by header %d are not a subset of blocks of " "an outer loop defined by header %d.", - loop_header->GetBlockId(), + id, outer_info->GetHeader()->GetBlockId())); } } @@ -483,7 +520,7 @@ void SSAChecker::VisitBinaryOperation(HBinaryOperation* op) { Primitive::PrettyDescriptor(op->InputAt(1)->GetType()))); } } else { - if (PrimitiveKind(op->InputAt(1)->GetType()) != PrimitiveKind(op->InputAt(0)->GetType())) { + if (PrimitiveKind(op->InputAt(0)->GetType()) != PrimitiveKind(op->InputAt(1)->GetType())) { AddError(StringPrintf( "Binary operation %s %d has inputs of different types: " "%s, and %s.", @@ -508,7 +545,7 @@ void SSAChecker::VisitBinaryOperation(HBinaryOperation* op) { "from its input type: %s vs %s.", op->DebugName(), op->GetId(), Primitive::PrettyDescriptor(op->GetType()), - Primitive::PrettyDescriptor(op->InputAt(1)->GetType()))); + Primitive::PrettyDescriptor(op->InputAt(0)->GetType()))); } } } diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h index 24fee373f9..45e8804edb 100644 --- a/compiler/optimizing/graph_checker.h +++ b/compiler/optimizing/graph_checker.h @@ -42,6 +42,9 @@ class GraphChecker : public HGraphDelegateVisitor { // Check `instruction`. void VisitInstruction(HInstruction* instruction) OVERRIDE; + // Perform control-flow graph checks on instruction. + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; + // Was the last visit of the graph valid? bool IsValid() const { return errors_.empty(); diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index bffd639e83..37b57533c1 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -130,6 +130,16 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } + if (invoke_instruction->IsInvokeStaticOrDirect() && + invoke_instruction->AsInvokeStaticOrDirect()->IsStaticWithImplicitClinitCheck()) { + // Case of a static method that cannot be inlined because it implicitly + // requires an initialization check of its declaring class. + VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + << " is not inlined because it is static and requires a clinit" + << " check that cannot be emitted due to Dex cache limitations"; + return false; + } + if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, can_use_dex_cache)) { resolved_method->SetShouldNotInline(); return false; diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 98c0eedeb2..2df7c166d8 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -62,6 +62,7 @@ class InstructionSimplifierVisitor : public HGraphVisitor { void VisitSub(HSub* instruction) OVERRIDE; void VisitUShr(HUShr* instruction) OVERRIDE; void VisitXor(HXor* instruction) OVERRIDE; + void VisitInstanceOf(HInstanceOf* instruction) OVERRIDE; OptimizingCompilerStats* stats_; bool simplification_occurred_ = false; @@ -159,6 +160,10 @@ void InstructionSimplifierVisitor::VisitNullCheck(HNullCheck* null_check) { void InstructionSimplifierVisitor::VisitCheckCast(HCheckCast* check_cast) { HLoadClass* load_class = check_cast->InputAt(1)->AsLoadClass(); + if (!check_cast->InputAt(0)->CanBeNull()) { + check_cast->ClearMustDoNullCheck(); + } + if (!load_class->IsResolved()) { // If the class couldn't be resolve it's not safe to compare against it. It's // default type would be Top which might be wider that the actual class type @@ -176,6 +181,12 @@ void InstructionSimplifierVisitor::VisitCheckCast(HCheckCast* check_cast) { } } +void InstructionSimplifierVisitor::VisitInstanceOf(HInstanceOf* instruction) { + if (!instruction->InputAt(0)->CanBeNull()) { + instruction->ClearMustDoNullCheck(); + } +} + void InstructionSimplifierVisitor::VisitSuspendCheck(HSuspendCheck* check) { HBasicBlock* block = check->GetBlock(); // Currently always keep the suspend check at entry. @@ -427,9 +438,16 @@ void InstructionSimplifierVisitor::VisitMul(HMul* instruction) { if (Primitive::IsIntOrLongType(type)) { int64_t factor = Int64FromConstant(input_cst); - // We expect the `0` case to have been handled in the constant folding pass. - DCHECK_NE(factor, 0); - if (IsPowerOfTwo(factor)) { + // Even though constant propagation also takes care of the zero case, other + // optimizations can lead to having a zero multiplication. + if (factor == 0) { + // Replace code looking like + // MUL dst, src, 0 + // with + // 0 + instruction->ReplaceWith(input_cst); + instruction->GetBlock()->RemoveInstruction(instruction); + } else if (IsPowerOfTwo(factor)) { // Replace code looking like // MUL dst, src, pow_of_2 // with diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc index 932192e4fd..abdf04ebb1 100644 --- a/compiler/optimizing/intrinsics_arm.cc +++ b/compiler/optimizing/intrinsics_arm.cc @@ -79,6 +79,7 @@ static void MoveFromReturnRegister(Location trg, Primitive::Type type, CodeGener static void MoveArguments(HInvoke* invoke, ArenaAllocator* arena, CodeGeneratorARM* codegen) { if (invoke->InputCount() == 0) { + // No argument to move. return; } diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index 117d6a4279..7a753b2da9 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -88,6 +88,7 @@ static void MoveFromReturnRegister(Location trg, static void MoveArguments(HInvoke* invoke, ArenaAllocator* arena, CodeGeneratorARM64* codegen) { if (invoke->InputCount() == 0) { + // No argument to move. return; } diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index a8e2cdf1f6..7275edb695 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -113,6 +113,7 @@ static void MoveFromReturnRegister(Location target, static void MoveArguments(HInvoke* invoke, ArenaAllocator* arena, CodeGeneratorX86* codegen) { if (invoke->InputCount() == 0) { + // No argument to move. return; } @@ -1038,7 +1039,7 @@ static void CreateLongIntToVoidLocations(ArenaAllocator* arena, Primitive::Type LocationSummary::kNoCall, kIntrinsified); locations->SetInAt(0, Location::RequiresRegister()); - HInstruction *value = invoke->InputAt(1); + HInstruction* value = invoke->InputAt(1); if (size == Primitive::kPrimByte) { locations->SetInAt(1, Location::ByteRegisterOrConstant(EDX, value)); } else { diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index 5d24d1fbfb..35daaf60bb 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -105,6 +105,7 @@ static void MoveFromReturnRegister(Location trg, static void MoveArguments(HInvoke* invoke, ArenaAllocator* arena, CodeGeneratorX86_64* codegen) { if (invoke->InputCount() == 0) { + // No argument to move. return; } diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 6ab57b8e50..c158ddf4ee 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -672,6 +672,11 @@ void HPhi::AddInput(HInstruction* input) { input->AddUseAt(this, inputs_.Size() - 1); } +void HPhi::RemoveInputAt(size_t index) { + RemoveAsUserOfInput(index); + inputs_.DeleteAt(index); +} + #define DEFINE_ACCEPT(name, super) \ void H##name::Accept(HGraphVisitor* visitor) { \ visitor->Visit##name(this); \ @@ -867,6 +872,15 @@ bool HBasicBlock::HasSinglePhi() const { return !GetPhis().IsEmpty() && GetFirstPhi()->GetNext() == nullptr; } +size_t HInstructionList::CountSize() const { + size_t size = 0; + HInstruction* current = first_instruction_; + for (; current != nullptr; current = current->GetNext()) { + size++; + } + return size; +} + void HInstructionList::SetBlockOfInstructions(HBasicBlock* block) const { for (HInstruction* current = first_instruction_; current != nullptr; @@ -898,40 +912,167 @@ void HInstructionList::Add(const HInstructionList& instruction_list) { } } -void HBasicBlock::DisconnectFromAll() { - DCHECK(dominated_blocks_.IsEmpty()) << "Unimplemented scenario"; +void HBasicBlock::DisconnectAndDelete() { + // Dominators must be removed after all the blocks they dominate. This way + // a loop header is removed last, a requirement for correct loop information + // iteration. + DCHECK(dominated_blocks_.IsEmpty()); + + // Remove the block from all loops it is included in. + for (HLoopInformationOutwardIterator it(*this); !it.Done(); it.Advance()) { + HLoopInformation* loop_info = it.Current(); + loop_info->Remove(this); + if (loop_info->IsBackEdge(*this)) { + // This deliberately leaves the loop in an inconsistent state and will + // fail SSAChecker unless the entire loop is removed during the pass. + loop_info->RemoveBackEdge(this); + } + } + // Disconnect the block from its predecessors and update their control-flow + // instructions. for (size_t i = 0, e = predecessors_.Size(); i < e; ++i) { - predecessors_.Get(i)->successors_.Delete(this); + HBasicBlock* predecessor = predecessors_.Get(i); + HInstruction* last_instruction = predecessor->GetLastInstruction(); + predecessor->RemoveInstruction(last_instruction); + predecessor->RemoveSuccessor(this); + if (predecessor->GetSuccessors().Size() == 1u) { + DCHECK(last_instruction->IsIf()); + predecessor->AddInstruction(new (graph_->GetArena()) HGoto()); + } else { + // The predecessor has no remaining successors and therefore must be dead. + // We deliberately leave it without a control-flow instruction so that the + // SSAChecker fails unless it is not removed during the pass too. + DCHECK_EQ(predecessor->GetSuccessors().Size(), 0u); + } } + predecessors_.Reset(); + + // Disconnect the block from its successors and update their dominators + // and phis. for (size_t i = 0, e = successors_.Size(); i < e; ++i) { - successors_.Get(i)->predecessors_.Delete(this); - } - dominator_->dominated_blocks_.Delete(this); + HBasicBlock* successor = successors_.Get(i); + // Delete this block from the list of predecessors. + size_t this_index = successor->GetPredecessorIndexOf(this); + successor->predecessors_.DeleteAt(this_index); + + // Check that `successor` has other predecessors, otherwise `this` is the + // dominator of `successor` which violates the order DCHECKed at the top. + DCHECK(!successor->predecessors_.IsEmpty()); + + // Recompute the successor's dominator. + HBasicBlock* old_dominator = successor->GetDominator(); + HBasicBlock* new_dominator = successor->predecessors_.Get(0); + for (size_t j = 1, f = successor->predecessors_.Size(); j < f; ++j) { + new_dominator = graph_->FindCommonDominator( + new_dominator, successor->predecessors_.Get(j)); + } + if (old_dominator != new_dominator) { + successor->SetDominator(new_dominator); + old_dominator->RemoveDominatedBlock(successor); + new_dominator->AddDominatedBlock(successor); + } - predecessors_.Reset(); + // Remove this block's entries in the successor's phis. + if (successor->predecessors_.Size() == 1u) { + // The successor has just one predecessor left. Replace phis with the only + // remaining input. + for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + HPhi* phi = phi_it.Current()->AsPhi(); + phi->ReplaceWith(phi->InputAt(1 - this_index)); + successor->RemovePhi(phi); + } + } else { + for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + phi_it.Current()->AsPhi()->RemoveInputAt(this_index); + } + } + } successors_.Reset(); - dominator_ = nullptr; - graph_ = nullptr; + + // Disconnect from the dominator. + dominator_->RemoveDominatedBlock(this); + SetDominator(nullptr); + + // Delete from the graph. The function safely deletes remaining instructions + // and updates the reverse post order. + graph_->DeleteDeadBlock(this); + SetGraph(nullptr); } void HBasicBlock::MergeWith(HBasicBlock* other) { - DCHECK(successors_.IsEmpty()) << "Unimplemented block merge scenario"; - DCHECK(dominated_blocks_.IsEmpty() - || (dominated_blocks_.Size() == 1 && dominated_blocks_.Get(0) == other)) - << "Unimplemented block merge scenario"; + DCHECK_EQ(GetGraph(), other->GetGraph()); + DCHECK(GetDominatedBlocks().Contains(other)); + DCHECK_EQ(GetSuccessors().Size(), 1u); + DCHECK_EQ(GetSuccessors().Get(0), other); + DCHECK_EQ(other->GetPredecessors().Size(), 1u); + DCHECK_EQ(other->GetPredecessors().Get(0), this); DCHECK(other->GetPhis().IsEmpty()); + // Move instructions from `other` to `this`. + DCHECK(EndsWithControlFlowInstruction()); + RemoveInstruction(GetLastInstruction()); + instructions_.Add(other->GetInstructions()); + other->instructions_.SetBlockOfInstructions(this); + other->instructions_.Clear(); + + // Remove `other` from the loops it is included in. + for (HLoopInformationOutwardIterator it(*other); !it.Done(); it.Advance()) { + HLoopInformation* loop_info = it.Current(); + loop_info->Remove(other); + if (loop_info->IsBackEdge(*other)) { + loop_info->ClearBackEdges(); + loop_info->AddBackEdge(this); + } + } + + // Update links to the successors of `other`. successors_.Reset(); - dominated_blocks_.Reset(); + while (!other->successors_.IsEmpty()) { + HBasicBlock* successor = other->successors_.Get(0); + successor->ReplacePredecessor(other, this); + } + + // Update the dominator tree. + dominated_blocks_.Delete(other); + for (size_t i = 0, e = other->GetDominatedBlocks().Size(); i < e; ++i) { + HBasicBlock* dominated = other->GetDominatedBlocks().Get(i); + dominated_blocks_.Add(dominated); + dominated->SetDominator(this); + } + other->dominated_blocks_.Reset(); + other->dominator_ = nullptr; + + // Clear the list of predecessors of `other` in preparation of deleting it. + other->predecessors_.Reset(); + + // Delete `other` from the graph. The function updates reverse post order. + graph_->DeleteDeadBlock(other); + other->SetGraph(nullptr); +} + +void HBasicBlock::MergeWithInlined(HBasicBlock* other) { + DCHECK_NE(GetGraph(), other->GetGraph()); + DCHECK(GetDominatedBlocks().IsEmpty()); + DCHECK(GetSuccessors().IsEmpty()); + DCHECK(!EndsWithControlFlowInstruction()); + DCHECK_EQ(other->GetPredecessors().Size(), 1u); + DCHECK(other->GetPredecessors().Get(0)->IsEntryBlock()); + DCHECK(other->GetPhis().IsEmpty()); + DCHECK(!other->IsInLoop()); + + // Move instructions from `other` to `this`. instructions_.Add(other->GetInstructions()); - other->GetInstructions().SetBlockOfInstructions(this); + other->instructions_.SetBlockOfInstructions(this); - while (!other->GetSuccessors().IsEmpty()) { - HBasicBlock* successor = other->GetSuccessors().Get(0); + // Update links to the successors of `other`. + successors_.Reset(); + while (!other->successors_.IsEmpty()) { + HBasicBlock* successor = other->successors_.Get(0); successor->ReplacePredecessor(other, this); } + // Update the dominator tree. for (size_t i = 0, e = other->GetDominatedBlocks().Size(); i < e; ++i) { HBasicBlock* dominated = other->GetDominatedBlocks().Get(i); dominated_blocks_.Add(dominated); @@ -973,6 +1114,24 @@ static void MakeRoomFor(GrowableArray<HBasicBlock*>* blocks, } } +void HGraph::DeleteDeadBlock(HBasicBlock* block) { + DCHECK_EQ(block->GetGraph(), this); + DCHECK(block->GetSuccessors().IsEmpty()); + DCHECK(block->GetPredecessors().IsEmpty()); + DCHECK(block->GetDominatedBlocks().IsEmpty()); + DCHECK(block->GetDominator() == nullptr); + + for (HBackwardInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + block->RemoveInstruction(it.Current()); + } + for (HBackwardInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { + block->RemovePhi(it.Current()->AsPhi()); + } + + reverse_post_order_.Delete(block); + blocks_.Put(block->GetBlockId(), nullptr); +} + void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { if (GetBlocks().Size() == 3) { // Simple case of an entry block, a body block, and an exit block. @@ -1005,7 +1164,7 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { HBasicBlock* first = entry_block_->GetSuccessors().Get(0); DCHECK(!first->IsInLoop()); - at->MergeWith(first); + at->MergeWithInlined(first); exit_block_->ReplaceWith(to); // Update all predecessors of the exit block (now the `to` block) @@ -1113,7 +1272,7 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // - Remove suspend checks, that hold an environment. // We must do this after the other blocks have been inlined, otherwise ids of // constants could overlap with the inner graph. - int parameter_index = 0; + size_t parameter_index = 0; for (HInstructionIterator it(entry_block_->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* current = it.Current(); if (current->IsNullConstant()) { @@ -1126,6 +1285,14 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // TODO: Don't duplicate floating-point constants. current->MoveBefore(outer_graph->GetEntryBlock()->GetLastInstruction()); } else if (current->IsParameterValue()) { + if (kIsDebugBuild + && invoke->IsInvokeStaticOrDirect() + && invoke->AsInvokeStaticOrDirect()->IsStaticWithExplicitClinitCheck()) { + // Ensure we do not use the last input of `invoke`, as it + // contains a clinit check which is not an actual argument. + size_t last_input_index = invoke->InputCount() - 1; + DCHECK(parameter_index != last_input_index); + } current->ReplaceWith(invoke->InputAt(parameter_index++)); } else { DCHECK(current->IsGoto() || current->IsSuspendCheck()); @@ -1137,53 +1304,6 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { invoke->GetBlock()->RemoveInstruction(invoke); } -void HGraph::MergeEmptyBranches(HBasicBlock* start_block, HBasicBlock* end_block) { - // Find the two branches of an If. - DCHECK_EQ(start_block->GetSuccessors().Size(), 2u); - HBasicBlock* left_branch = start_block->GetSuccessors().Get(0); - HBasicBlock* right_branch = start_block->GetSuccessors().Get(1); - - // Make sure this is a diamond control-flow path. - DCHECK_EQ(left_branch->GetSuccessors().Get(0), end_block); - DCHECK_EQ(right_branch->GetSuccessors().Get(0), end_block); - DCHECK_EQ(end_block->GetPredecessors().Size(), 2u); - DCHECK_EQ(start_block, end_block->GetDominator()); - - // Disconnect the branches and merge the two blocks. This will move - // all instructions from 'end_block' to 'start_block'. - DCHECK(left_branch->IsSingleGoto()); - DCHECK(right_branch->IsSingleGoto()); - left_branch->DisconnectFromAll(); - right_branch->DisconnectFromAll(); - start_block->RemoveInstruction(start_block->GetLastInstruction()); - start_block->MergeWith(end_block); - - // Delete the now redundant blocks from the graph. - blocks_.Put(left_branch->GetBlockId(), nullptr); - blocks_.Put(right_branch->GetBlockId(), nullptr); - blocks_.Put(end_block->GetBlockId(), nullptr); - - // Update reverse post order. - reverse_post_order_.Delete(left_branch); - reverse_post_order_.Delete(right_branch); - reverse_post_order_.Delete(end_block); - - // Update loops which contain the code. - for (HLoopInformationOutwardIterator it(*start_block); !it.Done(); it.Advance()) { - HLoopInformation* loop_info = it.Current(); - DCHECK(loop_info->Contains(*left_branch)); - DCHECK(loop_info->Contains(*right_branch)); - DCHECK(loop_info->Contains(*end_block)); - loop_info->Remove(left_branch); - loop_info->Remove(right_branch); - loop_info->Remove(end_block); - if (loop_info->IsBackEdge(*end_block)) { - loop_info->RemoveBackEdge(end_block); - loop_info->AddBackEdge(start_block); - } - } -} - std::ostream& operator<<(std::ostream& os, const ReferenceTypeInfo& rhs) { ScopedObjectAccess soa(Thread::Current()); os << "[" diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 0993a18046..946091f70d 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -97,6 +97,9 @@ class HInstructionList { void AddAfter(HInstruction* cursor, const HInstructionList& instruction_list); void Add(const HInstructionList& instruction_list); + // Return the number of instructions in the list. This is an expensive operation. + size_t CountSize() const; + private: HInstruction* first_instruction_; HInstruction* last_instruction_; @@ -168,7 +171,8 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // Inline this graph in `outer_graph`, replacing the given `invoke` instruction. void InlineInto(HGraph* outer_graph, HInvoke* invoke); - void MergeEmptyBranches(HBasicBlock* start_block, HBasicBlock* end_block); + // Removes `block` from the graph. + void DeleteDeadBlock(HBasicBlock* block); void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor); void SimplifyLoop(HBasicBlock* header); @@ -248,8 +252,9 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { return CreateConstant(value, &cached_long_constants_); } - private: HBasicBlock* FindCommonDominator(HBasicBlock* first, HBasicBlock* second) const; + + private: void VisitBlockForDominatorTree(HBasicBlock* block, HBasicBlock* predecessor, GrowableArray<size_t>* visits); @@ -451,6 +456,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { HBasicBlock* GetDominator() const { return dominator_; } void SetDominator(HBasicBlock* dominator) { dominator_ = dominator; } void AddDominatedBlock(HBasicBlock* block) { dominated_blocks_.Add(block); } + void RemoveDominatedBlock(HBasicBlock* block) { dominated_blocks_.Delete(block); } void ReplaceDominatedBlock(HBasicBlock* existing, HBasicBlock* new_block) { for (size_t i = 0, e = dominated_blocks_.Size(); i < e; ++i) { if (dominated_blocks_.Get(i) == existing) { @@ -550,7 +556,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { // that this method does not update the graph, reverse post order, loop // information, nor make sure the blocks are consistent (for example ending // with a control flow instruction). - void MergeWith(HBasicBlock* other); + void MergeWithInlined(HBasicBlock* other); // Replace `this` with `other`. Predecessors, successors, and dominated blocks // of `this` are moved to `other`. @@ -559,12 +565,17 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { // with a control flow instruction). void ReplaceWith(HBasicBlock* other); - // Disconnects `this` from all its predecessors, successors and the dominator. - // It assumes that `this` does not dominate any blocks. - // Note that this method does not update the graph, reverse post order, loop - // information, nor make sure the blocks are consistent (for example ending - // with a control flow instruction). - void DisconnectFromAll(); + // Merge `other` at the end of `this`. This method updates loops, reverse post + // order, links to predecessors, successors, dominators and deletes the block + // from the graph. The two blocks must be successive, i.e. `this` the only + // predecessor of `other` and vice versa. + void MergeWith(HBasicBlock* other); + + // Disconnects `this` from all its predecessors, successors and dominator, + // removes it from all loops it is included in and eventually from the graph. + // The block must not dominate any other block. Predecessors and successors + // are safely updated. + void DisconnectAndDelete(); void AddInstruction(HInstruction* instruction); void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor); @@ -1154,8 +1165,6 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { virtual bool CanThrow() const { return false; } bool HasSideEffects() const { return side_effects_.HasSideEffects(); } - virtual bool ActAsNullConstant() const { return false; } - // Does not apply for all instructions, but having this at top level greatly // simplifies the null check elimination. virtual bool CanBeNull() const { @@ -2080,8 +2089,6 @@ class HNullConstant : public HConstant { size_t ComputeHashCode() const OVERRIDE { return 0; } - bool ActAsNullConstant() const OVERRIDE { return true; } - DECLARE_INSTRUCTION(NullConstant); private: @@ -2103,11 +2110,6 @@ class HIntConstant : public HConstant { size_t ComputeHashCode() const OVERRIDE { return GetValue(); } - // TODO: Null is represented by the `0` constant. In most cases we replace it - // with a HNullConstant but we don't do it when comparing (a != null). This - // method is an workaround until we fix the above. - bool ActAsNullConstant() const OVERRIDE { return value_ == 0; } - bool IsMinusOne() const OVERRIDE { return GetValue() == -1; } bool IsZero() const OVERRIDE { return GetValue() == 0; } bool IsOne() const OVERRIDE { return GetValue() == 1; } @@ -2220,6 +2222,14 @@ class HInvoke : public HInstruction { class HInvokeStaticOrDirect : public HInvoke { public: + // Requirements of this method call regarding the class + // initialization (clinit) check of its declaring class. + enum class ClinitCheckRequirement { + kNone, // Class already initialized. + kExplicit, // Static call having explicit clinit check as last input. + kImplicit, // Static call implicitly requiring a clinit check. + }; + HInvokeStaticOrDirect(ArenaAllocator* arena, uint32_t number_of_arguments, Primitive::Type return_type, @@ -2227,11 +2237,13 @@ class HInvokeStaticOrDirect : public HInvoke { uint32_t dex_method_index, bool is_recursive, InvokeType original_invoke_type, - InvokeType invoke_type) + InvokeType invoke_type, + ClinitCheckRequirement clinit_check_requirement) : HInvoke(arena, number_of_arguments, return_type, dex_pc, dex_method_index), original_invoke_type_(original_invoke_type), invoke_type_(invoke_type), - is_recursive_(is_recursive) {} + is_recursive_(is_recursive), + clinit_check_requirement_(clinit_check_requirement) {} bool CanDoImplicitNullCheckOn(HInstruction* obj) const OVERRIDE { UNUSED(obj); @@ -2245,12 +2257,60 @@ class HInvokeStaticOrDirect : public HInvoke { bool IsRecursive() const { return is_recursive_; } bool NeedsDexCache() const OVERRIDE { return !IsRecursive(); } + // Is this instruction a call to a static method? + bool IsStatic() const { + return GetInvokeType() == kStatic; + } + + // Remove the art::HClinitCheck or art::HLoadClass instruction as + // last input (only relevant for static calls with explicit clinit + // check). + void RemoveClinitCheckOrLoadClassAsLastInput() { + DCHECK(IsStaticWithExplicitClinitCheck()); + size_t last_input_index = InputCount() - 1; + HInstruction* last_input = InputAt(last_input_index); + DCHECK(last_input != nullptr); + DCHECK(last_input->IsClinitCheck() || last_input->IsLoadClass()) << last_input->DebugName(); + RemoveAsUserOfInput(last_input_index); + inputs_.DeleteAt(last_input_index); + clinit_check_requirement_ = ClinitCheckRequirement::kImplicit; + DCHECK(IsStaticWithImplicitClinitCheck()); + } + + // Is this a call to a static method whose declaring class has an + // explicit intialization check in the graph? + bool IsStaticWithExplicitClinitCheck() const { + return IsStatic() && (clinit_check_requirement_ == ClinitCheckRequirement::kExplicit); + } + + // Is this a call to a static method whose declaring class has an + // implicit intialization check requirement? + bool IsStaticWithImplicitClinitCheck() const { + return IsStatic() && (clinit_check_requirement_ == ClinitCheckRequirement::kImplicit); + } + DECLARE_INSTRUCTION(InvokeStaticOrDirect); + protected: + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { + const HUserRecord<HInstruction*> input_record = HInvoke::InputRecordAt(i); + if (kIsDebugBuild && IsStaticWithExplicitClinitCheck() && (i == InputCount() - 1)) { + HInstruction* input = input_record.GetInstruction(); + // `input` is the last input of a static invoke marked as having + // an explicit clinit check. It must either be: + // - an art::HClinitCheck instruction, set by art::HGraphBuilder; or + // - an art::HLoadClass instruction, set by art::PrepareForRegisterAllocation. + DCHECK(input != nullptr); + DCHECK(input->IsClinitCheck() || input->IsLoadClass()) << input->DebugName(); + } + return input_record; + } + private: const InvokeType original_invoke_type_; const InvokeType invoke_type_; const bool is_recursive_; + ClinitCheckRequirement clinit_check_requirement_; DISALLOW_COPY_AND_ASSIGN(HInvokeStaticOrDirect); }; @@ -2755,6 +2815,7 @@ class HPhi : public HInstruction { size_t InputCount() const OVERRIDE { return inputs_.Size(); } void AddInput(HInstruction* input); + void RemoveInputAt(size_t index); Primitive::Type GetType() const OVERRIDE { return type_; } void SetType(Primitive::Type type) { type_ = type; } @@ -3223,7 +3284,6 @@ class HLoadString : public HExpression<0> { DISALLOW_COPY_AND_ASSIGN(HLoadString); }; -// TODO: Pass this check to HInvokeStaticOrDirect nodes. /** * Performs an initialization check on its Class object input. */ @@ -3364,6 +3424,7 @@ class HInstanceOf : public HExpression<2> { uint32_t dex_pc) : HExpression(Primitive::kPrimBoolean, SideEffects::None()), class_is_final_(class_is_final), + must_do_null_check_(true), dex_pc_(dex_pc) { SetRawInputAt(0, object); SetRawInputAt(1, constant); @@ -3383,10 +3444,15 @@ class HInstanceOf : public HExpression<2> { bool IsClassFinal() const { return class_is_final_; } + // Used only in code generation. + bool MustDoNullCheck() const { return must_do_null_check_; } + void ClearMustDoNullCheck() { must_do_null_check_ = false; } + DECLARE_INSTRUCTION(InstanceOf); private: const bool class_is_final_; + bool must_do_null_check_; const uint32_t dex_pc_; DISALLOW_COPY_AND_ASSIGN(HInstanceOf); @@ -3427,6 +3493,7 @@ class HCheckCast : public HTemplateInstruction<2> { uint32_t dex_pc) : HTemplateInstruction(SideEffects::None()), class_is_final_(class_is_final), + must_do_null_check_(true), dex_pc_(dex_pc) { SetRawInputAt(0, object); SetRawInputAt(1, constant); @@ -3445,6 +3512,9 @@ class HCheckCast : public HTemplateInstruction<2> { bool CanThrow() const OVERRIDE { return true; } + bool MustDoNullCheck() const { return must_do_null_check_; } + void ClearMustDoNullCheck() { must_do_null_check_ = false; } + uint32_t GetDexPc() const { return dex_pc_; } bool IsClassFinal() const { return class_is_final_; } @@ -3453,6 +3523,7 @@ class HCheckCast : public HTemplateInstruction<2> { private: const bool class_is_final_; + bool must_do_null_check_; const uint32_t dex_pc_; DISALLOW_COPY_AND_ASSIGN(HCheckCast); diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc index b13e07eb22..c46a21955c 100644 --- a/compiler/optimizing/optimization.cc +++ b/compiler/optimizing/optimization.cc @@ -21,9 +21,9 @@ namespace art { -void HOptimization::MaybeRecordStat(MethodCompilationStat compilation_stat) const { +void HOptimization::MaybeRecordStat(MethodCompilationStat compilation_stat, size_t count) const { if (stats_ != nullptr) { - stats_->RecordStat(compilation_stat); + stats_->RecordStat(compilation_stat, count); } } diff --git a/compiler/optimizing/optimization.h b/compiler/optimizing/optimization.h index 8b2028177b..ccf8de9f6a 100644 --- a/compiler/optimizing/optimization.h +++ b/compiler/optimizing/optimization.h @@ -48,7 +48,7 @@ class HOptimization : public ValueObject { void Check(); protected: - void MaybeRecordStat(MethodCompilationStat compilation_stat) const; + void MaybeRecordStat(MethodCompilationStat compilation_stat, size_t count = 1) const; HGraph* const graph_; // Used to record stats about the optimization. diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 218894fe02..05451bcaa6 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -324,11 +324,11 @@ static void RunOptimizations(HGraph* graph, HDeadCodeElimination dce2(graph, stats, "dead_code_elimination_final"); HConstantFolding fold1(graph); InstructionSimplifier simplify1(graph, stats); - HBooleanSimplifier boolean_not(graph); + HBooleanSimplifier boolean_simplify(graph); HInliner inliner(graph, dex_compilation_unit, dex_compilation_unit, driver, stats); - HConstantFolding fold2(graph); + HConstantFolding fold2(graph, "constant_folding_after_inlining"); SideEffectsAnalysis side_effects(graph); GVNOptimization gvn(graph, side_effects); LICM licm(graph, side_effects); @@ -343,10 +343,10 @@ static void RunOptimizations(HGraph* graph, &dce1, &fold1, &simplify1, + &inliner, // BooleanSimplifier depends on the InstructionSimplifier removing redundant // suspend checks to recognize empty blocks. - &boolean_not, - &inliner, + &boolean_simplify, &fold2, &side_effects, &gvn, diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h index e6508c9851..65c84e6942 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -58,8 +58,8 @@ class OptimizingCompilerStats { public: OptimizingCompilerStats() {} - void RecordStat(MethodCompilationStat stat) { - compile_stats_[stat]++; + void RecordStat(MethodCompilationStat stat, size_t count = 1) { + compile_stats_[stat] += count; } void Log() const { diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index f5d8d82571..fa6b3c292c 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -79,4 +79,26 @@ void PrepareForRegisterAllocation::VisitCondition(HCondition* condition) { } } +void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + if (invoke->IsStaticWithExplicitClinitCheck()) { + size_t last_input_index = invoke->InputCount() - 1; + HInstruction* last_input = invoke->InputAt(last_input_index); + DCHECK(last_input->IsLoadClass()) << last_input->DebugName(); + + // Remove a load class instruction as last input of a static + // invoke, which has been added (along with a clinit check, + // removed by PrepareForRegisterAllocation::VisitClinitCheck + // previously) by the graph builder during the creation of the + // static invoke instruction, but is no longer required at this + // stage (i.e., after inlining has been performed). + invoke->RemoveClinitCheckOrLoadClassAsLastInput(); + + // If the load class instruction is no longer used, remove it from + // the graph. + if (!last_input->HasUses()) { + last_input->GetBlock()->RemoveInstruction(last_input); + } + } +} + } // namespace art diff --git a/compiler/optimizing/prepare_for_register_allocation.h b/compiler/optimizing/prepare_for_register_allocation.h index c28507c925..d7f277fa0d 100644 --- a/compiler/optimizing/prepare_for_register_allocation.h +++ b/compiler/optimizing/prepare_for_register_allocation.h @@ -39,6 +39,7 @@ class PrepareForRegisterAllocation : public HGraphDelegateVisitor { void VisitBoundType(HBoundType* bound_type) OVERRIDE; void VisitClinitCheck(HClinitCheck* check) OVERRIDE; void VisitCondition(HCondition* condition) OVERRIDE; + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; DISALLOW_COPY_AND_ASSIGN(PrepareForRegisterAllocation); }; diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index de6941c983..12b1c2b9bd 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -68,11 +68,11 @@ void ReferenceTypePropagation::BoundTypeForIfNotNull(HBasicBlock* block) { } HInstruction* input0 = ifInput->InputAt(0); HInstruction* input1 = ifInput->InputAt(1); - HInstruction* obj; + HInstruction* obj = nullptr; - if ((input0->GetType() == Primitive::kPrimNot) && input1->ActAsNullConstant()) { + if (input1->IsNullConstant()) { obj = input0; - } else if ((input1->GetType() == Primitive::kPrimNot) && input0->ActAsNullConstant()) { + } else if (input0->IsNullConstant()) { obj = input1; } else { return; diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index f8e00f6873..0fdf051957 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -378,7 +378,7 @@ void RegisterAllocator::ProcessInstruction(HInstruction* instruction) { // Split just before first register use. size_t first_register_use = current->FirstRegisterUse(); if (first_register_use != kNoLifetime) { - LiveInterval* split = Split(current, first_register_use - 1); + LiveInterval* split = SplitBetween(current, current->GetStart(), first_register_use - 1); // Don't add directly to `unhandled`, it needs to be sorted and the start // of this new interval might be after intervals already in the list. AddSorted(&unhandled, split); @@ -997,7 +997,7 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) { // If the first use of that instruction is after the last use of the found // register, we split this interval just before its first register use. AllocateSpillSlotFor(current); - LiveInterval* split = Split(current, first_register_use - 1); + LiveInterval* split = SplitBetween(current, current->GetStart(), first_register_use - 1); if (current == split) { DumpInterval(std::cerr, current); DumpAllIntervals(std::cerr); @@ -1100,6 +1100,31 @@ void RegisterAllocator::AddSorted(GrowableArray<LiveInterval*>* array, LiveInter } } +LiveInterval* RegisterAllocator::SplitBetween(LiveInterval* interval, size_t from, size_t to) { + HBasicBlock* block_from = liveness_.GetBlockFromPosition(from); + HBasicBlock* block_to = liveness_.GetBlockFromPosition(to); + DCHECK(block_from != nullptr); + DCHECK(block_to != nullptr); + + // Both locations are in the same block. We split at the given location. + if (block_from == block_to) { + return Split(interval, to); + } + + // If `to` is in a loop, find the outermost loop header which does not contain `from`. + for (HLoopInformationOutwardIterator it(*block_to); !it.Done(); it.Advance()) { + HBasicBlock* header = it.Current()->GetHeader(); + if (block_from->GetLifetimeStart() >= header->GetLifetimeStart()) { + break; + } + block_to = header; + } + + // Split at the start of the found block, to piggy back on existing moves + // due to resolution if non-linear control flow (see `ConnectSplitSiblings`). + return Split(interval, block_to->GetLifetimeStart()); +} + LiveInterval* RegisterAllocator::Split(LiveInterval* interval, size_t position) { DCHECK_GE(position, interval->GetStart()); DCHECK(!interval->IsDeadAt(position)); diff --git a/compiler/optimizing/register_allocator.h b/compiler/optimizing/register_allocator.h index 717be75533..dc9c708eea 100644 --- a/compiler/optimizing/register_allocator.h +++ b/compiler/optimizing/register_allocator.h @@ -86,8 +86,12 @@ class RegisterAllocator { // Add `interval` in the given sorted list. static void AddSorted(GrowableArray<LiveInterval*>* array, LiveInterval* interval); - // Split `interval` at the position `at`. The new interval starts at `at`. - LiveInterval* Split(LiveInterval* interval, size_t at); + // Split `interval` at the position `position`. The new interval starts at `position`. + LiveInterval* Split(LiveInterval* interval, size_t position); + + // Split `interval` at a position between `from` and `to`. The method will try + // to find an optimal split position. + LiveInterval* SplitBetween(LiveInterval* interval, size_t from, size_t to); // Returns whether `reg` is blocked by the code generator. bool IsBlocked(int reg) const; diff --git a/compiler/optimizing/register_allocator_test.cc b/compiler/optimizing/register_allocator_test.cc index 182cd0e833..8c6d904a4c 100644 --- a/compiler/optimizing/register_allocator_test.cc +++ b/compiler/optimizing/register_allocator_test.cc @@ -854,6 +854,10 @@ TEST(RegisterAllocatorTest, SpillInactive) { X86InstructionSetFeatures::FromCppDefines()); x86::CodeGeneratorX86 codegen(graph, *features_x86.get(), CompilerOptions()); SsaLivenessAnalysis liveness(graph, &codegen); + // Populate the instructions in the liveness object, to please the register allocator. + for (size_t i = 0; i < 32; ++i) { + liveness.instructions_from_lifetime_position_.Add(user); + } RegisterAllocator register_allocator(&allocator, &codegen, liveness); register_allocator.unhandled_core_intervals_.Add(fourth); diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index fe70d3a861..97254edb5e 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -998,6 +998,15 @@ class SsaLivenessAnalysis : public ValueObject { return instructions_from_lifetime_position_.Get(index); } + HBasicBlock* GetBlockFromPosition(size_t index) const { + HInstruction* instruction = GetInstructionFromPosition(index / 2); + if (instruction == nullptr) { + // If we are at a block boundary, get the block following. + instruction = GetInstructionFromPosition((index / 2) + 1); + } + return instruction->GetBlock(); + } + HInstruction* GetTempUser(LiveInterval* temp) const { // A temporary shares the same lifetime start as the instruction that requires it. DCHECK(temp->IsTemp()); @@ -1068,6 +1077,8 @@ class SsaLivenessAnalysis : public ValueObject { GrowableArray<HInstruction*> instructions_from_lifetime_position_; size_t number_of_ssa_values_; + ART_FRIEND_TEST(RegisterAllocatorTest, SpillInactive); + DISALLOW_COPY_AND_ASSIGN(SsaLivenessAnalysis); }; diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc new file mode 100644 index 0000000000..8344fc3237 --- /dev/null +++ b/compiler/optimizing/stack_map_stream.cc @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2015 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 "stack_map_stream.h" + +namespace art { + +void StackMapStream::BeginStackMapEntry(uint32_t dex_pc, + uint32_t native_pc_offset, + uint32_t register_mask, + BitVector* sp_mask, + uint32_t num_dex_registers, + uint8_t inlining_depth) { + DCHECK_EQ(0u, current_entry_.dex_pc) << "EndStackMapEntry not called after BeginStackMapEntry"; + current_entry_.dex_pc = dex_pc; + current_entry_.native_pc_offset = native_pc_offset; + current_entry_.register_mask = register_mask; + current_entry_.sp_mask = sp_mask; + current_entry_.num_dex_registers = num_dex_registers; + current_entry_.inlining_depth = inlining_depth; + current_entry_.dex_register_locations_start_index = dex_register_locations_.Size(); + current_entry_.inline_infos_start_index = inline_infos_.Size(); + current_entry_.dex_register_map_hash = 0; + current_entry_.same_dex_register_map_as_ = kNoSameDexMapFound; + if (num_dex_registers != 0) { + current_entry_.live_dex_registers_mask = + new (allocator_) ArenaBitVector(allocator_, num_dex_registers, true); + } else { + current_entry_.live_dex_registers_mask = nullptr; + } + + if (sp_mask != nullptr) { + stack_mask_max_ = std::max(stack_mask_max_, sp_mask->GetHighestBitSet()); + } + if (inlining_depth > 0) { + number_of_stack_maps_with_inline_info_++; + } + + dex_pc_max_ = std::max(dex_pc_max_, dex_pc); + native_pc_offset_max_ = std::max(native_pc_offset_max_, native_pc_offset); + register_mask_max_ = std::max(register_mask_max_, register_mask); +} + +void StackMapStream::EndStackMapEntry() { + current_entry_.same_dex_register_map_as_ = FindEntryWithTheSameDexMap(); + stack_maps_.Add(current_entry_); + current_entry_ = StackMapEntry(); +} + +void StackMapStream::AddDexRegisterEntry(uint16_t dex_register, + DexRegisterLocation::Kind kind, + int32_t value) { + DCHECK_LT(dex_register, current_entry_.num_dex_registers); + + if (kind != DexRegisterLocation::Kind::kNone) { + // Ensure we only use non-compressed location kind at this stage. + DCHECK(DexRegisterLocation::IsShortLocationKind(kind)) + << DexRegisterLocation::PrettyDescriptor(kind); + DexRegisterLocation location(kind, value); + + // Look for Dex register `location` in the location catalog (using the + // companion hash map of locations to indices). Use its index if it + // is already in the location catalog. If not, insert it (in the + // location catalog and the hash map) and use the newly created index. + auto it = location_catalog_entries_indices_.Find(location); + if (it != location_catalog_entries_indices_.end()) { + // Retrieve the index from the hash map. + dex_register_locations_.Add(it->second); + } else { + // Create a new entry in the location catalog and the hash map. + size_t index = location_catalog_entries_.Size(); + location_catalog_entries_.Add(location); + dex_register_locations_.Add(index); + location_catalog_entries_indices_.Insert(std::make_pair(location, index)); + } + + current_entry_.live_dex_registers_mask->SetBit(dex_register); + current_entry_.dex_register_map_hash += + (1 << (dex_register % (sizeof(current_entry_.dex_register_map_hash) * kBitsPerByte))); + current_entry_.dex_register_map_hash += static_cast<uint32_t>(value); + current_entry_.dex_register_map_hash += static_cast<uint32_t>(kind); + } +} + +void StackMapStream::AddInlineInfoEntry(uint32_t method_index) { + InlineInfoEntry entry; + entry.method_index = method_index; + inline_infos_.Add(entry); +} + +size_t StackMapStream::PrepareForFillIn() { + int stack_mask_number_of_bits = stack_mask_max_ + 1; // Need room for max element too. + stack_mask_size_ = RoundUp(stack_mask_number_of_bits, kBitsPerByte) / kBitsPerByte; + inline_info_size_ = ComputeInlineInfoSize(); + dex_register_maps_size_ = ComputeDexRegisterMapsSize(); + stack_maps_size_ = stack_maps_.Size() + * StackMap::ComputeStackMapSize(stack_mask_size_, + inline_info_size_, + dex_register_maps_size_, + dex_pc_max_, + native_pc_offset_max_, + register_mask_max_); + dex_register_location_catalog_size_ = ComputeDexRegisterLocationCatalogSize(); + + // Note: use RoundUp to word-size here if you want CodeInfo objects to be word aligned. + needed_size_ = CodeInfo::kFixedSize + + dex_register_location_catalog_size_ + + stack_maps_size_ + + dex_register_maps_size_ + + inline_info_size_; + + dex_register_location_catalog_start_ = CodeInfo::kFixedSize; + stack_maps_start_ = dex_register_location_catalog_start_ + dex_register_location_catalog_size_; + dex_register_maps_start_ = stack_maps_start_ + stack_maps_size_; + inline_infos_start_ = dex_register_maps_start_ + dex_register_maps_size_; + + return needed_size_; +} + +size_t StackMapStream::ComputeDexRegisterLocationCatalogSize() const { + size_t size = DexRegisterLocationCatalog::kFixedSize; + for (size_t location_catalog_entry_index = 0; + location_catalog_entry_index < location_catalog_entries_.Size(); + ++location_catalog_entry_index) { + DexRegisterLocation dex_register_location = + location_catalog_entries_.Get(location_catalog_entry_index); + size += DexRegisterLocationCatalog::EntrySize(dex_register_location); + } + return size; +} + +size_t StackMapStream::ComputeDexRegisterMapSize(const StackMapEntry& entry) const { + // Size of the map in bytes. + size_t size = DexRegisterMap::kFixedSize; + // Add the live bit mask for the Dex register liveness. + size += DexRegisterMap::GetLiveBitMaskSize(entry.num_dex_registers); + // Compute the size of the set of live Dex register entries. + size_t number_of_live_dex_registers = 0; + for (size_t dex_register_number = 0; + dex_register_number < entry.num_dex_registers; + ++dex_register_number) { + if (entry.live_dex_registers_mask->IsBitSet(dex_register_number)) { + ++number_of_live_dex_registers; + } + } + size_t map_entries_size_in_bits = + DexRegisterMap::SingleEntrySizeInBits(location_catalog_entries_.Size()) + * number_of_live_dex_registers; + size_t map_entries_size_in_bytes = + RoundUp(map_entries_size_in_bits, kBitsPerByte) / kBitsPerByte; + size += map_entries_size_in_bytes; + return size; +} + +size_t StackMapStream::ComputeDexRegisterMapsSize() const { + size_t size = 0; + for (size_t i = 0; i < stack_maps_.Size(); ++i) { + StackMapEntry entry = stack_maps_.Get(i); + if (entry.same_dex_register_map_as_ == kNoSameDexMapFound) { + // Entries with the same dex map will have the same offset. + size += ComputeDexRegisterMapSize(entry); + } + } + return size; +} + +size_t StackMapStream::ComputeInlineInfoSize() const { + return inline_infos_.Size() * InlineInfo::SingleEntrySize() + // For encoding the depth. + + (number_of_stack_maps_with_inline_info_ * InlineInfo::kFixedSize); +} + +void StackMapStream::FillIn(MemoryRegion region) { + DCHECK_EQ(0u, current_entry_.dex_pc) << "EndStackMapEntry not called after BeginStackMapEntry"; + DCHECK_NE(0u, needed_size_) << "PrepareForFillIn not called before FillIn"; + + CodeInfo code_info(region); + DCHECK_EQ(region.size(), needed_size_); + code_info.SetOverallSize(region.size()); + + MemoryRegion dex_register_locations_region = region.Subregion( + dex_register_maps_start_, dex_register_maps_size_); + + MemoryRegion inline_infos_region = region.Subregion( + inline_infos_start_, inline_info_size_); + + code_info.SetEncoding(inline_info_size_, + dex_register_maps_size_, + dex_pc_max_, + native_pc_offset_max_, + register_mask_max_); + code_info.SetNumberOfStackMaps(stack_maps_.Size()); + code_info.SetStackMaskSize(stack_mask_size_); + DCHECK_EQ(code_info.GetStackMapsSize(), stack_maps_size_); + + // Set the Dex register location catalog. + code_info.SetNumberOfDexRegisterLocationCatalogEntries(location_catalog_entries_.Size()); + MemoryRegion dex_register_location_catalog_region = region.Subregion( + dex_register_location_catalog_start_, dex_register_location_catalog_size_); + DexRegisterLocationCatalog dex_register_location_catalog(dex_register_location_catalog_region); + // Offset in `dex_register_location_catalog` where to store the next + // register location. + size_t location_catalog_offset = DexRegisterLocationCatalog::kFixedSize; + for (size_t i = 0, e = location_catalog_entries_.Size(); i < e; ++i) { + DexRegisterLocation dex_register_location = location_catalog_entries_.Get(i); + dex_register_location_catalog.SetRegisterInfo(location_catalog_offset, dex_register_location); + location_catalog_offset += DexRegisterLocationCatalog::EntrySize(dex_register_location); + } + // Ensure we reached the end of the Dex registers location_catalog. + DCHECK_EQ(location_catalog_offset, dex_register_location_catalog_region.size()); + + uintptr_t next_dex_register_map_offset = 0; + uintptr_t next_inline_info_offset = 0; + for (size_t i = 0, e = stack_maps_.Size(); i < e; ++i) { + StackMap stack_map = code_info.GetStackMapAt(i); + StackMapEntry entry = stack_maps_.Get(i); + + stack_map.SetDexPc(code_info, entry.dex_pc); + stack_map.SetNativePcOffset(code_info, entry.native_pc_offset); + stack_map.SetRegisterMask(code_info, entry.register_mask); + if (entry.sp_mask != nullptr) { + stack_map.SetStackMask(code_info, *entry.sp_mask); + } + + if (entry.num_dex_registers == 0) { + // No dex map available. + stack_map.SetDexRegisterMapOffset(code_info, StackMap::kNoDexRegisterMap); + } else { + // Search for an entry with the same dex map. + if (entry.same_dex_register_map_as_ != kNoSameDexMapFound) { + // If we have a hit reuse the offset. + stack_map.SetDexRegisterMapOffset(code_info, + code_info.GetStackMapAt(entry.same_dex_register_map_as_) + .GetDexRegisterMapOffset(code_info)); + } else { + // New dex registers maps should be added to the stack map. + MemoryRegion register_region = + dex_register_locations_region.Subregion( + next_dex_register_map_offset, + ComputeDexRegisterMapSize(entry)); + next_dex_register_map_offset += register_region.size(); + DexRegisterMap dex_register_map(register_region); + stack_map.SetDexRegisterMapOffset( + code_info, register_region.start() - dex_register_locations_region.start()); + + // Set the live bit mask. + dex_register_map.SetLiveBitMask(entry.num_dex_registers, *entry.live_dex_registers_mask); + + // Set the dex register location mapping data. + for (size_t dex_register_number = 0, index_in_dex_register_locations = 0; + dex_register_number < entry.num_dex_registers; + ++dex_register_number) { + if (entry.live_dex_registers_mask->IsBitSet(dex_register_number)) { + size_t location_catalog_entry_index = + dex_register_locations_.Get(entry.dex_register_locations_start_index + + index_in_dex_register_locations); + dex_register_map.SetLocationCatalogEntryIndex( + index_in_dex_register_locations, + location_catalog_entry_index, + entry.num_dex_registers, + location_catalog_entries_.Size()); + ++index_in_dex_register_locations; + } + } + } + } + + // Set the inlining info. + if (entry.inlining_depth != 0) { + MemoryRegion inline_region = inline_infos_region.Subregion( + next_inline_info_offset, + InlineInfo::kFixedSize + entry.inlining_depth * InlineInfo::SingleEntrySize()); + next_inline_info_offset += inline_region.size(); + InlineInfo inline_info(inline_region); + + // Currently relative to the dex register map. + stack_map.SetInlineDescriptorOffset( + code_info, inline_region.start() - dex_register_locations_region.start()); + + inline_info.SetDepth(entry.inlining_depth); + for (size_t j = 0; j < entry.inlining_depth; ++j) { + InlineInfoEntry inline_entry = inline_infos_.Get(j + entry.inline_infos_start_index); + inline_info.SetMethodReferenceIndexAtDepth(j, inline_entry.method_index); + } + } else { + if (inline_info_size_ != 0) { + stack_map.SetInlineDescriptorOffset(code_info, StackMap::kNoInlineInfo); + } + } + } +} + +size_t StackMapStream::FindEntryWithTheSameDexMap() { + size_t current_entry_index = stack_maps_.Size(); + auto entries_it = dex_map_hash_to_stack_map_indices_.find(current_entry_.dex_register_map_hash); + if (entries_it == dex_map_hash_to_stack_map_indices_.end()) { + // We don't have a perfect hash functions so we need a list to collect all stack maps + // which might have the same dex register map. + GrowableArray<uint32_t> stack_map_indices(allocator_, 1); + stack_map_indices.Add(current_entry_index); + dex_map_hash_to_stack_map_indices_.Put(current_entry_.dex_register_map_hash, stack_map_indices); + return kNoSameDexMapFound; + } + + // We might have collisions, so we need to check whether or not we really have a match. + for (size_t i = 0; i < entries_it->second.Size(); i++) { + size_t test_entry_index = entries_it->second.Get(i); + if (HaveTheSameDexMaps(stack_maps_.Get(test_entry_index), current_entry_)) { + return test_entry_index; + } + } + entries_it->second.Add(current_entry_index); + return kNoSameDexMapFound; +} + +bool StackMapStream::HaveTheSameDexMaps(const StackMapEntry& a, const StackMapEntry& b) const { + if (a.live_dex_registers_mask == nullptr && b.live_dex_registers_mask == nullptr) { + return true; + } + if (a.live_dex_registers_mask == nullptr || b.live_dex_registers_mask == nullptr) { + return false; + } + if (a.num_dex_registers != b.num_dex_registers) { + return false; + } + + int index_in_dex_register_locations = 0; + for (uint32_t i = 0; i < a.num_dex_registers; i++) { + if (a.live_dex_registers_mask->IsBitSet(i) != b.live_dex_registers_mask->IsBitSet(i)) { + return false; + } + if (a.live_dex_registers_mask->IsBitSet(i)) { + size_t a_loc = dex_register_locations_.Get( + a.dex_register_locations_start_index + index_in_dex_register_locations); + size_t b_loc = dex_register_locations_.Get( + b.dex_register_locations_start_index + index_in_dex_register_locations); + if (a_loc != b_loc) { + return false; + } + ++index_in_dex_register_locations; + } + } + return true; +} + +} // namespace art diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h index 9a9e068a9b..0c626be89f 100644 --- a/compiler/optimizing/stack_map_stream.h +++ b/compiler/optimizing/stack_map_stream.h @@ -70,13 +70,18 @@ class StackMapStream : public ValueObject { native_pc_offset_max_(0), register_mask_max_(0), number_of_stack_maps_with_inline_info_(0), - dex_map_hash_to_stack_map_indices_(std::less<uint32_t>(), allocator->Adapter()) {} - - // Compute bytes needed to encode a mask with the given maximum element. - static uint32_t StackMaskEncodingSize(int max_element) { - int number_of_bits = max_element + 1; // Need room for max element too. - return RoundUp(number_of_bits, kBitsPerByte) / kBitsPerByte; - } + dex_map_hash_to_stack_map_indices_(std::less<uint32_t>(), allocator->Adapter()), + current_entry_(), + stack_mask_size_(0), + inline_info_size_(0), + dex_register_maps_size_(0), + stack_maps_size_(0), + dex_register_location_catalog_size_(0), + dex_register_location_catalog_start_(0), + stack_maps_start_(0), + dex_register_maps_start_(0), + inline_infos_start_(0), + needed_size_(0) {} // See runtime/stack_map.h to know what these fields contain. struct StackMapEntry { @@ -90,380 +95,42 @@ class StackMapStream : public ValueObject { size_t inline_infos_start_index; BitVector* live_dex_registers_mask; uint32_t dex_register_map_hash; + size_t same_dex_register_map_as_; }; struct InlineInfoEntry { uint32_t method_index; }; - void AddStackMapEntry(uint32_t dex_pc, - uint32_t native_pc_offset, - uint32_t register_mask, - BitVector* sp_mask, - uint32_t num_dex_registers, - uint8_t inlining_depth) { - StackMapEntry entry; - entry.dex_pc = dex_pc; - entry.native_pc_offset = native_pc_offset; - entry.register_mask = register_mask; - entry.sp_mask = sp_mask; - entry.num_dex_registers = num_dex_registers; - entry.inlining_depth = inlining_depth; - entry.dex_register_locations_start_index = dex_register_locations_.Size(); - entry.inline_infos_start_index = inline_infos_.Size(); - entry.dex_register_map_hash = 0; - if (num_dex_registers != 0) { - entry.live_dex_registers_mask = - new (allocator_) ArenaBitVector(allocator_, num_dex_registers, true); - } else { - entry.live_dex_registers_mask = nullptr; - } - stack_maps_.Add(entry); - - if (sp_mask != nullptr) { - stack_mask_max_ = std::max(stack_mask_max_, sp_mask->GetHighestBitSet()); - } - if (inlining_depth > 0) { - number_of_stack_maps_with_inline_info_++; - } - - dex_pc_max_ = std::max(dex_pc_max_, dex_pc); - native_pc_offset_max_ = std::max(native_pc_offset_max_, native_pc_offset); - register_mask_max_ = std::max(register_mask_max_, register_mask); - } - - void AddInlineInfoEntry(uint32_t method_index) { - InlineInfoEntry entry; - entry.method_index = method_index; - inline_infos_.Add(entry); - } - - size_t ComputeNeededSize() { - size_t size = CodeInfo::kFixedSize - + ComputeDexRegisterLocationCatalogSize() - + ComputeStackMapsSize() - + ComputeDexRegisterMapsSize() - + ComputeInlineInfoSize(); - // Note: use RoundUp to word-size here if you want CodeInfo objects to be word aligned. - return size; - } - - size_t ComputeStackMaskSize() const { - return StackMaskEncodingSize(stack_mask_max_); - } - - size_t ComputeStackMapsSize() { - return stack_maps_.Size() * StackMap::ComputeStackMapSize( - ComputeStackMaskSize(), - ComputeInlineInfoSize(), - ComputeDexRegisterMapsSize(), - dex_pc_max_, - native_pc_offset_max_, - register_mask_max_); - } - - // Compute the size of the Dex register location catalog of `entry`. - size_t ComputeDexRegisterLocationCatalogSize() const { - size_t size = DexRegisterLocationCatalog::kFixedSize; - for (size_t location_catalog_entry_index = 0; - location_catalog_entry_index < location_catalog_entries_.Size(); - ++location_catalog_entry_index) { - DexRegisterLocation dex_register_location = - location_catalog_entries_.Get(location_catalog_entry_index); - size += DexRegisterLocationCatalog::EntrySize(dex_register_location); - } - return size; - } - - size_t ComputeDexRegisterMapSize(const StackMapEntry& entry) const { - // Size of the map in bytes. - size_t size = DexRegisterMap::kFixedSize; - // Add the live bit mask for the Dex register liveness. - size += DexRegisterMap::GetLiveBitMaskSize(entry.num_dex_registers); - // Compute the size of the set of live Dex register entries. - size_t number_of_live_dex_registers = 0; - for (size_t dex_register_number = 0; - dex_register_number < entry.num_dex_registers; - ++dex_register_number) { - if (entry.live_dex_registers_mask->IsBitSet(dex_register_number)) { - ++number_of_live_dex_registers; - } - } - size_t map_entries_size_in_bits = - DexRegisterMap::SingleEntrySizeInBits(location_catalog_entries_.Size()) - * number_of_live_dex_registers; - size_t map_entries_size_in_bytes = - RoundUp(map_entries_size_in_bits, kBitsPerByte) / kBitsPerByte; - size += map_entries_size_in_bytes; - return size; - } - - // Compute the size of all the Dex register maps. - size_t ComputeDexRegisterMapsSize() { - size_t size = 0; - for (size_t i = 0; i < stack_maps_.Size(); ++i) { - if (FindEntryWithTheSameDexMap(i) == kNoSameDexMapFound) { - // Entries with the same dex map will have the same offset. - size += ComputeDexRegisterMapSize(stack_maps_.Get(i)); - } - } - return size; - } - - // Compute the size of all the inline information pieces. - size_t ComputeInlineInfoSize() const { - return inline_infos_.Size() * InlineInfo::SingleEntrySize() - // For encoding the depth. - + (number_of_stack_maps_with_inline_info_ * InlineInfo::kFixedSize); - } + void BeginStackMapEntry(uint32_t dex_pc, + uint32_t native_pc_offset, + uint32_t register_mask, + BitVector* sp_mask, + uint32_t num_dex_registers, + uint8_t inlining_depth); + void EndStackMapEntry(); - size_t ComputeDexRegisterLocationCatalogStart() const { - return CodeInfo::kFixedSize; - } - - size_t ComputeStackMapsStart() const { - return ComputeDexRegisterLocationCatalogStart() + ComputeDexRegisterLocationCatalogSize(); - } - - size_t ComputeDexRegisterMapsStart() { - return ComputeStackMapsStart() + ComputeStackMapsSize(); - } - - size_t ComputeInlineInfoStart() { - return ComputeDexRegisterMapsStart() + ComputeDexRegisterMapsSize(); - } + void AddDexRegisterEntry(uint16_t dex_register, + DexRegisterLocation::Kind kind, + int32_t value); - void FillIn(MemoryRegion region) { - CodeInfo code_info(region); - DCHECK_EQ(region.size(), ComputeNeededSize()); - code_info.SetOverallSize(region.size()); + void AddInlineInfoEntry(uint32_t method_index); - size_t stack_mask_size = ComputeStackMaskSize(); - - size_t dex_register_map_size = ComputeDexRegisterMapsSize(); - size_t inline_info_size = ComputeInlineInfoSize(); - - MemoryRegion dex_register_locations_region = region.Subregion( - ComputeDexRegisterMapsStart(), - dex_register_map_size); - - MemoryRegion inline_infos_region = region.Subregion( - ComputeInlineInfoStart(), - inline_info_size); - - code_info.SetEncoding(inline_info_size, - dex_register_map_size, - dex_pc_max_, - native_pc_offset_max_, - register_mask_max_); - code_info.SetNumberOfStackMaps(stack_maps_.Size()); - code_info.SetStackMaskSize(stack_mask_size); - DCHECK_EQ(code_info.GetStackMapsSize(), ComputeStackMapsSize()); - - // Set the Dex register location catalog. - code_info.SetNumberOfDexRegisterLocationCatalogEntries( - location_catalog_entries_.Size()); - MemoryRegion dex_register_location_catalog_region = region.Subregion( - ComputeDexRegisterLocationCatalogStart(), - ComputeDexRegisterLocationCatalogSize()); - DexRegisterLocationCatalog dex_register_location_catalog(dex_register_location_catalog_region); - // Offset in `dex_register_location_catalog` where to store the next - // register location. - size_t location_catalog_offset = DexRegisterLocationCatalog::kFixedSize; - for (size_t i = 0, e = location_catalog_entries_.Size(); i < e; ++i) { - DexRegisterLocation dex_register_location = location_catalog_entries_.Get(i); - dex_register_location_catalog.SetRegisterInfo(location_catalog_offset, dex_register_location); - location_catalog_offset += DexRegisterLocationCatalog::EntrySize(dex_register_location); - } - // Ensure we reached the end of the Dex registers location_catalog. - DCHECK_EQ(location_catalog_offset, dex_register_location_catalog_region.size()); - - uintptr_t next_dex_register_map_offset = 0; - uintptr_t next_inline_info_offset = 0; - for (size_t i = 0, e = stack_maps_.Size(); i < e; ++i) { - StackMap stack_map = code_info.GetStackMapAt(i); - StackMapEntry entry = stack_maps_.Get(i); - - stack_map.SetDexPc(code_info, entry.dex_pc); - stack_map.SetNativePcOffset(code_info, entry.native_pc_offset); - stack_map.SetRegisterMask(code_info, entry.register_mask); - if (entry.sp_mask != nullptr) { - stack_map.SetStackMask(code_info, *entry.sp_mask); - } - - if (entry.num_dex_registers == 0) { - // No dex map available. - stack_map.SetDexRegisterMapOffset(code_info, StackMap::kNoDexRegisterMap); - } else { - // Search for an entry with the same dex map. - size_t entry_with_same_map = FindEntryWithTheSameDexMap(i); - if (entry_with_same_map != kNoSameDexMapFound) { - // If we have a hit reuse the offset. - stack_map.SetDexRegisterMapOffset(code_info, - code_info.GetStackMapAt(entry_with_same_map).GetDexRegisterMapOffset(code_info)); - } else { - // New dex registers maps should be added to the stack map. - MemoryRegion register_region = - dex_register_locations_region.Subregion( - next_dex_register_map_offset, - ComputeDexRegisterMapSize(entry)); - next_dex_register_map_offset += register_region.size(); - DexRegisterMap dex_register_map(register_region); - stack_map.SetDexRegisterMapOffset( - code_info, register_region.start() - dex_register_locations_region.start()); - - // Set the live bit mask. - dex_register_map.SetLiveBitMask(entry.num_dex_registers, *entry.live_dex_registers_mask); - - // Set the dex register location mapping data. - for (size_t dex_register_number = 0, index_in_dex_register_locations = 0; - dex_register_number < entry.num_dex_registers; - ++dex_register_number) { - if (entry.live_dex_registers_mask->IsBitSet(dex_register_number)) { - size_t location_catalog_entry_index = - dex_register_locations_.Get(entry.dex_register_locations_start_index - + index_in_dex_register_locations); - dex_register_map.SetLocationCatalogEntryIndex( - index_in_dex_register_locations, - location_catalog_entry_index, - entry.num_dex_registers, - location_catalog_entries_.Size()); - ++index_in_dex_register_locations; - } - } - } - } - - // Set the inlining info. - if (entry.inlining_depth != 0) { - MemoryRegion inline_region = inline_infos_region.Subregion( - next_inline_info_offset, - InlineInfo::kFixedSize + entry.inlining_depth * InlineInfo::SingleEntrySize()); - next_inline_info_offset += inline_region.size(); - InlineInfo inline_info(inline_region); - - // Currently relative to the dex register map. - stack_map.SetInlineDescriptorOffset( - code_info, inline_region.start() - dex_register_locations_region.start()); - - inline_info.SetDepth(entry.inlining_depth); - for (size_t j = 0; j < entry.inlining_depth; ++j) { - InlineInfoEntry inline_entry = inline_infos_.Get(j + entry.inline_infos_start_index); - inline_info.SetMethodReferenceIndexAtDepth(j, inline_entry.method_index); - } - } else { - if (inline_info_size != 0) { - stack_map.SetInlineDescriptorOffset(code_info, StackMap::kNoInlineInfo); - } - } - } - } - - void AddDexRegisterEntry(uint16_t dex_register, DexRegisterLocation::Kind kind, int32_t value) { - StackMapEntry entry = stack_maps_.Get(stack_maps_.Size() - 1); - DCHECK_LT(dex_register, entry.num_dex_registers); - - if (kind != DexRegisterLocation::Kind::kNone) { - // Ensure we only use non-compressed location kind at this stage. - DCHECK(DexRegisterLocation::IsShortLocationKind(kind)) - << DexRegisterLocation::PrettyDescriptor(kind); - DexRegisterLocation location(kind, value); - - // Look for Dex register `location` in the location catalog (using the - // companion hash map of locations to indices). Use its index if it - // is already in the location catalog. If not, insert it (in the - // location catalog and the hash map) and use the newly created index. - auto it = location_catalog_entries_indices_.Find(location); - if (it != location_catalog_entries_indices_.end()) { - // Retrieve the index from the hash map. - dex_register_locations_.Add(it->second); - } else { - // Create a new entry in the location catalog and the hash map. - size_t index = location_catalog_entries_.Size(); - location_catalog_entries_.Add(location); - dex_register_locations_.Add(index); - location_catalog_entries_indices_.Insert(std::make_pair(location, index)); - } - - entry.live_dex_registers_mask->SetBit(dex_register); - entry.dex_register_map_hash += - (1 << (dex_register % (sizeof(entry.dex_register_map_hash) * kBitsPerByte))); - entry.dex_register_map_hash += static_cast<uint32_t>(value); - entry.dex_register_map_hash += static_cast<uint32_t>(kind); - stack_maps_.Put(stack_maps_.Size() - 1, entry); - } - } + // Prepares the stream to fill in a memory region. Must be called before FillIn. + // Returns the size (in bytes) needed to store this stream. + size_t PrepareForFillIn(); + void FillIn(MemoryRegion region); private: - // Returns the index of an entry with the same dex register map - // or kNoSameDexMapFound if no such entry exists. - size_t FindEntryWithTheSameDexMap(size_t entry_index) { - StackMapEntry entry = stack_maps_.Get(entry_index); - auto entries_it = dex_map_hash_to_stack_map_indices_.find(entry.dex_register_map_hash); - if (entries_it == dex_map_hash_to_stack_map_indices_.end()) { - // We don't have a perfect hash functions so we need a list to collect all stack maps - // which might have the same dex register map. - GrowableArray<uint32_t> stack_map_indices(allocator_, 1); - stack_map_indices.Add(entry_index); - dex_map_hash_to_stack_map_indices_.Put(entry.dex_register_map_hash, stack_map_indices); - return kNoSameDexMapFound; - } - - // TODO: We don't need to add ourselves to the map if we can guarantee that - // FindEntryWithTheSameDexMap is called just once per stack map entry. - // A good way to do this is to cache the offset in the stack map entry. This - // is easier to do if we add markers when the stack map constructions begins - // and when it ends. + size_t ComputeDexRegisterLocationCatalogSize() const; + size_t ComputeDexRegisterMapSize(const StackMapEntry& entry) const; + size_t ComputeDexRegisterMapsSize() const; + size_t ComputeInlineInfoSize() const; - // We might have collisions, so we need to check whether or not we should - // add the entry to the map. `needs_to_be_added` keeps track of this. - bool needs_to_be_added = true; - size_t result = kNoSameDexMapFound; - for (size_t i = 0; i < entries_it->second.Size(); i++) { - size_t test_entry_index = entries_it->second.Get(i); - if (test_entry_index == entry_index) { - needs_to_be_added = false; - } else if (HaveTheSameDexMaps(stack_maps_.Get(test_entry_index), entry)) { - result = test_entry_index; - needs_to_be_added = false; - break; - } - } - if (needs_to_be_added) { - entries_it->second.Add(entry_index); - } - return result; - } - - bool HaveTheSameDexMaps(const StackMapEntry& a, const StackMapEntry& b) const { - if (a.live_dex_registers_mask == nullptr && b.live_dex_registers_mask == nullptr) { - return true; - } - if (a.live_dex_registers_mask == nullptr || b.live_dex_registers_mask == nullptr) { - return false; - } - if (a.num_dex_registers != b.num_dex_registers) { - return false; - } - - int index_in_dex_register_locations = 0; - for (uint32_t i = 0; i < a.num_dex_registers; i++) { - if (a.live_dex_registers_mask->IsBitSet(i) != b.live_dex_registers_mask->IsBitSet(i)) { - return false; - } - if (a.live_dex_registers_mask->IsBitSet(i)) { - size_t a_loc = dex_register_locations_.Get( - a.dex_register_locations_start_index + index_in_dex_register_locations); - size_t b_loc = dex_register_locations_.Get( - b.dex_register_locations_start_index + index_in_dex_register_locations); - if (a_loc != b_loc) { - return false; - } - ++index_in_dex_register_locations; - } - } - return true; - } + // Returns the index of an entry with the same dex register map as the current_entry, + // or kNoSameDexMapFound if no such entry exists. + size_t FindEntryWithTheSameDexMap(); + bool HaveTheSameDexMaps(const StackMapEntry& a, const StackMapEntry& b) const; ArenaAllocator* allocator_; GrowableArray<StackMapEntry> stack_maps_; @@ -476,8 +143,7 @@ class StackMapStream : public ValueObject { DexRegisterLocationHashFn> LocationCatalogEntriesIndices; LocationCatalogEntriesIndices location_catalog_entries_indices_; - // A set of concatenated maps of Dex register locations indices to - // `location_catalog_entries_`. + // A set of concatenated maps of Dex register locations indices to `location_catalog_entries_`. GrowableArray<size_t> dex_register_locations_; GrowableArray<InlineInfoEntry> inline_infos_; int stack_mask_max_; @@ -488,6 +154,18 @@ class StackMapStream : public ValueObject { ArenaSafeMap<uint32_t, GrowableArray<uint32_t>> dex_map_hash_to_stack_map_indices_; + StackMapEntry current_entry_; + size_t stack_mask_size_; + size_t inline_info_size_; + size_t dex_register_maps_size_; + size_t stack_maps_size_; + size_t dex_register_location_catalog_size_; + size_t dex_register_location_catalog_start_; + size_t stack_maps_start_; + size_t dex_register_maps_start_; + size_t inline_infos_start_; + size_t needed_size_; + static constexpr uint32_t kNoSameDexMapFound = -1; DISALLOW_COPY_AND_ASSIGN(StackMapStream); diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc index 8d160bc81e..3291a77021 100644 --- a/compiler/optimizing/stack_map_test.cc +++ b/compiler/optimizing/stack_map_test.cc @@ -40,11 +40,12 @@ TEST(StackMapTest, Test1) { ArenaBitVector sp_mask(&arena, 0, false); size_t number_of_dex_registers = 2; - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kInStack, 0); // Short location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Short location. + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); @@ -123,20 +124,22 @@ TEST(StackMapTest, Test2) { sp_mask1.SetBit(2); sp_mask1.SetBit(4); size_t number_of_dex_registers = 2; - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask1, number_of_dex_registers, 2); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask1, number_of_dex_registers, 2); stream.AddDexRegisterEntry(0, Kind::kInStack, 0); // Short location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Large location. stream.AddInlineInfoEntry(42); stream.AddInlineInfoEntry(82); + stream.EndStackMapEntry(); ArenaBitVector sp_mask2(&arena, 0, true); sp_mask2.SetBit(3); sp_mask1.SetBit(8); - stream.AddStackMapEntry(1, 128, 0xFF, &sp_mask2, number_of_dex_registers, 0); + stream.BeginStackMapEntry(1, 128, 0xFF, &sp_mask2, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kInRegister, 18); // Short location. stream.AddDexRegisterEntry(1, Kind::kInFpuRegister, 3); // Short location. + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); @@ -273,11 +276,12 @@ TEST(StackMapTest, TestNonLiveDexRegisters) { ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 2; - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kNone, 0); // No location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Large location. + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); @@ -353,7 +357,7 @@ TEST(StackMapTest, DexRegisterMapOffsetOverflow) { ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 1024; // Create the first stack map (and its Dex register map). - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); uint32_t number_of_dex_live_registers_in_dex_register_map_0 = number_of_dex_registers - 8; for (uint32_t i = 0; i < number_of_dex_live_registers_in_dex_register_map_0; ++i) { // Use two different Dex register locations to populate this map, @@ -362,13 +366,15 @@ TEST(StackMapTest, DexRegisterMapOffsetOverflow) { // art::DexRegisterMap::SingleEntrySizeInBits). stream.AddDexRegisterEntry(i, Kind::kConstant, i % 2); // Short location. } + stream.EndStackMapEntry(); // Create the second stack map (and its Dex register map). - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); for (uint32_t i = 0; i < number_of_dex_registers; ++i) { stream.AddDexRegisterEntry(i, Kind::kConstant, 0); // Short location. } + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); @@ -413,19 +419,22 @@ TEST(StackMapTest, TestShareDexRegisterMap) { ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 2; // First stack map. - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kInRegister, 0); // Short location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Large location. + stream.EndStackMapEntry(); // Second stack map, which should share the same dex register map. - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kInRegister, 0); // Short location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Large location. + stream.EndStackMapEntry(); // Third stack map (doesn't share the dex register map). - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); stream.AddDexRegisterEntry(0, Kind::kInRegister, 2); // Short location. stream.AddDexRegisterEntry(1, Kind::kConstant, -2); // Large location. + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); @@ -462,9 +471,10 @@ TEST(StackMapTest, TestNoDexRegisterMap) { ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 0; - stream.AddStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(0, 64, 0x3, &sp_mask, number_of_dex_registers, 0); + stream.EndStackMapEntry(); - size_t size = stream.ComputeNeededSize(); + size_t size = stream.PrepareForFillIn(); void* memory = arena.Alloc(size, kArenaAllocMisc); MemoryRegion region(memory, size); stream.FillIn(region); diff --git a/compiler/utils/growable_array.h b/compiler/utils/growable_array.h index 821e28b4a0..e4b1e7d0e9 100644 --- a/compiler/utils/growable_array.h +++ b/compiler/utils/growable_array.h @@ -46,6 +46,14 @@ class GrowableArray : public ArenaObject<kArenaAllocGrowableArray> { } } + bool Contains(T value) const { + for (size_t i = 0; i < num_used_; ++i) { + if (elem_list_[i] == value) { + return true; + } + } + return false; + } // Expand the list size to at least new length. void Resize(size_t new_length) { diff --git a/compiler/utils/test_dex_file_builder.h b/compiler/utils/test_dex_file_builder.h new file mode 100644 index 0000000000..ab039aa215 --- /dev/null +++ b/compiler/utils/test_dex_file_builder.h @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ART_COMPILER_UTILS_TEST_DEX_FILE_BUILDER_H_ +#define ART_COMPILER_UTILS_TEST_DEX_FILE_BUILDER_H_ + +#include <cstring> +#include <set> +#include <map> +#include <vector> + +#include "dex_file.h" +#include "utils.h" + +namespace art { + +class TestDexFileBuilder { + public: + TestDexFileBuilder() + : strings_(), types_(), fields_(), protos_(), dex_file_data_() { + } + + void AddString(const std::string& str) { + CHECK(dex_file_data_.empty()); + auto it = strings_.emplace(str, IdxAndDataOffset()).first; + CHECK_LT(it->first.length(), 128u); // Don't allow multi-byte length in uleb128. + } + + void AddType(const std::string& descriptor) { + CHECK(dex_file_data_.empty()); + AddString(descriptor); + types_.emplace(descriptor, 0u); + } + + void AddField(const std::string& class_descriptor, const std::string& type, + const std::string& name) { + CHECK(dex_file_data_.empty()); + AddType(class_descriptor); + AddType(type); + AddString(name); + FieldKey key = { class_descriptor, type, name }; + fields_.emplace(key, 0u); + } + + void AddMethod(const std::string& class_descriptor, const std::string& signature, + const std::string& name) { + CHECK(dex_file_data_.empty()); + AddType(class_descriptor); + AddString(name); + + ProtoKey proto_key = CreateProtoKey(signature); + AddString(proto_key.shorty); + AddType(proto_key.return_type); + for (const auto& arg_type : proto_key.args) { + AddType(arg_type); + } + auto it = protos_.emplace(proto_key, IdxAndDataOffset()).first; + const ProtoKey* proto = &it->first; // Valid as long as the element remains in protos_. + + MethodKey method_key = { + class_descriptor, name, proto + }; + methods_.emplace(method_key, 0u); + } + + // NOTE: The builder holds the actual data, so it must live as long as the dex file. + std::unique_ptr<const DexFile> Build(const std::string& dex_location) { + CHECK(dex_file_data_.empty()); + union { + uint8_t data[sizeof(DexFile::Header)]; + uint64_t force_alignment; + } header_data; + std::memset(header_data.data, 0, sizeof(header_data.data)); + DexFile::Header* header = reinterpret_cast<DexFile::Header*>(&header_data.data); + std::copy_n(DexFile::kDexMagic, 4u, header->magic_); + std::copy_n(DexFile::kDexMagicVersion, 4u, header->magic_ + 4u); + header->header_size_ = sizeof(header); + header->endian_tag_ = DexFile::kDexEndianConstant; + header->link_size_ = 0u; // Unused. + header->link_off_ = 0u; // Unused. + header->map_off_ = 0u; // Unused. + + uint32_t data_section_size = 0u; + + uint32_t string_ids_offset = sizeof(DexFile::Header); + uint32_t string_idx = 0u; + for (auto& entry : strings_) { + entry.second.idx = string_idx; + string_idx += 1u; + entry.second.data_offset = data_section_size; + data_section_size += entry.first.length() + 1u /* length */ + 1u /* null-terminator */; + } + header->string_ids_size_ = strings_.size(); + header->string_ids_off_ = strings_.empty() ? 0u : string_ids_offset; + + uint32_t type_ids_offset = string_ids_offset + strings_.size() * sizeof(DexFile::StringId); + uint32_t type_idx = 0u; + for (auto& entry : types_) { + entry.second = type_idx; + type_idx += 1u; + } + header->type_ids_size_ = types_.size(); + header->type_ids_off_ = types_.empty() ? 0u : type_ids_offset; + + uint32_t proto_ids_offset = type_ids_offset + types_.size() * sizeof(DexFile::TypeId); + uint32_t proto_idx = 0u; + for (auto& entry : protos_) { + entry.second.idx = proto_idx; + proto_idx += 1u; + size_t num_args = entry.first.args.size(); + if (num_args != 0u) { + entry.second.data_offset = RoundUp(data_section_size, 4u); + data_section_size = entry.second.data_offset + 4u + num_args * sizeof(DexFile::TypeItem); + } else { + entry.second.data_offset = 0u; + } + } + header->proto_ids_size_ = protos_.size(); + header->proto_ids_off_ = protos_.empty() ? 0u : proto_ids_offset; + + uint32_t field_ids_offset = proto_ids_offset + protos_.size() * sizeof(DexFile::ProtoId); + uint32_t field_idx = 0u; + for (auto& entry : fields_) { + entry.second = field_idx; + field_idx += 1u; + } + header->field_ids_size_ = fields_.size(); + header->field_ids_off_ = fields_.empty() ? 0u : field_ids_offset; + + uint32_t method_ids_offset = field_ids_offset + fields_.size() * sizeof(DexFile::FieldId); + uint32_t method_idx = 0u; + for (auto& entry : methods_) { + entry.second = method_idx; + method_idx += 1u; + } + header->method_ids_size_ = methods_.size(); + header->method_ids_off_ = methods_.empty() ? 0u : method_ids_offset; + + // No class defs. + header->class_defs_size_ = 0u; + header->class_defs_off_ = 0u; + + uint32_t data_section_offset = method_ids_offset + methods_.size() * sizeof(DexFile::MethodId); + header->data_size_ = data_section_size; + header->data_off_ = (data_section_size != 0u) ? data_section_offset : 0u; + + uint32_t total_size = data_section_offset + data_section_size; + + dex_file_data_.resize(total_size); + std::memcpy(&dex_file_data_[0], header_data.data, sizeof(DexFile::Header)); + + for (const auto& entry : strings_) { + CHECK_LT(entry.first.size(), 128u); + uint32_t raw_offset = data_section_offset + entry.second.data_offset; + dex_file_data_[raw_offset] = static_cast<uint8_t>(entry.first.size()); + std::memcpy(&dex_file_data_[raw_offset + 1], entry.first.c_str(), entry.first.size() + 1); + Write32(string_ids_offset + entry.second.idx * sizeof(DexFile::StringId), raw_offset); + } + + for (const auto& entry : types_) { + Write32(type_ids_offset + entry.second * sizeof(DexFile::TypeId), GetStringIdx(entry.first)); + ++type_idx; + } + + for (const auto& entry : protos_) { + size_t num_args = entry.first.args.size(); + uint32_t type_list_offset = + (num_args != 0u) ? data_section_offset + entry.second.data_offset : 0u; + uint32_t raw_offset = proto_ids_offset + entry.second.idx * sizeof(DexFile::ProtoId); + Write32(raw_offset + 0u, GetStringIdx(entry.first.shorty)); + Write16(raw_offset + 4u, GetTypeIdx(entry.first.return_type)); + Write32(raw_offset + 8u, type_list_offset); + if (num_args != 0u) { + CHECK_NE(entry.second.data_offset, 0u); + Write32(type_list_offset, num_args); + for (size_t i = 0; i != num_args; ++i) { + Write16(type_list_offset + 4u + i * sizeof(DexFile::TypeItem), + GetTypeIdx(entry.first.args[i])); + } + } + } + + for (const auto& entry : fields_) { + uint32_t raw_offset = field_ids_offset + entry.second * sizeof(DexFile::FieldId); + Write16(raw_offset + 0u, GetTypeIdx(entry.first.class_descriptor)); + Write16(raw_offset + 2u, GetTypeIdx(entry.first.type)); + Write32(raw_offset + 4u, GetStringIdx(entry.first.name)); + } + + for (const auto& entry : methods_) { + uint32_t raw_offset = method_ids_offset + entry.second * sizeof(DexFile::MethodId); + Write16(raw_offset + 0u, GetTypeIdx(entry.first.class_descriptor)); + auto it = protos_.find(*entry.first.proto); + CHECK(it != protos_.end()); + Write16(raw_offset + 2u, it->second.idx); + Write32(raw_offset + 4u, GetStringIdx(entry.first.name)); + } + + // Leave checksum and signature as zeros. + + std::string error_msg; + std::unique_ptr<const DexFile> dex_file(DexFile::Open( + &dex_file_data_[0], dex_file_data_.size(), dex_location, 0u, nullptr, &error_msg)); + CHECK(dex_file != nullptr) << error_msg; + return std::move(dex_file); + } + + uint32_t GetStringIdx(const std::string& type) { + auto it = strings_.find(type); + CHECK(it != strings_.end()); + return it->second.idx; + } + + uint32_t GetTypeIdx(const std::string& type) { + auto it = types_.find(type); + CHECK(it != types_.end()); + return it->second; + } + + uint32_t GetFieldIdx(const std::string& class_descriptor, const std::string& type, + const std::string& name) { + FieldKey key = { class_descriptor, type, name }; + auto it = fields_.find(key); + CHECK(it != fields_.end()); + return it->second; + } + + uint32_t GetMethodIdx(const std::string& class_descriptor, const std::string& signature, + const std::string& name) { + ProtoKey proto_key = CreateProtoKey(signature); + MethodKey method_key = { class_descriptor, name, &proto_key }; + auto it = methods_.find(method_key); + CHECK(it != methods_.end()); + return it->second; + } + + private: + struct IdxAndDataOffset { + uint32_t idx; + uint32_t data_offset; + }; + + struct FieldKey { + const std::string class_descriptor; + const std::string type; + const std::string name; + }; + struct FieldKeyComparator { + bool operator()(const FieldKey& lhs, const FieldKey& rhs) const { + if (lhs.class_descriptor != rhs.class_descriptor) { + return lhs.class_descriptor < rhs.class_descriptor; + } + if (lhs.name != rhs.name) { + return lhs.name < rhs.name; + } + return lhs.type < rhs.type; + } + }; + + struct ProtoKey { + std::string shorty; + std::string return_type; + std::vector<std::string> args; + }; + struct ProtoKeyComparator { + bool operator()(const ProtoKey& lhs, const ProtoKey& rhs) const { + if (lhs.return_type != rhs.return_type) { + return lhs.return_type < rhs.return_type; + } + size_t min_args = std::min(lhs.args.size(), rhs.args.size()); + for (size_t i = 0; i != min_args; ++i) { + if (lhs.args[i] != rhs.args[i]) { + return lhs.args[i] < rhs.args[i]; + } + } + return lhs.args.size() < rhs.args.size(); + } + }; + + struct MethodKey { + std::string class_descriptor; + std::string name; + const ProtoKey* proto; + }; + struct MethodKeyComparator { + bool operator()(const MethodKey& lhs, const MethodKey& rhs) const { + if (lhs.class_descriptor != rhs.class_descriptor) { + return lhs.class_descriptor < rhs.class_descriptor; + } + if (lhs.name != rhs.name) { + return lhs.name < rhs.name; + } + return ProtoKeyComparator()(*lhs.proto, *rhs.proto); + } + }; + + ProtoKey CreateProtoKey(const std::string& signature) { + CHECK_EQ(signature[0], '('); + const char* args = signature.c_str() + 1; + const char* args_end = std::strchr(args, ')'); + CHECK(args_end != nullptr); + const char* return_type = args_end + 1; + + ProtoKey key = { + std::string() + ((*return_type == '[') ? 'L' : *return_type), + return_type, + std::vector<std::string>() + }; + while (args != args_end) { + key.shorty += (*args == '[') ? 'L' : *args; + const char* arg_start = args; + while (*args == '[') { + ++args; + } + if (*args == 'L') { + do { + ++args; + CHECK_NE(args, args_end); + } while (*args != ';'); + } + ++args; + key.args.emplace_back(arg_start, args); + } + return key; + } + + void Write32(size_t offset, uint32_t value) { + CHECK_LE(offset + 4u, dex_file_data_.size()); + CHECK_EQ(dex_file_data_[offset + 0], 0u); + CHECK_EQ(dex_file_data_[offset + 1], 0u); + CHECK_EQ(dex_file_data_[offset + 2], 0u); + CHECK_EQ(dex_file_data_[offset + 3], 0u); + dex_file_data_[offset + 0] = static_cast<uint8_t>(value >> 0); + dex_file_data_[offset + 1] = static_cast<uint8_t>(value >> 8); + dex_file_data_[offset + 2] = static_cast<uint8_t>(value >> 16); + dex_file_data_[offset + 3] = static_cast<uint8_t>(value >> 24); + } + + void Write16(size_t offset, uint32_t value) { + CHECK_LE(value, 0xffffu); + CHECK_LE(offset + 2u, dex_file_data_.size()); + CHECK_EQ(dex_file_data_[offset + 0], 0u); + CHECK_EQ(dex_file_data_[offset + 1], 0u); + dex_file_data_[offset + 0] = static_cast<uint8_t>(value >> 0); + dex_file_data_[offset + 1] = static_cast<uint8_t>(value >> 8); + } + + std::map<std::string, IdxAndDataOffset> strings_; + std::map<std::string, uint32_t> types_; + std::map<FieldKey, uint32_t, FieldKeyComparator> fields_; + std::map<ProtoKey, IdxAndDataOffset, ProtoKeyComparator> protos_; + std::map<MethodKey, uint32_t, MethodKeyComparator> methods_; + + std::vector<uint8_t> dex_file_data_; +}; + +} // namespace art + +#endif // ART_COMPILER_UTILS_TEST_DEX_FILE_BUILDER_H_ diff --git a/compiler/utils/test_dex_file_builder_test.cc b/compiler/utils/test_dex_file_builder_test.cc new file mode 100644 index 0000000000..ee6e35dcce --- /dev/null +++ b/compiler/utils/test_dex_file_builder_test.cc @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 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 "test_dex_file_builder.h" + +#include "dex_file-inl.h" +#include "gtest/gtest.h" + +namespace art { + +TEST(TestDexFileBuilderTest, SimpleTest) { + TestDexFileBuilder builder; + builder.AddString("Arbitrary string"); + builder.AddType("Ljava/lang/Class;"); + builder.AddField("LTestClass;", "[I", "intField"); + builder.AddMethod("LTestClass;", "()I", "foo"); + builder.AddMethod("LTestClass;", "(Ljava/lang/Object;[Ljava/lang/Object;)LTestClass;", "bar"); + const char* dex_location = "TestDexFileBuilder/SimpleTest"; + std::unique_ptr<const DexFile> dex_file(builder.Build(dex_location)); + ASSERT_TRUE(dex_file != nullptr); + EXPECT_STREQ(dex_location, dex_file->GetLocation().c_str()); + + static const char* const expected_strings[] = { + "Arbitrary string", + "I", + "LLL", // shorty + "LTestClass;", + "Ljava/lang/Class;", + "Ljava/lang/Object;", + "[I", + "[Ljava/lang/Object;", + "bar", + "foo", + "intField", + }; + ASSERT_EQ(arraysize(expected_strings), dex_file->NumStringIds()); + for (size_t i = 0; i != arraysize(expected_strings); ++i) { + EXPECT_STREQ(expected_strings[i], dex_file->GetStringData(dex_file->GetStringId(i))) << i; + } + + static const char* const expected_types[] = { + "I", + "LTestClass;", + "Ljava/lang/Class;", + "Ljava/lang/Object;", + "[I", + "[Ljava/lang/Object;", + }; + ASSERT_EQ(arraysize(expected_types), dex_file->NumTypeIds()); + for (size_t i = 0; i != arraysize(expected_types); ++i) { + EXPECT_STREQ(expected_types[i], dex_file->GetTypeDescriptor(dex_file->GetTypeId(i))) << i; + } + + ASSERT_EQ(1u, dex_file->NumFieldIds()); + EXPECT_STREQ("[I TestClass.intField", PrettyField(0u, *dex_file).c_str()); + + ASSERT_EQ(2u, dex_file->NumProtoIds()); + ASSERT_EQ(2u, dex_file->NumMethodIds()); + EXPECT_STREQ("TestClass TestClass.bar(java.lang.Object, java.lang.Object[])", + PrettyMethod(0u, *dex_file).c_str()); + EXPECT_STREQ("int TestClass.foo()", + PrettyMethod(1u, *dex_file).c_str()); + + EXPECT_EQ(0u, builder.GetStringIdx("Arbitrary string")); + EXPECT_EQ(2u, builder.GetTypeIdx("Ljava/lang/Class;")); + EXPECT_EQ(0u, builder.GetFieldIdx("LTestClass;", "[I", "intField")); + EXPECT_EQ(1u, builder.GetMethodIdx("LTestClass;", "()I", "foo")); + EXPECT_EQ(0u, builder.GetMethodIdx("LTestClass;", "(Ljava/lang/Object;[Ljava/lang/Object;)LTestClass;", "bar")); +} + +} // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 2a3a346a4c..b764095fc5 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1217,9 +1217,9 @@ class Dex2Oat FINAL { if (!UseSwap(image_, dex_files_)) { close(swap_fd_); swap_fd_ = -1; - LOG(INFO) << "Decided to run without swap."; + VLOG(compiler) << "Decided to run without swap."; } else { - LOG(INFO) << "Accepted running with swap."; + LOG(INFO) << "Large app, accepted running with swap."; } } // Note that dex2oat won't close the swap_fd_. The compiler driver's swap space will do that. diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 8f6162ffa0..599c22a088 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -669,6 +669,18 @@ ENTRY art_quick_aput_obj END art_quick_aput_obj // Macro to facilitate adding new allocation entrypoints. +.macro ONE_ARG_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME r1, r2 @ save callee saves in case of GC + mov r1, r9 @ pass Thread::Current + bl \entrypoint @ (uint32_t type_idx, Method* method, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + \return +END \name +.endm + +// Macro to facilitate adding new allocation entrypoints. .macro TWO_ARG_DOWNCALL name, entrypoint, return .extern \entrypoint ENTRY \name @@ -693,10 +705,9 @@ ENTRY \name END \name .endm -TWO_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER - -TWO_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -TWO_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER /* * Called by managed code to resolve a static field and load a non-wide value. @@ -805,11 +816,10 @@ END art_quick_set64_instance /* * Entry from managed code to resolve a string, this stub will allocate a String and deliver an - * exception on error. On success the String is returned. R0 holds the referring method, - * R1 holds the string index. The fast path check for hit in strings cache has already been - * performed. + * exception on error. On success the String is returned. R0 holds the string index. The fast + * path check for hit in strings cache has already been performed. */ -TWO_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // Generate the allocation entrypoints for each allocator. GENERATE_ALL_ALLOC_ENTRYPOINTS diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index cbd4b7c337..1e78877de7 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1261,6 +1261,18 @@ ENTRY art_quick_aput_obj END art_quick_aput_obj // Macro to facilitate adding new allocation entrypoints. +.macro ONE_ARG_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save callee saves in case of GC + mov x1, xSELF // pass Thread::Current + bl \entrypoint // (uint32_t type_idx, Method* method, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + \return +END \name +.endm + +// Macro to facilitate adding new allocation entrypoints. .macro TWO_ARG_DOWNCALL name, entrypoint, return .extern \entrypoint ENTRY \name @@ -1339,10 +1351,10 @@ TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, * initializer and deliver the exception on error. On success the static storage base is * returned. */ -TWO_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -TWO_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -TWO_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 @@ -1386,11 +1398,10 @@ END art_quick_set64_static /* * Entry from managed code to resolve a string, this stub will allocate a String and deliver an - * exception on error. On success the String is returned. x0 holds the referring method, - * w1 holds the string index. The fast path check for hit in strings cache has already been - * performed. + * exception on error. On success the String is returned. w0 holds the string index. The fast + * path check for hit in strings cache has already been performed. */ -TWO_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // Generate the allocation entrypoints for each allocator. GENERATE_ALL_ALLOC_ENTRYPOINTS diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 622c48f55f..356a145eae 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -982,6 +982,16 @@ ENTRY art_quick_set_obj_instance RETURN_IF_ZERO END art_quick_set_obj_instance +.macro ONE_ARG_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case of GC + jal \entrypoint + move $a1, rSELF # pass Thread::Current + \return +END \name +.endm + // Macro to facilitate adding new allocation entrypoints. .macro TWO_ARG_DOWNCALL name, entrypoint, return .extern \entrypoint @@ -1008,29 +1018,28 @@ GENERATE_ALL_ALLOC_ENTRYPOINTS /* * Entry from managed code to resolve a string, this stub will allocate a String and deliver an - * exception on error. On success the String is returned. R0 holds the referring method, - * R1 holds the string index. The fast path check for hit in strings cache has already been - * performed. + * exception on error. On success the String is returned. A0 holds the string index. The fast + * path check for hit in strings cache has already been performed. */ -TWO_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER /* * Entry from managed code when uninitialized static storage, this stub will run the class * initializer and deliver the exception on error. On success the static storage base is * returned. */ -TWO_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER /* * Entry from managed code when dex cache misses for a type_idx. */ -TWO_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER /* * Entry from managed code when type_idx needs to be checked for access and dex cache may also * miss. */ -TWO_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER /* * Called by managed code when the value in rSUSPEND has been decremented to 0. diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index bf18dd5f82..f867aa8367 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -945,45 +945,6 @@ ENTRY art_quick_aput_obj END art_quick_aput_obj /* - * Entry from managed code when uninitialized static storage, this stub will run the class - * initializer and deliver the exception on error. On success the static storage base is - * returned. - */ - .extern artInitializeStaticStorageFromCode -ENTRY art_quick_initialize_static_storage - SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case of GC - # artInitializeStaticStorageFromCode(uint32_t type_idx, Method* referrer, Thread*) - jal artInitializeStaticStorageFromCode - move $a2, rSELF # pass Thread::Current - RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_initialize_static_storage - - /* - * Entry from managed code when dex cache misses for a type_idx. - */ - .extern artInitializeTypeFromCode -ENTRY art_quick_initialize_type - SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case of GC - # artInitializeTypeFromCode(uint32_t type_idx, Method* referrer, Thread*) - jal artInitializeTypeFromCode - move $a2, rSELF # pass Thread::Current - RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_initialize_type - - /* - * Entry from managed code when type_idx needs to be checked for access and dex cache may also - * miss. - */ - .extern artInitializeTypeAndVerifyAccessFromCode -ENTRY art_quick_initialize_type_and_verify_access - SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case of GC - # artInitializeTypeFromCode(uint32_t type_idx, Method* referrer, Thread*) - jal artInitializeTypeAndVerifyAccessFromCode - move $a2, rSELF # pass Thread::Current - RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_initialize_type_and_verify_access - - /* * Called by managed code to resolve a static field and load a boolean primitive value. */ .extern artGetBooleanStaticFromCode @@ -1272,20 +1233,16 @@ ENTRY art_quick_set_obj_instance RETURN_IF_ZERO END art_quick_set_obj_instance - /* - * Entry from managed code to resolve a string, this stub will allocate a String and deliver an - * exception on error. On success the String is returned. R0 holds the referring method, - * R1 holds the string index. The fast path check for hit in strings cache has already been - * performed. - */ - .extern artResolveStringFromCode -ENTRY art_quick_resolve_string +// Macro to facilitate adding new allocation entrypoints. +.macro ONE_ARG_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case of GC - # artResolveStringFromCode(Method* referrer, uint32_t string_idx, Thread*, $sp) - jal artResolveStringFromCode - move $a2, rSELF # pass Thread::Current - RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_resolve_string + jal \entrypoint + move $a1, rSELF # pass Thread::Current + \return +END \name +.endm // Macro to facilitate adding new allocation entrypoints. .macro TWO_ARG_DOWNCALL name, entrypoint, return @@ -1312,6 +1269,31 @@ END \name GENERATE_ALL_ALLOC_ENTRYPOINTS /* + * Entry from managed code to resolve a string, this stub will allocate a String and deliver an + * exception on error. On success the String is returned. A0 holds the string index. The fast + * path check for hit in strings cache has already been performed. + */ +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER + + /* + * Entry from managed code when uninitialized static storage, this stub will run the class + * initializer and deliver the exception on error. On success the static storage base is + * returned. + */ +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER + + /* + * Entry from managed code when dex cache misses for a type_idx. + */ +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER + + /* + * Entry from managed code when type_idx needs to be checked for access and dex cache may also + * miss. + */ +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER + + /* * Called by managed code when the value in rSUSPEND has been decremented to 0. */ .extern artTestSuspendFromCode diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index c5d8b8fc9b..55e3dff76c 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -910,10 +910,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab_instrument GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab_instrumented, RegionTLABInstrumented) -TWO_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_EAX_ZERO diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 8185deb9e2..570624c054 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -980,10 +980,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab_instrument GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab_instrumented, RegionTLABInstrumented) -TWO_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO -TWO_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO +ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_EAX_ZERO diff --git a/runtime/barrier.cc b/runtime/barrier.cc index 66ee8709a7..f80a65f0ba 100644 --- a/runtime/barrier.cc +++ b/runtime/barrier.cc @@ -86,7 +86,7 @@ void Barrier::SetCountLocked(Thread* self, int count) { } Barrier::~Barrier() { - CHECK(!count_) << "Attempted to destroy barrier with non zero count"; + CHECK_EQ(count_, 0) << "Attempted to destroy barrier with non zero count"; } } // namespace art diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 64b7ecdabe..9292cff88e 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -22,6 +22,8 @@ #include "class_linker-inl.h" #include "common_throws.h" #include "dex_file.h" +#include "entrypoints/quick/callee_save_frame.h" +#include "handle_scope-inl.h" #include "indirect_reference_table.h" #include "invoke_type.h" #include "jni_internal.h" @@ -30,11 +32,31 @@ #include "mirror/class-inl.h" #include "mirror/object-inl.h" #include "mirror/throwable.h" -#include "handle_scope-inl.h" +#include "nth_caller_visitor.h" +#include "runtime.h" #include "thread.h" namespace art { +inline mirror::ArtMethod* GetCalleeSaveMethodCaller(Thread* self, Runtime::CalleeSaveType type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + auto* refs_only_sp = self->GetManagedStack()->GetTopQuickFrame(); + DCHECK_EQ(refs_only_sp->AsMirrorPtr(), Runtime::Current()->GetCalleeSaveMethod(type)); + + const size_t callee_frame_size = GetCalleeSaveFrameSize(kRuntimeISA, type); + auto* caller_sp = reinterpret_cast<StackReference<mirror::ArtMethod>*>( + reinterpret_cast<uintptr_t>(refs_only_sp) + callee_frame_size); + auto* caller = caller_sp->AsMirrorPtr(); + + if (kIsDebugBuild) { + NthCallerVisitor visitor(self, 1, true); + visitor.WalkStack(); + CHECK(caller == visitor.caller); + } + + return caller; +} + template <const bool kAccessCheck> ALWAYS_INLINE inline mirror::Class* CheckObjectAlloc(uint32_t type_idx, diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h index b7e8d50790..1fd8a949a9 100644 --- a/runtime/entrypoints/quick/quick_default_externs.h +++ b/runtime/entrypoints/quick/quick_default_externs.h @@ -34,10 +34,10 @@ class Object; extern "C" void art_quick_check_cast(const art::mirror::Class*, const art::mirror::Class*); // DexCache entrypoints. -extern "C" void* art_quick_initialize_static_storage(uint32_t, art::mirror::ArtMethod*); -extern "C" void* art_quick_initialize_type(uint32_t, art::mirror::ArtMethod*); -extern "C" void* art_quick_initialize_type_and_verify_access(uint32_t, art::mirror::ArtMethod*); -extern "C" void* art_quick_resolve_string(uint32_t, art::mirror::ArtMethod*); +extern "C" void* art_quick_initialize_static_storage(uint32_t); +extern "C" void* art_quick_initialize_type(uint32_t); +extern "C" void* art_quick_initialize_type_and_verify_access(uint32_t); +extern "C" void* art_quick_resolve_string(uint32_t); // Field entrypoints. extern "C" int art_quick_set8_instance(uint32_t, void*, int8_t); diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc index 348495d354..46629f5958 100644 --- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc @@ -26,41 +26,41 @@ namespace art { extern "C" mirror::Class* artInitializeStaticStorageFromCode(uint32_t type_idx, - mirror::ArtMethod* referrer, Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // Called to ensure static storage base is initialized for direct static field reads and writes. // A class may be accessing another class' fields when it doesn't have access, as access has been // given by inheritance. ScopedQuickEntrypointChecks sqec(self); - return ResolveVerifyAndClinit(type_idx, referrer, self, true, false); + auto* caller = GetCalleeSaveMethodCaller(self, Runtime::kRefsOnly); + return ResolveVerifyAndClinit(type_idx, caller, self, true, false); } extern "C" mirror::Class* artInitializeTypeFromCode(uint32_t type_idx, - mirror::ArtMethod* referrer, Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // Called when method->dex_cache_resolved_types_[] misses. ScopedQuickEntrypointChecks sqec(self); - return ResolveVerifyAndClinit(type_idx, referrer, self, false, false); + auto* caller = GetCalleeSaveMethodCaller(self, Runtime::kRefsOnly); + return ResolveVerifyAndClinit(type_idx, caller, self, false, false); } extern "C" mirror::Class* artInitializeTypeAndVerifyAccessFromCode(uint32_t type_idx, - mirror::ArtMethod* referrer, Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // Called when caller isn't guaranteed to have access to a type and the dex cache may be // unpopulated. ScopedQuickEntrypointChecks sqec(self); - return ResolveVerifyAndClinit(type_idx, referrer, self, false, true); + auto* caller = GetCalleeSaveMethodCaller(self, Runtime::kRefsOnly); + return ResolveVerifyAndClinit(type_idx, caller, self, false, true); } extern "C" mirror::String* artResolveStringFromCode(int32_t string_idx, - mirror::ArtMethod* referrer, Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); - return ResolveStringFromCode(referrer, string_idx); + auto* caller = GetCalleeSaveMethodCaller(self, Runtime::kRefsOnly); + return ResolveStringFromCode(caller, string_idx); } } // namespace art diff --git a/runtime/entrypoints/quick/quick_entrypoints_list.h b/runtime/entrypoints/quick/quick_entrypoints_list.h index eaf874eae8..6d9e483a9c 100644 --- a/runtime/entrypoints/quick/quick_entrypoints_list.h +++ b/runtime/entrypoints/quick/quick_entrypoints_list.h @@ -33,10 +33,10 @@ V(InstanceofNonTrivial, uint32_t, const mirror::Class*, const mirror::Class*) \ V(CheckCast, void, const mirror::Class*, const mirror::Class*) \ \ - V(InitializeStaticStorage, void*, uint32_t, mirror::ArtMethod*) \ - V(InitializeTypeAndVerifyAccess, void*, uint32_t, mirror::ArtMethod*) \ - V(InitializeType, void*, uint32_t, mirror::ArtMethod*) \ - V(ResolveString, void*, uint32_t, mirror::ArtMethod*) \ + V(InitializeStaticStorage, void*, uint32_t) \ + V(InitializeTypeAndVerifyAccess, void*, uint32_t) \ + V(InitializeType, void*, uint32_t) \ + V(ResolveString, void*, uint32_t) \ \ V(Set8Instance, int, uint32_t, void*, int8_t) \ V(Set8Static, int, uint32_t, int8_t) \ diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index af01a02787..8524348164 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -65,6 +65,7 @@ static void EnableDebugFeatures(uint32_t debug_flags) { DEBUG_ENABLE_SAFEMODE = 1 << 3, DEBUG_ENABLE_JNI_LOGGING = 1 << 4, DEBUG_ENABLE_JIT = 1 << 5, + DEBUG_GENERATE_CFI = 1 << 6, }; Runtime* const runtime = Runtime::Current(); @@ -111,6 +112,12 @@ static void EnableDebugFeatures(uint32_t debug_flags) { } runtime->GetJITOptions()->SetUseJIT(use_jit); + const bool generate_cfi = (debug_flags & DEBUG_GENERATE_CFI) != 0; + if (generate_cfi) { + runtime->AddCompilerOption("--include-cfi"); + debug_flags &= ~DEBUG_GENERATE_CFI; + } + // This is for backwards compatibility with Dalvik. debug_flags &= ~DEBUG_ENABLE_ASSERT; diff --git a/runtime/reflection.cc b/runtime/reflection.cc index e54673831f..3099094ed4 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -615,11 +615,21 @@ jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaM // Wrap any exception with "Ljava/lang/reflect/InvocationTargetException;" and return early. if (soa.Self()->IsExceptionPending()) { + // If we get another exception when we are trying to wrap, then just use that instead. jthrowable th = soa.Env()->ExceptionOccurred(); - soa.Env()->ExceptionClear(); + soa.Self()->ClearException(); jclass exception_class = soa.Env()->FindClass("java/lang/reflect/InvocationTargetException"); + if (exception_class == nullptr) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } jmethodID mid = soa.Env()->GetMethodID(exception_class, "<init>", "(Ljava/lang/Throwable;)V"); + CHECK(mid != nullptr); jobject exception_instance = soa.Env()->NewObject(exception_class, mid, th); + if (exception_instance == nullptr) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } soa.Env()->Throw(reinterpret_cast<jthrowable>(exception_instance)); return nullptr; } diff --git a/runtime/stack.cc b/runtime/stack.cc index aa3e320127..e49bc1d78f 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -345,7 +345,7 @@ bool StackVisitor::SetVReg(mirror::ArtMethod* m, uint16_t vreg, uint32_t new_val DCHECK(context_ != nullptr); // You can't reliably write registers without a context. DCHECK(m == GetMethod()); if (m->IsOptimized(sizeof(void*))) { - return SetVRegFromOptimizedCode(m, vreg, new_value, kind); + return false; } else { return SetVRegFromQuickCode(m, vreg, new_value, kind); } @@ -382,57 +382,6 @@ bool StackVisitor::SetVRegFromQuickCode(mirror::ArtMethod* m, uint16_t vreg, uin } } -bool StackVisitor::SetVRegFromOptimizedCode(mirror::ArtMethod* m, uint16_t vreg, uint32_t new_value, - VRegKind kind) { - const void* code_pointer = m->GetQuickOatCodePointer(sizeof(void*)); - DCHECK(code_pointer != nullptr); - uint32_t native_pc_offset = m->NativeQuickPcOffset(cur_quick_frame_pc_); - CodeInfo code_info = m->GetOptimizedCodeInfo(); - StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset); - const DexFile::CodeItem* code_item = m->GetCodeItem(); - DCHECK(code_item != nullptr) << PrettyMethod(m); // Can't be null or how would we compile - // its instructions? - uint16_t number_of_dex_registers = code_item->registers_size_; - DCHECK_LT(vreg, number_of_dex_registers); - DexRegisterMap dex_register_map = - code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers); - DexRegisterLocation::Kind location_kind = - dex_register_map.GetLocationKind(vreg, number_of_dex_registers, code_info); - uint32_t dex_pc = m->ToDexPc(cur_quick_frame_pc_, false); - switch (location_kind) { - case DexRegisterLocation::Kind::kInStack: { - const int32_t offset = - dex_register_map.GetStackOffsetInBytes(vreg, number_of_dex_registers, code_info); - uint8_t* addr = reinterpret_cast<uint8_t*>(cur_quick_frame_) + offset; - *reinterpret_cast<uint32_t*>(addr) = new_value; - return true; - } - case DexRegisterLocation::Kind::kInRegister: - case DexRegisterLocation::Kind::kInFpuRegister: { - uint32_t reg = dex_register_map.GetMachineRegister(vreg, number_of_dex_registers, code_info); - return SetRegisterIfAccessible(reg, new_value, kind); - } - case DexRegisterLocation::Kind::kConstant: - LOG(ERROR) << StringPrintf("Cannot change value of DEX register v%u used as a constant at " - "DEX pc 0x%x (native pc 0x%x) of method %s", - vreg, dex_pc, native_pc_offset, - PrettyMethod(cur_quick_frame_->AsMirrorPtr()).c_str()); - return false; - case DexRegisterLocation::Kind::kNone: - LOG(ERROR) << StringPrintf("No location for DEX register v%u at DEX pc 0x%x " - "(native pc 0x%x) of method %s", - vreg, dex_pc, native_pc_offset, - PrettyMethod(cur_quick_frame_->AsMirrorPtr()).c_str()); - return false; - default: - LOG(FATAL) << StringPrintf("Unknown location for DEX register v%u at DEX pc 0x%x " - "(native pc 0x%x) of method %s", - vreg, dex_pc, native_pc_offset, - PrettyMethod(cur_quick_frame_->AsMirrorPtr()).c_str()); - UNREACHABLE(); - } -} - bool StackVisitor::SetRegisterIfAccessible(uint32_t reg, uint32_t new_value, VRegKind kind) { const bool is_float = (kind == kFloatVReg) || (kind == kDoubleLoVReg) || (kind == kDoubleHiVReg); if (!IsAccessibleRegister(reg, is_float)) { @@ -477,7 +426,7 @@ bool StackVisitor::SetVRegPair(mirror::ArtMethod* m, uint16_t vreg, uint64_t new DCHECK(context_ != nullptr); // You can't reliably write registers without a context. DCHECK(m == GetMethod()); if (m->IsOptimized(sizeof(void*))) { - return SetVRegPairFromOptimizedCode(m, vreg, new_value, kind_lo, kind_hi); + return false; } else { return SetVRegPairFromQuickCode(m, vreg, new_value, kind_lo, kind_hi); } @@ -515,15 +464,6 @@ bool StackVisitor::SetVRegPairFromQuickCode( } } -bool StackVisitor::SetVRegPairFromOptimizedCode( - mirror::ArtMethod* m, uint16_t vreg, uint64_t new_value, VRegKind kind_lo, VRegKind kind_hi) { - uint32_t low_32bits = Low32Bits(new_value); - uint32_t high_32bits = High32Bits(new_value); - bool success = SetVRegFromOptimizedCode(m, vreg, low_32bits, kind_lo); - success &= SetVRegFromOptimizedCode(m, vreg + 1, high_32bits, kind_hi); - return success; -} - bool StackVisitor::SetRegisterPairIfAccessible(uint32_t reg_lo, uint32_t reg_hi, uint64_t new_value, bool is_float) { if (!IsAccessibleRegister(reg_lo, is_float) || !IsAccessibleRegister(reg_hi, is_float)) { diff --git a/runtime/stack.h b/runtime/stack.h index ed9e458529..e2af5eefd2 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -668,18 +668,12 @@ class StackVisitor { bool SetVRegFromQuickCode(mirror::ArtMethod* m, uint16_t vreg, uint32_t new_value, VRegKind kind) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - bool SetVRegFromOptimizedCode(mirror::ArtMethod* m, uint16_t vreg, uint32_t new_value, - VRegKind kind) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); bool SetRegisterIfAccessible(uint32_t reg, uint32_t new_value, VRegKind kind) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); bool SetVRegPairFromQuickCode(mirror::ArtMethod* m, uint16_t vreg, uint64_t new_value, VRegKind kind_lo, VRegKind kind_hi) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - bool SetVRegPairFromOptimizedCode(mirror::ArtMethod* m, uint16_t vreg, uint64_t new_value, - VRegKind kind_lo, VRegKind kind_hi) - SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); bool SetRegisterPairIfAccessible(uint32_t reg_lo, uint32_t reg_hi, uint64_t new_value, bool is_float) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); diff --git a/runtime/thread.cc b/runtime/thread.cc index fa65bceff1..9f7c303af9 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -572,13 +572,13 @@ void Thread::ShortDump(std::ostream& os) const { if (GetThreadId() != 0) { // If we're in kStarting, we won't have a thin lock id or tid yet. os << GetThreadId() - << ",tid=" << GetTid() << ','; + << ",tid=" << GetTid() << ','; } os << GetState() - << ",Thread*=" << this - << ",peer=" << tlsPtr_.opeer - << ",\"" << *tlsPtr_.name << "\"" - << "]"; + << ",Thread*=" << this + << ",peer=" << tlsPtr_.opeer + << ",\"" << (tlsPtr_.name != nullptr ? *tlsPtr_.name : "null") << "\"" + << "]"; } void Thread::Dump(std::ostream& os) const { @@ -1171,9 +1171,14 @@ bool Thread::IsStillStarting() const { } void Thread::AssertPendingException() const { - if (UNLIKELY(!IsExceptionPending())) { - LOG(FATAL) << "Pending exception expected."; - } + CHECK(IsExceptionPending()) << "Pending exception expected."; +} + +void Thread::AssertPendingOOMException() const { + AssertPendingException(); + auto* e = GetException(); + CHECK_EQ(e->GetClass(), DecodeJObject(WellKnownClasses::java_lang_OutOfMemoryError)->AsClass()) + << e->Dump(); } void Thread::AssertNoPendingException() const { diff --git a/runtime/thread.h b/runtime/thread.h index dd9e7345df..35b785df63 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -336,6 +336,7 @@ class Thread { } void AssertPendingException() const; + void AssertPendingOOMException() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); void AssertNoPendingException() const; void AssertNoPendingExceptionForNewException(const char* msg) const; diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc index ae24b77625..d8f8950fbd 100644 --- a/runtime/utils_test.cc +++ b/runtime/utils_test.cc @@ -384,7 +384,8 @@ TEST_F(UtilsTest, GetSystemImageFilename) { TEST_F(UtilsTest, ExecSuccess) { std::vector<std::string> command; if (kIsTargetBuild) { - command.push_back("/system/bin/id"); + std::string android_root(GetAndroidRoot()); + command.push_back(android_root + "/bin/id"); } else { command.push_back("/usr/bin/id"); } diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index a803df8b7e..a2d0427244 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -39,6 +39,7 @@ jclass WellKnownClasses::java_lang_ClassNotFoundException; jclass WellKnownClasses::java_lang_Daemons; jclass WellKnownClasses::java_lang_Error; jclass WellKnownClasses::java_lang_Object; +jclass WellKnownClasses::java_lang_OutOfMemoryError; jclass WellKnownClasses::java_lang_reflect_AbstractMethod; jclass WellKnownClasses::java_lang_reflect_ArtMethod; jclass WellKnownClasses::java_lang_reflect_Constructor; @@ -176,6 +177,7 @@ void WellKnownClasses::Init(JNIEnv* env) { java_lang_ClassNotFoundException = CacheClass(env, "java/lang/ClassNotFoundException"); java_lang_Daemons = CacheClass(env, "java/lang/Daemons"); java_lang_Object = CacheClass(env, "java/lang/Object"); + java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError"); java_lang_Error = CacheClass(env, "java/lang/Error"); java_lang_reflect_AbstractMethod = CacheClass(env, "java/lang/reflect/AbstractMethod"); java_lang_reflect_ArtMethod = CacheClass(env, "java/lang/reflect/ArtMethod"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index 2df1c0e6b0..cef9d55524 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -50,6 +50,7 @@ struct WellKnownClasses { static jclass java_lang_Daemons; static jclass java_lang_Error; static jclass java_lang_Object; + static jclass java_lang_OutOfMemoryError; static jclass java_lang_reflect_AbstractMethod; static jclass java_lang_reflect_ArtMethod; static jclass java_lang_reflect_Constructor; diff --git a/test/080-oom-throw/expected.txt b/test/080-oom-throw/expected.txt index 73cc0d8b3e..904393bc3b 100644 --- a/test/080-oom-throw/expected.txt +++ b/test/080-oom-throw/expected.txt @@ -1,2 +1,3 @@ +Test reflection correctly threw NEW_ARRAY correctly threw OOME NEW_INSTANCE correctly threw OOME diff --git a/test/080-oom-throw/src/Main.java b/test/080-oom-throw/src/Main.java index c93f8bbc54..f007b2535c 100644 --- a/test/080-oom-throw/src/Main.java +++ b/test/080-oom-throw/src/Main.java @@ -14,6 +14,9 @@ * limitations under the License. */ +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + public class Main { static class ArrayMemEater { static boolean sawOome; @@ -68,6 +71,10 @@ public class Main { } public static void main(String[] args) { + if (triggerReflectionOOM()) { + System.out.println("Test reflection correctly threw"); + } + if (triggerArrayOOM()) { System.out.println("NEW_ARRAY correctly threw OOME"); } @@ -76,4 +83,46 @@ public class Main { System.out.println("NEW_INSTANCE correctly threw OOME"); } } + + static Object[] holder; + + public static void blowup() throws Exception { + int size = 32 * 1024 * 1024; + for (int i = 0; i < holder.length; ) { + try { + holder[i] = new char[size]; + i++; + } catch (OutOfMemoryError oome) { + size = size / 2; + if (size == 0) { + break; + } + } + } + holder[0] = new char[100000]; + } + + static boolean triggerReflectionOOM() { + try { + Class<?> c = Main.class; + Method m = c.getMethod("blowup", (Class[]) null); + holder = new Object[1000000]; + m.invoke(null); + holder = null; + System.out.println("Didn't throw from blowup"); + } catch (OutOfMemoryError e) { + holder = null; + } catch (InvocationTargetException e) { + holder = null; + if (!(e.getCause() instanceof OutOfMemoryError)) { + System.out.println("InvocationTargetException cause not OOME " + e.getCause()); + return false; + } + } catch (Exception e) { + holder = null; + System.out.println("Unexpected exception " + e); + return false; + } + return true; + } } diff --git a/test/104-growth-limit/src/Main.java b/test/104-growth-limit/src/Main.java index d666377b54..d31cbf1fe1 100644 --- a/test/104-growth-limit/src/Main.java +++ b/test/104-growth-limit/src/Main.java @@ -29,26 +29,28 @@ public class Main { final Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime"); final Object runtime = get_runtime.invoke(null); final Method clear_growth_limit = vm_runtime.getDeclaredMethod("clearGrowthLimit"); + List<byte[]> l = new ArrayList<byte[]>(); try { - List<byte[]> l = new ArrayList<byte[]>(); while (true) { // Allocate a MB at a time l.add(new byte[1048576]); alloc1++; } } catch (OutOfMemoryError e) { + l = null; } // Expand the heap to the maximum size. clear_growth_limit.invoke(runtime); int alloc2 = 1; + l = new ArrayList<byte[]>(); try { - List<byte[]> l = new ArrayList<byte[]>(); while (true) { // Allocate a MB at a time l.add(new byte[1048576]); alloc2++; } } catch (OutOfMemoryError e2) { + l = null; if (alloc1 > alloc2) { System.out.println("ERROR: Allocated less memory after growth" + "limit cleared (" + alloc1 + " MBs > " + alloc2 + " MBs"); diff --git a/test/458-checker-instruction-simplification/src/Main.java b/test/458-checker-instruction-simplification/src/Main.java index 65be6cb4c4..f0578ef1ea 100644 --- a/test/458-checker-instruction-simplification/src/Main.java +++ b/test/458-checker-instruction-simplification/src/Main.java @@ -374,6 +374,15 @@ public class Main { // CHECK-DAG: [[Or:i\d+]] Or [ [[Add1]] [[Add2]] ] // CHECK-DAG: Return [ [[Or]] ] + // CHECK-START: int Main.AddNegs2(int, int) GVN (after) + // CHECK-DAG: [[Arg1:i\d+]] ParameterValue + // CHECK-DAG: [[Arg2:i\d+]] ParameterValue + // CHECK-DAG: [[Neg1:i\d+]] Neg [ [[Arg1]] ] + // CHECK-DAG: [[Neg2:i\d+]] Neg [ [[Arg2]] ] + // CHECK-DAG: [[Add:i\d+]] Add [ [[Neg1]] [[Neg2]] ] + // CHECK-DAG: [[Or:i\d+]] Or [ [[Add]] [[Add]] ] + // CHECK-DAG: Return [ [[Or]] ] + public static int AddNegs2(int arg1, int arg2) { int temp1 = -arg1; int temp2 = -arg2; @@ -530,6 +539,12 @@ public class Main { // CHECK-NOT: Neg // CHECK-NOT: Add + // CHECK-START: int Main.NegNeg2(int) constant_folding_after_inlining (after) + // CHECK: [[Const0:i\d+]] IntConstant 0 + // CHECK-NOT: Neg + // CHECK-NOT: Add + // CHECK: Return [ [[Const0]] ] + public static int NegNeg2(int arg) { int temp = -arg; return temp + -temp; diff --git a/test/474-checker-boolean-input/src/Main.java b/test/474-checker-boolean-input/src/Main.java index 1ebe14ede8..9151986ca2 100644 --- a/test/474-checker-boolean-input/src/Main.java +++ b/test/474-checker-boolean-input/src/Main.java @@ -23,35 +23,11 @@ public class Main { } /* - * Test that zero/one constants are accepted as Boolean inputs. - */ - - // CHECK-START: boolean Main.TestConstAsBoolean() inliner (before) - // CHECK-DAG: [[Invoke:z\d+]] InvokeStaticOrDirect - // CHECK-DAG: BooleanNot [ [[Invoke]] ] - - // CHECK-START: boolean Main.TestConstAsBoolean() inliner (after) - // CHECK-DAG: [[Const:i\d+]] IntConstant 1 - // CHECK-DAG: BooleanNot [ [[Const]] ] - - public static boolean InlineConst() { - return true; - } - - public static boolean TestConstAsBoolean() { - return InlineConst() != true ? true : false; - } - - /* * Test that integer Phis are accepted as Boolean inputs until * we implement a suitable type analysis. */ - // CHECK-START: boolean Main.TestPhiAsBoolean(int) inliner (before) - // CHECK-DAG: [[Invoke:z\d+]] InvokeStaticOrDirect - // CHECK-DAG: BooleanNot [ [[Invoke]] ] - - // CHECK-START: boolean Main.TestPhiAsBoolean(int) inliner (after) + // CHECK-START: boolean Main.TestPhiAsBoolean(int) boolean_simplifier (after) // CHECK-DAG: [[Phi:i\d+]] Phi // CHECK-DAG: BooleanNot [ [[Phi]] ] @@ -71,11 +47,7 @@ public class Main { * we implement a suitable type analysis. */ - // CHECK-START: boolean Main.TestAndAsBoolean(boolean, boolean) inliner (before) - // CHECK-DAG: [[Invoke:z\d+]] InvokeStaticOrDirect - // CHECK-DAG: BooleanNot [ [[Invoke]] ] - - // CHECK-START: boolean Main.TestAndAsBoolean(boolean, boolean) inliner (after) + // CHECK-START: boolean Main.TestAndAsBoolean(boolean, boolean) boolean_simplifier (after) // CHECK-DAG: [[And:i\d+]] And // CHECK-DAG: BooleanNot [ [[And]] ] @@ -92,11 +64,7 @@ public class Main { * we implement a suitable type analysis. */ - // CHECK-START: boolean Main.TestOrAsBoolean(boolean, boolean) inliner (before) - // CHECK-DAG: [[Invoke:z\d+]] InvokeStaticOrDirect - // CHECK-DAG: BooleanNot [ [[Invoke]] ] - - // CHECK-START: boolean Main.TestOrAsBoolean(boolean, boolean) inliner (after) + // CHECK-START: boolean Main.TestOrAsBoolean(boolean, boolean) boolean_simplifier (after) // CHECK-DAG: [[Or:i\d+]] Or // CHECK-DAG: BooleanNot [ [[Or]] ] @@ -113,11 +81,7 @@ public class Main { * we implement a suitable type analysis. */ - // CHECK-START: boolean Main.TestXorAsBoolean(boolean, boolean) inliner (before) - // CHECK-DAG: [[Invoke:z\d+]] InvokeStaticOrDirect - // CHECK-DAG: BooleanNot [ [[Invoke]] ] - - // CHECK-START: boolean Main.TestXorAsBoolean(boolean, boolean) inliner (after) + // CHECK-START: boolean Main.TestXorAsBoolean(boolean, boolean) boolean_simplifier (after) // CHECK-DAG: [[Xor:i\d+]] Xor // CHECK-DAG: BooleanNot [ [[Xor]] ] @@ -132,7 +96,6 @@ public class Main { public static void main(String[] args) { f1 = true; f2 = false; - assertBoolEquals(false, TestConstAsBoolean()); assertBoolEquals(true, TestPhiAsBoolean(0)); assertBoolEquals(false, TestPhiAsBoolean(42)); assertBoolEquals(true, TestAndAsBoolean(true, false)); diff --git a/test/475-simplify-mul-zero/expected.txt b/test/475-simplify-mul-zero/expected.txt new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/test/475-simplify-mul-zero/expected.txt @@ -0,0 +1 @@ +5 diff --git a/test/475-simplify-mul-zero/info.txt b/test/475-simplify-mul-zero/info.txt new file mode 100644 index 0000000000..0db11f2823 --- /dev/null +++ b/test/475-simplify-mul-zero/info.txt @@ -0,0 +1,2 @@ +Regression check for optimizing simplify instruction pass. +Mul should expect zero constant as input.
\ No newline at end of file diff --git a/test/475-simplify-mul-zero/src/Main.java b/test/475-simplify-mul-zero/src/Main.java new file mode 100644 index 0000000000..57adcff01e --- /dev/null +++ b/test/475-simplify-mul-zero/src/Main.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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) { + long l3 = 2207693990L; + int i12 = 5; + + for (int i = 1; i < 2; ++i) { + i12 ^= (int)(-((-(-(l3 - l3))) * i)); + } + + System.out.println(i12); + } +} diff --git a/test/476-clinit-check-inlining-static-invoke/expected.txt b/test/476-clinit-check-inlining-static-invoke/expected.txt new file mode 100644 index 0000000000..c55bf72369 --- /dev/null +++ b/test/476-clinit-check-inlining-static-invoke/expected.txt @@ -0,0 +1,2 @@ +checkClinitCheckBeforeStaticMethodInvoke START +checkClinitCheckBeforeStaticMethodInvoke PASSED diff --git a/test/476-clinit-check-inlining-static-invoke/info.txt b/test/476-clinit-check-inlining-static-invoke/info.txt new file mode 100644 index 0000000000..1a439fceaf --- /dev/null +++ b/test/476-clinit-check-inlining-static-invoke/info.txt @@ -0,0 +1,3 @@ +Regression test for a bug where an inlined call to a static method +failed to emit a prior initialization check of the method's declaring +class. diff --git a/test/476-clinit-check-inlining-static-invoke/src/Main.java b/test/476-clinit-check-inlining-static-invoke/src/Main.java new file mode 100644 index 0000000000..a7d3bcd2d2 --- /dev/null +++ b/test/476-clinit-check-inlining-static-invoke/src/Main.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 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) { + checkClinitCheckBeforeStaticMethodInvoke(); + } + + static void checkClinitCheckBeforeStaticMethodInvoke() { + System.out.println("checkClinitCheckBeforeStaticMethodInvoke START"); + + // Call static method to cause implicit class initialization, even + // if it is inlined. + ClassWithClinit.$opt$inline$StaticMethod(); + if (!classWithClinitInitialized) { + System.out.println("checkClinitCheckBeforeStaticMethodInvoke FAILED"); + return; + } + + System.out.println("checkClinitCheckBeforeStaticMethodInvoke PASSED"); + } + + static class ClassWithClinit { + static { + Main.classWithClinitInitialized = true; + } + + static void $opt$inline$StaticMethod() { + } + } + + static boolean classWithClinitInitialized = false; +} diff --git a/test/478-checker-clinit-check-pruning/expected.txt b/test/478-checker-clinit-check-pruning/expected.txt new file mode 100644 index 0000000000..387e1a7cb1 --- /dev/null +++ b/test/478-checker-clinit-check-pruning/expected.txt @@ -0,0 +1,6 @@ +Main$ClassWithClinit1's static initializer +Main$ClassWithClinit2's static initializer +Main$ClassWithClinit3's static initializer +Main$ClassWithClinit4's static initializer +Main$ClassWithClinit5's static initializer +Main$ClassWithClinit6's static initializer diff --git a/test/478-checker-clinit-check-pruning/info.txt b/test/478-checker-clinit-check-pruning/info.txt new file mode 100644 index 0000000000..deb64de364 --- /dev/null +++ b/test/478-checker-clinit-check-pruning/info.txt @@ -0,0 +1,3 @@ +Test ensuring class initializations checks (and load class instructions) +added by the graph builder during the construction of a static invoke +are properly pruned. diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java new file mode 100644 index 0000000000..6da8945c43 --- /dev/null +++ b/test/478-checker-clinit-check-pruning/src/Main.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2015 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 { + + /* + * Ensure an inlined static invoke explicitly triggers the + * initialization check of the called method's declaring class, and + * that the corresponding load class instruction does not get + * removed before register allocation & code generation. + */ + + // CHECK-START: void Main.invokeStaticInlined() builder (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // CHECK-START: void Main.invokeStaticInlined() inliner (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + + // CHECK-START: void Main.invokeStaticInlined() inliner (after) + // CHECK-NOT: InvokeStaticOrDirect + + // The following checks ensure the clinit check instruction added by + // the builder is pruned by the PrepareForRegisterAllocation, while + // the load class instruction is preserved. As the control flow + // graph is not dumped after (nor before) this step, we check the + // CFG as it is before the next pass (liveness analysis) instead. + + // CHECK-START: void Main.invokeStaticInlined() liveness (before) + // CHECK-DAG: LoadClass + + // CHECK-START: void Main.invokeStaticInlined() liveness (before) + // CHECK-NOT: ClinitCheck + // CHECK-NOT: InvokeStaticOrDirect + + static void invokeStaticInlined() { + ClassWithClinit1.$opt$inline$StaticMethod(); + } + + static class ClassWithClinit1 { + static { + System.out.println("Main$ClassWithClinit1's static initializer"); + } + + static void $opt$inline$StaticMethod() { + } + } + + /* + * Ensure a non-inlined static invoke eventually has an implicit + * initialization check of the called method's declaring class. + */ + + // CHECK-START: void Main.invokeStaticNotInlined() builder (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // CHECK-START: void Main.invokeStaticNotInlined() inliner (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // The following checks ensure the clinit check and load class + // instructions added by the builder are pruned by the + // PrepareForRegisterAllocation. As the control flow graph is not + // dumped after (nor before) this step, we check the CFG as it is + // before the next pass (liveness analysis) instead. + + // CHECK-START: void Main.invokeStaticNotInlined() liveness (before) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main.invokeStaticNotInlined() liveness (before) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + static void invokeStaticNotInlined() { + ClassWithClinit2.staticMethod(); + } + + static class ClassWithClinit2 { + static { + System.out.println("Main$ClassWithClinit2's static initializer"); + } + + static boolean doThrow = false; + + static void staticMethod() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + } + } + + /* + * Ensure an inlined call to a static method whose declaring class + * is statically known to have been initialized does not require an + * explicit clinit check. + */ + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + // CHECK-NOT: InvokeStaticOrDirect + + static class ClassWithClinit3 { + static void invokeStaticInlined() { + // The invocation of invokeStaticInlined triggers the + // initialization of ClassWithClinit3, meaning that the + // hereinbelow call to $opt$inline$StaticMethod does not need a + // clinit check. + $opt$inline$StaticMethod(); + } + + static { + System.out.println("Main$ClassWithClinit3's static initializer"); + } + + static void $opt$inline$StaticMethod() { + } + } + + /* + * Ensure an non-inlined call to a static method whose declaring + * class is statically known to have been initialized does not + * require an explicit clinit check. + */ + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + static class ClassWithClinit4 { + static void invokeStaticNotInlined() { + // The invocation of invokeStaticNotInlined triggers the + // initialization of ClassWithClinit4, meaning that the + // hereinbelow call to staticMethod does not need a clinit + // check. + staticMethod(); + } + + static { + System.out.println("Main$ClassWithClinit4's static initializer"); + } + + static boolean doThrow = false; + + static void staticMethod() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + } + } + + /* + * Ensure an inlined call to a static method whose declaring class + * is a super class of the caller's class does not require an + * explicit clinit check. + */ + + // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + // CHECK-NOT: InvokeStaticOrDirect + + static class ClassWithClinit5 { + static void $opt$inline$StaticMethod() { + } + + static { + System.out.println("Main$ClassWithClinit5's static initializer"); + } + } + + static class SubClassOfClassWithClinit5 extends ClassWithClinit5 { + static void invokeStaticInlined() { + ClassWithClinit5.$opt$inline$StaticMethod(); + } + } + + /* + * Ensure an non-inlined call to a static method whose declaring + * class is a super class of the caller's class does not require an + * explicit clinit check. + */ + + // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + static class ClassWithClinit6 { + static boolean doThrow = false; + + static void staticMethod() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + } + + static { + System.out.println("Main$ClassWithClinit6's static initializer"); + } + } + + static class SubClassOfClassWithClinit6 extends ClassWithClinit6 { + static void invokeStaticNotInlined() { + ClassWithClinit6.staticMethod(); + } + } + + // TODO: Add a test for the case of a static method whose declaring + // class type index is not available (i.e. when `storage_index` + // equals `DexFile::kDexNoIndex` in + // art::HGraphBuilder::BuildInvoke). + + public static void main(String[] args) { + invokeStaticInlined(); + invokeStaticNotInlined(); + ClassWithClinit3.invokeStaticInlined(); + ClassWithClinit4.invokeStaticNotInlined(); + SubClassOfClassWithClinit5.invokeStaticInlined(); + SubClassOfClassWithClinit6.invokeStaticNotInlined(); + } +} diff --git a/test/480-checker-dead-blocks/expected.txt b/test/480-checker-dead-blocks/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/480-checker-dead-blocks/expected.txt diff --git a/test/480-checker-dead-blocks/info.txt b/test/480-checker-dead-blocks/info.txt new file mode 100644 index 0000000000..5aeafac744 --- /dev/null +++ b/test/480-checker-dead-blocks/info.txt @@ -0,0 +1 @@ +Test removal of dead blocks.
\ No newline at end of file diff --git a/test/480-checker-dead-blocks/src/Main.java b/test/480-checker-dead-blocks/src/Main.java new file mode 100644 index 0000000000..560ce952a4 --- /dev/null +++ b/test/480-checker-dead-blocks/src/Main.java @@ -0,0 +1,147 @@ +/* +* Copyright (C) 2015 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 assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static boolean inlineTrue() { + return true; + } + + public static boolean inlineFalse() { + return false; + } + + // CHECK-START: int Main.testTrueBranch(int, int) dead_code_elimination_final (before) + // CHECK-DAG: [[ArgX:i\d+]] ParameterValue + // CHECK-DAG: [[ArgY:i\d+]] ParameterValue + // CHECK-DAG: If + // CHECK-DAG: [[Add:i\d+]] Add [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: [[Sub:i\d+]] Sub [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: [[Phi:i\d+]] Phi [ [[Add]] [[Sub]] ] + // CHECK-DAG: Return [ [[Phi]] ] + + // CHECK-START: int Main.testTrueBranch(int, int) dead_code_elimination_final (after) + // CHECK-DAG: [[ArgX:i\d+]] ParameterValue + // CHECK-DAG: [[ArgY:i\d+]] ParameterValue + // CHECK-DAG: [[Add:i\d+]] Add [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: Return [ [[Add]] ] + + // CHECK-START: int Main.testTrueBranch(int, int) dead_code_elimination_final (after) + // CHECK-NOT: If + // CHECK-NOT: Sub + // CHECK-NOT: Phi + + public static int testTrueBranch(int x, int y) { + int z; + if (inlineTrue()) { + z = x + y; + } else { + z = x - y; + } + return z; + } + + // CHECK-START: int Main.testFalseBranch(int, int) dead_code_elimination_final (before) + // CHECK-DAG: [[ArgX:i\d+]] ParameterValue + // CHECK-DAG: [[ArgY:i\d+]] ParameterValue + // CHECK-DAG: If + // CHECK-DAG: [[Add:i\d+]] Add [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: [[Sub:i\d+]] Sub [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: [[Phi:i\d+]] Phi [ [[Add]] [[Sub]] ] + // CHECK-DAG: Return [ [[Phi]] ] + + // CHECK-START: int Main.testFalseBranch(int, int) dead_code_elimination_final (after) + // CHECK-DAG: [[ArgX:i\d+]] ParameterValue + // CHECK-DAG: [[ArgY:i\d+]] ParameterValue + // CHECK-DAG: [[Sub:i\d+]] Sub [ [[ArgX]] [[ArgY]] ] + // CHECK-DAG: Return [ [[Sub]] ] + + // CHECK-START: int Main.testFalseBranch(int, int) dead_code_elimination_final (after) + // CHECK-NOT: If + // CHECK-NOT: Add + // CHECK-NOT: Phi + + public static int testFalseBranch(int x, int y) { + int z; + if (inlineFalse()) { + z = x + y; + } else { + z = x - y; + } + return z; + } + + // CHECK-START: int Main.testRemoveLoop(int) dead_code_elimination_final (before) + // CHECK: Mul + + // CHECK-START: int Main.testRemoveLoop(int) dead_code_elimination_final (after) + // CHECK-NOT: Mul + + public static int testRemoveLoop(int x) { + if (inlineFalse()) { + for (int i = 0; i < x; ++i) { + x *= x; + } + } + return x; + } + + // CHECK-START: int Main.testInfiniteLoop(int) dead_code_elimination_final (before) + // CHECK-DAG: Return + // CHECK-DAG: Exit + + // CHECK-START: int Main.testInfiniteLoop(int) dead_code_elimination_final (after) + // CHECK-NOT: Return + // CHECK-NOT: Exit + + public static int testInfiniteLoop(int x) { + while (inlineTrue()) { + x++; + } + return x; + } + + // CHECK-START: int Main.testDeadLoop(int) dead_code_elimination_final (before) + // CHECK-DAG: If + // CHECK-DAG: Add + + // CHECK-START: int Main.testDeadLoop(int) dead_code_elimination_final (after) + // CHECK-DAG: [[Arg:i\d+]] ParameterValue + // CHECK-DAG: Return [ [[Arg]] ] + + // CHECK-START: int Main.testRemoveLoop(int) dead_code_elimination_final (after) + // CHECK-NOT: If + // CHECK-NOT: Add + + public static int testDeadLoop(int x) { + while (inlineFalse()) { + x++; + } + return x; + } + + public static void main(String[] args) { + assertIntEquals(7, testTrueBranch(4, 3)); + assertIntEquals(1, testFalseBranch(4, 3)); + assertIntEquals(42, testRemoveLoop(42)); + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index e31140107a..4ecabcc936 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -354,6 +354,16 @@ ifeq ($(TARGET_ARCH),arm) endif endif +ifdef TARGET_2ND_ARCH + ifeq ($(TARGET_2ND_ARCH),arm) + ifneq (,$(filter 32,$(ALL_ADDRESS_SIZES))) + ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ + $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ + $(IMAGE_TYPES),$(PICTEST_TYPES),$(DEBUGGABLE_TYPES),$(TEST_ART_BROKEN_ARM_RUN_TESTS),32) + endif + endif +endif + TEST_ART_BROKEN_ARM_RUN_TESTS := # Known broken tests for the arm64 optimizing compiler backend. diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 414e4df9f5..1c44958eea 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -225,7 +225,8 @@ if [ "$DEBUGGER" = "y" ]; then fi if [ "$USE_JVM" = "y" ]; then - ${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -classpath classes $MAIN "$@" + # Xmx is necessary since we don't pass down the ART flags to JVM. + ${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes $MAIN "$@" exit fi @@ -363,6 +364,7 @@ if [ "$HOST" = "n" ]; then export ANDROID_ROOT=$ANDROID_ROOT && \ $mkdir_cmdline && \ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \ + export PATH=$ANDROID_ROOT/bin:$PATH && \ $dex2oat_cmdline && \ $dalvikvm_cmdline" @@ -92,6 +92,7 @@ fi ANDROID_DATA=$ANDROID_DATA \ ANDROID_ROOT=$ANDROID_ROOT \ LD_LIBRARY_PATH=$LD_LIBRARY_PATH \ + PATH=$ANDROID_ROOT/bin:$PATH \ $invoke_with $ANDROID_ROOT/bin/$DALVIKVM $lib \ -XXlib:$LIBART \ -Ximage:$ANDROID_ROOT/framework/core.art \ diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index 503ec71293..a007fa2171 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -35,6 +35,7 @@ if [ ! -f $test_jar -o ! -f $junit_jar ]; then fi art="/data/local/tmp/system/bin/art" +art_debugee="sh /data/local/tmp/system/bin/art" # We use Quick's image on target because optimizing's image is not compiled debuggable. image="-Ximage:/data/art-test/core.art" args=$@ @@ -50,6 +51,7 @@ while true; do # Specify bash explicitly since the art script cannot, since it has to run on the device # with mksh. art="bash out/host/linux-x86/bin/art" + art_debugee="bash out/host/linux-x86/bin/art" # We force generation of a new image to avoid build-time and run-time classpath differences. image="-Ximage:/system/non/existent" # We do not need a device directory on host. @@ -81,7 +83,7 @@ vogar $vm_command \ --vm-arg -Djpda.settings.verbose=true \ --vm-arg -Djpda.settings.syncPort=34016 \ --vm-arg -Djpda.settings.transportAddress=127.0.0.1:55107 \ - --vm-arg -Djpda.settings.debuggeeJavaPath="\"$art $image $debuggee_args\"" \ + --vm-arg -Djpda.settings.debuggeeJavaPath="\"$art_debugee $image $debuggee_args\"" \ --classpath $test_jar \ --classpath $junit_jar \ --vm-arg -Xcompiler-option --vm-arg --compiler-backend=Optimizing \ |