Merge "ART: Check for duplicate classes when loading oat files"
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index e0f0ae5..7d76795 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -191,6 +191,7 @@
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/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 ac95abd..3f5271d 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -23,6 +23,7 @@
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 @@
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 0850f42..eb87c29 100644
--- a/compiler/dex/bb_optimizations.h
+++ b/compiler/dex/bb_optimizations.h
@@ -270,7 +270,25 @@
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 b4559ef..c538d0b 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 @@
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 ec12221..bd7bd71 100644
--- a/compiler/dex/gvn_dead_code_elimination.cc
+++ b/compiler/dex/gvn_dead_code_elimination.cc
@@ -347,6 +347,21 @@
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 @@
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_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 @@
// 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 @@
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 9a19f29..bc75a01 100644
--- a/compiler/dex/gvn_dead_code_elimination.h
+++ b/compiler/dex/gvn_dead_code_elimination.h
@@ -111,6 +111,8 @@
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 @@
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 4d2b8b3..3eb372c 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, 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 566527a..0393410 100644
--- a/compiler/dex/local_value_numbering_test.cc
+++ b/compiler/dex/local_value_numbering_test.cc
@@ -158,10 +158,8 @@
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 12e67cd..b4aec98 100644
--- a/compiler/dex/mir_dataflow.cc
+++ b/compiler/dex/mir_dataflow.cc
@@ -123,7 +123,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0d131fb..e4570fd 100644
--- a/compiler/dex/mir_field_info.h
+++ b/compiler/dex/mir_field_info.h
@@ -179,6 +179,7 @@
friend class GlobalValueNumberingTest;
friend class GvnDeadCodeEliminationTest;
friend class LocalValueNumberingTest;
+ friend class TypeInferenceTest;
};
class MirSFieldLoweringInfo : public MirFieldInfo {
@@ -254,6 +255,7 @@
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 0c1cdde..b5c42f1 100644
--- a/compiler/dex/mir_graph.cc
+++ b/compiler/dex/mir_graph.cc
@@ -695,9 +695,10 @@
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 d6dc566..7f9698b 100644
--- a/compiler/dex/mir_graph.h
+++ b/compiler/dex/mir_graph.h
@@ -39,6 +39,7 @@
class GlobalValueNumbering;
class GvnDeadCodeElimination;
class PassManager;
+class TypeInference;
// Forward declaration.
class MIRGraph;
@@ -64,6 +65,7 @@
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 @@
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 @@
#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 @@
#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 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 @@
// 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 @@
*/
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 @@
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 @@
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 @@
bool EliminateDeadCodeGate();
bool EliminateDeadCode(BasicBlock* bb);
void EliminateDeadCodeEnd();
+ void GlobalValueNumberingCleanup();
bool EliminateSuspendChecksGate();
bool EliminateSuspendChecks(BasicBlock* bb);
@@ -1100,34 +1119,6 @@
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 @@
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 @@
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 @@
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 000144f..946c74b 100644
--- a/compiler/dex/mir_method_info.h
+++ b/compiler/dex/mir_method_info.h
@@ -232,6 +232,7 @@
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 ac7963d..0d5da32 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 @@
// 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 @@
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::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;
- }
+ DCHECK(temp_.ssa.ti != nullptr);
+ return temp_.ssa.ti->Apply(bb);
+}
- // Propagate type info.
- infer_changed = InferTypeAndSize(bb, mir, infer_changed);
- }
-
- 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 @@
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 @@
}
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 2e871da..3e193b4 100644
--- a/compiler/dex/pass_driver_me_opts.cc
+++ b/compiler/dex/pass_driver_me_opts.cc
@@ -46,6 +46,7 @@
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 a8b8a54..b35bc3d 100644
--- a/compiler/dex/pass_driver_me_post_opt.cc
+++ b/compiler/dex/pass_driver_me_post_opt.cc
@@ -41,7 +41,7 @@
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 1ab8625..e9fa0eb 100644
--- a/compiler/dex/post_opt_passes.h
+++ b/compiler/dex/post_opt_passes.h
@@ -263,12 +263,19 @@
};
/**
- * @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 @@
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 8d20f1b..7598e50 100644
--- a/compiler/dex/quick/arm/int_arm.cc
+++ b/compiler/dex/quick/arm/int_arm.cc
@@ -1326,11 +1326,6 @@
}
}
- // 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 @@
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 8f7eb59..f5e6c09 100644
--- a/compiler/dex/quick/dex_file_method_inliner.cc
+++ b/compiler/dex/quick/dex_file_method_inliner.cc
@@ -753,6 +753,7 @@
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 @@
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 @@
}
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 de5e041..0592c74 100644
--- a/compiler/dex/quick/gen_common.cc
+++ b/compiler/dex/quick/gen_common.cc
@@ -58,24 +58,19 @@
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 @@
private:
QuickEntrypointEnum trampoline_;
const int imm_;
- const RegStorage r_method_;
const RegStorage r_result_;
};
@@ -91,7 +85,7 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 8f08a51..6f227fc 100644
--- a/compiler/dex/quick/mir_to_lir.h
+++ b/compiler/dex/quick/mir_to_lir.h
@@ -1692,10 +1692,8 @@
* @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 39eb117..73cfe92 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -575,7 +575,7 @@
// (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 0000000..19d591b
--- /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 0000000..c9b29bf
--- /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 0000000..eaa2bfa
--- /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 2b78e38..948ba7b 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 @@
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 bad8335..e54cbf6 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -127,34 +127,67 @@
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 @@
}
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 @@
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 03c5c5c..02de11e 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -281,6 +281,18 @@
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 @@
}
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 03ae489..3983006 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 @@
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 06328f2..30c89f2 100644
--- a/compiler/optimizing/boolean_simplifier.cc
+++ b/compiler/optimizing/boolean_simplifier.cc
@@ -72,8 +72,8 @@
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 @@
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 818d671..ebd8243 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 @@
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 @@
}
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 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 @@
}
}
+ 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 @@
return compiling_class.Get() == cls.Get();
}
-
bool HGraphBuilder::BuildStaticFieldAccess(const Instruction& instruction,
uint32_t dex_pc,
bool is_put) {
@@ -764,7 +845,7 @@
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 @@
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 b14b69b..5163395 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -612,7 +612,7 @@
}
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 @@
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 @@
}
// 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 @@
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 38fa043..01748a9 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -176,7 +176,6 @@
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 @@
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 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 @@
}
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 @@
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 @@
__ 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 @@
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 23ba339..dada4ce 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -173,14 +173,13 @@
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 @@
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 @@
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 @@
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::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 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 3dcfca6..f0c14f6 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -174,7 +174,6 @@
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 @@
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 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 @@
}
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 @@
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 @@
__ 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 @@
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 b404f8d..642900f 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -197,7 +197,6 @@
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 @@
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 @@
}
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 @@
}
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 @@
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 @@
__ 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 @@
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 ac00824..66ff578 100644
--- a/compiler/optimizing/constant_folding.h
+++ b/compiler/optimizing/constant_folding.h
@@ -32,8 +32,8 @@
*/
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 02ad675..422223f 100644
--- a/compiler/optimizing/constant_folding_test.cc
+++ b/compiler/optimizing/constant_folding_test.cc
@@ -572,14 +572,19 @@
};
// 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 @@
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 8045cc5..91cd60a 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() {
+ RemoveDeadBlocks();
+ RemoveDeadInstructions();
+}
+
} // namespace art
diff --git a/compiler/optimizing/dead_code_elimination.h b/compiler/optimizing/dead_code_elimination.h
index cee9364..0bea0fc 100644
--- a/compiler/optimizing/dead_code_elimination.h
+++ b/compiler/optimizing/dead_code_elimination.h
@@ -40,6 +40,10 @@
"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 98ae1ec..3209d3e 100644
--- a/compiler/optimizing/dead_code_elimination_test.cc
+++ b/compiler/optimizing/dead_code_elimination_test.cc
@@ -169,20 +169,25 @@
"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 8950635..8906764 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -191,6 +191,30 @@
}
}
+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 @@
}
}
+ 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 @@
"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 @@
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 @@
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 @@
"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 24fee37..45e8804 100644
--- a/compiler/optimizing/graph_checker.h
+++ b/compiler/optimizing/graph_checker.h
@@ -42,6 +42,9 @@
// 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 bffd639..37b5753 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -130,6 +130,16 @@
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 98c0eed..2df7c16 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -62,6 +62,7 @@
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::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::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 @@
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 932192e..abdf04e 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -79,6 +79,7 @@
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 117d6a4..7a753b2 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -88,6 +88,7 @@
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 a8e2cdf..7275edb 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -113,6 +113,7 @@
static void MoveArguments(HInvoke* invoke, ArenaAllocator* arena, CodeGeneratorX86* codegen) {
if (invoke->InputCount() == 0) {
+ // No argument to move.
return;
}
@@ -1038,7 +1039,7 @@
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 5d24d1f..35daaf6 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -105,6 +105,7 @@
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 6ab57b8..c158ddf 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -672,6 +672,11 @@
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 @@
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 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);
+ }
}
- for (size_t i = 0, e = successors_.Size(); i < e; ++i) {
- successors_.Get(i)->predecessors_.Delete(this);
- }
- dominator_->dominated_blocks_.Delete(this);
-
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) {
+ 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);
+ }
+
+ // 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());
- successors_.Reset();
- dominated_blocks_.Reset();
+ // Move instructions from `other` to `this`.
+ DCHECK(EndsWithControlFlowInstruction());
+ RemoveInstruction(GetLastInstruction());
instructions_.Add(other->GetInstructions());
- other->GetInstructions().SetBlockOfInstructions(this);
+ other->instructions_.SetBlockOfInstructions(this);
+ other->instructions_.Clear();
- while (!other->GetSuccessors().IsEmpty()) {
- HBasicBlock* successor = other->GetSuccessors().Get(0);
+ // 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();
+ 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->instructions_.SetBlockOfInstructions(this);
+
+ // 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 @@
}
}
+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 @@
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 @@
// - 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 @@
// 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 @@
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 0993a18..946091f 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -97,6 +97,9 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
size_t ComputeHashCode() const OVERRIDE { return 0; }
- bool ActAsNullConstant() const OVERRIDE { return true; }
-
DECLARE_INSTRUCTION(NullConstant);
private:
@@ -2103,11 +2110,6 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b13e07e..c46a219 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 8b20281..ccf8de9 100644
--- a/compiler/optimizing/optimization.h
+++ b/compiler/optimizing/optimization.h
@@ -48,7 +48,7 @@
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 218894f..05451bc 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -324,11 +324,11 @@
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 @@
&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 e6508c9..65c84e6 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -58,8 +58,8 @@
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 f5d8d82..fa6b3c2 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -79,4 +79,26 @@
}
}
+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 c28507c..d7f277f 100644
--- a/compiler/optimizing/prepare_for_register_allocation.h
+++ b/compiler/optimizing/prepare_for_register_allocation.h
@@ -39,6 +39,7 @@
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 de6941c..12b1c2b 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -68,11 +68,11 @@
}
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 f8e00f6..0fdf051 100644
--- a/compiler/optimizing/register_allocator.cc
+++ b/compiler/optimizing/register_allocator.cc
@@ -378,7 +378,7 @@
// 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 @@
// 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 @@
}
}
+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 717be75..dc9c708 100644
--- a/compiler/optimizing/register_allocator.h
+++ b/compiler/optimizing/register_allocator.h
@@ -86,8 +86,12 @@
// 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 182cd0e..8c6d904 100644
--- a/compiler/optimizing/register_allocator_test.cc
+++ b/compiler/optimizing/register_allocator_test.cc
@@ -854,6 +854,10 @@
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 fe70d3a..97254ed 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -998,6 +998,15 @@
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 @@
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 0000000..8344fc3
--- /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 9a9e068..0c626be 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -70,13 +70,18 @@
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 @@
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);
+ 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();
- 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_++;
- }
+ void AddDexRegisterEntry(uint16_t dex_register,
+ DexRegisterLocation::Kind kind,
+ int32_t value);
- 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);
- 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);
- }
-
- 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 FillIn(MemoryRegion region) {
- CodeInfo code_info(region);
- DCHECK_EQ(region.size(), ComputeNeededSize());
- code_info.SetOverallSize(region.size());
-
- 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
+ size_t ComputeDexRegisterLocationCatalogSize() const;
+ size_t ComputeDexRegisterMapSize(const StackMapEntry& entry) const;
+ size_t ComputeDexRegisterMapsSize() const;
+ size_t ComputeInlineInfoSize() const;
+
+ // 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(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.
-
- // 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;
- }
+ size_t FindEntryWithTheSameDexMap();
+ bool HaveTheSameDexMaps(const StackMapEntry& a, const StackMapEntry& b) const;
ArenaAllocator* allocator_;
GrowableArray<StackMapEntry> stack_maps_;
@@ -476,8 +143,7 @@
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 @@
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 8d160bc..3291a77 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -40,11 +40,12 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 821e28b..e4b1e7d 100644
--- a/compiler/utils/growable_array.h
+++ b/compiler/utils/growable_array.h
@@ -46,6 +46,14 @@
}
}
+ 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 0000000..ab039aa
--- /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 0000000..ee6e35d
--- /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 2a3a346..b764095 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1217,9 +1217,9 @@
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 8f6162f..599c22a 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -669,6 +669,18 @@
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 @@
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 @@
/*
* 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 cbd4b7c..1e78877 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1261,6 +1261,18 @@
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 @@
* 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 @@
/*
* 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 622c48f..356a145 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -982,6 +982,16 @@
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 @@
/*
* 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 bf18dd5..f867aa8 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -945,45 +945,6 @@
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 @@
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 @@
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 c5d8b8f..55e3dff 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -910,10 +910,10 @@
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 8185deb..570624c 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_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 66ee870..f80a65f 100644
--- a/runtime/barrier.cc
+++ b/runtime/barrier.cc
@@ -86,7 +86,7 @@
}
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 64b7ecd..9292cff 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 b7e8d50..1fd8a949a 100644
--- a/runtime/entrypoints/quick/quick_default_externs.h
+++ b/runtime/entrypoints/quick/quick_default_externs.h
@@ -34,10 +34,10 @@
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 348495d..46629f5 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 eaf874e..6d9e483 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 af01a02..8524348 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -65,6 +65,7 @@
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 @@
}
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 e546738..3099094 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -615,11 +615,21 @@
// 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 aa3e320..e49bc1d 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -345,7 +345,7 @@
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::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 @@
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::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 ed9e458..e2af5ee 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -668,18 +668,12 @@
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 fa65bce..9f7c303 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -572,13 +572,13 @@
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 @@
}
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 dd9e734..35b785d 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -336,6 +336,7 @@
}
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 ae24b77..d8f8950 100644
--- a/runtime/utils_test.cc
+++ b/runtime/utils_test.cc
@@ -384,7 +384,8 @@
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 a803df8..a2d0427 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -39,6 +39,7 @@
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 @@
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 2df1c0e..cef9d55 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -50,6 +50,7 @@
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 73cc0d8..904393b 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 c93f8bb..f007b25 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 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 @@
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 d666377..d31cbf1 100644
--- a/test/104-growth-limit/src/Main.java
+++ b/test/104-growth-limit/src/Main.java
@@ -29,26 +29,28 @@
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 65be6cb..f0578ef 100644
--- a/test/458-checker-instruction-simplification/src/Main.java
+++ b/test/458-checker-instruction-simplification/src/Main.java
@@ -374,6 +374,15 @@
// 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 @@
// 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 1ebe14e..9151986 100644
--- a/test/474-checker-boolean-input/src/Main.java
+++ b/test/474-checker-boolean-input/src/Main.java
@@ -23,35 +23,11 @@
}
/*
- * 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 @@
* 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 @@
* 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 @@
* 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 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 0000000..7ed6ff8
--- /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 0000000..0db11f2
--- /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 0000000..57adcff
--- /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 0000000..c55bf72
--- /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 0000000..1a439fc
--- /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 0000000..a7d3bcd
--- /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 0000000..387e1a7
--- /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 0000000..deb64de
--- /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 0000000..6da8945
--- /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 0000000..e69de29
--- /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 0000000..5aeafac
--- /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 0000000..560ce95
--- /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 e311401..4ecabcc 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -354,6 +354,16 @@
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 414e4df..1c44958 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -225,7 +225,8 @@
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 @@
export ANDROID_ROOT=$ANDROID_ROOT && \
$mkdir_cmdline && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \
+ export PATH=$ANDROID_ROOT/bin:$PATH && \
$dex2oat_cmdline && \
$dalvikvm_cmdline"
diff --git a/tools/art b/tools/art
index 6c89a60..85e6e2f 100644
--- a/tools/art
+++ b/tools/art
@@ -92,6 +92,7 @@
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 503ec71..a007fa2 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -35,6 +35,7 @@
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 @@
# 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 @@
--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 \