ART: Enable ISA features run-time detection for ARM64

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
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 0d35fec..4d7ae9b 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -99,7 +99,10 @@
       }
     }
   }
+
   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);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 35af918..4e62103 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -296,6 +296,10 @@
   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("");
@@ -875,9 +879,9 @@
       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) {
@@ -890,9 +894,9 @@
       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();
       }
     }
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 524bce0..d3bfb57 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <algorithm>
 #include <regex>
 #include <sstream>
 #include <string>
@@ -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"
@@ -2315,4 +2317,38 @@
   }));
 }
 
+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
diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc
index fcf3c75..fdf4dbd 100644
--- a/runtime/arch/arm/instruction_set_features_arm.cc
+++ b/runtime/arch/arm/instruction_set_features_arm.cc
@@ -319,8 +319,9 @@
   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") {
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc
index 4a2b9d5..196f358 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64.cc
@@ -315,8 +315,9 @@
   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") {
@@ -367,4 +368,17 @@
                                       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
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.h b/runtime/arch/arm64/instruction_set_features_arm64.h
index 4ec8fa2..432b9ef 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.h
+++ b/runtime/arch/arm64/instruction_set_features_arm64.h
@@ -98,6 +98,9 @@
       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,
diff --git a/runtime/arch/arm64/instruction_set_features_arm64_test.cc b/runtime/arch/arm64/instruction_set_features_arm64_test.cc
index 99d6b0d..eef8f08 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64_test.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64_test.cc
@@ -170,4 +170,54 @@
   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
diff --git a/runtime/arch/instruction_set_features.cc b/runtime/arch/instruction_set_features.cc
index 886b40a..c5c2d31 100644
--- a/runtime/arch/instruction_set_features.cc
+++ b/runtime/arch/instruction_set_features.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <algorithm>
+
 #include "instruction_set_features.h"
 
 #include <algorithm>
@@ -113,6 +115,16 @@
   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) {
@@ -184,44 +196,57 @@
 }
 
 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;
-    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 (!erase) {
-      ++it;
-    } else {
-      it = features.erase(it);
-    }
-    first = true;
+  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;
   }
-  // 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());
 
-  return AddFeaturesFromSplitString(features, error_msg);
+  bool use_default = false;
+  bool use_runtime_detection = false;
+  for (const std::string& feature : features) {
+    if (feature == "default") {
+      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;
+    }
+  }
+  // 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();
+  }
+
+  if (runtime_detected_features != nullptr) {
+    return AddRuntimeDetectedFeatures(runtime_detected_features.get());
+  } else {
+    return AddFeaturesFromSplitString(features, error_msg);
+  }
 }
 
 const ArmInstructionSetFeatures* InstructionSetFeatures::AsArmInstructionSetFeatures() const {
@@ -262,6 +287,12 @@
   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;
diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h
index f910a41..9222a7b 100644
--- a/runtime/arch/instruction_set_features.h
+++ b/runtime/arch/instruction_set_features.h
@@ -48,6 +48,20 @@
   // 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();
 
@@ -126,6 +140,10 @@
       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);
 };
diff --git a/runtime/arch/instruction_set_features_test.cc b/runtime/arch/instruction_set_features_test.cc
index 3a39a2a..d9b2e3f 100644
--- a/runtime/arch/instruction_set_features_test.cc
+++ b/runtime/arch/instruction_set_features_test.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <array>
+
 #include "instruction_set_features.h"
 
 #include <gtest/gtest.h>
@@ -161,4 +163,145 @@
       << "\nFeatures from build: " << *instruction_set_features.get();
 }
 
