Skip to content

Commit 6cfbf8c

Browse files
[libc] add support for function level attributes (#79891)
See discussion at https://discourse.llvm.org/t/rfc-support-function-attributes-in-user-interface/76624 Demo macro: ```c++ #if !defined(__LIBC_CONST_ATTR) && defined(__cplusplus) && defined(__GNUC__) #if __has_attribute(const) #define __LIBC_CONST_ATTR [[gnu::const]] #endif #endif #if !defined(__LIBC_CONST_ATTR) && defined(__GNUC__) #if __has_attribute(const) #define __LIBC_CONST_ATTR __attribute__((const)) #endif #endif #if !defined(__LIBC_CONST_ATTR) #define __LIBC_CONST_ATTR #endif ```
1 parent 54826d4 commit 6cfbf8c

File tree

4 files changed

+231
-21
lines changed

4 files changed

+231
-21
lines changed

libc/spec/spec.td

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,38 @@ class ArgSpec<Type type, list<Annotation> annotations = [], string name = ""> {
170170
string Name = name;
171171
}
172172

173-
class FunctionSpec<string name, RetValSpec return, list<ArgSpec> args> {
173+
// The following classes are used to describe function attributes.
174+
// In the future, we may consider supporting parameter attributes as well.
175+
// https://clang.llvm.org/docs/AttributeReference.html
176+
class FunctionAttr<string style, string attr> {
177+
string Attr = attr;
178+
// The style of the attribute, e.g. "gnu", "cxx11", "declspec".
179+
// - "gnu" is for GNU-style attributes: __attribute__((...))
180+
// - "cxx11" is for C++11-style attributes: [[...]]
181+
// - "declspec" is for Microsoft-style attributes: __declspec(...)
182+
string Style = style;
183+
184+
// For the time being, we are only interested in identifer-like attributes.
185+
// We can extend this to support function-like attributes if needed.
186+
// For example, in the future, we can #define __LIBC_ATTRIBUTE_NODISCARD(...) [[nodiscard(__VA_ARGS__)]]
187+
// int FunctionLike = 0;
188+
}
189+
class GnuFunctionAttr<string attr> : FunctionAttr<"gnu", attr> {}
190+
class Cxx11FunctionAttr<string attr, string namespace> : FunctionAttr<"cxx11", attr> {
191+
// The namespace of the attribute, e.g. "gnu" or "clang". Empty string means there is no namespace.
192+
string Namespace = namespace;
193+
}
194+
class DeclspecFunctionAttr<string attr> : FunctionAttr<"declspec", attr> {}
195+
class FunctionAttrSpec<string macro, list<FunctionAttr> instances> {
196+
list<FunctionAttr> Instances = instances;
197+
string Macro = macro;
198+
}
199+
200+
class FunctionSpec<string name, RetValSpec return, list<ArgSpec> args, list<FunctionAttrSpec> attrs = []> {
174201
string Name = name;
175202
RetValSpec Return = return;
176203
list<ArgSpec> Args = args;
204+
list<FunctionAttrSpec> Attributes = attrs;
177205
}
178206

179207
class GuardedFunctionSpec<string name, RetValSpec return, list<ArgSpec> args, string guard_macro> : FunctionSpec<name, return, args> {

libc/spec/stdc.td

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def StdC : StandardSpec<"stdc"> {
2626
[]
2727
>;
2828

29+
FunctionAttrSpec ConstAttr = FunctionAttrSpec<"__LIBC_CONST_ATTR", [
30+
Cxx11FunctionAttr<"const", "gnu">,
31+
GnuFunctionAttr<"const">,
32+
]>;
33+
2934
HeaderSpec CType = HeaderSpec<
3035
"ctype.h",
3136
[], // Macros
@@ -366,7 +371,7 @@ def StdC : StandardSpec<"stdc"> {
366371
FunctionSpec<"ceill", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>]>,
367372
GuardedFunctionSpec<"ceilf128", RetValSpec<Float128Type>, [ArgSpec<Float128Type>], "LIBC_COMPILER_HAS_FLOAT128">,
368373

369-
FunctionSpec<"fabs", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
374+
FunctionSpec<"fabs", RetValSpec<DoubleType>, [ArgSpec<DoubleType>], [ConstAttr]>,
370375
FunctionSpec<"fabsf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
371376
FunctionSpec<"fabsl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>]>,
372377
GuardedFunctionSpec<"fabsf128", RetValSpec<Float128Type>, [ArgSpec<Float128Type>], "LIBC_COMPILER_HAS_FLOAT128">,

libc/utils/HdrGen/PublicAPICommand.cpp

Lines changed: 193 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010

1111
#include "utils/LibcTableGenUtil/APIIndexer.h"
1212

13+
#include "llvm/ADT/STLExtras.h"
1314
#include "llvm/ADT/SmallVector.h"
1415
#include "llvm/ADT/StringExtras.h"
1516
#include "llvm/ADT/StringRef.h"
17+
#include "llvm/ADT/StringSwitch.h"
1618
#include "llvm/Support/SourceMgr.h"
1719
#include "llvm/TableGen/Record.h"
20+
#include <algorithm>
21+
#include <vector>
1822

1923
// Text blocks for macro definitions and type decls can be indented to
2024
// suit the surrounding tablegen listing. We need to dedent such blocks
@@ -47,12 +51,170 @@ static std::string getTypeHdrName(const std::string &Name) {
4751

4852
namespace llvm_libc {
4953

50-
void writeAPIFromIndex(APIIndexer &G,
51-
std::vector<std::string> EntrypointNameList,
52-
llvm::raw_ostream &OS) {
54+
static bool isAsciiStart(char C) {
55+
return (C >= 'A' && C <= 'Z') || (C >= 'a' && C <= 'z') || C == '_';
56+
}
57+
58+
static bool isAsciiContinue(char C) {
59+
return isAsciiStart(C) || (C >= '0' && C <= '9');
60+
}
61+
62+
static bool isAsciiIdentifier(llvm::StringRef S) {
63+
if (S.empty())
64+
return false;
65+
if (!isAsciiStart(S[0]))
66+
return false;
67+
for (char C : S.drop_front())
68+
if (!isAsciiContinue(C))
69+
return false;
70+
return true;
71+
}
72+
73+
static AttributeStyle getAttributeStyle(llvm::Record *Instance) {
74+
llvm::StringRef Style = Instance->getValueAsString("Style");
75+
return llvm::StringSwitch<AttributeStyle>(Style)
76+
.Case("cxx11", AttributeStyle::Cxx11)
77+
.Case("gnu", AttributeStyle::Gnu)
78+
.Case("declspec", AttributeStyle::Declspec)
79+
.Default(AttributeStyle::Gnu);
80+
}
81+
82+
static AttributeNamespace getAttributeNamespace(llvm::Record *Instance) {
83+
llvm::StringRef Namespace = Instance->getValueAsString("Namespace");
84+
return llvm::StringSwitch<AttributeNamespace>(Namespace)
85+
.Case("clang", AttributeNamespace::Clang)
86+
.Case("gnu", AttributeNamespace::Gnu)
87+
.Default(AttributeNamespace::None);
88+
}
89+
90+
using AttributeMap = llvm::DenseMap<llvm::StringRef, llvm::Record *>;
91+
92+
template <class SpecMap, class FuncList>
93+
static AttributeMap collectAttributeMacros(const SpecMap &Spec,
94+
const FuncList &Funcs) {
95+
llvm::DenseMap<llvm::StringRef, llvm::Record *> MacroAttr;
96+
for (const auto &Name : Funcs) {
97+
auto Iter = Spec.find(Name);
98+
if (Iter == Spec.end())
99+
continue;
100+
101+
llvm::Record *FunctionSpec = Iter->second;
102+
std::vector<llvm::Record *> Attributes =
103+
FunctionSpec->getValueAsListOfDefs("Attributes");
104+
for (llvm::Record *Attr : Attributes)
105+
MacroAttr[Attr->getValueAsString("Macro")] = Attr;
106+
}
107+
return MacroAttr;
108+
}
109+
110+
static void emitAttributeMacroDecls(const AttributeMap &MacroAttr,
111+
llvm::raw_ostream &OS) {
112+
for (auto &[Macro, Attr] : MacroAttr) {
113+
std::vector<llvm::Record *> Instances =
114+
Attr->getValueAsListOfDefs("Instances");
115+
llvm::SmallVector<std::pair<AttributeStyle, llvm::Record *>> Styles;
116+
std::transform(Instances.begin(), Instances.end(),
117+
std::back_inserter(Styles),
118+
[&](llvm::Record *Instance)
119+
-> std::pair<AttributeStyle, llvm::Record *> {
120+
auto Style = getAttributeStyle(Instance);
121+
return {Style, Instance};
122+
});
123+
// 1. If __cplusplus is defined and cxx11 style is provided, define the
124+
// macro using cxx11 version with the following priority:
125+
// 1a. If there is no namespace (so the macro is supposed to be
126+
// compiler-independent), use this version first. This macro will be
127+
// tested via __has_cpp_attribute.
128+
// 1b. If the attribute is a clang attribute, check for __clang__.
129+
// 1c. If the attribute is a gnu attribute, check for __GNUC__.
130+
// 2. Otherwise, if __GNUC__ is defined and gnu style is provided,
131+
// define the macro using gnu version;
132+
// 3. Otherwise, if _MSC_VER is defined and __declspec is provided, define
133+
// the macro using __declspec version;
134+
// 4. Fallback to empty macro.
135+
std::sort(Styles.begin(), Styles.end(), [&](auto &a, auto &b) {
136+
if (a.first == AttributeStyle::Cxx11 && b.first == AttributeStyle::Cxx11)
137+
return getAttributeNamespace(a.second) <
138+
getAttributeNamespace(b.second);
139+
return a.first < b.first;
140+
});
141+
for (auto &[Style, Instance] : Styles) {
142+
llvm::StringRef Attr = Instance->getValueAsString("Attr");
143+
if (Style == AttributeStyle::Cxx11) {
144+
OS << "#if !defined(" << Macro << ") && defined(__cplusplus)";
145+
AttributeNamespace Namespace = getAttributeNamespace(Instance);
146+
if (Namespace == AttributeNamespace::Clang)
147+
OS << " && defined(__clang__)\n";
148+
else if (Namespace == AttributeNamespace::Gnu)
149+
OS << " && defined(__GNUC__)\n";
150+
else
151+
OS << '\n';
152+
if (isAsciiIdentifier(Attr) && Namespace != AttributeNamespace::None)
153+
OS << "#if __has_attribute(" << Attr << ")\n";
154+
else
155+
OS << "#if __has_cpp_attribute(" << Attr << ")\n";
156+
OS << "#define " << Macro << " [[";
157+
if (Namespace == AttributeNamespace::Clang)
158+
OS << "clang::";
159+
else if (Namespace == AttributeNamespace::Gnu)
160+
OS << "gnu::";
161+
OS << Attr << "]]\n";
162+
if (isAsciiIdentifier(Attr))
163+
OS << "#endif\n";
164+
OS << "#endif\n";
165+
}
166+
if (Style == AttributeStyle::Gnu) {
167+
OS << "#if !defined(" << Macro << ") && defined(__GNUC__)\n";
168+
if (isAsciiIdentifier(Attr))
169+
OS << "#if __has_attribute(" << Attr << ")\n";
170+
OS << "#define " << Macro << " __attribute__((";
171+
OS << Attr << "))\n";
172+
if (isAsciiIdentifier(Attr))
173+
OS << "#endif\n";
174+
OS << "#endif\n";
175+
}
176+
if (Style == AttributeStyle::Declspec) {
177+
OS << "#if !defined(" << Macro << ") && defined(_MSC_VER)\n";
178+
OS << "#define " << Macro << " __declspec(";
179+
OS << Attr << ")\n";
180+
OS << "#endif\n";
181+
}
182+
}
183+
OS << "#if !defined(" << Macro << ")\n";
184+
OS << "#define " << Macro << '\n';
185+
OS << "#endif\n";
186+
}
187+
188+
if (!MacroAttr.empty())
189+
OS << '\n';
190+
}
191+
192+
static void emitAttributeMacroForFunction(const llvm::Record *FunctionSpec,
193+
llvm::raw_ostream &OS) {
194+
std::vector<llvm::Record *> Attributes =
195+
FunctionSpec->getValueAsListOfDefs("Attributes");
196+
llvm::interleave(
197+
Attributes.begin(), Attributes.end(),
198+
[&](llvm::Record *Attr) { OS << Attr->getValueAsString("Macro"); },
199+
[&]() { OS << ' '; });
200+
if (!Attributes.empty())
201+
OS << ' ';
202+
}
203+
204+
static void emitUndefsForAttributeMacros(const AttributeMap &MacroAttr,
205+
llvm::raw_ostream &OS) {
206+
if (!MacroAttr.empty())
207+
OS << '\n';
208+
for (auto &[Macro, Attr] : MacroAttr)
209+
OS << "#undef " << Macro << '\n';
210+
}
211+
212+
static void writeAPIFromIndex(APIIndexer &G,
213+
std::vector<std::string> EntrypointNameList,
214+
llvm::raw_ostream &OS) {
53215
for (auto &Pair : G.MacroDefsMap) {
54216
const std::string &Name = Pair.first;
55-
if (G.MacroSpecMap.find(Name) == G.MacroSpecMap.end())
217+
if (!G.MacroSpecMap.count(Name))
56218
llvm::PrintFatalError(Name + " not found in any standard spec.\n");
57219

58220
llvm::Record *MacroDef = Pair.second;
@@ -62,7 +224,7 @@ void writeAPIFromIndex(APIIndexer &G,
62224
}
63225

64226
for (auto &TypeName : G.RequiredTypes) {
65-
if (G.TypeSpecMap.find(TypeName) == G.TypeSpecMap.end())
227+
if (!G.TypeSpecMap.count(TypeName))
66228
llvm::PrintFatalError(TypeName + " not found in any standard spec.\n");
67229
OS << "#include <llvm-libc-types/" << getTypeHdrName(TypeName) << ".h>\n";
68230
}
@@ -71,7 +233,7 @@ void writeAPIFromIndex(APIIndexer &G,
71233
if (G.Enumerations.size() != 0)
72234
OS << "enum {" << '\n';
73235
for (const auto &Name : G.Enumerations) {
74-
if (G.EnumerationSpecMap.find(Name) == G.EnumerationSpecMap.end())
236+
if (!G.EnumerationSpecMap.count(Name))
75237
llvm::PrintFatalError(
76238
Name + " is not listed as an enumeration in any standard spec.\n");
77239

@@ -87,18 +249,25 @@ void writeAPIFromIndex(APIIndexer &G,
87249
if (G.Enumerations.size() != 0)
88250
OS << "};\n\n";
89251

252+
// Collect and declare macros for attributes
253+
AttributeMap MacroAttr =
254+
collectAttributeMacros(G.FunctionSpecMap, EntrypointNameList);
255+
emitAttributeMacroDecls(MacroAttr, OS);
256+
90257
OS << "__BEGIN_C_DECLS\n\n";
91258
for (auto &Name : EntrypointNameList) {
92-
if (G.FunctionSpecMap.find(Name) == G.FunctionSpecMap.end()) {
93-
continue; // Functions that aren't in this header file are skipped as
94-
// opposed to erroring out because the list of functions being
95-
// iterated over is the complete list of functions with
96-
// entrypoints. Thus this is filtering out the functions that
97-
// don't go to this header file, whereas the other, similar
98-
// conditionals above are more of a sanity check.
99-
}
259+
auto Iter = G.FunctionSpecMap.find(Name);
100260

101-
llvm::Record *FunctionSpec = G.FunctionSpecMap[Name];
261+
// Functions that aren't in this header file are skipped as
262+
// opposed to erroring out because the list of functions being
263+
// iterated over is the complete list of functions with
264+
// entrypoints. Thus this is filtering out the functions that
265+
// don't go to this header file, whereas the other, similar
266+
// conditionals above are more of a sanity check.
267+
if (Iter == G.FunctionSpecMap.end())
268+
continue;
269+
270+
llvm::Record *FunctionSpec = Iter->second;
102271
llvm::Record *RetValSpec = FunctionSpec->getValueAsDef("Return");
103272
llvm::Record *ReturnType = RetValSpec->getValueAsDef("ReturnType");
104273

@@ -110,6 +279,8 @@ void writeAPIFromIndex(APIIndexer &G,
110279
if (Guarded)
111280
OS << "#ifdef " << FunctionSpec->getValueAsString("Guard") << "\n";
112281

282+
// Emit attribute macros for the function. Space is automatically added.
283+
emitAttributeMacroForFunction(FunctionSpec, OS);
113284
OS << G.getTypeAsString(ReturnType) << " " << Name << "(";
114285

115286
auto ArgsList = FunctionSpec->getValueAsListOfDefs("Args");
@@ -130,13 +301,17 @@ void writeAPIFromIndex(APIIndexer &G,
130301

131302
// Make another pass over entrypoints to emit object declarations.
132303
for (const auto &Name : EntrypointNameList) {
133-
if (G.ObjectSpecMap.find(Name) == G.ObjectSpecMap.end())
304+
auto Iter = G.ObjectSpecMap.find(Name);
305+
if (Iter == G.ObjectSpecMap.end())
134306
continue;
135-
llvm::Record *ObjectSpec = G.ObjectSpecMap[Name];
307+
llvm::Record *ObjectSpec = Iter->second;
136308
auto Type = ObjectSpec->getValueAsString("Type");
137309
OS << "extern " << Type << " " << Name << ";\n";
138310
}
139311
OS << "__END_C_DECLS\n";
312+
313+
// Undef file-level attribute macros.
314+
emitUndefsForAttributeMacros(MacroAttr, OS);
140315
}
141316

142317
void writePublicAPI(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) {}
@@ -147,9 +322,8 @@ void PublicAPICommand::run(llvm::raw_ostream &OS, const ArgVector &Args,
147322
llvm::StringRef StdHeader,
148323
llvm::RecordKeeper &Records,
149324
const Command::ErrorReporter &Reporter) const {
150-
if (Args.size() != 0) {
325+
if (Args.size() != 0)
151326
Reporter.printFatalError("public_api command does not take any arguments.");
152-
}
153327

154328
APIIndexer G(StdHeader, Records);
155329
writeAPIFromIndex(G, EntrypointNameList, OS);

libc/utils/HdrGen/PublicAPICommand.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class RecordKeeper;
2525

2626
namespace llvm_libc {
2727

28+
enum class AttributeStyle { Cxx11 = 0, Gnu = 1, Declspec = 2 };
29+
enum class AttributeNamespace { None = 0, Clang = 1, Gnu = 2 };
30+
2831
class PublicAPICommand : public Command {
2932
private:
3033
const std::vector<std::string> &EntrypointNameList;

0 commit comments

Comments
 (0)