Skip to content

Commit 2fe4189

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 17265b1 commit 2fe4189

File tree

8 files changed

+328
-75
lines changed

8 files changed

+328
-75
lines changed

clang/include/clang/Driver/Driver.h

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

471471
/// BuildInputs - Construct the list of inputs and their types from
472472
/// 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: 76 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

@@ -1272,8 +1272,8 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
12721272
bool HasConfigFileTail = !ContainsError && CfgOptionsTail;
12731273

12741274
// All arguments, from both config file and command line.
1275-
InputArgList Args =
1276-
HasConfigFileHead ? std::move(*CfgOptionsHead) : std::move(*CLOptions);
1275+
auto UArgs = std::make_unique<InputArgList>(HasConfigFileHead ? std::move(*CfgOptionsHead) : std::move(*CLOptions));
1276+
InputArgList &Args = *UArgs;
12771277

12781278
if (HasConfigFileHead)
12791279
for (auto *Opt : *CLOptions)
@@ -1301,52 +1301,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
13011301
}
13021302
}
13031303

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

14241444
if (const Arg *A = Args.getLastArg(options::OPT_save_temps_EQ)) {
14251445
SaveTemps = llvm::StringSwitch<SaveTempsMode>(A->getValue())
@@ -1439,8 +1459,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
14391459
Offload = OffloadHostDevice;
14401460
}
14411461

1442-
setLTOMode(Args);
1443-
14441462
// Process -fembed-bitcode= flags.
14451463
if (Arg *A = Args.getLastArg(options::OPT_fembed_bitcode_EQ)) {
14461464
StringRef Name = A->getValue();
@@ -1494,16 +1512,9 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
14941512
}
14951513
}
14961514

1497-
std::unique_ptr<llvm::opt::InputArgList> UArgs =
1498-
std::make_unique<InputArgList>(std::move(Args));
1499-
15001515
// Perform the default argument translations.
15011516
DerivedArgList *TranslatedArgs = TranslateInputArgs(*UArgs);
15021517

1503-
// Owned by the host.
1504-
const ToolChain &TC = getToolChain(
1505-
*UArgs, computeTargetTriple(*this, TargetTriple, *UArgs));
1506-
15071518
// Check if the environment version is valid except wasm case.
15081519
llvm::Triple Triple = TC.getTriple();
15091520
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)