Skip to content

Commit b2de258

Browse files
committed
[Multilib] Custom flags processing for library selection
Select library variants in the multilib system using the flags passed following the '-fmultilib-flag=' format. Multilib flags that were not passed in the command-line have their default value fed into the library selection mechanism. A warning is shown if the flag's value name is invalid. If the wrong name is close enough to any valid one, according to edit distance, the closest valid value name is suggested. Details about this change can be found in this thread: https://discourse.llvm.org/t/rfc-multilib-custom-flags/81058
1 parent 7b8f0d9 commit b2de258

File tree

8 files changed

+329
-75
lines changed

8 files changed

+329
-75
lines changed

clang/include/clang/Driver/Driver.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ class Driver {
463463
/// ArgList.
464464
llvm::opt::InputArgList ParseArgStrings(ArrayRef<const char *> Args,
465465
bool UseDriverMode,
466-
bool &ContainsError);
466+
bool &ContainsError) const;
467467

468468
/// BuildInputs - Construct the list of inputs and their types from
469469
/// the given arguments.

clang/include/clang/Driver/Multilib.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,18 @@ class MultilibSet {
163163
const_iterator begin() const { return Multilibs.begin(); }
164164
const_iterator end() const { return Multilibs.end(); }
165165

166+
/// Process custom flags from \p Flags and returns an expanded flags list and
167+
/// a list of extra compilation arguments.
168+
/// Returns a pair where:
169+
/// - first: the new flags list including custom flags after processing.
170+
/// - second: the extra compilation arguments to be fed to the driver.
171+
std::pair<Multilib::flags_list, SmallVector<StringRef>>
172+
processCustomFlags(const Driver &D, const Multilib::flags_list &Flags) const;
173+
166174
/// Select compatible variants, \returns false if none are compatible
167175
bool select(const Driver &D, const Multilib::flags_list &Flags,
168-
llvm::SmallVectorImpl<Multilib> &) const;
176+
llvm::SmallVectorImpl<Multilib> &,
177+
llvm::SmallVector<StringRef> * = nullptr) const;
169178

170179
unsigned size() const { return Multilibs.size(); }
171180

