Skip to content

Commit 8727982

Browse files
authored
[Driver] Add exclusive-group feature to multilib.yaml. (#69447)
This allows a YAML-based multilib configuration to specify explicitly that a subset of its library directories are alternatives to each other, i.e. at most one of that subset should be selected. So if you have multiple sysroots each including a full set of headers and libraries, you can mark them as members of the same mutually exclusive group, and then you'll be sure that only one of them is selected, even if two or more are compatible with the compile options. This is particularly important in multilib setups including the libc++ headers, where selecting the include directories from two different sysroots can cause an actual build failure. This occurs when including <stdio.h>, for example: libc++'s stdio.h is included first, and will try to use `#include_next` to fetch the underlying libc's version. But if there are two include directories from separate multilibs, then both of their C++ include directories will end up on the include path first, followed by both the C directories. So the `#include_next` from the first libc++ stdio.h will include the second libc++ stdio.h, which will do nothing because it has the same include guard macro, and the libc header won't ever be included at all. If more than one of the options in an exclusive group matches the given flags, the last one wins. The syntax for specifying this in multilib.yaml is to define a Groups section in which you specify your group names, and for each one, declare it to have Type: Exclusive. (This reserves space in the syntax for maybe adding other group types later, such as a group of mutually _dependent_ things that you must have all or none of.) Then each Variant record that's a member of a group has a Group: property giving that group's name.
1 parent 6902082 commit 8727982

File tree

4 files changed

+218
-12
lines changed

4 files changed

+218
-12
lines changed

clang/include/clang/Driver/Multilib.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,22 @@ class Multilib {
3939
std::string IncludeSuffix;
4040
flags_list Flags;
4141

42+
// Optionally, a multilib can be assigned a string tag indicating that it's
43+
// part of a group of mutually exclusive possibilities. If two or more
44+
// multilibs have the same non-empty value of ExclusiveGroup, then only the
45+
// last matching one of them will be selected.
46+
//
47+
// Setting this to the empty string is a special case, indicating that the
48+
// directory is not mutually exclusive with anything else.
49+
std::string ExclusiveGroup;
50+
4251
public:
4352
/// GCCSuffix, OSSuffix & IncludeSuffix will be appended directly to the
4453
/// sysroot string so they must either be empty or begin with a '/' character.
4554
/// This is enforced with an assert in the constructor.
4655
Multilib(StringRef GCCSuffix = {}, StringRef OSSuffix = {},
47-
StringRef IncludeSuffix = {},
48-
const flags_list &Flags = flags_list());
56+
StringRef IncludeSuffix = {}, const flags_list &Flags = flags_list(),
57+
StringRef ExclusiveGroup = {});
4958

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

75+
/// Get the exclusive group label.
76+
const std::string &exclusiveGroup() const { return ExclusiveGroup; }
77+
6678
LLVM_DUMP_METHOD void dump() const;
6779
/// print summary of the Multilib
6880
void print(raw_ostream &OS) const;

clang/lib/Driver/Multilib.cpp

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "clang/Driver/Multilib.h"
1010
#include "clang/Basic/LLVM.h"
1111
#include "clang/Basic/Version.h"
12+
#include "llvm/ADT/DenseSet.h"
1213
#include "llvm/ADT/SmallString.h"
1314
#include "llvm/ADT/StringRef.h"
1415
#include "llvm/Support/Compiler.h"
@@ -29,9 +30,10 @@ using namespace driver;
2930
using namespace llvm::sys;
3031

3132
Multilib::Multilib(StringRef GCCSuffix, StringRef OSSuffix,
32-
StringRef IncludeSuffix, const flags_list &Flags)
33+
StringRef IncludeSuffix, const flags_list &Flags,
34+
StringRef ExclusiveGroup)
3335
: GCCSuffix(GCCSuffix), OSSuffix(OSSuffix), IncludeSuffix(IncludeSuffix),
34-
Flags(Flags) {
36+
Flags(Flags), ExclusiveGroup(ExclusiveGroup) {
3537
assert(GCCSuffix.empty() ||
3638
(StringRef(GCCSuffix).front() == '/' && GCCSuffix.size() > 1));
3739
assert(OSSuffix.empty() ||
@@ -96,13 +98,37 @@ bool MultilibSet::select(const Multilib::flags_list &Flags,
9698
llvm::SmallVector<Multilib> &Selected) const {
9799
llvm::StringSet<> FlagSet(expandFlags(Flags));
98100
Selected.clear();
99-
llvm::copy_if(Multilibs, std::back_inserter(Selected),
100-
[&FlagSet](const Multilib &M) {
101-
for (const std::string &F : M.flags())
102-
if (!FlagSet.contains(F))
103-
return false;
104-
return true;
105-
});
101+
102+
// Decide which multilibs we're going to select at all.
103+
llvm::DenseSet<StringRef> ExclusiveGroupsSelected;
104+
for (const Multilib &M : llvm::reverse(Multilibs)) {
105+
// If this multilib doesn't match all our flags, don't select it.
106+
if (!llvm::all_of(M.flags(), [&FlagSet](const std::string &F) {
107+
return FlagSet.contains(F);
108+
}))
109+
continue;
110+
111+
const std::string &group = M.exclusiveGroup();
112+
if (!group.empty()) {
113+
// If this multilib has the same ExclusiveGroup as one we've already
114+
// selected, skip it. We're iterating in reverse order, so the group
115+
// member we've selected already is preferred.
116+
//
117+
// Otherwise, add the group name to the set of groups we've already
118+
// selected a member of.
119+
auto [It, Inserted] = ExclusiveGroupsSelected.insert(group);
120+
if (!Inserted)
121+
continue;
122+
}
123+
124+
// Select this multilib.
125+
Selected.push_back(M);
126+
}
127+
128+
// We iterated in reverse order, so now put Selected back the right way
129+
// round.
130+
std::reverse(Selected.begin(), Selected.end());
131+
106132
return !Selected.empty();
107133
}
108134

@@ -138,10 +164,39 @@ static const VersionTuple MultilibVersionCurrent(1, 0);
138164
struct MultilibSerialization {
139165
std::string Dir;
140166
std::vector<std::string> Flags;
167+
std::string Group;
168+
};
169+
170+
enum class MultilibGroupType {
171+
/*
172+
* The only group type currently supported is 'Exclusive', which indicates a
173+
* group of multilibs of which at most one may be selected.
174+
*/
175+
Exclusive,
176+
177+
/*
178+
* Future possibility: a second group type indicating a set of library
179+
* directories that are mutually _dependent_ rather than mutually exclusive:
180+
* if you include one you must include them all.
181+
*
182+
* It might also be useful to allow groups to be members of other groups, so
183+
* that a mutually exclusive group could contain a mutually dependent set of
184+
* library directories, or vice versa.
185+
*
186+
* These additional features would need changes in the implementation, but
187+
* the YAML schema is set up so they can be added without requiring changes
188+
* in existing users' multilib.yaml files.
189+
*/
190+
};
191+
192+
struct MultilibGroupSerialization {
193+
std::string Name;
194+
MultilibGroupType Type;
141195
};
142196

143197
struct MultilibSetSerialization {
144198
llvm::VersionTuple MultilibVersion;
199+
std::vector<MultilibGroupSerialization> Groups;
145200
std::vector<MultilibSerialization> Multilibs;
146201
std::vector<MultilibSet::FlagMatcher> FlagMatchers;
147202
};
@@ -152,6 +207,7 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
152207
static void mapping(llvm::yaml::IO &io, MultilibSerialization &V) {
153208
io.mapRequired("Dir", V.Dir);
154209
io.mapRequired("Flags", V.Flags);
210+
io.mapOptional("Group", V.Group);
155211
}
156212
static std::string validate(IO &io, MultilibSerialization &V) {
157213
if (StringRef(V.Dir).starts_with("/"))
@@ -160,6 +216,19 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
160216
}
161217
};
162218

219+
template <> struct llvm::yaml::ScalarEnumerationTraits<MultilibGroupType> {
220+
static void enumeration(IO &io, MultilibGroupType &Val) {
221+
io.enumCase(Val, "Exclusive", MultilibGroupType::Exclusive);
222+
}
223+
};
224+
225+
template <> struct llvm::yaml::MappingTraits<MultilibGroupSerialization> {
226+
static void mapping(llvm::yaml::IO &io, MultilibGroupSerialization &V) {
227+
io.mapRequired("Name", V.Name);
228+
io.mapRequired("Type", V.Type);
229+
}
230+
};
231+
163232
template <> struct llvm::yaml::MappingTraits<MultilibSet::FlagMatcher> {
164233
static void mapping(llvm::yaml::IO &io, MultilibSet::FlagMatcher &M) {
165234
io.mapRequired("Match", M.Match);
@@ -180,6 +249,7 @@ template <> struct llvm::yaml::MappingTraits<MultilibSetSerialization> {
180249
static void mapping(llvm::yaml::IO &io, MultilibSetSerialization &M) {
181250
io.mapRequired("MultilibVersion", M.MultilibVersion);
182251
io.mapRequired("Variants", M.Multilibs);
252+
io.mapOptional("Groups", M.Groups);
183253
io.mapOptional("Mappings", M.FlagMatchers);
184254
}
185255
static std::string validate(IO &io, MultilibSetSerialization &M) {
@@ -191,11 +261,25 @@ template <> struct llvm::yaml::MappingTraits<MultilibSetSerialization> {
191261
if (M.MultilibVersion.getMinor() > MultilibVersionCurrent.getMinor())
192262
return "multilib version " + M.MultilibVersion.getAsString() +
193263
" is unsupported";
264+
for (const MultilibSerialization &Lib : M.Multilibs) {
265+
if (!Lib.Group.empty()) {
266+
bool Found = false;
267+
for (const MultilibGroupSerialization &Group : M.Groups)
268+
if (Group.Name == Lib.Group) {
269+
Found = true;
270+
break;
271+
}
272+
if (!Found)
273+
return "multilib \"" + Lib.Dir +
274+
"\" specifies undefined group name \"" + Lib.Group + "\"";
275+
}
276+
}
194277
return std::string{};
195278
}
196279
};
197280

198281
LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSerialization)
282+
LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibGroupSerialization)
199283
LLVM_YAML_IS_SEQUENCE_VECTOR(MultilibSet::FlagMatcher)
200284

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

220308
return MultilibSet(std::move(Multilibs), std::move(MS.FlagMatchers));
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# UNSUPPORTED: system-windows
2+
3+
# RUN: rm -rf %t
4+
5+
# RUN: mkdir -p %t/baremetal_multilib/bin
6+
# RUN: ln -s %clang %t/baremetal_multilib/bin/clang
7+
8+
# RUN: mkdir -p %t/baremetal_multilib/lib/clang-runtimes
9+
# RUN: ln -s %s %t/baremetal_multilib/lib/clang-runtimes/multilib.yaml
10+
11+
# RUN: %t/baremetal_multilib/bin/clang -no-canonical-prefixes -x c++ %s -### -o %t.out --target=thumbv7em-none-unknown-eabi --sysroot= 2>%t.err
12+
13+
# RUN: FileCheck -DSYSROOT=%t/baremetal_multilib %s < %t.err --check-prefix=POS
14+
# RUN: FileCheck -DSYSROOT=%t/baremetal_multilib %s < %t.err --check-prefix=NEG
15+
16+
# Expected results:
17+
#
18+
# Due to the Mappings section, all six of these library directories should
19+
# match the command-line flag --target=thumbv7em-none-unknown-eabi.
20+
#
21+
# The two "non_exclusive" directories, which don't have an ExclusiveGroup at
22+
# all, should both be selected. So should the two "own_group", each of which
23+
# specifies a different value of ExclusiveGroup. But the three "exclusive",
24+
# which have the _same_ ExclusiveGroup value, should not: the third one wins.
25+
# So we expect five of these seven directories to show up in the clang-cc1
26+
# command line, but not testdir1_exclusive or testdir2_exclusive.
27+
28+
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_non_exclusive/include/c++/v1"
29+
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_non_exclusive/include/c++/v1"
30+
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir3_exclusive/include/c++/v1"
31+
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_own_group/include/c++/v1"
32+
# POS-DAG: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_own_group/include/c++/v1"
33+
34+
# NEG-NOT: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir1_exclusive/include/c++/v1"
35+
# NEG-NOT: "-internal-isystem" "[[SYSROOT]]/bin/../lib/clang-runtimes/testdir2_exclusive/include/c++/v1"
36+
37+
---
38+
MultilibVersion: 1.0
39+
40+
Groups:
41+
- Name: actually_exclude_something
42+
Type: Exclusive
43+
44+
- Name: foo
45+
Type: Exclusive
46+
47+
- Name: bar
48+
Type: Exclusive
49+
50+
Variants:
51+
- Dir: testdir1_non_exclusive
52+
Flags: [--target=thumbv7m-none-unknown-eabi]
53+
54+
- Dir: testdir2_non_exclusive
55+
Flags: [--target=thumbv7em-none-unknown-eabi]
56+
57+
- Dir: testdir1_exclusive
58+
Flags: [--target=thumbv7m-none-unknown-eabi]
59+
Group: actually_exclude_something
60+
61+
- Dir: testdir2_exclusive
62+
Flags: [--target=thumbv7em-none-unknown-eabi]
63+
Group: actually_exclude_something
64+
65+
- Dir: testdir3_exclusive
66+
Flags: [--target=thumbv7em-none-unknown-eabi]
67+
Group: actually_exclude_something
68+
69+
- Dir: testdir1_own_group
70+
Flags: [--target=thumbv7m-none-unknown-eabi]
71+
Group: foo
72+
73+
- Dir: testdir2_own_group
74+
Flags: [--target=thumbv7em-none-unknown-eabi]
75+
Group: bar
76+
77+
Mappings:
78+
- Match: --target=thumbv7em-none-unknown-eabi
79+
Flags: [--target=thumbv7m-none-unknown-eabi]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# UNSUPPORTED: system-windows
2+
3+
# RUN: rm -rf %t
4+
5+
# RUN: mkdir -p %t/baremetal_multilib/bin
6+
# RUN: ln -s %clang %t/baremetal_multilib/bin/clang
7+
8+
# RUN: mkdir -p %t/baremetal_multilib/lib/clang-runtimes
9+
# RUN: ln -s %s %t/baremetal_multilib/lib/clang-runtimes/multilib.yaml
10+
11+
# RUN: %t/baremetal_multilib/bin/clang -no-canonical-prefixes -x c++ %s -### -o %t.out --target=thumbv7em-none-unknown-eabi --sysroot= 2>%t.err
12+
# RUN: FileCheck %s < %t.err
13+
14+
---
15+
MultilibVersion: 1.0
16+
17+
Groups:
18+
- Name: group1
19+
Type: Nonsense
20+
21+
Variants:
22+
- Dir: testdir1
23+
Flags: [--target=thumbv7m-none-unknown-eabi]
24+
Group: nonexistent_group_name
25+
26+
# CHECK: error: unknown enumerated scalar
27+
# CHECK: error: multilib "testdir1" specifies undefined group name "nonexistent_group_name"

0 commit comments

Comments
 (0)