Skip to content

Commit

Permalink
ART: Enable ISA features run-time detection for ARM64
Browse files Browse the repository at this point in the history
On a target run-time detected ISA features can be more accurate than
instruction set features based on a build-time information such as an
instruction set variant or C++ defines. Build-time based features can
be too generic and do not include all features a target CPU supports.

This CL enables instruction feature run-time detection in the JIT/AOT
compilers:

- The value "runtime" to the option "--instruction-set-features" to try
to detect CPU features at run time. If a target does not support run-time
detection it has the same effect as the value "default".
- Runtime uses "--instruction-set-features=runtime" if run-time detection is
supported.

The CL also cleans up how an instruction set feature string is processed
by InstructionSetFeatures::AddFeaturesFromString. It used to make redundant
uses of Trim in subclasses. The calls are replaced with DCHECKs
verifying that feature names are already trimmed.

Test: m test-art-target-gtest
Test: m test-art-host-gtest
Test: art/test.py --target --optimizing --interpreter --jit
Test: art/test.py --host --optimizing --interpreter --jit
Test: Pixel 3 UI booted

Change-Id: I223d5bc968d589dba5c09f6b03ee8c25987610b0
  • Loading branch information
xueliang.zhong authored and Evgeny Astigeevich committed Jan 30, 2019
1 parent 5247113 commit 7f88c1a
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 50 deletions.
3 changes: 3 additions & 0 deletions compiler/jit/jit_compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ void JitCompiler::ParseCompilerOptions() {
}
}
}

if (instruction_set_features == nullptr) {
// '--instruction-set-features/--instruction-set-variant' were not used.
// Use build-time defined features.
instruction_set_features = InstructionSetFeatures::FromCppDefines();
}
compiler_options_->instruction_set_features_ = std::move(instruction_set_features);
Expand Down
14 changes: 9 additions & 5 deletions dex2oat/dex2oat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ NO_RETURN static void Usage(const char* fmt, ...) {
UsageError(" Default: arm");
UsageError("");
UsageError(" --instruction-set-features=...,: Specify instruction set features");
UsageError(" On target the value 'runtime' can be used to detect features at run time.");
UsageError(" If target does not support run-time detection the value 'runtime'");
UsageError(" has the same effect as the value 'default'.");
UsageError(" Note: the value 'runtime' has no effect if it is used on host.");
UsageError(" Example: --instruction-set-features=div");
UsageError(" Default: default");
UsageError("");
Expand Down Expand Up @@ -875,9 +879,9 @@ class Dex2Oat final {
oat_unstripped_ = std::move(parser_options->oat_symbols);
}

// If no instruction set feature was given, use the default one for the target
// instruction set.
if (compiler_options_->instruction_set_features_.get() == nullptr) {
if (compiler_options_->instruction_set_features_ == nullptr) {
// '--instruction-set-features/--instruction-set-variant' were not used.
// Use features for the 'default' variant.
compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant(
compiler_options_->instruction_set_, "default", &parser_options->error_msg);
if (compiler_options_->instruction_set_features_ == nullptr) {
Expand All @@ -890,9 +894,9 @@ class Dex2Oat final {
std::unique_ptr<const InstructionSetFeatures> runtime_features(
InstructionSetFeatures::FromCppDefines());
if (!compiler_options_->GetInstructionSetFeatures()->Equals(runtime_features.get())) {
LOG(WARNING) << "Mismatch between dex2oat instruction set features ("
LOG(WARNING) << "Mismatch between dex2oat instruction set features to use ("
<< *compiler_options_->GetInstructionSetFeatures()
<< ") and those of dex2oat executable (" << *runtime_features
<< ") and those from CPP defines (" << *runtime_features
<< ") for the command line:\n" << CommandLine();
}
}
Expand Down
36 changes: 36 additions & 0 deletions dex2oat/dex2oat_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#include <algorithm>
#include <regex>
#include <sstream>
#include <string>
Expand All @@ -28,6 +29,7 @@

#include "common_runtime_test.h"

#include "arch/instruction_set_features.h"
#include "base/macros.h"
#include "base/mutex-inl.h"
#include "base/utils.h"
Expand Down Expand Up @@ -2315,4 +2317,38 @@ TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) {
}));
}

