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 @@ -88,8 +89,19 @@ std::function(const Multilib &M)>; using FilterCallback = llvm::function_ref; + /// Uses regular expressions to simplify flags 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. NoMatchFlags can be used for this purpose. + struct FlagMatcher { + std::string Regex; + std::vector MatchFlags, NoMatchFlags; + }; + private: multilib_list Multilibs; + std::vector FlagMatchers; IncludeDirsFunc IncludeCallback; IncludeDirsFunc FilePathsCallback; @@ -116,6 +128,15 @@ unsigned size() const { return Multilibs.size(); } + /// Get the given flags plus flags found by matching them against the + /// FlagMatchers and choosing the MatchFlags or NoMatchFlags of each + /// accordingly. The select method calls this method so in most cases it's not + /// necessary to call it directly. + Multilib::flag_set expandFlags(const Multilib::flag_set &) const; + + bool parse(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 @@ -16,10 +16,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; @@ -82,12 +82,13 @@ void MultilibSet::push_back(const Multilib &M) { Multilibs.push_back(M); } -MultilibSet::multilib_list +std::vector MultilibSet::select(const Multilib::flag_set &Flags) const { + Multilib::flag_set AllFlags(expandFlags(Flags)); multilib_list Result; llvm::copy_if(Multilibs, std::back_inserter(Result), - [&Flags](const Multilib &M) { - return std::includes(Flags.begin(), Flags.end(), + [&AllFlags](const Multilib &M) { + return std::includes(AllFlags.begin(), AllFlags.end(), M.flags().begin(), M.flags().end()); }); return Result; @@ -103,6 +104,108 @@ return true; } +Multilib::flag_set +MultilibSet::expandFlags(const Multilib::flag_set &InFlags) const { + Multilib::flag_set Result(InFlags); + for (const FlagMatcher &M : FlagMatchers) { + 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(InFlags, [&Regex](StringRef F) { + return Regex.match(F); + }) != InFlags.end()) { + Result.insert(M.MatchFlags.begin(), M.MatchFlags.end()); + } else { + Result.insert(M.NoMatchFlags.begin(), M.NoMatchFlags.end()); + } + } + return Result; +} + +struct MultilibSerialization { + std::string Dir; + std::vector Flags, PrintArgs; +}; + +struct MultilibSetSerialization { + std::vector Multilibs; + std::vector FlagMatchers; +}; + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSerialization &V) { + io.mapRequired("dir", V.Dir); + io.mapRequired("flags", V.Flags); + io.mapRequired("printArgs", V.PrintArgs); + } + 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::FlagMatcher &M) { + io.mapRequired("regex", M.Regex); + io.mapOptional("matchFlags", M.MatchFlags); + io.mapOptional("noMatchFlags", M.NoMatchFlags); + } + static std::string validate(IO &io, MultilibSet::FlagMatcher &M) { + llvm::Regex Regex(M.Regex); + std::string RegexError; + if (!Regex.isValid(RegexError)) { + return RegexError; + } + if (M.MatchFlags.empty() && M.NoMatchFlags.empty()) + return "value required for 'matchFlags' or 'noMatchFlags'"; + return std::string{}; + } +}; + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSetSerialization &M) { + io.mapRequired("variants", M.Multilibs); + io.mapOptional("flagMap", M.FlagMatchers); + } +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSerialization) +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSet::FlagMatcher) + +bool MultilibSet::parse(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::flag_set(M.Flags.begin(), M.Flags.end()), + M.PrintArgs); + } + FlagMatchers = std::move(MS.FlagMatchers); + 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 @@ -187,3 +187,347 @@ 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 parse(MultilibSet &MS, std::string &Diagnostic, const char *Data) { + return MS.parse(llvm::MemoryBufferRef(Data, "TEST"), diagnosticCallback, + &Diagnostic); +} + +static bool parse(MultilibSet &MS, const char *Data) { + return MS.parse(llvm::MemoryBufferRef(Data, "TEST")); +} + +TEST(MultilibTest, ParseInvalid) { + std::string Diagnostic; + + MultilibSet MS; + + EXPECT_FALSE(parse(MS, Diagnostic, "{}")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'variants'")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: +- dir: /abc + flags: [] + printArgs: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("paths must be relative")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: +- flags: [] + printArgs: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'dir'")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: +- dir: . + printArgs: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'flags'")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: +- dir: . + flags: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("missing required key 'printArgs'")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: [] +flagMap: +- regex: abc +)")); + EXPECT_TRUE( + StringRef(Diagnostic) + .contains("value required for 'matchFlags' or 'noMatchFlags'")) + << Diagnostic; + + EXPECT_FALSE(parse(MS, Diagnostic, R"( +variants: [] +flagMap: +- dir: . + regex: '(' + printArgs: [] + +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("parentheses not balanced")) + << Diagnostic; +} + +TEST(MultilibTest, Parse) { + MultilibSet MS; + EXPECT_TRUE(parse(MS, R"( +variants: +- dir: . + flags: [] + printArgs: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parse(MS, R"( +variants: +- dir: abc + flags: [] + printArgs: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/abc", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parse(MS, R"( +variants: +- dir: pqr + flags: [] + printArgs: [-mfloat-abi=soft] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/pqr", MS.begin()->gccSuffix()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft"}), + MS.begin()->getPrintArgs()); + + EXPECT_TRUE(parse(MS, R"( +variants: +- dir: pqr + flags: [] + printArgs: [-mfloat-abi=soft, -fno-exceptions] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft", "-fno-exceptions"}), + MS.begin()->getPrintArgs()); + + EXPECT_TRUE(parse(MS, R"( +variants: +- dir: a + flags: [] + printArgs: [] +- dir: b + flags: [] + printArgs: [] +)")); + EXPECT_EQ(2U, MS.size()); +} + +TEST(MultilibTest, SelectSoft) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parse(MS, R"( +variants: +- dir: s + flags: [softabi] + printArgs: [] +flagMap: +- regex: mfloat-abi=soft + matchFlags: [softabi] +- regex: mfloat-abi=softfp + matchFlags: [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(parse(MS, R"( +variants: +- dir: f + flags: [mfloat-abi=softfp] + printArgs: [] +)")); + 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(parse(MS, R"( +variants: +- dir: h + flags: [mfloat-abi=hard] + printArgs: [] +)")); + 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(parse(MS, R"( +variants: +- dir: s + flags: [softabi] + printArgs: [] +- dir: f + flags: [softabi, hasfp] + printArgs: [] +- dir: h + flags: [hardabi, hasfp] + printArgs: [] +flagMap: +- regex: mfloat-abi=(soft|softfp) + matchFlags: [softabi] +- regex: mfloat-abi=hard + matchFlags: [hardabi] +- regex: mfloat-abi=soft + noMatchFlags: [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(parse(MS, R"( +variants: +- dir: h + flags: [hardabi, hasfp] + printArgs: [] +- dir: f + flags: [softabi, hasfp] + printArgs: [] +- dir: s + flags: [softabi] + printArgs: [] +flagMap: +- regex: mfloat-abi=(soft|softfp) + matchFlags: [softabi] +- regex: mfloat-abi=hard + matchFlags: [hardabi] +- regex: mfloat-abi=soft + noMatchFlags: [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 = R"( +variants: +- dir: thumb/v6-m/nofp + flags: [target=thumbv6m-none-eabi] + printArgs: [--target=thumbv6m-none-eabi, -mfloat-abi=soft] + +- dir: thumb/v7-m/nofp + flags: [target=thumbv7m-none-eabi] + printArgs: [--target=thumbv7m-none-eabi, -mfloat-abi=soft] + +- dir: thumb/v7e-m/nofp + flags: [target=thumbv7em-none-eabi] + printArgs: [--target=thumbv7em-none-eabi, -mfloat-abi=soft, -mfpu=none] + +- dir: thumb/v8-m.main/nofp + flags: [target=thumbv8m.main-none-eabi] + printArgs: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8m.main+nofp] + +- dir: thumb/v8.1-m.main/nofp/nomve + flags: [target=thumbv8.1m.main-none-eabi] + printArgs: [--target=arm-none-eabi, -mfloat-abi=soft, -march=armv8.1m.main+nofp+nomve] + +- dir: thumb/v7e-m/fpv4_sp_d16 + flags: [target=thumbv7em-none-eabihf, mfpu=fpv4-sp-d16] + printArgs: [--target=thumbv7em-none-eabihf, -mfpu=fpv4-sp-d16] + +- dir: thumb/v7e-m/fpv5_d16 + flags: [target=thumbv7em-none-eabihf, mfpu=fpv5-d16] + printArgs: [--target=thumbv7em-none-eabihf, -mfpu=fpv5-d16] + +- dir: thumb/v8-m.main/fp + flags: [target=thumbv8m.main-none-eabihf] + printArgs: [--target=thumbv8m.main-none-eabihf] + +- dir: thumb/v8.1-m.main/fp + flags: [target=thumbv8.1m.main-none-eabihf] + printArgs: [--target=thumbv8.1m.main-none-eabihf] + +- dir: thumb/v8.1-m.main/nofp/mve + flags: [target=thumbv8.1m.main-none-eabihf, march=+mve] + printArgs: [--target=arm-none-eabihf, -march=armv8.1m.main+nofp+mve] + +flagMap: +- regex: target=thumbv8(\.[0-9]+)?m\.base-none-eabi + matchFlags: [target=thumbv6m-none-eabi] +- regex: thumbv8\.[1-9]m\.main-none-eabi + matchFlags: [target=thumbv8.1m.main-none-eabi] +- regex: thumbv8\.[1-9]m\.main-none-eabihf + matchFlags: [target=thumbv8.1m.main-none-eabihf] +)"; + + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parse(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()); +}