clang/include/clang/Driver/ToolChain.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,13 @@ class ToolChain {
686686
/// Add warning options that need to be passed to cc1 for this target.
687687
virtual void addClangWarningOptions(llvm::opt::ArgStringList &CC1Args) const;
688688

689+
// Get the list of extra driver arguments strings requested by the multilib
690+
// configuration.
691+
virtual SmallVector<std::string>
692+
getMultilibDriverArgsStr(llvm::opt::ArgList &Args) const {
693+
return {};
694+
};
695+
689696
// GetRuntimeLibType - Determine the runtime library type to use with the
690697
// given compilation arguments.
691698
virtual RuntimeLibType

clang/lib/Driver/Driver.cpp

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ void Driver::setDriverMode(StringRef Value) {
262262
}
263263

264264
InputArgList Driver::ParseArgStrings(ArrayRef<const char *> ArgStrings,
265-
bool UseDriverMode, bool &ContainsError) {
265+
bool UseDriverMode, bool &ContainsError) const {
266266
llvm::PrettyStackTraceString CrashInfo("Command line argument parsing");
267267
ContainsError = false;
268268

@@ -1252,8 +1252,9 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
12521252
bool HasConfigFile = !ContainsError && (CfgOptions.get() != nullptr);
12531253

12541254
// All arguments, from both config file and command line.
1255-
InputArgList Args = std::move(HasConfigFile ? std::move(*CfgOptions)
1256-
: std::move(*CLOptions));
1255+
auto UArgs = std::make_unique<InputArgList>(std::move(HasConfigFile ? std::move(*CfgOptions)
1256+
: std::move(*CLOptions)));
1257+
InputArgList &Args = *UArgs;
12571258

12581259
if (HasConfigFile)
12591260
for (auto *Opt : *CLOptions) {
@@ -1287,52 +1288,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
12871288
}
12881289
}
12891290

1290-
// Check for working directory option before accessing any files
1291-
if (Arg *WD = Args.getLastArg(options::OPT_working_directory))
1292-
if (VFS->setCurrentWorkingDirectory(WD->getValue()))
1293-
Diag(diag::err_drv_unable_to_set_working_directory) << WD->getValue();
1294-
1295-
// Check for missing include directories.
1296-
if (!Diags.isIgnored(diag::warn_missing_include_dirs, SourceLocation())) {
1297-
for (auto IncludeDir : Args.getAllArgValues(options::OPT_I_Group)) {
1298-
if (!VFS->exists(IncludeDir))
1299-
Diag(diag::warn_missing_include_dirs) << IncludeDir;
1300-
}
1301-
}
1302-
1303-
// FIXME: This stuff needs to go into the Compilation, not the driver.
1304-
bool CCCPrintPhases;
1305-
1306-
// -canonical-prefixes, -no-canonical-prefixes are used very early in main.
1307-
Args.ClaimAllArgs(options::OPT_canonical_prefixes);
1308-
Args.ClaimAllArgs(options::OPT_no_canonical_prefixes);
1309-
1310-
// f(no-)integated-cc1 is also used very early in main.
1311-
Args.ClaimAllArgs(options::OPT_fintegrated_cc1);
1312-
Args.ClaimAllArgs(options::OPT_fno_integrated_cc1);
1313-
1314-
// Ignore -pipe.
1315-
Args.ClaimAllArgs(options::OPT_pipe);
1316-
1317-
// Extract -ccc args.
1318-
//
1319-
// FIXME: We need to figure out where this behavior should live. Most of it
1320-
// should be outside in the client; the parts that aren't should have proper
1321-
// options, either by introducing new ones or by overloading gcc ones like -V
1322-
// or -b.
1323-
CCCPrintPhases = Args.hasArg(options::OPT_ccc_print_phases);
1324-
CCCPrintBindings = Args.hasArg(options::OPT_ccc_print_bindings);
1325-
if (const Arg *A = Args.getLastArg(options::OPT_ccc_gcc_name))
1326-
CCCGenericGCCName = A->getValue();
1327-
1328-
// Process -fproc-stat-report options.
1329-
if (const Arg *A = Args.getLastArg(options::OPT_fproc_stat_report_EQ)) {
1330-
CCPrintProcessStats = true;
1331-
CCPrintStatReportFilename = A->getValue();
1332-
}
1333-
if (Args.hasArg(options::OPT_fproc_stat_report))
1334-
CCPrintProcessStats = true;
1335-
13361291
// FIXME: TargetTriple is used by the target-prefixed calls to as/ld
13371292
// and getToolChain is const.
13381293
if (IsCLMode()) {
@@ -1385,6 +1340,79 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
13851340
TargetTriple = A->getValue();
13861341
if (const Arg *A = Args.getLastArg(options::OPT_ccc_install_dir))
13871342
Dir = Dir = A->getValue();
1343+
if (const Arg *A = Args.getLastArg(options::OPT__sysroot_EQ))
1344+
SysRoot = A->getValue();
1345+
if (const Arg *A = Args.getLastArg(options::OPT_resource_dir))
1346+
ResourceDir = A->getValue();
1347+
if (const Arg *A = Args.getLastArg(options::OPT__dyld_prefix_EQ))
1348+
DyldPrefix = A->getValue();
1349+
1350+
setLTOMode(Args);
1351+
1352+
// Owned by the host.
1353+
const ToolChain &TC =
1354+
getToolChain(Args, computeTargetTriple(*this, TargetTriple, Args));
1355+
1356+
SmallVector<std::string> MultilibDriverArgsStr =
1357+
TC.getMultilibDriverArgsStr(Args);
1358+
SmallVector<const char *> MLArgsChar(
1359+
llvm::map_range(MultilibDriverArgsStr, [&Args](const auto &S) {
1360+
return Args.MakeArgString(S);
1361+
}));
1362+
bool MLContainsError;
1363+
auto MultilibDriverArgList = std::make_unique<InputArgList>(
1364+
ParseArgStrings(MLArgsChar, /*UseDriverMode=*/false, MLContainsError));
1365+
if (!MLContainsError)
1366+
for (auto *Opt : *MultilibDriverArgList) {
1367+
appendOneArg(Args, Opt, nullptr);
1368+
}
1369+
1370+
// Check for working directory option before accessing any files
1371+
if (Arg *WD = Args.getLastArg(options::OPT_working_directory))
1372+
if (VFS->setCurrentWorkingDirectory(WD->getValue()))
1373+
Diag(diag::err_drv_unable_to_set_working_directory) << WD->getValue();
1374+
1375+
// Check for missing include directories.
1376+
if (!Diags.isIgnored(diag::warn_missing_include_dirs, SourceLocation())) {
1377+
for (auto IncludeDir : Args.getAllArgValues(options::OPT_I_Group)) {
1378+
if (!VFS->exists(IncludeDir))
1379+
Diag(diag::warn_missing_include_dirs) << IncludeDir;
1380+
}
1381+
}
1382+
1383+
// FIXME: This stuff needs to go into the Compilation, not the driver.
1384+
bool CCCPrintPhases;
1385+
1386+
// -canonical-prefixes, -no-canonical-prefixes are used very early in main.
1387+
Args.ClaimAllArgs(options::OPT_canonical_prefixes);
1388+
Args.ClaimAllArgs(options::OPT_no_canonical_prefixes);
1389+
1390+
// f(no-)integated-cc1 is also used very early in main.
1391+
Args.ClaimAllArgs(options::OPT_fintegrated_cc1);
1392+
Args.ClaimAllArgs(options::OPT_fno_integrated_cc1);
1393+
1394+
// Ignore -pipe.
1395+
Args.ClaimAllArgs(options::OPT_pipe);
1396+
1397+
// Extract -ccc args.
1398+
//
1399+
// FIXME: We need to figure out where this behavior should live. Most of it
1400+
// should be outside in the client; the parts that aren't should have proper
1401+
// options, either by introducing new ones or by overloading gcc ones like -V
1402+
// or -b.
1403+
CCCPrintPhases = Args.hasArg(options::OPT_ccc_print_phases);
1404+
CCCPrintBindings = Args.hasArg(options::OPT_ccc_print_bindings);
1405+
if (const Arg *A = Args.getLastArg(options::OPT_ccc_gcc_name))
1406+
CCCGenericGCCName = A->getValue();
1407+
1408+
// Process -fproc-stat-report options.
1409+
if (const Arg *A = Args.getLastArg(options::OPT_fproc_stat_report_EQ)) {
1410+
CCPrintProcessStats = true;
1411+
CCPrintStatReportFilename = A->getValue();
1412+
}
1413+
if (Args.hasArg(options::OPT_fproc_stat_report))
1414+
CCPrintProcessStats = true;
1415+
13881416
for (const Arg *A : Args.filtered(options::OPT_B)) {
13891417
A->claim();
13901418
PrefixDirs.push_back(A->getValue(0));
@@ -1399,13 +1427,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
13991427
CompilerPath = Split.second;
14001428
}
14011429
}
1402-
if (const Arg *A = Args.getLastArg(options::OPT__sysroot_EQ))
1403-
SysRoot = A->getValue();
1404-
if (const Arg *A = Args.getLastArg(options::OPT__dyld_prefix_EQ))
1405-
DyldPrefix = A->getValue();
1406-
1407-
if (const Arg *A = Args.getLastArg(options::OPT_resource_dir))
1408-
ResourceDir = A->getValue();
14091430

14101431
if (const Arg *A = Args.getLastArg(options::OPT_save_temps_EQ)) {
14111432
SaveTemps = llvm::StringSwitch<SaveTempsMode>(A->getValue())
@@ -1425,8 +1446,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
14251446
Offload = OffloadHostDevice;
14261447
}
14271448

1428-
setLTOMode(Args);
1429-
14301449
// Process -fembed-bitcode= flags.
14311450
if (Arg *A = Args.getLastArg(options::OPT_fembed_bitcode_EQ)) {
14321451
StringRef Name = A->getValue();
@@ -1480,16 +1499,9 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
14801499
}
14811500
}
14821501

