Skip to content

[Multilib] Custom flags processing for library selection #110659

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 9 commits into from
Jan 16, 2025
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
2 changes: 1 addition & 1 deletion clang/include/clang/Driver/Driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ class Driver {
/// ArgList.
llvm::opt::InputArgList ParseArgStrings(ArrayRef<const char *> Args,
bool UseDriverMode,
bool &ContainsError);
bool &ContainsError) const;

/// BuildInputs - Construct the list of inputs and their types from
/// the given arguments.
Expand Down
11 changes: 10 additions & 1 deletion clang/include/clang/Driver/Multilib.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,18 @@ class MultilibSet {
const_iterator begin() const { return Multilibs.begin(); }
const_iterator end() const { return Multilibs.end(); }

/// Process custom flags from \p Flags and returns an expanded flags list and
/// a list of macro defines.
/// Returns a pair where:
/// - first: the new flags list including custom flags after processing.
/// - second: the extra macro defines to be fed to the driver.
std::pair<Multilib::flags_list, SmallVector<StringRef>>
processCustomFlags(const Driver &D, const Multilib::flags_list &Flags) const;

/// Select compatible variants, \returns false if none are compatible
bool select(const Driver &D, const Multilib::flags_list &Flags,
llvm::SmallVectorImpl<Multilib> &) const;
llvm::SmallVectorImpl<Multilib> &,
llvm::SmallVector<StringRef> * = nullptr) const;

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

Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Driver/ToolChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,13 @@ class ToolChain {
/// Add warning options that need to be passed to cc1 for this target.
virtual void addClangWarningOptions(llvm::opt::ArgStringList &CC1Args) const;

// Get the list of extra macro defines requested by the multilib
// configuration.
virtual SmallVector<std::string>
getMultilibMacroDefinesStr(llvm::opt::ArgList &Args) const {
return {};
};

// GetRuntimeLibType - Determine the runtime library type to use with the
// given compilation arguments.
virtual RuntimeLibType
Expand Down
29 changes: 24 additions & 5 deletions clang/lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ void Driver::setDriverMode(StringRef Value) {
}

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

Expand Down Expand Up @@ -1627,13 +1628,31 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
std::unique_ptr<llvm::opt::InputArgList> UArgs =
std::make_unique<InputArgList>(std::move(Args));

// Owned by the host.
const ToolChain &TC =
getToolChain(*UArgs, computeTargetTriple(*this, TargetTriple, *UArgs));

{
SmallVector<std::string> MultilibMacroDefinesStr =
TC.getMultilibMacroDefinesStr(*UArgs);
SmallVector<const char *> MLMacroDefinesChar(
llvm::map_range(MultilibMacroDefinesStr, [&UArgs](const auto &S) {
return UArgs->MakeArgString(Twine("-D") + Twine(S));
}));
bool MLContainsError;
auto MultilibMacroDefineList =
std::make_unique<InputArgList>(ParseArgStrings(
MLMacroDefinesChar, /*UseDriverMode=*/false, MLContainsError));
if (!MLContainsError) {
for (auto *Opt : *MultilibMacroDefineList) {
appendOneArg(*UArgs, Opt);
}
}
}

// Perform the default argument translations.
DerivedArgList *TranslatedArgs = TranslateInputArgs(*UArgs);

// Owned by the host.
const ToolChain &TC = getToolChain(
*UArgs, computeTargetTriple(*this, TargetTriple, *UArgs));

// Check if the environment version is valid except wasm case.
llvm::Triple Triple = TC.getTriple();
if (!Triple.isWasm()) {
Expand Down
139 changes: 136 additions & 3 deletions clang/lib/Driver/Multilib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,145 @@ MultilibSet &MultilibSet::FilterOut(FilterCallback F) {

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

bool MultilibSet::select(const Driver &D, const Multilib::flags_list &Flags,
llvm::SmallVectorImpl<Multilib> &Selected) const {
llvm::StringSet<> FlagSet(expandFlags(Flags));
static void DiagnoseUnclaimedMultilibCustomFlags(
const Driver &D, const SmallVector<StringRef> &UnclaimedCustomFlagValues,
const SmallVector<custom_flag::Declaration> &CustomFlagDecls) {
struct EditDistanceInfo {
StringRef FlagValue;
unsigned EditDistance;
};
const unsigned MaxEditDistance = 5;

for (StringRef Unclaimed : UnclaimedCustomFlagValues) {
std::optional<EditDistanceInfo> BestCandidate;
for (const auto &Decl : CustomFlagDecls) {
for (const auto &Value : Decl.ValueList) {
const std::string &FlagValueName = Value.Name;
unsigned EditDistance =
Unclaimed.edit_distance(FlagValueName, /*AllowReplacements=*/true,
/*MaxEditDistance=*/MaxEditDistance);
if (!BestCandidate || (EditDistance <= MaxEditDistance &&
EditDistance < BestCandidate->EditDistance)) {
BestCandidate = {FlagValueName, EditDistance};
}
}
}
if (!BestCandidate)
D.Diag(clang::diag::err_drv_unsupported_opt)
<< (custom_flag::Prefix + Unclaimed).str();
else
D.Diag(clang::diag::err_drv_unsupported_opt_with_suggestion)
<< (custom_flag::Prefix + Unclaimed).str()
<< (custom_flag::Prefix + BestCandidate->FlagValue).str();
}
}

namespace clang::driver::custom_flag {
// Map implemented using linear searches as the expected size is too small for
// the overhead of a search tree or a hash table.
class ValueNameToDetailMap {
SmallVector<std::pair<StringRef, const ValueDetail *>> Mapping;

public:
template <typename It>
ValueNameToDetailMap(It FlagDeclsBegin, It FlagDeclsEnd) {
for (auto DeclIt = FlagDeclsBegin; DeclIt != FlagDeclsEnd; ++DeclIt) {
const Declaration &Decl = *DeclIt;
for (const auto &Value : Decl.ValueList)
Mapping.emplace_back(Value.Name, &Value);
}
}

const ValueDetail *get(StringRef Key) const {
auto Iter = llvm::find_if(
Mapping, [&](const auto &Pair) { return Pair.first == Key; });
return Iter != Mapping.end() ? Iter->second : nullptr;
}
};
} // namespace clang::driver::custom_flag

std::pair<Multilib::flags_list, SmallVector<StringRef>>
MultilibSet::processCustomFlags(const Driver &D,
const Multilib::flags_list &Flags) const {
Multilib::flags_list Result;
SmallVector<StringRef> MacroDefines;

// Custom flag values detected in the flags list
SmallVector<const custom_flag::ValueDetail *> ClaimedCustomFlagValues;

// Arguments to -fmultilib-flag=<arg> that don't correspond to any valid
// custom flag value. An error will be printed out for each of these.
SmallVector<StringRef> UnclaimedCustomFlagValueStrs;

const auto ValueNameToValueDetail = custom_flag::ValueNameToDetailMap(
CustomFlagDecls.begin(), CustomFlagDecls.end());

for (StringRef Flag : Flags) {
if (!Flag.starts_with(custom_flag::Prefix)) {
Result.push_back(Flag.str());
continue;
}

StringRef CustomFlagValueStr = Flag.substr(custom_flag::Prefix.size());
const custom_flag::ValueDetail *Detail =
ValueNameToValueDetail.get(CustomFlagValueStr);
if (Detail)
ClaimedCustomFlagValues.push_back(Detail);
else
UnclaimedCustomFlagValueStrs.push_back(CustomFlagValueStr);
}

// Set of custom flag declarations for which a value was passed in the flags
// list. This is used to, firstly, detect multiple values for the same flag
// declaration (in this case, the last one wins), and secondly, to detect
// which declarations had no value passed in (in this case, the default value
// is selected).
llvm::SmallPtrSet<custom_flag::Declaration *, 32> TriggeredCustomFlagDecls;

// Detect multiple values for the same flag declaration. Last one wins.
for (auto *CustomFlagValue : llvm::reverse(ClaimedCustomFlagValues)) {
if (!TriggeredCustomFlagDecls.insert(CustomFlagValue->Decl).second)
continue;
Result.push_back(std::string(custom_flag::Prefix) + CustomFlagValue->Name);
if (CustomFlagValue->MacroDefines)
MacroDefines.append(CustomFlagValue->MacroDefines->begin(),
CustomFlagValue->MacroDefines->end());
}

// Detect flag declarations with no value passed in. Select default value.
for (const auto &Decl : CustomFlagDecls) {
if (TriggeredCustomFlagDecls.contains(&Decl))
continue;
const custom_flag::ValueDetail &CustomFlagValue =
Decl.ValueList[*Decl.DefaultValueIdx];
Result.push_back(std::string(custom_flag::Prefix) + CustomFlagValue.Name);
if (CustomFlagValue.MacroDefines)
MacroDefines.append(CustomFlagValue.MacroDefines->begin(),
CustomFlagValue.MacroDefines->end());
}

DiagnoseUnclaimedMultilibCustomFlags(D, UnclaimedCustomFlagValueStrs,
CustomFlagDecls);

return {Result, MacroDefines};
}

bool MultilibSet::select(
const Driver &D, const Multilib::flags_list &Flags,
llvm::SmallVectorImpl<Multilib> &Selected,
llvm::SmallVector<StringRef> *CustomFlagMacroDefines) const {
auto [FlagsWithCustom, CFMacroDefines] = processCustomFlags(D, Flags);
llvm::StringSet<> FlagSet(expandFlags(FlagsWithCustom));
Selected.clear();
bool AnyErrors = false;

// Determining the list of macro defines depends only on the custom flags
// passed in. The library variants actually selected are not relevant in
// this. Therefore this assignment can take place before the selection
// happens.
if (CustomFlagMacroDefines)
*CustomFlagMacroDefines = std::move(CFMacroDefines);

// Decide which multilibs we're going to select at all.
llvm::DenseSet<StringRef> ExclusiveGroupsSelected;
for (const Multilib &M : llvm::reverse(Multilibs)) {
Expand Down
22 changes: 17 additions & 5 deletions clang/lib/Driver/ToolChains/BareMetal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,11 @@ static bool isPPCBareMetal(const llvm::Triple &Triple) {
Triple.getEnvironment() == llvm::Triple::EABI;
}

static void findMultilibsFromYAML(const ToolChain &TC, const Driver &D,
StringRef MultilibPath, const ArgList &Args,
DetectedMultilibs &Result) {
static void
findMultilibsFromYAML(const ToolChain &TC, const Driver &D,
StringRef MultilibPath, const ArgList &Args,
DetectedMultilibs &Result,
SmallVector<StringRef> &CustomFlagsMacroDefines) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MB =
D.getVFS().getBufferForFile(MultilibPath);
if (!MB)
Expand All @@ -175,7 +177,8 @@ static void findMultilibsFromYAML(const ToolChain &TC, const Driver &D,
if (ErrorOrMultilibSet.getError())
return;
Result.Multilibs = ErrorOrMultilibSet.get();
if (Result.Multilibs.select(D, Flags, Result.SelectedMultilibs))
if (Result.Multilibs.select(D, Flags, Result.SelectedMultilibs,
&CustomFlagsMacroDefines))
return;
D.Diag(clang::diag::warn_drv_missing_multilib) << llvm::join(Flags, " ");
std::stringstream ss;
Expand Down Expand Up @@ -234,9 +237,13 @@ void BareMetal::findMultilibs(const Driver &D, const llvm::Triple &Triple,
// If multilib.yaml is found, update sysroot so it doesn't use a target
// specific suffix
SysRoot = computeBaseSysRoot(D, /*IncludeTriple=*/false);
findMultilibsFromYAML(*this, D, *MultilibPath, Args, Result);
SmallVector<StringRef> CustomFlagMacroDefines;
findMultilibsFromYAML(*this, D, *MultilibPath, Args, Result,
CustomFlagMacroDefines);
SelectedMultilibs = Result.SelectedMultilibs;
Multilibs = Result.Multilibs;
MultilibMacroDefines.append(CustomFlagMacroDefines.begin(),
CustomFlagMacroDefines.end());
} else if (isRISCVBareMetal(Triple)) {
if (findRISCVMultilibs(D, Triple, Args, Result)) {
SelectedMultilibs = Result.SelectedMultilibs;
Expand Down Expand Up @@ -551,3 +558,8 @@ SanitizerMask BareMetal::getSupportedSanitizers() const {
}
return Res;
}

SmallVector<std::string>
BareMetal::getMultilibMacroDefinesStr(llvm::opt::ArgList &Args) const {
return MultilibMacroDefines;
}
5 changes: 5 additions & 0 deletions clang/lib/Driver/ToolChains/BareMetal.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,17 @@ class LLVM_LIBRARY_VISIBILITY BareMetal : public ToolChain {
std::string computeSysRoot() const override;
SanitizerMask getSupportedSanitizers() const override;

SmallVector<std::string>
getMultilibMacroDefinesStr(llvm::opt::ArgList &Args) const override;

private:
using OrderedMultilibs =
llvm::iterator_range<llvm::SmallVector<Multilib>::const_reverse_iterator>;
OrderedMultilibs getOrderedMultilibs() const;

std::string SysRoot;

SmallVector<std::string> MultilibMacroDefines;
};

} // namespace toolchains
Expand Down
81 changes: 81 additions & 0 deletions clang/test/Driver/baremetal-multilib-custom-flags.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# UNSUPPORTED: system-windows

# RUN: %clang --multi-lib-config=%s -no-canonical-prefixes -x c %s -### -o /dev/null 2>&1 \
# RUN: --target=thumbv8m.main-none-eabi -mfpu=none --sysroot= \
# RUN: | FileCheck --check-prefix=CHECK-DEFAULT %s

# CHECK-DEFAULT: "-cc1" "-triple" "thumbv8m.main-unknown-none-eabi"
# CHECK-DEFAULT-SAME: "-internal-isystem" "[[SYSROOT:[^"]*]]/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/nofp/include"
# CHECK-DEFAULT-NEXT: "-L[[SYSROOT]]/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/nofp/lib"

# RUN: %clang --multi-lib-config=%s -no-canonical-prefixes -x c %s -### -o /dev/null 2>&1 \
# RUN: --target=thumbv8m.main-none-eabi -mfpu=none -fmultilib-flag=no-multithreaded --sysroot= \
# RUN: | FileCheck --check-prefix=CHECK-NOMULTI %s

# CHECK-NOMULTI: "-cc1" "-triple" "thumbv8m.main-unknown-none-eabi"
# CHECK-NOMULTI-SAME: "-internal-isystem" "[[SYSROOT:[^"]*]]/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/nofp/include"
# CHECK-NOMULTI-NEXT: "-L[[SYSROOT]]/bin/../lib/clang-runtimes/arm-none-eabi/thumb/v8-m.main/nofp/lib"

# RUN: %clang --multi-lib-config=%s -no-canonical-prefixes -x c %s -### -o /dev/null 2>&1 \
# RUN: --target=thumbv8m.main-none-eabi -mfpu=none -fmultilib-flag=multithreaded --sysroot= \
# RUN: | FileCheck --check-prefix=CHECK-MULTI %s

# CHECK-MULTI: "-cc1" "-triple" "thumbv8m.main-unknown-none-eabi"
# CHECK-MULTI-SAME: "-internal-isystem" "[[SYSROOT:[^"]*]]/bin/../lib/clang-runtimes/arm-none-eabi/multithreaded/thumb/v8-m.main/nofp/include"
# CHECK-MULTI-NEXT: "-L[[SYSROOT]]/bin/../lib/clang-runtimes/arm-none-eabi/multithreaded/thumb/v8-m.main/nofp/lib"

# RUN: not %clang --multi-lib-config=%s -no-canonical-prefixes -x c %s -### -o /dev/null 2>&1 \
# RUN: --target=thumbv8m.main-none-eabi -mfpu=none -fmultilib-flag=singlethreaded -fmultilib-flag=no-io --sysroot= \
# RUN: | FileCheck --check-prefix=CHECK-ERROR %s
# CHECK-ERROR-DAG: error: unsupported option '-fmultilib-flag=singlethreaded'
# CHECK-ERROR-DAG: error: unsupported option '-fmultilib-flag=no-io'; did you mean '-fmultilib-flag=io-none'?

# RUN: %clang --multi-lib-config=%s -no-canonical-prefixes -x c %s -### -o /dev/null 2>&1 \
# RUN: --target=thumbv8m.main-none-eabi -mfpu=none -print-multi-lib --sysroot= \
# RUN: | FileCheck --check-prefix=CHECK-PRINT-MULTI-LIB %s
# CHECK-PRINT-MULTI-LIB: arm-none-eabi/thumb/v8-m.main/nofp;@-target=thumbv8m.main-unknown-none-eabi@mfpu=none@fmultilib-flag=no-multithreaded
# CHECK-PRINT-MULTI-LIB: arm-none-eabi/multithreaded/thumb/v8-m.main/nofp;@-target=thumbv8m.main-unknown-none-eabi@mfpu=none@fmultilib-flag=multithreaded

# RUN: %clang --target=arm-none-eabi --multi-lib-config=%s -x c %s -fmultilib-flag=no-multithreaded -### -o /dev/null 2>&1 \
# RUN: | FileCheck --check-prefix=CHECK-MACRODEFINES-NOMULTI %s
# CHECK-MACRODEFINES-NOMULTI: "-D" "__SINGLE_THREAD__"

# RUN: %clang --target=arm-none-eabi --multi-lib-config=%s -x c %s -fmultilib-flag=io-semihosting -### -o /dev/null 2>&1 \
# RUN: | FileCheck --check-prefix=CHECK-MACRODEFINES-IO-SEMIHOSTING %s
# CHECK-MACRODEFINES-IO-SEMIHOSTING: "-D" "SEMIHOSTING"

# RUN: %clang --target=arm-none-eabi --multi-lib-config=%s -x c %s -fmultilib-flag=io-linux-syscalls -### -o /dev/null 2>&1 \
# RUN: | FileCheck --check-prefix=CHECK-MACRODEFINES-IO-LINUX %s
# CHECK-MACRODEFINES-IO-LINUX: "-D" "LINUX_SYSCALLS"
# CHECK-MACRODEFINES-IO-LINUX-SAME: "-D" "HOSTED"

---
MultilibVersion: 1.0

Groups:
- Name: stdlib
Type: Exclusive

Variants:
- Dir: arm-none-eabi/thumb/v8-m.main/nofp
Flags: [--target=thumbv8m.main-unknown-none-eabi, -mfpu=none, -fmultilib-flag=no-multithreaded]
Group: stdlib
- Dir: arm-none-eabi/multithreaded/thumb/v8-m.main/nofp
Flags: [--target=thumbv8m.main-unknown-none-eabi, -mfpu=none, -fmultilib-flag=multithreaded]
Group: stdlib

Flags:
- Name: multithreading
Values:
- Name: no-multithreaded
MacroDefines: [__SINGLE_THREAD__]
- Name: multithreaded
Default: no-multithreaded
- Name: io
Values:
- Name: io-none
- Name: io-semihosting
MacroDefines: [SEMIHOSTING]
- Name: io-linux-syscalls
MacroDefines: [LINUX_SYSCALLS, HOSTED]
Default: io-none
Loading