+TEST(InstructionSetFeaturesTest, FeaturesFromRuntimeDetection) {
+  if (!InstructionSetFeatures::IsRuntimeDetectionSupported()) {
+    EXPECT_EQ(InstructionSetFeatures::FromRuntimeDetection(), nullptr);
+  }
+}
+
+// The instruction set feature string must not contain 'default' together with
+// other feature names.
+//
+// Test that InstructionSetFeatures::AddFeaturesFromString returns nullptr and
+// an error is reported when the value 'default' is specified together
+// with other feature names in an instruction set feature string.
+TEST(InstructionSetFeaturesTest, AddFeaturesFromStringWithDefaultAndOtherNames) {
+  std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
+      InstructionSetFeatures::FromCppDefines());
+  std::vector<std::string> invalid_feature_strings = {
+    "a,default",
+    "default,a",
+    "a,default,b",
+    "a,b,default",
+    "default,a,b,c",
+    "a,b,default,c,d",
+    "a, default ",
+    " default , a",
+    "a, default , b",
+    "default,runtime"
+  };
+
+  for (const std::string& invalid_feature_string : invalid_feature_strings) {
+    std::string error_msg;
+    EXPECT_EQ(cpp_defined_features->AddFeaturesFromString(invalid_feature_string, &error_msg),
+              nullptr) << " Invalid feature string: '" << invalid_feature_string << "'";
+    EXPECT_EQ(error_msg,
+              "Specific instruction set feature(s) cannot be used when 'default' is used.");
+  }
+}
+
+// The instruction set feature string must not contain 'runtime' together with
+// other feature names.
+//
+// Test that InstructionSetFeatures::AddFeaturesFromString returns nullptr and
+// an error is reported when the value 'runtime' is specified together
+// with other feature names in an instruction set feature string.
+TEST(InstructionSetFeaturesTest, AddFeaturesFromStringWithRuntimeAndOtherNames) {
+  std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
+      InstructionSetFeatures::FromCppDefines());
+  std::vector<std::string> invalid_feature_strings = {
+    "a,runtime",
+    "runtime,a",
+    "a,runtime,b",
+    "a,b,runtime",
+    "runtime,a,b,c",
+    "a,b,runtime,c,d",
+    "a, runtime ",
+    " runtime , a",
+    "a, runtime , b",
+    "runtime,default"
+  };
+
+  for (const std::string& invalid_feature_string : invalid_feature_strings) {
+    std::string error_msg;
+    EXPECT_EQ(cpp_defined_features->AddFeaturesFromString(invalid_feature_string, &error_msg),
+              nullptr) << " Invalid feature string: '" << invalid_feature_string << "'";
+    EXPECT_EQ(error_msg,
+              "Specific instruction set feature(s) cannot be used when 'runtime' is used.");
+  }
+}
+
+// Spaces and multiple commas are ignores in a instruction set feature string.
+//
+// Test that a use of spaces and multiple commas with 'default' and 'runtime'
+// does not cause errors.
+TEST(InstructionSetFeaturesTest, AddFeaturesFromValidStringContainingDefaultOrRuntime) {
+  std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
+      InstructionSetFeatures::FromCppDefines());
+  std::vector<std::string> valid_feature_strings = {
+    "default",
+    ",,,default",
+    "default,,,,",
+    ",,,default,,,,",
+    "default, , , ",
+    " , , ,default",
+    " , , ,default, , , ",
+    " default , , , ",
+    ",,,runtime",
+    "runtime,,,,",
+    ",,,runtime,,,,",
+    "runtime, , , ",
+    " , , ,runtime",
+    " , , ,runtime, , , ",
+    " runtime , , , "
+  };
+  for (const std::string& valid_feature_string : valid_feature_strings) {
+    std::string error_msg;
+    EXPECT_NE(cpp_defined_features->AddFeaturesFromString(valid_feature_string, &error_msg),
+              nullptr) << " Valid feature string: '" << valid_feature_string << "'";
+    EXPECT_TRUE(error_msg.empty()) << error_msg;
+  }
+}
+
+// Spaces and multiple commas are ignores in a instruction set feature string.
+//
+// Test that a use of spaces and multiple commas without any feature names
+// causes errors.
+TEST(InstructionSetFeaturesTest, AddFeaturesFromInvalidStringWithoutFeatureNames) {
+  std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
+      InstructionSetFeatures::FromCppDefines());
+  std::vector<std::string> invalid_feature_strings = {
+    " ",
+    "       ",
+    ",",
+    ",,",
+    " , , ,,,,,,",
+    "\t",
+    "  \t     ",
+    ",",
+    ",,",
+    " , , ,,,,,,"
+  };
+  for (const std::string& invalid_feature_string : invalid_feature_strings) {
+    std::string error_msg;
+    EXPECT_EQ(cpp_defined_features->AddFeaturesFromString(invalid_feature_string, &error_msg),
+              nullptr) << " Invalid feature string: '" << invalid_feature_string << "'";
+    EXPECT_EQ(error_msg, "No instruction set features specified");
+  }
+}
+
+TEST(InstructionSetFeaturesTest, AddFeaturesFromStringRuntime) {
+  std::unique_ptr<const InstructionSetFeatures> cpp_defined_features(
+      InstructionSetFeatures::FromCppDefines());
+  std::string error_msg;
+
+  const std::unique_ptr<const InstructionSetFeatures> features =
+      cpp_defined_features->AddFeaturesFromString("runtime", &error_msg);
+  EXPECT_NE(features, nullptr);
+  EXPECT_TRUE(error_msg.empty()) << error_msg;
+  if (!InstructionSetFeatures::IsRuntimeDetectionSupported()) {
+    EXPECT_TRUE(features->Equals(cpp_defined_features.get()));
+  }
+}
+
 }  // namespace art
