Skip to content

Commit

Permalink
Reapply 'Implement target_clones multiversioning'
Browse files Browse the repository at this point in the history
See discussion in D51650, this change was a little aggressive in an
error while doing a 'while we were here', so this removes that error
condition, as it is apparently useful.

This reverts commit bb49346.
  • Loading branch information
Erich Keane committed Nov 29, 2021
1 parent 814aaba commit fc53eb6
Show file tree
Hide file tree
Showing 18 changed files with 794 additions and 56 deletions.
7 changes: 6 additions & 1 deletion clang/include/clang/AST/Decl.h
Expand Up @@ -1840,7 +1840,8 @@ enum class MultiVersionKind {
None,
Target,
CPUSpecific,
CPUDispatch
CPUDispatch,
TargetClones
};

/// Represents a function declaration or definition.
Expand Down Expand Up @@ -2459,6 +2460,10 @@ class FunctionDecl : public DeclaratorDecl,
/// the target functionality.
bool isTargetMultiVersion() const;

/// True if this function is a multiversioned dispatch function as a part of
/// the target-clones functionality.
bool isTargetClonesMultiVersion() const;

/// \brief Get the associated-constraints of this function declaration.
/// Currently, this will either be a vector of size 1 containing the
/// trailing-requires-clause or an empty vector.
Expand Down
34 changes: 34 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -2677,6 +2677,40 @@ def Target : InheritableAttr {
}];
}

def TargetClones : InheritableAttr {
let Spellings = [GCC<"target_clones">];
let Args = [VariadicStringArgument<"featuresStrs">];
let Documentation = [TargetClonesDocs];
let Subjects = SubjectList<[Function], ErrorDiag>;
let AdditionalMembers = [{
StringRef getFeatureStr(unsigned Index) const {
return *(featuresStrs_begin() + Index);
}
// 'default' is always moved to the end, so it isn't considered
// when mangling the index.
unsigned getMangledIndex(unsigned Index) const {
if (getFeatureStr(Index) == "default")
return std::count_if(featuresStrs_begin(), featuresStrs_end(),
[](StringRef S) { return S != "default"; });

return std::count_if(featuresStrs_begin(), featuresStrs_begin() + Index,
[](StringRef S) { return S != "default"; });
}

// True if this is the first of this version to appear in the config string.
// This is used to make sure we don't try to emit this function multiple
// times.
bool isFirstOfVersion(unsigned Index) const {
StringRef FeatureStr(getFeatureStr(Index));
return 0 == std::count_if(
featuresStrs_begin(), featuresStrs_begin() + Index,
[FeatureStr](StringRef S) { return S == FeatureStr; });
}
}];
}

def : MutualExclusions<[TargetClones, Target, CPUDispatch, CPUSpecific]>;

def MinVectorWidth : InheritableAttr {
let Spellings = [Clang<"min_vector_width">];
let Args = [UnsignedArgument<"VectorWidth">];
Expand Down
34 changes: 34 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -2233,6 +2233,40 @@ Additionally, a function may not become multiversioned after its first use.
}];
}

def TargetClonesDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Clang supports the ``target_clones("OPTIONS")`` attribute. This attribute may be
attached to a function declaration and causes function multiversioning, where
multiple versions of the function will be emitted with different code
generation options. Additionally, these versions will be resolved at runtime
based on the priority of their attribute options. All ``target_clone`` functions
are considered multiversioned functions.

All multiversioned functions must contain a ``default`` (fallback)
implementation, otherwise usages of the function are considered invalid.
Additionally, a function may not become multiversioned after its first use.

The options to ``target_clones`` can either be a target-specific architecture
(specified as ``arch=CPU``), or one of a list of subtarget features.

Example "subtarget features" from the x86 backend include: "mmx", "sse", "sse4.2",
"avx", "xop" and largely correspond to the machine specific options handled by
the front end.

