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 @@ -71,9 +73,10 @@ const std::string &includeSuffix() const { return IncludeSuffix; } /// Get the set of flags that indicate this multilib's use. - /// Flags are arbitrary strings although typically they will look similar to - /// command line options. A multilib is considered compatible if its flags are - /// a subset of the flags derived from the Clang command line options. + /// Flags 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 flags + /// are a subset of the flags derived from the Clang command line options. const flags_list &flags() const { return Flags; } /// Returns the options that should be used for clang -print-multi-lib @@ -101,8 +104,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; @@ -129,6 +143,16 @@ 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; + + 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,18 +8,19 @@ #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/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 -#include using namespace clang; using namespace driver; @@ -116,10 +117,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) { @@ -140,6 +138,126 @@ 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, PrintOptions; +}; + +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); + 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::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) + +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::flags_list(M.Flags.begin(), M.Flags.end()), + Multilib::PrintOptionsType::List, M.PrintOptions); + } + 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 @@ -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" @@ -203,3 +204,392 @@ Multilib({}, {}, {}, {"+x"}, Multilib::PrintOptionsType::List, {"-y"}) .getPrintOptions()); } + +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, const char *Data) { + return MS.parseYaml(llvm::MemoryBufferRef(Data, "TEST")); +} + +// 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: [] + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("paths must be relative")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Flags: [] + 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 'Flags'")) + << Diagnostic; + + EXPECT_FALSE(parseYaml(MS, Diagnostic, YAML_PREAMBLE R"( +Variants: +- Dir: . + Flags: [] +)")); + EXPECT_TRUE( + StringRef(Diagnostic).contains("missing required key 'PrintOptions'")) + << 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: '(' + PrintOptions: [] +)")); + EXPECT_TRUE(StringRef(Diagnostic).contains("parentheses not balanced")) + << Diagnostic; +} + +TEST(MultilibTest, Parse) { + MultilibSet MS; + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: . + Flags: [] + PrintOptions: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: abc + Flags: [] + PrintOptions: [] +)")); + EXPECT_EQ(1U, MS.size()); + EXPECT_EQ("/abc", MS.begin()->gccSuffix()); + + EXPECT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: pqr + Flags: [] + 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 + Flags: [] + 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 + Flags: [] + PrintOptions: [] +- Dir: b + Flags: [] + PrintOptions: [] +)")); + EXPECT_EQ(2U, MS.size()); +} + +TEST(MultilibTest, SelectSoft) { + MultilibSet MS; + Multilib Selected; + ASSERT_TRUE(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: s + Flags: [softabi] + PrintOptions: [] +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(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: f + Flags: [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 + Flags: [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 + Flags: [softabi] + PrintOptions: [] +- Dir: f + Flags: [softabi, hasfp] + PrintOptions: [] +- Dir: h + Flags: [hardabi, hasfp] + PrintOptions: [] +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(parseYaml(MS, YAML_PREAMBLE R"( +Variants: +- Dir: h + Flags: [hardabi, hasfp] + PrintOptions: [] +- Dir: f + Flags: [softabi, hasfp] + PrintOptions: [] +- Dir: s + Flags: [softabi] + PrintOptions: [] +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 = YAML_PREAMBLE R"( +Variants: +- Dir: thumb/v6-m/nofp + Flags: [target=thumbv6m-none-eabi] + PrintOptions: [--target=thumbv6m-none-eabi, -mfloat-abi=soft] + +- Dir: thumb/v7-m/nofp + Flags: [target=thumbv7m-none-eabi] + PrintOptions: [--target=thumbv7m-none-eabi, -mfloat-abi=soft] + +- Dir: thumb/v7e-m/nofp + Flags: [target=thumbv7em-none-eabi] + PrintOptions: [--target=thumbv7em-none-eabi, -mfloat-abi=soft, -mfpu=none] + +- Dir: thumb/v8-m.main/nofp + Flags: [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 + Flags: [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 + Flags: [target=thumbv7em-none-eabihf, mfpu=fpv4-sp-d16] + PrintOptions: [--target=thumbv7em-none-eabihf, -mfpu=fpv4-sp-d16] + +- Dir: thumb/v7e-m/fpv5_d16 + Flags: [target=thumbv7em-none-eabihf, mfpu=fpv5-d16] + PrintOptions: [--target=thumbv7em-none-eabihf, -mfpu=fpv5-d16] + +- Dir: thumb/v8-m.main/fp + Flags: [target=thumbv8m.main-none-eabihf] + PrintOptions: [--target=thumbv8m.main-none-eabihf] + +- Dir: thumb/v8.1-m.main/fp + Flags: [target=thumbv8.1m.main-none-eabihf] + PrintOptions: [--target=thumbv8.1m.main-none-eabihf] + +- Dir: thumb/v8.1-m.main/nofp/mve + Flags: [target=thumbv8.1m.main-none-eabihf, march=+mve] + PrintOptions: [--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(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()); +}