Skip to content

[Driver] Add ExclusiveGroup feature to multilib.yaml. #69447

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions clang/include/clang/Driver/Multilib.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,22 @@ class Multilib {
std::string IncludeSuffix;
flags_list Flags;

// Optionally, a multilib can be assigned a string tag indicating that it's
// part of a group of mutually exclusive possibilities. If two or more
// multilibs have the same non-empty value of ExclusiveGroup, then only the
// last matching one of them will be selected.
//
// Setting this to the empty string is a special case, indicating that the
// directory is not mutually exclusive with anything else.
std::string ExclusiveGroup;

public:
/// GCCSuffix, OSSuffix & IncludeSuffix will be appended directly to the
/// sysroot string so they must either be empty or begin with a '/' character.
/// This is enforced with an assert in the constructor.
Multilib(StringRef GCCSuffix = {}, StringRef OSSuffix = {},
StringRef IncludeSuffix = {},
const flags_list &Flags = flags_list());
StringRef IncludeSuffix = {}, const flags_list &Flags = flags_list(),
StringRef ExclusiveGroup = {});

/// Get the detected GCC installation path suffix for the multi-arch
/// target variant. Always starts with a '/', unless empty
Expand All @@ -63,6 +72,9 @@ class Multilib {
/// All elements begin with either '-' or '!'
const flags_list &flags() const { return Flags; }

/// Get the exclusive group label.
const std::string &exclusiveGroup() const { return ExclusiveGroup; }

LLVM_DUMP_METHOD void dump() const;
/// print summary of the Multilib
void print(raw_ostream &OS) const;
Expand Down
108 changes: 98 additions & 10 deletions clang/lib/Driver/Multilib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "clang/Driver/Multilib.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Version.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
Expand All @@ -29,9 +30,10 @@ using namespace driver;
using namespace llvm::sys;

Multilib::Multilib(StringRef GCCSuffix, StringRef OSSuffix,
StringRef IncludeSuffix, const flags_list &Flags)
StringRef IncludeSuffix, const flags_list &Flags,
StringRef ExclusiveGroup)
: GCCSuffix(GCCSuffix), OSSuffix(OSSuffix), IncludeSuffix(IncludeSuffix),
Flags(Flags) {
Flags(Flags), ExclusiveGroup(ExclusiveGroup) {
assert(GCCSuffix.empty() ||
(StringRef(GCCSuffix).front() == '/' && GCCSuffix.size() > 1));
assert(OSSuffix.empty() ||
Expand Down Expand Up @@ -96,13 +98,37 @@ bool MultilibSet::select(const Multilib::flags_list &Flags,
llvm::SmallVector<Multilib> &Selected) const {
llvm::StringSet<> FlagSet(expandFlags(Flags));
Selected.clear();
llvm::copy_if(Multilibs, std::back_inserter(Selected),
[&FlagSet](const Multilib &M) {
for (const std::string &F : M.flags())
if (!FlagSet.contains(F))
return false;
return true;
});

// Decide which multilibs we're going to select at all.
llvm::DenseSet<StringRef> ExclusiveGroupsSelected;
for (const Multilib &M : llvm::reverse(Multilibs)) {
// If this multilib doesn't match all our flags, don't select it.
if (!llvm::all_of(M.flags(), [&FlagSet](const std::string &F) {
return FlagSet.contains(F);
}))
continue;

const std::string &group = M.exclusiveGroup();
if (!group.empty()) {
// If this multilib has the same ExclusiveGroup as one we've already
// selected, skip it. We're iterating in reverse order, so the group
// member we've selected already is preferred.
//
// Otherwise, add the group name to the set of groups we've already
// selected a member of.
auto [It, Inserted] = ExclusiveGroupsSelected.insert(group);
if (!Inserted)
continue;
}

// Select this multilib.
Selected.push_back(M);
}

// We iterated in reverse order, so now put Selected back the right way
// round.
std::reverse(Selected.begin(), Selected.end());

return !Selected.empty();
}

Expand Down Expand Up @@ -138,10 +164,39 @@ static const VersionTuple MultilibVersionCurrent(1, 0);
struct MultilibSerialization {
std::string Dir;
std::vector<std::string> Flags;
std::string Group;
};

enum class MultilibGroupType {
/*
* The only group type currently supported is 'Exclusive', which indicates a
* group of multilibs of which at most one may be selected.
*/
Exclusive,

/*
* Future possibility: a second group type indicating a set of library
* directories that are mutually _dependent_ rather than mutually exclusive:
* if you include one you must include them all.
*
* It might also be useful to allow groups to be members of other groups, so
* that a mutually exclusive group could contain a mutually dependent set of
* library directories, or vice versa.
*
* These additional features would need changes in the implementation, but
* the YAML schema is set up so they can be added without requiring changes
* in existing users' multilib.yaml files.
*/
};

struct MultilibGroupSerialization {
std::string Name;
MultilibGroupType Type;
};

struct MultilibSetSerialization {
llvm::VersionTuple MultilibVersion;
std::vector<MultilibGroupSerialization> Groups;
std::vector<MultilibSerialization> Multilibs;
std::vector<MultilibSet::FlagMatcher> FlagMatchers;
};
Expand All @@ -152,6 +207,7 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
static void mapping(llvm::yaml::IO &io, MultilibSerialization &V) {
io.mapRequired("Dir", V.Dir);
io.mapRequired("Flags", V.Flags);
io.mapOptional("Group", V.Group);
}
static std::string validate(IO &io, MultilibSerialization &V) {
if (StringRef(V.Dir).starts_with("/"))
Expand All @@ -160,6 +216,19 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
}
};

template <> struct llvm::yaml::ScalarEnumerationTraits<MultilibGroupType> {
static void enumeration(IO &io, MultilibGroupType &Val) {
io.enumCase(Val, "Exclusive", MultilibGroupType::Exclusive);
}
};

template <> struct llvm::yaml::MappingTraits<MultilibGroupSerialization> {
static void mapping(llvm::yaml::IO &io, MultilibGroupSerialization &V) {
io.mapRequired("Name", V.Name);
io.mapRequired("Type", V.Type);
}
};

template <> struct llvm::yaml::MappingTraits<MultilibSet::FlagMatcher> {
static void mapping(llvm::yaml::IO &io, MultilibSet::FlagMatcher &M) {
io.mapRequired("Match", M.Match);
Expand All @@ -180,6 +249,7 @@ template <> struct llvm::yaml::MappingTraits<MultilibSetSerialization> {
static void mapping(llvm::yaml::IO &io, MultilibSetSerialization &M) {
io.mapRequired("MultilibVersion", M.MultilibVersion);
io.mapRequired("Variants", M.Multilibs);
io.mapOptional("Groups", M.Groups);
io.mapOptional("Mappings", M.FlagMatchers);
}
static std::string validate(IO &io, MultilibSetSerialization &M) {
Expand All @@ -191,11 +261,25 @@ template <> struct llvm::yaml::MappingTraits<MultilibSetSerialization> {
if (M.MultilibVersion.getMinor() > MultilibVersionCurrent.getMinor())
return "multilib version " + M.MultilibVersion.getAsString() +
" is unsupported";
for (const MultilibSerialization &Lib : M.Multilibs) {
if (!Lib.Group.empty()) {
bool Found = false;
for (const MultilibGroupSerialization &Group : M.Groups)
if (Group.Name == Lib.Group) {
Found = true;
break;
}
if (!Found)
return "multilib \"" + Lib.Dir +
"\" specifies undefined group name \"" + Lib.Group + "\"";
}
}
return std::string{};
}
};

LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSerialization)
LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibGroupSerialization)
LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSet::FlagMatcher)

llvm::ErrorOr<MultilibSet>
Expand All @@ -214,7 +298,11 @@ MultilibSet::parseYaml(llvm::MemoryBufferRef Input,
std::string Dir;
if (M.Dir != ".")
Dir = "/" + M.Dir;
Multilibs.emplace_back(Dir, Dir, Dir, M.Flags);
// We transfer M.Group straight into the ExclusiveGroup parameter for the
// Multilib constructor. If we later support more than one type of group,
// we'll have to look up the group name in MS.Groups, check its type, and
// decide what to do here.
Multilibs.emplace_back(Dir, Dir, Dir, M.Flags, M.Group);
}

return MultilibSet(std::move(Multilibs), std::move(MS.FlagMatchers));
Expand Down
79 changes: 79 additions & 0 deletions clang/test/Driver/baremetal-multilib-exclusive-group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# UNSUPPORTED: system-windows

# RUN: rm -rf %t

# RUN: mkdir -p %t/baremetal_multilib/bin
# RUN: ln -s %clang %t/baremetal_multilib/bin/clang

# RUN: mkdir -p %t/baremetal_multilib/lib/clang-runtimes
# RUN: ln -s %s %t/baremetal_multilib/lib/clang-runtimes/multilib.yaml

# RUN: %t/baremetal_multilib/bin/clang -no-canonical-prefixes -x c++ %s -### -o %t.out --target=thumbv7em-none-unknown-eabi --sysroot= 2>%t.err

# RUN: FileCheck -DSYSROOT=%t/baremetal_multilib %s < %t.err --check-prefix=POS
# RUN: FileCheck -DSYSROOT=%t/baremetal_multilib %s < %t.err --check-prefix=NEG

# Expected results:
#
# Due to the Mappings section, all six of these library directories should
# match the command-line flag --target=thumbv7em-none-unknown-eabi.
#
# The two "non_exclusive" directories, which don't have an ExclusiveGroup at
# all, should both be selected. So should the two "own_group", each of which
# specifies a different value of ExclusiveGroup. But the three "exclusive",
# which have the _same_ ExclusiveGroup value, should not: the third one wins.
# So we expect five of these seven directories to show up in the clang-cc1
# command line, but not testdir1_exclusive or testdir2_exclusive.

# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_non_exclusive/include/c++/v1"
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_non_exclusive/include/c++/v1"
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir3_exclusive/include/c++/v1"
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_own_group/include/c++/v1"
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_own_group/include/c++/v1"

# NEG-NOT: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_exclusive/include/c++/v1"
# NEG-NOT: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_exclusive/include/c++/v1"

---
MultilibVersion: 1.0

Groups:
- Name: actually_exclude_something
Type: Exclusive

- Name: foo
Type: Exclusive

- Name: bar
Type: Exclusive

Variants:
- Dir: testdir1_non_exclusive
Flags: [--target=thumbv7m-none-unknown-eabi]

- Dir: testdir2_non_exclusive
Flags: [--target=thumbv7em-none-unknown-eabi]

- Dir: testdir1_exclusive
Flags: [--target=thumbv7m-none-unknown-eabi]
Group: actually_exclude_something

- Dir: testdir2_exclusive
Flags: [--target=thumbv7em-none-unknown-eabi]
Group: actually_exclude_something

- Dir: testdir3_exclusive
Flags: [--target=thumbv7em-none-unknown-eabi]
Group: actually_exclude_something

- Dir: testdir1_own_group
Flags: [--target=thumbv7m-none-unknown-eabi]
Group: foo

- Dir: testdir2_own_group
Flags: [--target=thumbv7em-none-unknown-eabi]
Group: bar

Mappings:
- Match: --target=thumbv7em-none-unknown-eabi
Flags: [--target=thumbv7m-none-unknown-eabi]
27 changes: 27 additions & 0 deletions clang/test/Driver/baremetal-multilib-group-error.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# UNSUPPORTED: system-windows

# RUN: rm -rf %t

# RUN: mkdir -p %t/baremetal_multilib/bin
# RUN: ln -s %clang %t/baremetal_multilib/bin/clang

# RUN: mkdir -p %t/baremetal_multilib/lib/clang-runtimes
# RUN: ln -s %s %t/baremetal_multilib/lib/clang-runtimes/multilib.yaml

# RUN: %t/baremetal_multilib/bin/clang -no-canonical-prefixes -x c++ %s -### -o %t.out --target=thumbv7em-none-unknown-eabi --sysroot= 2>%t.err
# RUN: FileCheck %s < %t.err

---
MultilibVersion: 1.0

Groups:
- Name: group1
Type: Nonsense

Variants:
- Dir: testdir1
Flags: [--target=thumbv7m-none-unknown-eabi]
Group: nonexistent_group_name

# CHECK: error: unknown enumerated scalar
# CHECK: error: multilib "testdir1" specifies undefined group name "nonexistent_group_name"