diff --git a/clang/include/clang/Driver/Multilib.h b/clang/include/clang/Driver/Multilib.h --- a/clang/include/clang/Driver/Multilib.h +++ b/clang/include/clang/Driver/Multilib.h @@ -14,6 +14,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/SourceMgr.h" #include #include #include @@ -60,9 +61,10 @@ const std::string &includeSuffix() const { return IncludeSuffix; } /// Get the set of tags that indicate this multilib's use. - /// Tags are arbitrary strings although typically they will look similar to - /// command line options. A multilib is considered compatible if its tags are - /// a subset of the tags derived from the Clang command line options. + /// Tags are arbitrary strings, some of which are derived from command-line + /// options and look similar to them, and others can be defined by a + /// particular multilib.yaml. A multilib is considered compatible if its tags + /// are a subset of the tags derived from the Clang command line options. const tag_set &tags() const { return Tags; } /// Returns the options that should be used for clang -print-multi-lib @@ -90,8 +92,19 @@ std::function(const Multilib &M)>; using FilterCallback = llvm::function_ref; + /// Uses regular expressions to simplify tags used for multilib selection. + /// For example, we may wish to simplify armv8, armv8.1, armv8.2 etc. to + /// simply "v8". It's also possible to negate matches. For example, it may be + /// appropriate to infer that if mfpu=none *doesn't* match then an FPU is + /// available. NoMatchTags can be used for this purpose. + struct TagMatcher { + std::string Regex; + std::vector MatchTags, NoMatchTags; + }; + private: multilib_list Multilibs; + std::vector TagMatchers; IncludeDirsFunc IncludeCallback; IncludeDirsFunc FilePathsCallback; @@ -118,6 +131,16 @@ unsigned size() const { return Multilibs.size(); } + /// Get the given tags plus tags found by matching them against the + /// TagMatchers and choosing the MatchTags or NoMatchTags of each + /// accordingly. The select method calls this method so in most cases it's not + /// necessary to call it directly. + Multilib::tag_set expandTags(const Multilib::tag_set &) const; + + bool parseYaml(llvm::MemoryBufferRef, + llvm::SourceMgr::DiagHandlerTy = nullptr, + void *DiagHandlerCtxt = nullptr); + LLVM_DUMP_METHOD void dump() const; void print(raw_ostream &OS) const; diff --git a/clang/lib/Driver/Multilib.cpp b/clang/lib/Driver/Multilib.cpp --- a/clang/lib/Driver/Multilib.cpp +++ b/clang/lib/Driver/Multilib.cpp @@ -8,6 +8,7 @@ #include "clang/Driver/Multilib.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/Version.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -16,10 +17,10 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" -#include #include -#include using namespace clang; using namespace driver; @@ -80,12 +81,12 @@ void MultilibSet::push_back(const Multilib &M) { Multilibs.push_back(M); } -MultilibSet::multilib_list -MultilibSet::select(const Multilib::tag_set &Tags) const { +std::vector MultilibSet::select(const Multilib::tag_set &Tags) const { + Multilib::tag_set AllTags(expandTags(Tags)); multilib_list Result; llvm::copy_if(Multilibs, std::back_inserter(Result), - [&Tags](const Multilib &M) { - return std::includes(Tags.begin(), Tags.end(), + [&AllTags](const Multilib &M) { + return std::includes(AllTags.begin(), AllTags.end(), M.tags().begin(), M.tags().end()); }); return Result; @@ -100,6 +101,143 @@ return true; } +Multilib::tag_set +MultilibSet::expandTags(const Multilib::tag_set &InTags) const { + Multilib::tag_set Result(InTags); + for (const TagMatcher &M : TagMatchers) { + std::string RegexString(M.Regex); + + // Make the regular expression match the whole string. + if (!StringRef(M.Regex).starts_with("^")) + RegexString.insert(RegexString.begin(), '^'); + if (!StringRef(M.Regex).ends_with("$")) + RegexString.push_back('$'); + + const llvm::Regex Regex(RegexString); + assert(Regex.isValid()); + if (llvm::find_if(InTags, [&Regex](StringRef F) { + return Regex.match(F); + }) != InTags.end()) { + Result.insert(M.MatchTags.begin(), M.MatchTags.end()); + } else { + Result.insert(M.NoMatchTags.begin(), M.NoMatchTags.end()); + } + } + return Result; +} + +namespace { + +struct MultilibSerialization { + std::string Dir; + std::vector Tags, PrintOptions; +}; + +struct MultilibSetSerialization { + std::string ClangMinimumVersion; + std::vector Multilibs; + std::vector TagMatchers; +}; + +} // end anonymous namespace + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSerialization &V) { + io.mapRequired("Dir", V.Dir); + io.mapRequired("Tags", V.Tags); + io.mapRequired("PrintOptions", V.PrintOptions); + } + static std::string validate(IO &io, MultilibSerialization &V) { + if (StringRef(V.Dir).starts_with("/")) + return "paths must be relative. \"" + V.Dir + "\" starts with \"/\"\n"; + return std::string{}; + } +}; + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSet::TagMatcher &M) { + io.mapRequired("Regex", M.Regex); + io.mapOptional("MatchTags", M.MatchTags); + io.mapOptional("NoMatchTags", M.NoMatchTags); + } + static std::string validate(IO &io, MultilibSet::TagMatcher &M) { + llvm::Regex Regex(M.Regex); + std::string RegexError; + if (!Regex.isValid(RegexError)) + return RegexError; + if (M.MatchTags.empty() && M.NoMatchTags.empty()) + return "value required for 'MatchTags' or 'NoMatchTags'"; + return std::string{}; + } +}; + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSetSerialization &M) { + io.mapRequired("ClangMinimumVersion", M.ClangMinimumVersion); + io.mapRequired("Variants", M.Multilibs); + io.mapOptional("TagMap", M.TagMatchers); + } + static std::string validate(IO &io, MultilibSetSerialization &M) { + if (M.ClangMinimumVersion.empty()) + return "missing required key 'ClangMinimumVersion'. Expected " + "MAJOR.MINOR.PATCHLEVEL"; + + SmallVector ClangMinimumVersion, + ClangVersion = {CLANG_VERSION_MAJOR, CLANG_VERSION_MINOR, + CLANG_VERSION_PATCHLEVEL}; + + SmallVector MinVerStrings; + StringRef(M.ClangMinimumVersion).split(MinVerStrings, '.'); + + if (MinVerStrings.size() != 3) + return "not a valid version string. Expected MAJOR.MINOR.PATCHLEVEL but " + "got \"" + + M.ClangMinimumVersion + "\""; + + for (StringRef S : MinVerStrings) { + int V; + if (S.getAsInteger(10, V)) + return "not a valid version string. Expected MAJOR.MINOR.PATCHLEVEL " + "where all components are decimal integers but got \"" + + M.ClangMinimumVersion + "\""; + ClangMinimumVersion.push_back(V); + } + + if (ClangVersion < ClangMinimumVersion) { + return "clang version " CLANG_VERSION_STRING + " is less than ClangMinimumVersion: " + + M.ClangMinimumVersion; + } + return std::string{}; + } +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSerialization) +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSet::TagMatcher) + +bool MultilibSet::parseYaml(llvm::MemoryBufferRef Input, + llvm::SourceMgr::DiagHandlerTy DiagHandler, + void *DiagHandlerCtxt) { + MultilibSetSerialization MS; + llvm::yaml::Input YamlInput(Input, nullptr, DiagHandler, DiagHandlerCtxt); + YamlInput >> MS; + if (YamlInput.error()) + return false; + + Multilibs.clear(); + Multilibs.reserve(MS.Multilibs.size()); + for (const auto &M : MS.Multilibs) { + std::string Dir; + if (M.Dir != ".") + Dir = "/" + M.Dir; + Multilibs.emplace_back(Dir, Dir, Dir, + Multilib::tag_set(M.Tags.begin(), M.Tags.end()), + M.PrintOptions); + } + TagMatchers = std::move(MS.TagMatchers); + return true; +} + LLVM_DUMP_METHOD void MultilibSet::dump() const { print(llvm::errs()); } diff --git a/clang/unittests/Driver/MultilibTest.cpp b/clang/unittests/Driver/MultilibTest.cpp --- a/clang/unittests/Driver/MultilibTest.cpp +++ b/clang/unittests/Driver/MultilibTest.cpp @@ -13,6 +13,7 @@ #include "clang/Driver/Multilib.h" #include "../../lib/Driver/ToolChains/CommonArgs.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/Version.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" @@ -187,3 +188,408 @@ EXPECT_EQ("/a", Selection[0].gccSuffix()); EXPECT_EQ("/b", Selection[1].gccSuffix()); } + +static void diagnosticCallback(const llvm::SMDiagnostic &D, void *Out) { + *reinterpret_cast(Out) = D.getMessage(); +} + +static bool parseYaml(MultilibSet &MS, std::string &Diagnostic, + const char *Data) { + return MS.parseYaml(llvm::MemoryBufferRef(Data, "TEST"), diagnosticCallback, + &Diagnostic); +} + +static bool parseYaml(MultilibSet &MS, std::string &Diagnostic, + const std::string &Data) { + return MS.parseYaml(llvm::MemoryBufferRef(Data, "TEST"), diagnosticCallback, + &Diagnostic); +} + +static bool parseYaml(MultilibSet &MS, const char *Data) { + return MS.parseYaml(llvm::MemoryBufferRef(Data, "TEST")); +} + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) +// Avoid using MULTILIB_CLANG_VERSION in case it has extra non-numeric parts. +#define MULTILIB_CLANG_VERSION \ + STRINGIFY(CLANG_VERSION_MAJOR) \ + "." STRINGIFY(CLANG_VERSION_MINOR) "." STRINGIFY(CLANG_VERSION_PATCHLEVEL) +#define YAML_PREAMBLE "ClangMinimumVersion: " MULTILIB_CLANG_VERSION "\n" + +TEST(MultilibTest, ParseInvalid) { + std::string Diagnostic; + + MultilibSet MS; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, R"( +Variants: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic) + .contains("missing required key 'ClangMinimumVersion'")) + << Diagnostic; + + // Require all 3 major.minor.patch version components + EXPECT_FALSE(parseYaml(MS, Diagnostic, R"( +ClangMinimumVersion: )" STRINGIFY(CLANG_VERSION_MAJOR) R"(.0 +Variants: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic) + .contains("not a valid version string. Expected " + "MAJOR.MINOR.PATCHLEVEL but got \"" STRINGIFY( + CLANG_VERSION_MAJOR) ".0\"")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, R"( +ClangMinimumVersion: )" MULTILIB_CLANG_VERSION R"(a +Variants: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic) + .contains("not a valid version string. Expected " + "MAJOR.MINOR.PATCHLEVEL where all components are decimal " + "integers but got \"" MULTILIB_CLANG_VERSION "a\"")) + << Diagnostic; + + // Reject configurations that require a later clang version + EXPECT_FALSE(parseYaml(MS, Diagnostic, + R"( +ClangMinimumVersion: )" + std::to_string(CLANG_VERSION_MAJOR + 1) + + R"(.0.0 +Variants: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic) + .contains("clang version " MULTILIB_CLANG_VERSION + " is less than ClangMinimumVersion: " + + std::to_string(CLANG_VERSION_MAJOR + 1) + ".0.0")) + << Diagnostic; + + // but accept configurations that only need an earlier clang version + EXPECT_TRUE(parseYaml(MS, Diagnostic, R"( +ClangMinimumVersion: 16.0.0 +Variants: [] +)")) << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE)); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'Variants'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Dir: /abc + Tags: [] + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("paths must be relative")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Tags: [] + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'Dir'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Dir: . + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'Tags'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Dir: . + Tags: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("missing required key 'PrintOptions'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: [] +TagMap: +- Regex: abc +)")); + EXPECT_TRUE(StringRef(Diagnostic) + .contains("value required for 'MatchTags' or 'NoMatchTags'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: [] +TagMap: +- Dir: . + Regex: '(' + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("parentheses not balanced")) + << Diagnostic; +} + +TEST(MultilibTest, Parse) { + MultilibSet MS; + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: . + Tags: [] + PrintOptions: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: abc + Tags: [] + PrintOptions: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/abc", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: pqr + Tags: [] + PrintOptions: [-mfloat-abi=soft] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/pqr", MS.begin()->gccSuffix()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft"}), + MS.begin()->getPrintOptions()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: pqr + Tags: [] + PrintOptions: [-mfloat-abi=soft, -fno-exceptions] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft", "-fno-exceptions"}), + MS.begin()->getPrintOptions()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: a + Tags: [] + PrintOptions: [] +- Dir: b + Tags: [] + PrintOptions: [] +)")); + EXPECT_EQ(2U, MS.size()); +} + +TEST(MultilibTest, SelectSoft) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: s + Tags: [softabi] + PrintOptions: [] +TagMap: +- Regex: mfloat-abi=soft + MatchTags: [softabi] +- Regex: mfloat-abi=softfp + MatchTags: [softabi] +)")); + EXPECT_TRUE(MS.select({"mfloat-abi=soft"}, Selected)); + EXPECT_TRUE(MS.select({"mfloat-abi=softfp"}, Selected)); + EXPECT_FALSE(MS.select({"mfloat-abi=hard"}, Selected)); +} + +TEST(MultilibTest, SelectSoftFP) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: f + Tags: [mfloat-abi=softfp] + PrintOptions: [] +)")); + EXPECT_FALSE(MS.select({"mfloat-abi=soft"}, Selected)); + EXPECT_TRUE(MS.select({"mfloat-abi=softfp"}, Selected)); + EXPECT_FALSE(MS.select({"mfloat-abi=hard"}, Selected)); +} + +TEST(MultilibTest, SelectHard) { + // If hard float is all that's available then select that only if compiling + // with hard float. + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: h + Tags: [mfloat-abi=hard] + PrintOptions: [] +)")); + EXPECT_FALSE(MS.select({"mfloat-abi=soft"}, Selected)); + EXPECT_FALSE(MS.select({"mfloat-abi=softfp"}, Selected)); + EXPECT_TRUE(MS.select({"mfloat-abi=hard"}, Selected)); +} + +TEST(MultilibTest, SelectFloatABI) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: s + Tags: [softabi] + PrintOptions: [] +- Dir: f + Tags: [softabi, hasfp] + PrintOptions: [] +- Dir: h + Tags: [hardabi, hasfp] + PrintOptions: [] +TagMap: +- Regex: mfloat-abi=(soft|softfp) + MatchTags: [softabi] +- Regex: mfloat-abi=hard + MatchTags: [hardabi] +- Regex: mfloat-abi=soft + NoMatchTags: [hasfp] +)")); + MS.select({"mfloat-abi=soft"}, Selected); + EXPECT_EQ("/s", Selected.gccSuffix()); + MS.select({"mfloat-abi=softfp"}, Selected); + EXPECT_EQ("/f", Selected.gccSuffix()); + MS.select({"mfloat-abi=hard"}, Selected); + EXPECT_EQ("/h", Selected.gccSuffix()); +} + +TEST(MultilibTest, SelectFloatABIReversed) { + // If soft is specified after softfp then softfp will never be + // selected because soft is compatible with softfp and last wins. + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: h + Tags: [hardabi, hasfp] + PrintOptions: [] +- Dir: f + Tags: [softabi, hasfp] + PrintOptions: [] +- Dir: s + Tags: [softabi] + PrintOptions: [] +TagMap: +- Regex: mfloat-abi=(soft|softfp) + MatchTags: [softabi] +- Regex: mfloat-abi=hard + MatchTags: [hardabi] +- Regex: mfloat-abi=soft + NoMatchTags: [hasfp] +)")); + MS.select({"mfloat-abi=soft"}, Selected); + EXPECT_EQ("/s", Selected.gccSuffix()); + MS.select({"mfloat-abi=softfp"}, Selected); + EXPECT_EQ("/s", Selected.gccSuffix()); + MS.select({"mfloat-abi=hard"}, Selected); + EXPECT_EQ("/h", Selected.gccSuffix()); +} + +TEST(MultilibTest, SelectMClass) { + const char *MultilibSpec = YAML_PREAMBLE R"( +Variants: +- Dir: thumb/v6-m/nofp + Tags: [target=thumbv6m-none-eabi] + PrintOptions: [--target=thumbv6m-none-eabi, -mfloat-abi=soft] + +- Dir: thumb/v7-m/nofp + Tags: [target=thumbv7m-none-eabi] + PrintOptions: [--target=thumbv7m-none-eabi, -mfloat-abi=soft] + +- Dir: thumb/v7e-m/nofp + Tags: [target=thumbv7em-none-eabi] + PrintOptions: [--target=thumbv7em-none-eabi, -mfloat-abi=soft, -mfpu=none] + +- Dir: thumb/v8-m.main/nofp + Tags: [target=thumbv8m.main-none-eabi] + PrintOptions: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8m.main+nofp] + +- Dir: thumb/v8.1-m.main/nofp/nomve + Tags: [target=thumbv8.1m.main-none-eabi] + PrintOptions: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8.1m.main+nofp+nomve] + +- Dir: thumb/v7e-m/fpv4_sp_d16 + Tags: [target=thumbv7em-none-eabihf, mfpu=fpv4-sp-d16] + PrintOptions: [--target=thumbv7em-none-eabihf, -mfpu=fpv4-sp-d16] + +- Dir: thumb/v7e-m/fpv5_d16 + Tags: [target=thumbv7em-none-eabihf, mfpu=fpv5-d16] + PrintOptions: [--target=thumbv7em-none-eabihf, -mfpu=fpv5-d16] + +- Dir: thumb/v8-m.main/fp + Tags: [target=thumbv8m.main-none-eabihf] + PrintOptions: [--target=thumbv8m.main-none-eabihf] + +- Dir: thumb/v8.1-m.main/fp + Tags: [target=thumbv8.1m.main-none-eabihf] + PrintOptions: [--target=thumbv8.1m.main-none-eabihf] + +- Dir: thumb/v8.1-m.main/nofp/mve + Tags: [target=thumbv8.1m.main-none-eabihf, march=+mve] + PrintOptions: [--target=arm-none-eabihf, -march=armv8.1m.main+nofp+mve] + +TagMap: +- Regex: target=thumbv8(\.[0-9]+)?m\.base-none-eabi + MatchTags: [target=thumbv6m-none-eabi] +- Regex: thumbv8\.[1-9]m\.main-none-eabi + MatchTags: [target=thumbv8.1m.main-none-eabi] +- Regex: thumbv8\.[1-9]m\.main-none-eabihf + MatchTags: [target=thumbv8.1m.main-none-eabihf] +)"; + + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, MultilibSpec)); + + EXPECT_TRUE( + MS.select({"target=thumbv6m-none-eabi", "mfloat-abi=soft"}, Selected)); + EXPECT_EQ("/thumb/v6-m/nofp", Selected.gccSuffix()); + + EXPECT_TRUE( + MS.select({"target=thumbv7m-none-eabi", "mfloat-abi=soft"}, Selected)); + EXPECT_EQ("/thumb/v7-m/nofp", Selected.gccSuffix()); + + EXPECT_TRUE( + MS.select({"target=thumbv7em-none-eabi", "mfloat-abi=soft", "mfpu=none"}, + Selected)); + EXPECT_EQ("/thumb/v7e-m/nofp", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select({"target=thumbv8m.main-none-eabi", "mfloat-abi=soft"}, + Selected)); + EXPECT_EQ("/thumb/v8-m.main/nofp", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select( + {"target=thumbv8.1m.main-none-eabi", "mfloat-abi=soft", "mfpu=none"}, + Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/nofp/nomve", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select( + {"target=thumbv7em-none-eabihf", "mfloat-abi=hard", "mfpu=fpv4-sp-d16"}, + Selected)); + EXPECT_EQ("/thumb/v7e-m/fpv4_sp_d16", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select( + {"target=thumbv7em-none-eabihf", "mfloat-abi=hard", "mfpu=fpv5-d16"}, + Selected)); + EXPECT_EQ("/thumb/v7e-m/fpv5_d16", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select({"target=thumbv8m.main-none-eabihf", "mfloat-abi=hard"}, + Selected)); + EXPECT_EQ("/thumb/v8-m.main/fp", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select( + {"target=thumbv8.1m.main-none-eabihf", "mfloat-abi=hard"}, Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/fp", Selected.gccSuffix()); + + EXPECT_TRUE(MS.select({"target=thumbv8.1m.main-none-eabihf", + "mfloat-abi=hard", "mfpu=none", "march=+mve"}, + Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/nofp/mve", Selected.gccSuffix()); +}