class Dex2oatISAFeaturesRuntimeDetectionTest : public Dex2oatTest {
protected:
void RunTest(const std::vector<std::string>& extra_args = {}) {
std::string dex_location = GetScratchDir() + "/Dex2OatSwapTest.jar";
std::string odex_location = GetOdexDir() + "/Dex2OatSwapTest.odex";

Copy(GetTestDexFileName(), dex_location);

ASSERT_TRUE(GenerateOdexForTest(dex_location,
odex_location,
CompilerFilter::kSpeed,
extra_args));
}

std::string GetTestDexFileName() {
return GetDexSrc1();
}
};

TEST_F(Dex2oatISAFeaturesRuntimeDetectionTest, TestCurrentRuntimeFeaturesAsDex2OatArguments) {
std::vector<std::string> argv;
Runtime::Current()->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);
auto option_pos =
std::find(std::begin(argv), std::end(argv), "--instruction-set-features=runtime");
if (InstructionSetFeatures::IsRuntimeDetectionSupported()) {
EXPECT_TRUE(kIsTargetBuild);
EXPECT_NE(option_pos, std::end(argv));
} else {
EXPECT_EQ(option_pos, std::end(argv));
}

RunTest();
}

} // namespace art
5 changes: 3 additions & 2 deletions runtime/arch/arm/instruction_set_features_arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,9 @@ ArmInstructionSetFeatures::AddFeaturesFromSplitString(
bool has_atomic_ldrd_strd = has_atomic_ldrd_strd_;
bool has_div = has_div_;
bool has_armv8a = has_armv8a_;
for (auto i = features.begin(); i != features.end(); i++) {
std::string feature = android::base::Trim(*i);
for (const std::string& feature : features) {
DCHECK_EQ(android::base::Trim(feature), feature)
<< "Feature name is not trimmed: '" << feature << "'";
if (feature == "div") {
has_div = true;
} else if (feature == "-div") {
Expand Down
18 changes: 16 additions & 2 deletions runtime/arch/arm64/instruction_set_features_arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,9 @@ Arm64InstructionSetFeatures::AddFeaturesFromSplitString(
bool has_lse = has_lse_;
bool has_fp16 = has_fp16_;
bool has_dotprod = has_dotprod_;
for (auto i = features.begin(); i != features.end(); i++) {
std::string feature = android::base::Trim(*i);
for (const std::string& feature : features) {
DCHECK_EQ(android::base::Trim(feature), feature)
<< "Feature name is not trimmed: '" << feature << "'";
if (feature == "a53") {
is_a53 = true;
} else if (feature == "-a53") {
Expand Down Expand Up @@ -367,4 +368,17 @@ Arm64InstructionSetFeatures::AddFeaturesFromSplitString(
has_dotprod));
}

std::unique_ptr<const InstructionSetFeatures>
Arm64InstructionSetFeatures::AddRuntimeDetectedFeatures(
const InstructionSetFeatures *features) const {
const Arm64InstructionSetFeatures *arm64_features = features->AsArm64InstructionSetFeatures();
return std::unique_ptr<const InstructionSetFeatures>(
new Arm64InstructionSetFeatures(fix_cortex_a53_835769_,
fix_cortex_a53_843419_,
arm64_features->has_crc_,
arm64_features->has_lse_,
arm64_features->has_fp16_,
arm64_features->has_dotprod_));
}

} // namespace art
3 changes: 3 additions & 0 deletions runtime/arch/arm64/instruction_set_features_arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class Arm64InstructionSetFeatures final : public InstructionSetFeatures {
AddFeaturesFromSplitString(const std::vector<std::string>& features,
std::string* error_msg) const override;

std::unique_ptr<const InstructionSetFeatures>
AddRuntimeDetectedFeatures(const InstructionSetFeatures *features) const override;

private:
Arm64InstructionSetFeatures(bool needs_a53_835769_fix,
bool needs_a53_843419_fix,
Expand Down
50 changes: 50 additions & 0 deletions runtime/arch/arm64/instruction_set_features_arm64_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,54 @@ TEST(Arm64InstructionSetFeaturesTest, Arm64AddFeaturesFromString) {
EXPECT_EQ(armv8_2a_cpu_features->AsBitmap(), 14U);
}

TEST(Arm64InstructionSetFeaturesTest, IsRuntimeDetectionSupported) {
if (kRuntimeISA == InstructionSet::kArm64) {
EXPECT_TRUE(InstructionSetFeatures::IsRuntimeDetectionSupported());
}
}

TEST(Arm64InstructionSetFeaturesTest, FeaturesFromRuntimeDetection) {
if (kRuntimeISA != InstructionSet::kArm64) {
return;
}

std::unique_ptr<const InstructionSetFeatures> hwcap_features(
InstructionSetFeatures::FromHwcap());
std::unique_ptr<const InstructionSetFeatures> runtime_detected_features(
InstructionSetFeatures::FromRuntimeDetection());
std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
InstructionSetFeatures::FromCppDefines());
EXPECT_NE(runtime_detected_features, nullptr);
EXPECT_TRUE(InstructionSetFeatures::IsRuntimeDetectionSupported());
EXPECT_TRUE(runtime_detected_features->Equals(hwcap_features.get()));
EXPECT_TRUE(runtime_detected_features->HasAtLeast(cpp_defined_features.get()));
}

TEST(Arm64InstructionSetFeaturesTest, AddFeaturesFromStringRuntime) {
std::unique_ptr<const InstructionSetFeatures> features(
InstructionSetFeatures::FromBitmap(InstructionSet::kArm64, 0x0));
std::unique_ptr<const InstructionSetFeatures> hwcap_features(
InstructionSetFeatures::FromHwcap());

std::string error_msg;
features = features->AddFeaturesFromString("runtime", &error_msg);

EXPECT_NE(features, nullptr);
EXPECT_TRUE(error_msg.empty());

if (kRuntimeISA == InstructionSet::kArm64) {
EXPECT_TRUE(features->Equals(hwcap_features.get()));
EXPECT_EQ(features->GetFeatureString(), hwcap_features->GetFeatureString());
}

std::unique_ptr<const InstructionSetFeatures> a53_features(
Arm64InstructionSetFeatures::FromVariant("cortex-a53", &error_msg));
features = a53_features->AddFeaturesFromString("runtime", &error_msg);
EXPECT_NE(features, nullptr);
EXPECT_TRUE(error_msg.empty()) << error_msg;
const Arm64InstructionSetFeatures *arm64_features = features->AsArm64InstructionSetFeatures();
EXPECT_TRUE(arm64_features->NeedFixCortexA53_835769());
EXPECT_TRUE(arm64_features->NeedFixCortexA53_843419());
}

} // namespace art
93 changes: 62 additions & 31 deletions runtime/arch/instruction_set_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

#include <algorithm>

#include "instruction_set_features.h"

#include <algorithm>
Expand Down Expand Up @@ -113,6 +115,16 @@ std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromCppDef
UNREACHABLE();
}

std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromRuntimeDetection() {
switch (kRuntimeISA) {
#ifdef ART_TARGET_ANDROID
case InstructionSet::kArm64:
return Arm64InstructionSetFeatures::FromHwcap();
#endif
default:
return nullptr;
}
}

std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromCpuInfo() {
switch (kRuntimeISA) {
Expand Down Expand Up @@ -184,44 +196,57 @@ std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromAssemb
}

std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::AddFeaturesFromString(
const std::string& feature_list, std::string* error_msg) const {
if (feature_list.empty()) {
*error_msg = "No instruction set features specified";
return std::unique_ptr<const InstructionSetFeatures>();
}
const std::string& feature_list, /* out */ std::string* error_msg) const {
std::vector<std::string> features;
Split(feature_list, ',', &features);
bool use_default = false; // Have we seen the 'default' feature?
bool first = false; // Is this first feature?
for (auto it = features.begin(); it != features.end();) {
if (use_default) {
*error_msg = "Unexpected instruction set features after 'default'";
return std::unique_ptr<const InstructionSetFeatures>();
}
std::string feature = android::base::Trim(*it);
bool erase = false;
std::transform(std::begin(features), std::end(features), std::begin(features),
[](const std::string &s) { return android::base::Trim(s); });
auto empty_strings_begin = std::copy_if(std::begin(features), std::end(features),
std::begin(features),
[](const std::string& s) { return !s.empty(); });
features.erase(empty_strings_begin, std::end(features));
if (features.empty()) {
*error_msg = "No instruction set features specified";
return nullptr;
}

bool use_default = false;
bool use_runtime_detection = false;
for (const std::string& feature : features) {
if (feature == "default") {
if (!first) {
use_default = true;
erase = true;
} else {
*error_msg = "Unexpected instruction set features before 'default'";
return std::unique_ptr<const InstructionSetFeatures>();
if (features.size() > 1) {
*error_msg = "Specific instruction set feature(s) cannot be used when 'default' is used.";
return nullptr;
}
use_default = true;
features.pop_back();
break;
} else if (feature == "runtime") {
if (features.size() > 1) {
*error_msg = "Specific instruction set feature(s) cannot be used when 'runtime' is used.";
return nullptr;
}
use_runtime_detection = true;
features.pop_back();
break;
}
if (!erase) {
++it;
} else {
it = features.erase(it);
}
first = true;
}
// Expectation: "default" is standalone, no other flags. But an empty features vector after
// processing can also come along if the handled flags are the only ones in the list. So
// logically, we check "default -> features.empty."
DCHECK(!use_default || features.empty());
// Expectation: "default" and "runtime" are standalone, no other feature names.
// But an empty features vector after processing can also come along if the
// handled feature names are the only ones in the list. So
// logically, we check "default or runtime => features.empty."
DCHECK((!use_default && !use_runtime_detection) || features.empty());

std::unique_ptr<const InstructionSetFeatures> runtime_detected_features;
if (use_runtime_detection) {
runtime_detected_features = FromRuntimeDetection();
}

return AddFeaturesFromSplitString(features, error_msg);
if (runtime_detected_features != nullptr) {
return AddRuntimeDetectedFeatures(runtime_detected_features.get());
} else {
return AddFeaturesFromSplitString(features, error_msg);
}
}

const ArmInstructionSetFeatures* InstructionSetFeatures::AsArmInstructionSetFeatures() const {
Expand Down Expand Up @@ -262,6 +287,12 @@ bool InstructionSetFeatures::FindVariantInArray(const char* const variants[], si
return std::find(begin, end, variant) != end;
}

std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::AddRuntimeDetectedFeatures(
const InstructionSetFeatures *features ATTRIBUTE_UNUSED) const {
UNIMPLEMENTED(FATAL) << kRuntimeISA;
UNREACHABLE();
}

std::ostream& operator<<(std::ostream& os, const InstructionSetFeatures& rhs) {
os << "ISA: " << rhs.GetInstructionSet() << " Feature string: " << rhs.GetFeatureString();
return os;
Expand Down
18 changes: 18 additions & 0 deletions runtime/arch/instruction_set_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ class InstructionSetFeatures {
// Turn C pre-processor #defines into the equivalent instruction set features for kRuntimeISA.
static std::unique_ptr<const InstructionSetFeatures> FromCppDefines();

// Check if run-time detection of instruction set features is supported.
//
// Return: true - if run-time detection is supported on a target device.
// false - otherwise
static bool IsRuntimeDetectionSupported() {
return FromRuntimeDetection() != nullptr;
}

// Use run-time detection to get instruction set features.
//
// Return: a set of detected features or nullptr if runtime detection is not
// supported on a target.
static std::unique_ptr<const InstructionSetFeatures> FromRuntimeDetection();

// Process /proc/cpuinfo and use kRuntimeISA to produce InstructionSetFeatures.
static std::unique_ptr<const InstructionSetFeatures> FromCpuInfo();

Expand Down Expand Up @@ -126,6 +140,10 @@ class InstructionSetFeatures {
AddFeaturesFromSplitString(const std::vector<std::string>& features,
std::string* error_msg) const = 0;

// Add run-time detected architecture specific features in sub-classes.
virtual std::unique_ptr<const InstructionSetFeatures>
AddRuntimeDetectedFeatures(const InstructionSetFeatures *features ATTRIBUTE_UNUSED) const;

private:
DISALLOW_COPY_AND_ASSIGN(InstructionSetFeatures);
};
Expand Down
Loading

0 comments on commit 7f88c1a

Please sign in to comment.