The versions can either be listed as a comma-separated sequence of string
literals or as a single string literal containing a comma-separated list of
versions. For compatibility with GCC, the two formats can be mixed. For
example, the following will emit 4 versions of the function:

.. code-block:: c++

__attribute__((target_clones("arch=atom,avx2","arch=ivybridge","default")))
void foo() {}

}];
}

def MinVectorWidthDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
7 changes: 6 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Expand Up @@ -1275,9 +1275,14 @@ def : DiagGroup<"spirv-compat", [SpirCompat]>; // Alias.
// Warning for the GlobalISel options.
def GlobalISel : DiagGroup<"global-isel">;

// A warning group for the GNU extension to allow mixed specifier types for
// target-clones multiversioning.
def TargetClonesMixedSpecifiers : DiagGroup<"target-clones-mixed-specifiers">;

// A warning group specifically for warnings related to function
// multiversioning.
def FunctionMultiVersioning : DiagGroup<"function-multiversion">;
def FunctionMultiVersioning
: DiagGroup<"function-multiversion", [TargetClonesMixedSpecifiers]>;

def NoDeref : DiagGroup<"noderef">;

Expand Down
39 changes: 30 additions & 9 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2981,7 +2981,8 @@ def err_invalid_branch_protection_spec : Error<
"invalid or misplaced branch protection specification '%0'">;
def warn_unsupported_target_attribute
: Warning<"%select{unsupported|duplicate|unknown}0%select{| architecture|"
" tune CPU}1 '%2' in the 'target' attribute string; 'target' "
" tune CPU}1 '%2' in the '%select{target|target_clones}3' "
"attribute string; '%select{target|target_clones}3' "
"attribute ignored">,
InGroup<IgnoredAttributes>;
def err_attribute_unsupported
Expand Down Expand Up @@ -9864,6 +9865,8 @@ def warn_duplicate_attribute_exact : Warning<
def warn_duplicate_attribute : Warning<
"attribute %0 is already applied with different arguments">,
InGroup<IgnoredAttributes>;
def err_disallowed_duplicate_attribute : Error<
"attribute %0 cannot appear more than once on a declaration">;

def warn_sync_fetch_and_nand_semantics_change : Warning<
"the semantics of this intrinsic changed with GCC "
Expand Down Expand Up @@ -11254,21 +11257,26 @@ def err_multiversion_duplicate : Error<
"multiversioned function redeclarations require identical target attributes">;
def err_multiversion_noproto : Error<
"multiversioned function must have a prototype">;
def err_multiversion_disallowed_other_attr : Error<
"attribute '%select{target|cpu_specific|cpu_dispatch}0' multiversioning cannot be combined"
" with attribute %1">;
def err_multiversion_disallowed_other_attr
: Error<"attribute "
"'%select{|target|cpu_specific|cpu_dispatch|target_clones}0' "
"multiversioning cannot be combined"
" with attribute %1">;
def err_multiversion_mismatched_attrs
: Error<"attributes on multiversioned functions must all match, attribute "
"%0 %select{is missing|has different arguments}1">;
def err_multiversion_diff : Error<
"multiversioned function declaration has a different %select{calling convention"
"|return type|constexpr specification|inline specification|linkage|"
"language linkage}0">;
def err_multiversion_doesnt_support : Error<
"attribute '%select{target|cpu_specific|cpu_dispatch}0' multiversioned functions do not "
"yet support %select{function templates|virtual functions|"
"deduced return types|constructors|destructors|deleted functions|"
"defaulted functions|constexpr functions|consteval function}1">;
def err_multiversion_doesnt_support
: Error<"attribute "
"'%select{|target|cpu_specific|cpu_dispatch|target_clones}0' "
"multiversioned functions do not "
"yet support %select{function templates|virtual functions|"
"deduced return types|constructors|destructors|deleted functions|"
"defaulted functions|constexpr functions|consteval "
"function|lambdas}1">;
def err_multiversion_not_allowed_on_main : Error<
"'main' cannot be a multiversioned function">;
def err_multiversion_not_supported : Error<
Expand All @@ -11285,6 +11293,19 @@ def warn_multiversion_duplicate_entries : Warning<
def warn_dispatch_body_ignored : Warning<
"body of cpu_dispatch function will be ignored">,
InGroup<FunctionMultiVersioning>;
def err_target_clone_must_have_default
: Error<"'target_clones' multiversioning requires a default target">;
def err_target_clone_doesnt_match
: Error<"'target_clones' attribute does not match previous declaration">;
def warn_target_clone_mixed_values
: ExtWarn<
"mixing 'target_clones' specifier mechanisms is permitted for GCC "
"compatibility; use a comma separated sequence of string literals, "
"or a string literal containing a comma-separated list of versions">,
InGroup<TargetClonesMixedSpecifiers>;
def warn_target_clone_duplicate_options
: Warning<"version list contains duplicate entries">,
InGroup<FunctionMultiVersioning>;

