Faster instance-of for final classes.
If a class is final and not and array we can generate a 1/0 based on class
equality.
Use a method's declaring class if it matches the instance-of class (common in
Java equals methods).
Don't do a class pointer comparison in the case of an abstract or interface
class, just go straight into the slow-path.
Fix performance bug where peep-hole verifier pass didn't merge into the
fall-through line if the next instruction wasn't interesting.
Change-Id: Idb47ec6acebfd25a344ed74adaacba02fafc7df2
diff --git a/src/compiler/dex/quick/gen_common.cc b/src/compiler/dex/quick/gen_common.cc
index 055b67c..0091a80 100644
--- a/src/compiler/dex/quick/gen_common.cc
+++ b/src/compiler/dex/quick/gen_common.cc
@@ -883,23 +883,81 @@
CallRuntimeHelperRegLocation(ENTRYPOINT_OFFSET(pDeliverException), rl_src, true);
}
-void Mir2Lir::GenInstanceof(uint32_t type_idx, RegLocation rl_dest,
- RegLocation rl_src)
-{
+// For final classes there are no sub-classes to check and so we can answer the instance-of
+// question with simple comparisons.
+void Mir2Lir::GenInstanceofFinal(bool use_declaring_class, uint32_t type_idx, RegLocation rl_dest,
+ RegLocation rl_src) {
+ RegLocation object = LoadValue(rl_src, kCoreReg);
+ RegLocation rl_result = EvalLoc(rl_dest, kCoreReg, true);
+ int result_reg = rl_result.low_reg;
+ if (result_reg == object.low_reg) {
+ result_reg = AllocTypedTemp(false, kCoreReg);
+ }
+ LoadConstant(result_reg, 0); // assume false
+ LIR* null_branchover = OpCmpImmBranch(kCondEq, object.low_reg, 0, NULL);
+
+ int check_class = AllocTypedTemp(false, kCoreReg);
+ int object_class = AllocTypedTemp(false, kCoreReg);
+
+ LoadCurrMethodDirect(check_class);
+ if (use_declaring_class) {
+ LoadWordDisp(check_class, mirror::AbstractMethod::DeclaringClassOffset().Int32Value(),
+ check_class);
+ LoadWordDisp(object.low_reg, mirror::Object::ClassOffset().Int32Value(), object_class);
+ } else {
+ LoadWordDisp(check_class, mirror::AbstractMethod::DexCacheResolvedTypesOffset().Int32Value(),
+ check_class);
+ LoadWordDisp(object.low_reg, mirror::Object::ClassOffset().Int32Value(), object_class);
+ int32_t offset_of_type =
+ mirror::Array::DataOffset(sizeof(mirror::Class*)).Int32Value() +
+ (sizeof(mirror::Class*) * type_idx);
+ LoadWordDisp(check_class, offset_of_type, check_class);
+ }
+
+ LIR* ne_branchover = NULL;
+ if (cu_->instruction_set == kThumb2) {
+ OpRegReg(kOpCmp, check_class, object_class); // Same?
+ OpIT(kCondEq, ""); // if-convert the test
+ LoadConstant(result_reg, 1); // .eq case - load true
+ } else {
+ ne_branchover = OpCmpBranch(kCondNe, check_class, object_class, NULL);
+ LoadConstant(result_reg, 1); // eq case - load true
+ }
+ LIR* target = NewLIR0(kPseudoTargetLabel);
+ null_branchover->target = target;
+ if (ne_branchover != NULL) {
+ ne_branchover->target = target;
+ }
+ FreeTemp(object_class);
+ FreeTemp(check_class);
+ if (IsTemp(result_reg)) {
+ OpRegCopy(rl_result.low_reg, result_reg);
+ FreeTemp(result_reg);
+ }
+ StoreValue(rl_dest, rl_result);
+}
+
+void Mir2Lir::GenInstanceofCallingHelper(bool needs_access_check, bool type_known_final,
+ bool type_known_abstract, bool use_declaring_class,
+ bool can_assume_type_is_in_dex_cache,
+ uint32_t type_idx, RegLocation rl_dest,
+ RegLocation rl_src) {
FlushAllRegs();
// May generate a call - use explicit registers
LockCallTemps();
LoadCurrMethodDirect(TargetReg(kArg1)); // kArg1 <= current Method*
int class_reg = TargetReg(kArg2); // kArg2 will hold the Class*
- if (!cu_->compiler_driver->CanAccessTypeWithoutChecks(cu_->method_idx,
- *cu_->dex_file,
- type_idx)) {
+ if (needs_access_check) {
// Check we have access to type_idx and if not throw IllegalAccessError,
// returns Class* in kArg0
CallRuntimeHelperImm(ENTRYPOINT_OFFSET(pInitializeTypeAndVerifyAccessFromCode),
type_idx, true);
OpRegCopy(class_reg, TargetReg(kRet0)); // Align usage with fast path
LoadValueDirectFixed(rl_src, TargetReg(kArg0)); // kArg0 <= ref
+ } else if (use_declaring_class) {
+ LoadValueDirectFixed(rl_src, TargetReg(kArg0)); // kArg0 <= ref
+ LoadWordDisp(TargetReg(kArg1),
+ mirror::AbstractMethod::DeclaringClassOffset().Int32Value(), class_reg);
} else {
// Load dex cache entry into class_reg (kArg2)
LoadValueDirectFixed(rl_src, TargetReg(kArg0)); // kArg0 <= ref
@@ -909,8 +967,7 @@
mirror::Array::DataOffset(sizeof(mirror::Class*)).Int32Value() + (sizeof(mirror::Class*)
* type_idx);
LoadWordDisp(class_reg, offset_of_type, class_reg);
- if (!cu_->compiler_driver->CanAssumeTypeIsPresentInDexCache(
- *cu_->dex_file, type_idx)) {
+ if (!can_assume_type_is_in_dex_cache) {
// Need to test presence of type in dex cache at runtime
LIR* hop_branch = OpCmpImmBranch(kCondNe, class_reg, 0, NULL);
// Not resolved
@@ -926,47 +983,88 @@
/* kArg0 is ref, kArg2 is class. If ref==null, use directly as bool result */
RegLocation rl_result = GetReturn(false);
if (cu_->instruction_set == kMips) {
- LoadConstant(rl_result.low_reg, 0); // store false result for if branch is taken
+ // On MIPS rArg0 != rl_result, place false in result if branch is taken.
+ LoadConstant(rl_result.low_reg, 0);
}
LIR* branch1 = OpCmpImmBranch(kCondEq, TargetReg(kArg0), 0, NULL);
+
/* load object->klass_ */
DCHECK_EQ(mirror::Object::ClassOffset().Int32Value(), 0);
LoadWordDisp(TargetReg(kArg0), mirror::Object::ClassOffset().Int32Value(), TargetReg(kArg1));
/* kArg0 is ref, kArg1 is ref->klass_, kArg2 is class */
LIR* branchover = NULL;
- if (cu_->instruction_set == kThumb2) {
- /* Uses conditional nullification */
- int r_tgt = LoadHelper(ENTRYPOINT_OFFSET(pInstanceofNonTrivialFromCode));
- OpRegReg(kOpCmp, TargetReg(kArg1), TargetReg(kArg2)); // Same?
- OpIT(kCondEq, "EE"); // if-convert the test
- LoadConstant(TargetReg(kArg0), 1); // .eq case - load true
- OpRegCopy(TargetReg(kArg0), TargetReg(kArg2)); // .ne case - arg0 <= class
- OpReg(kOpBlx, r_tgt); // .ne case: helper(class, ref->class)
- FreeTemp(r_tgt);
+ if (type_known_final) {
+ // rl_result == ref == null == 0.
+ if (cu_->instruction_set == kThumb2) {
+ OpRegReg(kOpCmp, TargetReg(kArg1), TargetReg(kArg2)); // Same?
+ OpIT(kCondEq, "E"); // if-convert the test
+ LoadConstant(rl_result.low_reg, 1); // .eq case - load true
+ LoadConstant(rl_result.low_reg, 0); // .ne case - load false
+ } else {
+ LoadConstant(rl_result.low_reg, 0); // ne case - load false
+ branchover = OpCmpBranch(kCondNe, TargetReg(kArg1), TargetReg(kArg2), NULL);
+ LoadConstant(rl_result.low_reg, 1); // eq case - load true
+ }
} else {
- /* Uses branchovers */
- LoadConstant(rl_result.low_reg, 1); // assume true
- branchover = OpCmpBranch(kCondEq, TargetReg(kArg1), TargetReg(kArg2), NULL);
- if (cu_->instruction_set != kX86) {
+ if (cu_->instruction_set == kThumb2) {
int r_tgt = LoadHelper(ENTRYPOINT_OFFSET(pInstanceofNonTrivialFromCode));
+ if (!type_known_abstract) {
+ /* Uses conditional nullification */
+ OpRegReg(kOpCmp, TargetReg(kArg1), TargetReg(kArg2)); // Same?
+ OpIT(kCondEq, "EE"); // if-convert the test
+ LoadConstant(TargetReg(kArg0), 1); // .eq case - load true
+ }
OpRegCopy(TargetReg(kArg0), TargetReg(kArg2)); // .ne case - arg0 <= class
OpReg(kOpBlx, r_tgt); // .ne case: helper(class, ref->class)
FreeTemp(r_tgt);
} else {
- OpRegCopy(TargetReg(kArg0), TargetReg(kArg2));
- OpThreadMem(kOpBlx, ENTRYPOINT_OFFSET(pInstanceofNonTrivialFromCode));
+ if (!type_known_abstract) {
+ /* Uses branchovers */
+ LoadConstant(rl_result.low_reg, 1); // assume true
+ branchover = OpCmpBranch(kCondEq, TargetReg(kArg1), TargetReg(kArg2), NULL);
+ }
+ if (cu_->instruction_set != kX86) {
+ int r_tgt = LoadHelper(ENTRYPOINT_OFFSET(pInstanceofNonTrivialFromCode));
+ OpRegCopy(TargetReg(kArg0), TargetReg(kArg2)); // .ne case - arg0 <= class
+ OpReg(kOpBlx, r_tgt); // .ne case: helper(class, ref->class)
+ FreeTemp(r_tgt);
+ } else {
+ OpRegCopy(TargetReg(kArg0), TargetReg(kArg2));
+ OpThreadMem(kOpBlx, ENTRYPOINT_OFFSET(pInstanceofNonTrivialFromCode));
+ }
}
}
+ // TODO: only clobber when type isn't final?
ClobberCalleeSave();
/* branch targets here */
LIR* target = NewLIR0(kPseudoTargetLabel);
StoreValue(rl_dest, rl_result);
branch1->target = target;
- if (cu_->instruction_set != kThumb2) {
+ if (branchover != NULL) {
branchover->target = target;
}
}
+void Mir2Lir::GenInstanceof(uint32_t type_idx, RegLocation rl_dest, RegLocation rl_src) {
+ bool type_known_final, type_known_abstract, use_declaring_class;
+ bool needs_access_check = !cu_->compiler_driver->CanAccessTypeWithoutChecks(cu_->method_idx,
+ *cu_->dex_file,
+ type_idx,
+ &type_known_final,
+ &type_known_abstract,
+ &use_declaring_class);
+ bool can_assume_type_is_in_dex_cache = !needs_access_check &&
+ cu_->compiler_driver->CanAssumeTypeIsPresentInDexCache(*cu_->dex_file, type_idx);
+
+ if ((use_declaring_class || can_assume_type_is_in_dex_cache) && type_known_final) {
+ GenInstanceofFinal(use_declaring_class, type_idx, rl_dest, rl_src);
+ } else {
+ GenInstanceofCallingHelper(needs_access_check, type_known_final, type_known_abstract,
+ use_declaring_class, can_assume_type_is_in_dex_cache,
+ type_idx, rl_dest, rl_src);
+ }
+}
+
void Mir2Lir::GenCheckCast(uint32_t insn_idx, uint32_t type_idx, RegLocation rl_src)
{
DexCompilationUnit* cu = mir_graph_->GetCurrentDexCompilationUnit();
diff --git a/src/compiler/dex/quick/mir_to_lir.h b/src/compiler/dex/quick/mir_to_lir.h
index 1df78cf..9eb4524 100644
--- a/src/compiler/dex/quick/mir_to_lir.h
+++ b/src/compiler/dex/quick/mir_to_lir.h
@@ -699,6 +699,14 @@
}
private:
+ void GenInstanceofFinal(bool use_declaring_class, uint32_t type_idx, RegLocation rl_dest,
+ RegLocation rl_src);
+ void GenInstanceofCallingHelper(bool needs_access_check, bool type_known_final,
+ bool type_known_abstract, bool use_declaring_class,
+ bool can_assume_type_is_in_dex_cache,
+ uint32_t type_idx, RegLocation rl_dest,
+ RegLocation rl_src);
+
void ClobberBody(RegisterInfo* p);
void ResetDefBody(RegisterInfo* p) {
p->def_start = NULL;
diff --git a/src/compiler/driver/compiler_driver.cc b/src/compiler/driver/compiler_driver.cc
index 3dddc75..1b8f87d 100644
--- a/src/compiler/driver/compiler_driver.cc
+++ b/src/compiler/driver/compiler_driver.cc
@@ -577,7 +577,18 @@
}
bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const DexFile& dex_file,
- uint32_t type_idx) {
+ uint32_t type_idx,
+ bool* type_known_final, bool* type_known_abstract,
+ bool* equals_referrers_class) {
+ if (type_known_final != NULL) {
+ *type_known_final = false;
+ }
+ if (type_known_abstract != NULL) {
+ *type_known_abstract = false;
+ }
+ if (equals_referrers_class != NULL) {
+ *equals_referrers_class = false;
+ }
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
// Get type from dex cache assuming it was populated by the verifier
@@ -587,6 +598,9 @@
return false; // Unknown class needs access checks.
}
const DexFile::MethodId& method_id = dex_file.GetMethodId(referrer_idx);
+ if (equals_referrers_class != NULL) {
+ *equals_referrers_class = (method_id.class_idx_ == type_idx);
+ }
mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_);
if (referrer_class == NULL) {
stats_->TypeNeedsAccessCheck();
@@ -597,6 +611,12 @@
bool result = referrer_class->CanAccess(resolved_class);
if (result) {
stats_->TypeDoesntNeedAccessCheck();
+ if (type_known_final != NULL) {
+ *type_known_final = resolved_class->IsFinal() && !resolved_class->IsArrayClass();
+ }
+ if (type_known_abstract != NULL) {
+ *type_known_abstract = resolved_class->IsAbstract();
+ }
} else {
stats_->TypeNeedsAccessCheck();
}
diff --git a/src/compiler/driver/compiler_driver.h b/src/compiler/driver/compiler_driver.h
index 1b5bd0d..9fd3c81 100644
--- a/src/compiler/driver/compiler_driver.h
+++ b/src/compiler/driver/compiler_driver.h
@@ -138,7 +138,9 @@
// Are runtime access checks necessary in the compiled code?
bool CanAccessTypeWithoutChecks(uint32_t referrer_idx, const DexFile& dex_file,
- uint32_t type_idx)
+ uint32_t type_idx, bool* type_known_final = NULL,
+ bool* type_known_abstract = NULL,
+ bool* equals_referrers_class = NULL)
LOCKS_EXCLUDED(Locks::mutator_lock_);
// Are runtime access and instantiable checks necessary in the code?
diff --git a/src/verifier/method_verifier.cc b/src/verifier/method_verifier.cc
index 2798ab3..5eec58b 100644
--- a/src/verifier/method_verifier.cc
+++ b/src/verifier/method_verifier.cc
@@ -1253,6 +1253,11 @@
if (dead_start >= 0) {
LogVerifyInfo() << "dead code " << reinterpret_cast<void*>(dead_start) << "-" << reinterpret_cast<void*>(insn_idx - 1);
}
+ // To dump the state of the verify after a method, do something like:
+ // if (PrettyMethod(dex_method_idx_, *dex_file_) ==
+ // "boolean java.lang.String.equals(java.lang.Object)") {
+ // LOG(INFO) << info_messages_.str();
+ // }
}
return true;
}
@@ -2528,12 +2533,12 @@
if (!CheckNotMoveException(code_item_->insns_, next_insn_idx)) {
return false;
}
+ if (NULL != fallthrough_line.get()) {
+ // Make workline consistent with fallthrough computed from peephole optimization.
+ work_line_->CopyFromLine(fallthrough_line.get());
+ }
RegisterLine* next_line = reg_table_.GetLine(next_insn_idx);
if (next_line != NULL) {
- if (NULL != fallthrough_line.get()) {
- // Make workline consistent with fallthrough computed from peephole optimization.
- work_line_->CopyFromLine(fallthrough_line.get());
- }
// Merge registers into what we have for the next instruction,
// and set the "changed" flag if needed.
if (!UpdateRegisters(next_insn_idx, work_line_.get())) {