using helpers::Int32ConstantFrom; using helpers::Uint64ConstantFrom;
// AArch32 instruction latencies. // We currently assume that all ARM CPUs share the same instruction latency list. // The following latencies were tuned based on performance experiments and // automatic tuning using differential evolution approach on various benchmarks. static constexpr uint32_t kArmIntegerOpLatency = 2; static constexpr uint32_t kArmFloatingPointOpLatency = 11; static constexpr uint32_t kArmDataProcWithShifterOpLatency = 4; static constexpr uint32_t kArmMulIntegerLatency = 6; static constexpr uint32_t kArmMulFloatingPointLatency = 11; static constexpr uint32_t kArmDivIntegerLatency = 10; static constexpr uint32_t kArmDivFloatLatency = 20; static constexpr uint32_t kArmDivDoubleLatency = 25; static constexpr uint32_t kArmTypeConversionFloatingPointIntegerLatency = 11; static constexpr uint32_t kArmMemoryLoadLatency = 9; static constexpr uint32_t kArmMemoryStoreLatency = 9; static constexpr uint32_t kArmMemoryBarrierLatency = 6; static constexpr uint32_t kArmBranchLatency = 4; static constexpr uint32_t kArmCallLatency = 5; static constexpr uint32_t kArmCallInternalLatency = 29; static constexpr uint32_t kArmLoadStringInternalLatency = 10; static constexpr uint32_t kArmNopLatency = 2; static constexpr uint32_t kArmLoadWithBakerReadBarrierLatency = 18; static constexpr uint32_t kArmRuntimeTypeCheckLatency = 46;
class SchedulingLatencyVisitorARM final
: public SchedulingLatencyVisitor<SchedulingLatencyVisitorARM> { public: explicit SchedulingLatencyVisitorARM(CodeGenerator* codegen)
: codegen_(down_cast<CodeGeneratorARMVIXL*>(codegen)) {}
// Default visitor for instructions not handled specifically below. void VisitInstruction([[maybe_unused]] HInstruction*) {
last_visited_latency_ = kArmIntegerOpLatency;
}
// We add a second unused parameter to be able to use this macro like the others // defined in `nodes.h`. #define FOR_EACH_SCHEDULED_ARM_INSTRUCTION(M) \
M(ArrayGet, unused) \
M(ArrayLength, unused) \
M(ArraySet, unused) \
M(Add, unused) \
M(Sub, unused) \
M(And, unused) \
M(Or, unused) \
M(Ror, unused) \
M(Xor, unused) \
M(Shl, unused) \
M(Shr, unused) \
M(UShr, unused) \
M(Mul, unused) \
M(Div, unused) \
M(Condition, unused) \
M(Compare, unused) \
M(BoundsCheck, unused) \
M(InstanceFieldGet, unused) \
M(InstanceFieldSet, unused) \
M(InstanceOf, unused) \
M(Invoke, unused) \
M(LoadString, unused) \
M(NewArray, unused) \
M(NewInstance, unused) \
M(Rem, unused) \
M(StaticFieldGet, unused) \
M(StaticFieldSet, unused) \
M(SuspendCheck, unused) \
M(TypeConversion, unused)
// The latency setting for each HInstruction depends on how CodeGenerator may generate code, // latency visitors may query CodeGenerator for such information for accurate latency settings.
CodeGeneratorARMVIXL* codegen_;
void SchedulingLatencyVisitorARM::HandleBinaryOperationLantencies(HBinaryOperation* instr) { switch (instr->GetResultType()) { case DataType::Type::kInt64: // HAdd and HSub long operations translate to ADDS+ADC or SUBS+SBC pairs, // so a bubble (kArmNopLatency) is added to represent the internal carry flag // dependency inside these pairs.
last_visited_internal_latency_ = kArmIntegerOpLatency + kArmNopLatency;
last_visited_latency_ = kArmIntegerOpLatency; break; case DataType::Type::kFloat32: case DataType::Type::kFloat64:
last_visited_latency_ = kArmFloatingPointOpLatency; break; default:
last_visited_latency_ = kArmIntegerOpLatency; break;
}
}
// Comparisons against 0 are common enough, so codegen has special handling for them. if (value == 0) { switch (cond) { case kCondNE: case kCondA: case kCondEQ: case kCondBE: // Orrs
last_visited_internal_latency_ += kArmIntegerOpLatency; return; case kCondLT: case kCondGE: // Cmp
last_visited_internal_latency_ += kArmIntegerOpLatency; return; case kCondB: case kCondAE: // Cmp
last_visited_internal_latency_ += kArmIntegerOpLatency; return; default: break;
}
}
switch (cond) { case kCondEQ: case kCondNE: case kCondB: case kCondBE: case kCondA: case kCondAE: { // Cmp, IT, Cmp
last_visited_internal_latency_ += 3 * kArmIntegerOpLatency; break;
} case kCondLE: case kCondGT: // Trivially true or false. if (value == std::numeric_limits<int64_t>::max()) { // Cmp
last_visited_internal_latency_ += kArmIntegerOpLatency; break;
}
FALLTHROUGH_INTENDED; case kCondGE: case kCondLT: { // Cmp, Sbcs
last_visited_internal_latency_ += 2 * kArmIntegerOpLatency; break;
} default:
LOG(FATAL) << "Unreachable";
UNREACHABLE();
}
}
switch (cond) { case kCondEQ: case kCondNE: case kCondB: case kCondBE: case kCondA: case kCondAE: { // Cmp, IT, Cmp
last_visited_internal_latency_ += 3 * kArmIntegerOpLatency; break;
} case kCondLE: case kCondGT: case kCondGE: case kCondLT: { // Cmp, Sbcs
last_visited_internal_latency_ += 2 * kArmIntegerOpLatency; break;
} default:
LOG(FATAL) << "Unreachable";
UNREACHABLE();
}
}
// The GenerateTest series of function all counted as internal latency. void SchedulingLatencyVisitorARM::HandleGenerateTest(HCondition* condition) { const DataType::Type type = condition->GetLeft()->GetType();
// Unlike codegen pass, we cannot check 'out' register IsLow() here, // because scheduling is before liveness(location builder) and register allocator, // so we can only choose to follow one path of codegen by assuming otu.IsLow() is true.
last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
last_visited_latency_ = kArmIntegerOpLatency;
}
IfCondition condition = cond->GetCondition();
HInstruction* right = cond->InputAt(1);
if (right->IsConstant()) { // Comparisons against 0 are common enough, so codegen has special handling for them. if (Uint64ConstantFrom(right) == 0) { switch (condition) { case kCondNE: case kCondA: case kCondEQ: case kCondBE: // Orr
last_visited_internal_latency_ += kArmIntegerOpLatency;
HandleGenerateConditionWithZero(condition); return; case kCondLT: case kCondGE:
FALLTHROUGH_INTENDED; case kCondAE: case kCondB:
HandleGenerateConditionWithZero(condition); return; case kCondLE: case kCondGT: default: break;
}
}
}
void SchedulingLatencyVisitorARM::HandleGenerateConditionIntegralOrNonPrimitive(HCondition* cond) { const DataType::Type type = cond->GetLeft()->GetType();
DCHECK(DataType::IsIntegralType(type) || type == DataType::Type::kReference) << type;
if (type == DataType::Type::kInt64) {
HandleGenerateConditionLong(cond); return;
}
IfCondition condition = cond->GetCondition();
HInstruction* right = cond->InputAt(1);
int64_t value;
if (right->IsConstant()) {
value = Uint64ConstantFrom(right);
// Comparisons against 0 are common enough, so codegen has special handling for them. if (value == 0) { switch (condition) { case kCondNE: case kCondA: case kCondEQ: case kCondBE: case kCondLT: case kCondGE: case kCondAE: case kCondB:
HandleGenerateConditionWithZero(condition); return; case kCondLE: case kCondGT: default: break;
}
}
}
if (shift_value >= 32) { // Different shift types actually generate similar code here, // no need to differentiate shift types like the codegen pass does, // which also avoids handling shift types from different ARM backends.
HandleGenerateDataProc(instruction);
} else {
DCHECK_GT(shift_value, 1U);
DCHECK_LT(shift_value, 32U);
void SchedulingLatencyVisitorARM::VisitIntermediateAddress([[maybe_unused]] HIntermediateAddress*) { // Although the code generated is a simple `add` instruction, we found through empirical results // that spacing it from its use in memory accesses was beneficial.
last_visited_internal_latency_ = kArmNopLatency;
last_visited_latency_ = kArmIntegerOpLatency;
}
void SchedulingLatencyVisitorARM::VisitIntermediateAddressIndex(
[[maybe_unused]] HIntermediateAddressIndex*) {
UNIMPLEMENTED(FATAL) << "IntermediateAddressIndex is not implemented for ARM";
}
switch (value_type) { case DataType::Type::kBool: case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: case DataType::Type::kInt32: { if (index->IsConstant()) {
last_visited_latency_ = kArmMemoryStoreLatency;
} else { if (has_intermediate_address) {
} else {
last_visited_internal_latency_ = kArmIntegerOpLatency;
}
last_visited_latency_ = kArmMemoryStoreLatency;
} break;
}
case DataType::Type::kReference: { if (instruction->InputAt(2)->IsNullConstant()) { if (index->IsConstant()) {
last_visited_latency_ = kArmMemoryStoreLatency;
} else {
last_visited_internal_latency_ = kArmIntegerOpLatency;
last_visited_latency_ = kArmMemoryStoreLatency;
}
} else { // Following the exact instructions of runtime type checks is too complicated, // just giving it a simple slow latency.
last_visited_latency_ = kArmRuntimeTypeCheckLatency;
} break;
}
void SchedulingLatencyVisitorARM::VisitBoundsCheck([[maybe_unused]] HBoundsCheck*) {
last_visited_internal_latency_ = kArmIntegerOpLatency; // Users do not use any data results.
last_visited_latency_ = 0;
}
switch (field_type) { case DataType::Type::kBool: case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: case DataType::Type::kInt32:
last_visited_latency_ = kArmMemoryLoadLatency; break;
switch (result_type) { case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16:
last_visited_latency_ = kArmIntegerOpLatency; // SBFX or UBFX break;
case DataType::Type::kInt32: switch (input_type) { case DataType::Type::kInt64:
last_visited_latency_ = kArmIntegerOpLatency; // MOV break; case DataType::Type::kFloat32: case DataType::Type::kFloat64:
last_visited_internal_latency_ = kArmTypeConversionFloatingPointIntegerLatency;
last_visited_latency_ = kArmFloatingPointOpLatency; break; default:
last_visited_latency_ = kArmIntegerOpLatency; break;
} break;
case DataType::Type::kInt64: switch (input_type) { case DataType::Type::kBool: case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: case DataType::Type::kInt32: // MOV and extension
last_visited_internal_latency_ = kArmIntegerOpLatency;
last_visited_latency_ = kArmIntegerOpLatency; break; case DataType::Type::kFloat32: case DataType::Type::kFloat64: // invokes runtime
last_visited_internal_latency_ = kArmCallInternalLatency; break; default:
last_visited_internal_latency_ = kArmIntegerOpLatency;
last_visited_latency_ = kArmIntegerOpLatency; break;
} break;
case DataType::Type::kFloat32: switch (input_type) { case DataType::Type::kBool: case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: case DataType::Type::kInt32:
last_visited_internal_latency_ = kArmTypeConversionFloatingPointIntegerLatency;
last_visited_latency_ = kArmFloatingPointOpLatency; break; case DataType::Type::kInt64: // invokes runtime
last_visited_internal_latency_ = kArmCallInternalLatency; break; case DataType::Type::kFloat64:
last_visited_latency_ = kArmFloatingPointOpLatency; break; default:
last_visited_latency_ = kArmFloatingPointOpLatency; break;
} break;
case DataType::Type::kFloat64: switch (input_type) { case DataType::Type::kBool: case DataType::Type::kUint8: case DataType::Type::kInt8: case DataType::Type::kUint16: case DataType::Type::kInt16: case DataType::Type::kInt32:
last_visited_internal_latency_ = kArmTypeConversionFloatingPointIntegerLatency;
last_visited_latency_ = kArmFloatingPointOpLatency; break; case DataType::Type::kInt64:
last_visited_internal_latency_ = 5 * kArmFloatingPointOpLatency;
last_visited_latency_ = kArmFloatingPointOpLatency; break; case DataType::Type::kFloat32:
last_visited_latency_ = kArmFloatingPointOpLatency; break; default:
last_visited_latency_ = kArmFloatingPointOpLatency; break;
} break;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.