1483-
std::unique_ptr<llvm::opt::InputArgList> UArgs =
1484-
std::make_unique<InputArgList>(std::move(Args));
1485-
14861502
// Perform the default argument translations.
14871503
DerivedArgList *TranslatedArgs = TranslateInputArgs(*UArgs);
14881504

1489-
// Owned by the host.
1490-
const ToolChain &TC = getToolChain(
1491-
*UArgs, computeTargetTriple(*this, TargetTriple, *UArgs));
1492-
14931505
// Check if the environment version is valid except wasm case.
14941506
llvm::Triple Triple = TC.getTriple();
14951507
if (!Triple.isWasm()) {

clang/lib/Driver/Multilib.cpp

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "clang/Driver/Driver.h"
1212
#include "llvm/ADT/DenseSet.h"
1313
#include "llvm/ADT/SmallSet.h"
14+
#include "llvm/ADT/SmallString.h"
1415
#include "llvm/ADT/StringRef.h"
1516
#include "llvm/Support/Compiler.h"
1617
#include "llvm/Support/ErrorHandling.h"
@@ -92,12 +93,141 @@ MultilibSet &MultilibSet::FilterOut(FilterCallback F) {
9293

9394
void MultilibSet::push_back(const Multilib &M) { Multilibs.push_back(M); }
9495

95-
bool MultilibSet::select(const Driver &D, const Multilib::flags_list &Flags,
96-
llvm::SmallVectorImpl<Multilib> &Selected) const {
97-
llvm::StringSet<> FlagSet(expandFlags(Flags));
96+
static void DiagnoseUnclaimedMultilibCustomFlags(
97+
const Driver &D, const SmallVector<StringRef> &UnclaimedCustomFlagValues,
98+
const SmallVector<custom_flag::DeclarationPtr> &CustomFlagDecls) {
99+
struct EditDistanceInfo {
100+
StringRef FlagValue;
101+
unsigned EditDistance;
102+
};
103+
const unsigned MaxEditDistance = 5;
104+
105+
for (StringRef Unclaimed : UnclaimedCustomFlagValues) {
106+
std::optional<EditDistanceInfo> BestCandidate;
107+
for (const auto &Decl : CustomFlagDecls) {
108+
for (const auto &Value : Decl->ValueList) {
109+
const std::string &FlagValueName = Value.Name;
110+
unsigned EditDistance =
111+
Unclaimed.edit_distance(FlagValueName, /*AllowReplacements=*/true,
112+
/*MaxEditDistance=*/MaxEditDistance);
113+
if (!BestCandidate || (EditDistance <= MaxEditDistance &&
114+
EditDistance < BestCandidate->EditDistance)) {
115+
BestCandidate = {FlagValueName, EditDistance};
116+
}
117+
}
118+
}
119+
if (!BestCandidate)
120+
D.Diag(clang::diag::err_drv_unsupported_opt)
121+
<< (custom_flag::Prefix + Unclaimed).str();
122+
else
123+
D.Diag(clang::diag::err_drv_unsupported_opt_with_suggestion)
124+
<< (custom_flag::Prefix + Unclaimed).str()
125+
<< (custom_flag::Prefix + BestCandidate->FlagValue).str();
126+
}
127+
}
128+
129+
namespace clang::driver::custom_flag {
130+
// Map implemented using linear searches as the expected size is too small for
131+
// the overhead of a search tree or a hash table.
132+
class ValueNameToDetailMap {
133+
SmallVector<std::pair<StringRef, const ValueDetail *>> Mapping;
134+
135+
public:
136+
template <typename It>
137+
ValueNameToDetailMap(It FlagDeclsBegin, It FlagDeclsEnd) {
138+
for (auto DeclIt = FlagDeclsBegin; DeclIt != FlagDeclsEnd; ++DeclIt) {
139+
const DeclarationPtr &Decl = *DeclIt;
140+
for (const auto &Value : Decl->ValueList)
141+
Mapping.emplace_back(Value.Name, &Value);
142+
}
143+
}
144+
145+
const ValueDetail *get(StringRef Key) const {
146+
auto Iter = llvm::find_if(
147+
Mapping, [&](const auto &Pair) { return Pair.first == Key; });
148+
return Iter != Mapping.end() ? Iter->second : nullptr;
149+
}
150+
};
151+
} // namespace clang::driver::custom_flag
152+
153+
std::pair<Multilib::flags_list, SmallVector<StringRef>>
154+
MultilibSet::processCustomFlags(const Driver &D,
155+
const Multilib::flags_list &Flags) const {
156+
Multilib::flags_list Result;
157+
SmallVector<StringRef> CompilationArgs;
158+
159+
// Custom flag values detected in the flags list
160+
SmallVector<const custom_flag::ValueDetail *> ClaimedCustomFlagValues;
161+
162+
// Arguments to -fmultilib-flag=<arg> that don't correspond to any valid
163+
// custom flag value. An error will be printed out for each of these.
164+
SmallVector<StringRef> UnclaimedCustomFlagValueStrs;
165+
166+
const auto ValueNameToValueDetail = custom_flag::ValueNameToDetailMap(
167+
CustomFlagDecls.begin(), CustomFlagDecls.end());
168+
169+
for (StringRef Flag : Flags) {
170+
if (!Flag.starts_with(custom_flag::Prefix)) {
171+
Result.push_back(Flag.str());
172+
continue;
173+
}
174+
175+
StringRef CustomFlagValueStr = Flag.substr(custom_flag::Prefix.size());
176+
const custom_flag::ValueDetail *Detail =
177+
ValueNameToValueDetail.get(CustomFlagValueStr);
178+
if (Detail)
179+
ClaimedCustomFlagValues.push_back(Detail);
180+
else
181+
UnclaimedCustomFlagValueStrs.push_back(CustomFlagValueStr);
182+
}
183+
184+
// Set of custom flag declarations for which a value was passed in the flags
185+
// list. This is used to, firstly, detect multiple values for the same flag
186+
// declaration (in this case, the last one wins), and secondly, to detect
187+
// which declarations had no value passed in (in this case, the default value
188+
// is selected).
189+
llvm::SmallSet<custom_flag::DeclarationPtr, 32> TriggeredCustomFlagDecls;
190+
191+
// Detect multiple values for the same flag declaration. Last one wins.
192+
for (auto *CustomFlagValue : llvm::reverse(ClaimedCustomFlagValues)) {
193+
if (!TriggeredCustomFlagDecls.insert(CustomFlagValue->Decl).second)
194+
continue;
195+
Result.push_back(std::string(custom_flag::Prefix) + CustomFlagValue->Name);
196+
if (CustomFlagValue->DriverArgs)
197+
CompilationArgs.append(CustomFlagValue->DriverArgs->begin(),
198+
CustomFlagValue->DriverArgs->end());
199+
}
200+
201+
// Detect flag declarations with no value passed in. Select default value.
202+
for (const auto &Decl : CustomFlagDecls) {
203+
if (TriggeredCustomFlagDecls.contains(Decl))
204+
continue;
205+
custom_flag::ValueDetail &CustomFlagValue =
206+
Decl->ValueList[*Decl->DefaultValueIdx];
207+
Result.push_back(std::string(custom_flag::Prefix) + CustomFlagValue.Name);
208+
if (CustomFlagValue.DriverArgs)
209+
CompilationArgs.append(CustomFlagValue.DriverArgs->begin(),
210+
CustomFlagValue.DriverArgs->end());
211+
}
212+
213+
DiagnoseUnclaimedMultilibCustomFlags(D, UnclaimedCustomFlagValueStrs,
214+
CustomFlagDecls);
215+
216+
return {Result, CompilationArgs};
217+
}
218+
219+
bool MultilibSet::select(
220+
const Driver &D, const Multilib::flags_list &Flags,
221+
llvm::SmallVectorImpl<Multilib> &Selected,
222+
llvm::SmallVector<StringRef> *CustomFlagCompilationArgs) const {
223+
auto [FlagsWithCustom, CFCompilationArgs] = processCustomFlags(D, Flags);
224+
llvm::StringSet<> FlagSet(expandFlags(FlagsWithCustom));
98225
Selected.clear();
99226
bool AnyErrors = false;
100227

228+
if (CustomFlagCompilationArgs)
229+
*CustomFlagCompilationArgs = std::move(CFCompilationArgs);
230+
101231
// Decide which multilibs we're going to select at all.
102232
llvm::DenseSet<StringRef> ExclusiveGroupsSelected;
103233
for (const Multilib &M : llvm::reverse(Multilibs)) {

0 commit comments

Comments
 (0)