// three-way comparison operator diagnostics
def err_implied_comparison_category_type_not_found : Error<
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -4351,6 +4351,10 @@ class Sema final {
llvm::Error isValidSectionSpecifier(StringRef Str);
bool checkSectionName(SourceLocation LiteralLoc, StringRef Str);
bool checkTargetAttr(SourceLocation LiteralLoc, StringRef Str);
bool checkTargetClonesAttrString(SourceLocation LiteralLoc, StringRef Str,
const StringLiteral *Literal,
bool &HasDefault, bool &HasCommas,
SmallVectorImpl<StringRef> &Strings);
bool checkMSInheritanceAttrOnDefinition(
CXXRecordDecl *RD, SourceRange Range, bool BestCase,
MSInheritanceModel SemanticSpelling);
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Expand Up @@ -11800,6 +11800,15 @@ void ASTContext::getFunctionFeatureMap(llvm::StringMap<bool> &FeatureMap,
Target->getTargetOpts().FeaturesAsWritten.begin(),
Target->getTargetOpts().FeaturesAsWritten.end());
Target->initFeatureMap(FeatureMap, getDiagnostics(), TargetCPU, Features);
} else if (const auto *TC = FD->getAttr<TargetClonesAttr>()) {
std::vector<std::string> Features;
StringRef VersionStr = TC->getFeatureStr(GD.getMultiVersionIndex());
if (VersionStr.startswith("arch="))
TargetCPU = VersionStr.drop_front(sizeof("arch=") - 1);
else if (VersionStr != "default")
Features.push_back((StringRef{"+"} + VersionStr).str());

Target->initFeatureMap(FeatureMap, getDiagnostics(), TargetCPU, Features);
} else {
FeatureMap = Target->getTargetOpts().FeatureMap;
}
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/AST/Decl.cpp
Expand Up @@ -3271,6 +3271,8 @@ MultiVersionKind FunctionDecl::getMultiVersionKind() const {
return MultiVersionKind::CPUDispatch;
if (hasAttr<CPUSpecificAttr>())
return MultiVersionKind::CPUSpecific;
if (hasAttr<TargetClonesAttr>())
return MultiVersionKind::TargetClones;
return MultiVersionKind::None;
}

Expand All @@ -3286,6 +3288,10 @@ bool FunctionDecl::isTargetMultiVersion() const {
return isMultiVersion() && hasAttr<TargetAttr>();
}

bool FunctionDecl::isTargetClonesMultiVersion() const {
return isMultiVersion() && hasAttr<TargetClonesAttr>();
}

