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 @@ -13,7 +13,9 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/SourceMgr.h" #include #include #include @@ -83,14 +85,27 @@ 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 both -mfloat-abi=soft and -mfloat-abi=softfp to + /// be treated as -mfloat-abi=soft. 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; public: MultilibSet() = default; - MultilibSet(multilib_list &&Multilibs) : Multilibs(Multilibs) {} + MultilibSet(multilib_list &&Multilibs, + std::vector &&FlagMatchers = {}) + : Multilibs(Multilibs), FlagMatchers(FlagMatchers) {} const multilib_list &getMultilibs() { return Multilibs; } @@ -111,6 +126,12 @@ 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. + llvm::StringSet<> expandFlags(const Multilib::flags_list &) const; + LLVM_DUMP_METHOD void dump() const; void print(raw_ostream &OS) const; @@ -127,6 +148,10 @@ } const IncludeDirsFunc &filePathsCallback() const { return FilePathsCallback; } + + static llvm::ErrorOr + parseYaml(llvm::MemoryBufferRef, llvm::SourceMgr::DiagHandlerTy = nullptr, + void *DiagHandlerCtxt = nullptr); }; raw_ostream &operator<<(raw_ostream &OS, const MultilibSet &MS); 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,14 +8,18 @@ #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" -#include "llvm/ADT/StringSet.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" +#include "llvm/Support/VersionTuple.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -91,10 +95,7 @@ MultilibSet::multilib_list MultilibSet::select(const Multilib::flags_list &Flags) const { - llvm::StringSet<> FlagSet; - for (const auto &Flag : Flags) - FlagSet.insert(Flag); - + llvm::StringSet<> FlagSet(expandFlags(Flags)); multilib_list Result; llvm::copy_if(Multilibs, std::back_inserter(Result), [&FlagSet](const Multilib &M) { @@ -115,6 +116,124 @@ return true; } +llvm::StringSet<> +MultilibSet::expandFlags(const Multilib::flags_list &InFlags) const { + llvm::StringSet<> Result; + for (const auto &F : InFlags) + Result.insert(F); + 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; +} + +namespace { + +// When updating this also update MULTILIB_VERSION in MultilibTest.cpp +static const VersionTuple MultilibVersionCurrent(1, 0); + +struct MultilibSerialization { + std::string Dir; + std::vector Flags; +}; + +struct MultilibSetSerialization { + llvm::VersionTuple MultilibVersion; + std::vector Multilibs; + std::vector FlagMatchers; +}; + +} // end anonymous namespace + +template <> struct llvm::yaml::MappingTraits { + static void mapping(llvm::yaml::IO &io, MultilibSerialization &V) { + io.mapRequired("Dir", V.Dir); + io.mapRequired("Flags", V.Flags); + } + static std::string validate(IO &io, MultilibSerialization &V) { + if (StringRef(V.Dir).starts_with("/")) + return "paths must be relative but \"" + V.Dir + "\" starts with \"/\""; + 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("MultilibVersion", M.MultilibVersion); + io.mapRequired("Variants", M.Multilibs); + io.mapOptional("FlagMap", M.FlagMatchers); + } + static std::string validate(IO &io, MultilibSetSerialization &M) { + if (M.MultilibVersion.empty()) + return "missing required key 'MultilibVersion'"; + if (M.MultilibVersion.getMajor() != MultilibVersionCurrent.getMajor()) + return "multilib version " + M.MultilibVersion.getAsString() + + " is unsupported"; + if (M.MultilibVersion.getMinor() > MultilibVersionCurrent.getMinor()) + return "multilib version " + M.MultilibVersion.getAsString() + + " is unsupported"; + return std::string{}; + } +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSerialization) +LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSet::FlagMatcher) + +llvm::ErrorOr +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 YamlInput.error(); + + multilib_list Multilibs; + 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, M.Flags); + } + + return MultilibSet(std::move(Multilibs), std::move(MS.FlagMatchers)); +} + 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,352 @@ 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) { + auto ErrorOrMS = MultilibSet::parseYaml(llvm::MemoryBufferRef(Data, "TEST"), + diagnosticCallback, &Diagnostic); + if (ErrorOrMS.getError()) + return false; + MS = std::move(ErrorOrMS.get()); + return true; +} + +static bool parseYaml(MultilibSet &MS, const char *Data) { + auto ErrorOrMS = MultilibSet::parseYaml(llvm::MemoryBufferRef(Data, "TEST")); + if (ErrorOrMS.getError()) + return false; + MS = std::move(ErrorOrMS.get()); + return true; +} + +// When updating this version also update MultilibVersionCurrent in Multilib.cpp +#define YAML_PREAMBLE "MultilibVersion: 1.0\n" + +TEST(MultilibTest, ParseInvalid) { + std::string Diagnostic; + + MultilibSet MS; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, R"( +Variants: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("missing required key 'MultilibVersion'")) + << Diagnostic; + + // Reject files with a different major version + EXPECT_FALSE(parseYaml(MS, Diagnostic, + R"( +MultilibVersion: 2.0 +Variants: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("multilib version 2.0 is unsupported")) + << Diagnostic; + EXPECT_FALSE(parseYaml(MS, Diagnostic, + R"( +MultilibVersion: 0.1 +Variants: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("multilib version 0.1 is unsupported")) + << Diagnostic; + + // Reject files with a later minor version + EXPECT_FALSE(parseYaml(MS, Diagnostic, + R"( +MultilibVersion: 1.9 +Variants: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("multilib version 1.9 is unsupported")) + << Diagnostic; + + // Accept files with the same major version and the same or earlier minor + // version + EXPECT_TRUE(parseYaml(MS, Diagnostic, R"( +MultilibVersion: 1.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 + Flags: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("paths must be relative")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Flags: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'Dir'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Dir: . +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("missing required key 'Flags'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: [] +FlagMap: +- Regex: abc +)")); + EXPECT_TRUE( + StringRef(Diagnostic) + .contains("value required for 'MatchFlags' or 'NoMatchFlags'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: [] +FlagMap: +- Dir: . + Regex: '(' + Flags: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("parentheses not balanced")) + << Diagnostic; +} + +TEST(MultilibTest, Parse) { + MultilibSet MS; + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: . + Flags: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: abc + Flags: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/abc", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: pqr + Flags: [-mfloat-abi=soft] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/pqr", MS.begin()->gccSuffix()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft"}), + MS.begin()->flags()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: pqr + Flags: [-mfloat-abi=soft, -fno-exceptions] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ(std::vector({"-mfloat-abi=soft", "-fno-exceptions"}), + MS.begin()->flags()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: a + Flags: [] +- Dir: b + Flags: [] +)")); + EXPECT_EQ(2U, MS.size()); +} + +TEST(MultilibTest, SelectSoft) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: s + Flags: [-mfloat-abi=soft] +FlagMap: +- Regex: -mfloat-abi=softfp + MatchFlags: [-mfloat-abi=soft] +)")); + 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 + Flags: [-mfloat-abi=softfp] +)")); + 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 + Flags: [-mfloat-abi=hard] +)")); + 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 + Flags: [-mfloat-abi=soft] +- Dir: f + Flags: [-mfloat-abi=softfp] +- Dir: h + Flags: [-mfloat-abi=hard] +FlagMap: +- Regex: -mfloat-abi=softfp + MatchFlags: [-mfloat-abi=soft] +)")); + 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 + Flags: [-mfloat-abi=hard] +- Dir: f + Flags: [-mfloat-abi=softfp] +- Dir: s + Flags: [-mfloat-abi=soft] +FlagMap: +- Regex: -mfloat-abi=softfp + MatchFlags: [-mfloat-abi=soft] +)")); + 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 + Flags: [--target=thumbv6m-none-unknown-eabi, -mfpu=none] + +- Dir: thumb/v7-m/nofp + Flags: [--target=thumbv7m-none-unknown-eabi, -mfpu=none] + +- Dir: thumb/v7e-m/nofp + Flags: [--target=thumbv7em-none-unknown-eabi, -mfpu=none] + +- Dir: thumb/v8-m.main/nofp + Flags: [--target=thumbv8m.main-none-unknown-eabi, -mfpu=none] + +- Dir: thumb/v8.1-m.main/nofp/nomve + Flags: [--target=thumbv8.1m.main-none-unknown-eabi, -mfpu=none] + +- Dir: thumb/v7e-m/fpv4_sp_d16 + Flags: [--target=thumbv7em-none-unknown-eabihf, -mfpu=fpv4-sp-d16] + +- Dir: thumb/v7e-m/fpv5_d16 + Flags: [--target=thumbv7em-none-unknown-eabihf, -mfpu=fpv5-d16] + +- Dir: thumb/v8-m.main/fp + Flags: [--target=thumbv8m.main-none-unknown-eabihf] + +- Dir: thumb/v8.1-m.main/fp + Flags: [--target=thumbv8.1m.main-none-unknown-eabihf] + +- Dir: thumb/v8.1-m.main/nofp/mve + Flags: [--target=thumbv8.1m.main-none-unknown-eabihf, -march=thumbv8.1m.main+mve] + +FlagMap: +- Regex: --target=thumbv8(\.[0-9]+)?m\.base-none-unknown-eabi + MatchFlags: [--target=thumbv6m-none-unknown-eabi] +- Regex: -target=thumbv8\.[1-9]m\.main-none-unknown-eabi + MatchFlags: [--target=thumbv8.1m.main-none-unknown-eabi] +- Regex: -target=thumbv8\.[1-9]m\.main-none-unknown-eabihf + MatchFlags: [--target=thumbv8.1m.main-none-unknown-eabihf] +- Regex: -march=thumbv8\.[1-9]m\.main.*\+mve($|\+).* + MatchFlags: [-march=thumbv8.1m.main+mve] +)"; + + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, MultilibSpec)); + + ASSERT_TRUE(MS.select({"--target=thumbv6m-none-unknown-eabi", "-mfpu=none"}, + Selected)); + EXPECT_EQ("/thumb/v6-m/nofp", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select({"--target=thumbv7m-none-unknown-eabi", "-mfpu=none"}, + Selected)); + EXPECT_EQ("/thumb/v7-m/nofp", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select({"--target=thumbv7em-none-unknown-eabi", "-mfpu=none"}, + Selected)); + EXPECT_EQ("/thumb/v7e-m/nofp", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select( + {"--target=thumbv8m.main-none-unknown-eabi", "-mfpu=none"}, Selected)); + EXPECT_EQ("/thumb/v8-m.main/nofp", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select( + {"--target=thumbv8.1m.main-none-unknown-eabi", "-mfpu=none"}, Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/nofp/nomve", Selected.gccSuffix()); + + ASSERT_TRUE( + MS.select({"--target=thumbv7em-none-unknown-eabihf", "-mfpu=fpv4-sp-d16"}, + Selected)); + EXPECT_EQ("/thumb/v7e-m/fpv4_sp_d16", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select( + {"--target=thumbv7em-none-unknown-eabihf", "-mfpu=fpv5-d16"}, Selected)); + EXPECT_EQ("/thumb/v7e-m/fpv5_d16", Selected.gccSuffix()); + + ASSERT_TRUE( + MS.select({"--target=thumbv8m.main-none-unknown-eabihf"}, Selected)); + EXPECT_EQ("/thumb/v8-m.main/fp", Selected.gccSuffix()); + + ASSERT_TRUE( + MS.select({"--target=thumbv8.1m.main-none-unknown-eabihf"}, Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/fp", Selected.gccSuffix()); + + ASSERT_TRUE(MS.select({"--target=thumbv8.1m.main-none-unknown-eabihf", + "-mfpu=none", "-march=thumbv8.1m.main+dsp+mve"}, + Selected)); + EXPECT_EQ("/thumb/v8.1-m.main/nofp/mve", Selected.gccSuffix()); +}