diff --git a/runtime/arch/mips/instruction_set_features_mips.cc b/runtime/arch/mips/instruction_set_features_mips.cc
index 952ed25..99ce536 100644
--- a/runtime/arch/mips/instruction_set_features_mips.cc
+++ b/runtime/arch/mips/instruction_set_features_mips.cc
@@ -214,8 +214,9 @@
   bool mips_isa_gte2 = mips_isa_gte2_;
   bool r6 = r6_;
   bool msa = msa_;
-  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 == "fpu32") {
       fpu_32bit = true;
     } else if (feature == "-fpu32") {
diff --git a/runtime/arch/mips64/instruction_set_features_mips64.cc b/runtime/arch/mips64/instruction_set_features_mips64.cc
index ea9f84b..2031433 100644
--- a/runtime/arch/mips64/instruction_set_features_mips64.cc
+++ b/runtime/arch/mips64/instruction_set_features_mips64.cc
@@ -114,8 +114,9 @@
 Mips64InstructionSetFeatures::AddFeaturesFromSplitString(
     const std::vector<std::string>& features, std::string* error_msg) const {
   bool msa = msa_;
-  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 == "msa") {
       msa = true;
     } else if (feature == "-msa") {
diff --git a/runtime/arch/x86/instruction_set_features_x86.cc b/runtime/arch/x86/instruction_set_features_x86.cc
index e9e983c..0c3d26e 100644
--- a/runtime/arch/x86/instruction_set_features_x86.cc
+++ b/runtime/arch/x86/instruction_set_features_x86.cc
@@ -311,8 +311,9 @@
   bool has_AVX = has_AVX_;
   bool has_AVX2 = has_AVX2_;
   bool has_POPCNT = has_POPCNT_;
-  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 == "ssse3") {
       has_SSSE3 = true;
     } else if (feature == "-ssse3") {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index feade83..26f21b0 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2475,10 +2475,15 @@
   instruction_set += GetInstructionSetString(kRuntimeISA);
   argv->push_back(instruction_set);
 
-  std::unique_ptr<const InstructionSetFeatures> features(InstructionSetFeatures::FromCppDefines());
-  std::string feature_string("--instruction-set-features=");
-  feature_string += features->GetFeatureString();
-  argv->push_back(feature_string);
+  if (InstructionSetFeatures::IsRuntimeDetectionSupported()) {
+    argv->push_back("--instruction-set-features=runtime");
+  } else {
+    std::unique_ptr<const InstructionSetFeatures> features(
+        InstructionSetFeatures::FromCppDefines());
+    std::string feature_string("--instruction-set-features=");
+    feature_string += features->GetFeatureString();
+    argv->push_back(feature_string);
+  }
 }
 
 void Runtime::CreateJitCodeCache(bool rwx_memory_allowed) {