void
FunctionDecl::setPreviousDeclaration(FunctionDecl *PrevDecl) {
redeclarable_base::setPreviousDecl(PrevDecl);
Expand Down
103 changes: 101 additions & 2 deletions clang/lib/CodeGen/CodeGenModule.cpp
Expand Up @@ -1266,6 +1266,20 @@ static bool isUniqueInternalLinkageDecl(GlobalDecl GD,
(CGM.getFunctionLinkage(GD) == llvm::GlobalValue::InternalLinkage);
}

static void AppendTargetClonesMangling(const CodeGenModule &CGM,
const TargetClonesAttr *Attr,
unsigned VersionIndex,
raw_ostream &Out) {
Out << '.';
StringRef FeatureStr = Attr->getFeatureStr(VersionIndex);
if (FeatureStr.startswith("arch="))
Out << "arch_" << FeatureStr.substr(sizeof("arch=") - 1);
else
Out << FeatureStr;

Out << '.' << Attr->getMangledIndex(VersionIndex);
}

static std::string getMangledNameImpl(CodeGenModule &CGM, GlobalDecl GD,
const NamedDecl *ND,
bool OmitMultiVersionMangling = false) {
Expand Down Expand Up @@ -1319,6 +1333,10 @@ static std::string getMangledNameImpl(CodeGenModule &CGM, GlobalDecl GD,
case MultiVersionKind::Target:
AppendTargetMangling(CGM, FD->getAttr<TargetAttr>(), Out);
break;
case MultiVersionKind::TargetClones:
AppendTargetClonesMangling(CGM, FD->getAttr<TargetClonesAttr>(),
GD.getMultiVersionIndex(), Out);
break;
case MultiVersionKind::None:
llvm_unreachable("None multiversion type isn't valid here");
}
Expand Down Expand Up @@ -1983,8 +2001,9 @@ bool CodeGenModule::GetCPUAndFeaturesAttributes(GlobalDecl GD,
FD = FD ? FD->getMostRecentDecl() : FD;
const auto *TD = FD ? FD->getAttr<TargetAttr>() : nullptr;
const auto *SD = FD ? FD->getAttr<CPUSpecificAttr>() : nullptr;
const auto *TC = FD ? FD->getAttr<TargetClonesAttr>() : nullptr;
bool AddedAttr = false;
if (TD || SD) {
if (TD || SD || TC) {
llvm::StringMap<bool> FeatureMap;
getContext().getFunctionFeatureMap(FeatureMap, GD);

Expand Down Expand Up @@ -3226,6 +3245,12 @@ void CodeGenModule::EmitMultiVersionFunctionDefinition(GlobalDecl GD,
for (unsigned I = 0; I < Spec->cpus_size(); ++I)
EmitGlobalFunctionDefinition(GD.getWithMultiVersionIndex(I), nullptr);
// Requires multiple emits.
} else if (FD->isTargetClonesMultiVersion()) {
auto *Clone = FD->getAttr<TargetClonesAttr>();
for (unsigned I = 0; I < Clone->featuresStrs_size(); ++I)
if (Clone->isFirstOfVersion(I))
EmitGlobalFunctionDefinition(GD.getWithMultiVersionIndex(I), nullptr);
EmitTargetClonesResolver(GD);
} else
EmitGlobalFunctionDefinition(GD, GV);
}
Expand Down Expand Up @@ -3307,6 +3332,63 @@ llvm::GlobalValue::LinkageTypes getMultiversionLinkage(CodeGenModule &CGM,
return llvm::GlobalValue::WeakODRLinkage;
}

void CodeGenModule::EmitTargetClonesResolver(GlobalDecl GD) {
const auto *FD = cast<FunctionDecl>(GD.getDecl());
assert(FD && "Not a FunctionDecl?");
const auto *TC = FD->getAttr<TargetClonesAttr>();
assert(TC && "Not a target_clones Function?");

QualType CanonTy = Context.getCanonicalType(FD->getType());
llvm::Type *DeclTy = getTypes().ConvertType(CanonTy);

if (const auto *CXXFD = dyn_cast<CXXMethodDecl>(FD)) {
const CGFunctionInfo &FInfo = getTypes().arrangeCXXMethodDeclaration(CXXFD);
DeclTy = getTypes().GetFunctionType(FInfo);
}

llvm::Function *ResolverFunc;
if (getTarget().supportsIFunc()) {
auto *IFunc = cast<llvm::GlobalIFunc>(
GetOrCreateMultiVersionResolver(GD, DeclTy, FD));
ResolverFunc = cast<llvm::Function>(IFunc->getResolver());
} else
ResolverFunc =
cast<llvm::Function>(GetOrCreateMultiVersionResolver(GD, DeclTy, FD));

SmallVector<CodeGenFunction::MultiVersionResolverOption, 10> Options;
for (unsigned VersionIndex = 0; VersionIndex < TC->featuresStrs_size();
++VersionIndex) {
if (!TC->isFirstOfVersion(VersionIndex))
continue;
StringRef Version = TC->getFeatureStr(VersionIndex);
StringRef MangledName =
getMangledName(GD.getWithMultiVersionIndex(VersionIndex));
llvm::Constant *Func = GetGlobalValue(MangledName);
assert(Func &&
"Should have already been created before calling resolver emit");

StringRef Architecture;
llvm::SmallVector<StringRef, 1> Feature;

if (Version.startswith("arch="))
Architecture = Version.drop_front(sizeof("arch=") - 1);
else if (Version != "default")
Feature.push_back(Version);

Options.emplace_back(cast<llvm::Function>(Func), Architecture, Feature);
}

const TargetInfo &TI = getTarget();
std::stable_sort(
Options.begin(), Options.end(),
[&TI](const CodeGenFunction::MultiVersionResolverOption &LHS,
const CodeGenFunction::MultiVersionResolverOption &RHS) {
return TargetMVPriority(TI, LHS) > TargetMVPriority(TI, RHS);
});
CodeGenFunction CGF(*this);
CGF.EmitMultiVersionResolver(ResolverFunc, Options);
}

void CodeGenModule::emitMultiVersionFunctions() {
std::vector<GlobalDecl> MVFuncsToEmit;
MultiVersionFuncs.swap(MVFuncsToEmit);
Expand Down Expand Up @@ -3511,8 +3593,25 @@ llvm::Constant *CodeGenModule::GetOrCreateMultiVersionResolver(
// Since this is the first time we've created this IFunc, make sure
// that we put this multiversioned function into the list to be
// replaced later if necessary (target multiversioning only).
if (!FD->isCPUDispatchMultiVersion() && !FD->isCPUSpecificMultiVersion())
if (FD->isTargetMultiVersion())
MultiVersionFuncs.push_back(GD);
else if (FD->isTargetClonesMultiVersion()) {
// In target_clones multiversioning, make sure we emit this if used.
auto DDI =
DeferredDecls.find(getMangledName(GD.getWithMultiVersionIndex(0)));
if (DDI != DeferredDecls.end()) {
addDeferredDeclToEmit(GD);
DeferredDecls.erase(DDI);
} else {
// Emit the symbol of the 1st variant, so that the deferred decls know we
// need it, otherwise the only global value will be the resolver/ifunc,
// which end up getting broken if we search for them with GetGlobalValue'.
GetOrCreateLLVMFunction(
getMangledName(GD.getWithMultiVersionIndex(0)), DeclTy, FD,
/*ForVTable=*/false, /*DontDefer=*/true,
/*IsThunk=*/false, llvm::AttributeList(), ForDefinition);
}
}

if (getTarget().supportsIFunc()) {
llvm::Type *ResolverType = llvm::FunctionType::get(
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Expand Up @@ -1500,6 +1500,7 @@ class CodeGenModule : public CodeGenTypeCache {
void EmitAliasDefinition(GlobalDecl GD);
void emitIFuncDefinition(GlobalDecl GD);
void emitCPUDispatchDefinition(GlobalDecl GD);
void EmitTargetClonesResolver(GlobalDecl GD);
void EmitObjCPropertyImplementations(const ObjCImplementationDecl *D);
void EmitObjCIvarInitializations(ObjCImplementationDecl *D);

Expand Down

0 comments on commit fc53eb6

Please sign in to comment.