llvm-project/clang/utils/TableGen/ClangBuiltinsEmitter.cpp
Joseph Huber 74ef2f0666
[AMDGPU] Fix type signature of pointers to vectors in builtins file (#179965)
Summary:
The script used to port this file incorrectly applied the pointer values
to the elements of the vector rather than the vector itself. This
resulted in the same type signature, but the text was confusing to read.
Furthermore, the current parser actually failed in these cases so it had
to be addressed.

After updating the script and the parser, this should be the more
understandable format.
2026-02-05 11:59:35 -06:00

608 lines
20 KiB
C++

//===-- ClangBuiltinsEmitter.cpp - Generate Clang builtins tables ---------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend emits Clang's builtins tables.
//
//===----------------------------------------------------------------------===//
#include "TableGenBackends.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/StringToOffsetTable.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <sstream>
using namespace llvm;
namespace {
enum class BuiltinType {
Builtin,
AtomicBuiltin,
LibBuiltin,
LangBuiltin,
TargetBuiltin,
TargetLibBuiltin,
};
class HeaderNameParser {
public:
HeaderNameParser(const Record *Builtin) {
for (char c : Builtin->getValueAsString("Header")) {
if (std::islower(c))
HeaderName += static_cast<char>(std::toupper(c));
else if (c == '.' || c == '_' || c == '/' || c == '-')
HeaderName += '_';
else
PrintFatalError(Builtin->getLoc(), "Unexpected header name");
}
}
void Print(raw_ostream &OS) const { OS << HeaderName; }
private:
std::string HeaderName;
};
struct Builtin {
BuiltinType BT;
std::string Name;
std::string Type;
std::string Attributes;
const Record *BuiltinRecord;
void EmitEnumerator(llvm::raw_ostream &OS) const {
OS << " BI";
// If there is a required name prefix, include its spelling in the
// enumerator.
if (auto *PrefixRecord =
BuiltinRecord->getValueAsOptionalDef("RequiredNamePrefix"))
OS << PrefixRecord->getValueAsString("Spelling");
OS << Name << ",\n";
}
void EmitInfo(llvm::raw_ostream &OS, const StringToOffsetTable &Table) const {
OS << " Builtin::Info{Builtin::Info::StrOffsets{"
<< Table.GetStringOffset(Name) << " /* " << Name << " */, "
<< Table.GetStringOffset(Type) << " /* " << Type << " */, "
<< Table.GetStringOffset(Attributes) << " /* " << Attributes << " */, ";
if (BT == BuiltinType::TargetBuiltin) {
const auto &Features = BuiltinRecord->getValueAsString("Features");
OS << Table.GetStringOffset(Features) << " /* " << Features << " */";
} else {
OS << "0";
}
OS << "}, ";
if (BT == BuiltinType::LibBuiltin || BT == BuiltinType::TargetLibBuiltin) {
OS << "HeaderDesc::";
HeaderNameParser{BuiltinRecord}.Print(OS);
} else {
OS << "HeaderDesc::NO_HEADER";
}
OS << ", ";
if (BT == BuiltinType::LibBuiltin || BT == BuiltinType::LangBuiltin ||
BT == BuiltinType::TargetLibBuiltin) {
OS << BuiltinRecord->getValueAsString("Languages");
} else {
OS << "ALL_LANGUAGES";
}
OS << "},\n";
}
void EmitXMacro(llvm::raw_ostream &OS) const {
if (BuiltinRecord->getValueAsBit("RequiresUndef"))
OS << "#undef " << Name << '\n';
switch (BT) {
case BuiltinType::LibBuiltin:
OS << "LIBBUILTIN";
break;
case BuiltinType::LangBuiltin:
OS << "LANGBUILTIN";
break;
case BuiltinType::Builtin:
OS << "BUILTIN";
break;
case BuiltinType::AtomicBuiltin:
OS << "ATOMIC_BUILTIN";
break;
case BuiltinType::TargetBuiltin:
OS << "TARGET_BUILTIN";
break;
case BuiltinType::TargetLibBuiltin:
OS << "TARGET_HEADER_BUILTIN";
break;
}
OS << "(" << Name << ", \"" << Type << "\", \"" << Attributes << "\"";
switch (BT) {
case BuiltinType::LibBuiltin: {
OS << ", ";
HeaderNameParser{BuiltinRecord}.Print(OS);
[[fallthrough]];
}
case BuiltinType::LangBuiltin: {
OS << ", " << BuiltinRecord->getValueAsString("Languages");
break;
}
case BuiltinType::TargetLibBuiltin: {
OS << ", ";
HeaderNameParser{BuiltinRecord}.Print(OS);
OS << ", " << BuiltinRecord->getValueAsString("Languages");
[[fallthrough]];
}
case BuiltinType::TargetBuiltin: {
OS << ", \"" << BuiltinRecord->getValueAsString("Features") << "\"";
break;
}
case BuiltinType::AtomicBuiltin:
case BuiltinType::Builtin:
break;
}
OS << ")\n";
}
};
class PrototypeParser {
public:
PrototypeParser(StringRef Substitution, const Record *Builtin)
: Loc(Builtin->getFieldLoc("Prototype")), Substitution(Substitution),
EnableOpenCLLong(Builtin->getValueAsBit("EnableOpenCLLong")) {
ParsePrototype(Builtin->getValueAsString("Prototype"));
}
std::string takeTypeString() && { return std::move(Type); }
private:
void ParsePrototype(StringRef Prototype) {
Prototype = Prototype.trim();
// Some builtins don't have an expressible prototype, simply emit an empty
// string for them.
if (Prototype.empty()) {
Type = "";
return;
}
ParseTypes(Prototype);
}
void ParseTypes(StringRef &Prototype) {
auto ReturnType = Prototype.take_until([](char c) { return c == '('; });
ParseType(ReturnType);
Prototype = Prototype.drop_front(ReturnType.size() + 1);
if (!Prototype.ends_with(")"))
PrintFatalError(Loc, "Expected closing brace at end of prototype");
Prototype = Prototype.drop_back();
// Look through the input parameters.
const size_t end = Prototype.size();
for (size_t I = 0; I != end;) {
const StringRef Current = Prototype.substr(I, end);
// Skip any leading space or commas
if (Current.starts_with(" ") || Current.starts_with(",")) {
++I;
continue;
}
// Check if we are in _ExtVector. We do this first because
// extended vectors are written in template form with the syntax
// _ExtVector< ..., ...>, so we need to make sure we are not
// detecting the comma of the template class as a separator for
// the parameters of the prototype. Note: the assumption is that
// we cannot have nested _ExtVector.
if (Current.starts_with("_ExtVector<") ||
Current.starts_with("_Vector<")) {
size_t Pos = Current.find('<');
int Depth = 1;
// There may be a nested address_space<...> modifier on the type.
while (Depth > 0 && ++Pos < Current.size()) {
if (Current[Pos] == '<')
++Depth;
else if (Current[Pos] == '>')
--Depth;
}
const size_t EndTemplate = Pos;
ParseType(Current.substr(0, EndTemplate + 1));
// Move the prototype beyond _ExtVector<...>
I += EndTemplate + 1;
continue;
}
// We know that we are past _ExtVector, therefore the first seen
// comma is the boundary of a parameter in the prototype.
if (size_t CommaPos = Current.find(',', 0)) {
if (CommaPos != StringRef::npos) {
StringRef T = Current.substr(0, CommaPos);
ParseType(T);
// Move the prototype beyond the comma.
I += CommaPos + 1;
continue;
}
}
// No more commas, parse final parameter.
ParseType(Current);
I = end;
}
}
void ParseType(StringRef T) {
T = T.trim();
auto ConsumeAddrSpace = [&]() -> std::optional<unsigned> {
T = T.trim();
if (!T.consume_back(">"))
return std::nullopt;
auto Open = T.find_last_of('<');
if (Open == StringRef::npos)
PrintFatalError(Loc, "Mismatched angle-brackets in type");
StringRef ArgStr = T.substr(Open + 1);
T = T.slice(0, Open);
if (!T.consume_back("address_space"))
PrintFatalError(Loc,
"Only `address_space<N>` supported as a parameterized "
"pointer or reference type qualifier");
unsigned Number = 0;
if (ArgStr.getAsInteger(10, Number))
PrintFatalError(
Loc, "Expected an integer argument to the address_space qualifier");
return Number;
};
if (T.consume_back("*")) {
// Pointers may have an address space qualifier immediately before them.
std::optional<unsigned> AS = ConsumeAddrSpace();
// Pointers can apply to already parsed types, like vectors.
if (!T.empty())
ParseType(T);
Type += "*";
if (AS)
Type += std::to_string(*AS);
} else if (T.consume_back("const")) {
ParseType(T);
Type += "C";
} else if (T.consume_back("volatile")) {
ParseType(T);
Type += "D";
} else if (T.consume_back("restrict")) {
ParseType(T);
Type += "R";
} else if (T.consume_back("&")) {
// References may have an address space qualifier immediately before them.
std::optional<unsigned> AS = ConsumeAddrSpace();
ParseType(T);
Type += "&";
if (AS)
Type += std::to_string(*AS);
} else if (T.consume_back(")")) {
ParseType(T);
Type += "&";
} else if (EnableOpenCLLong && T.consume_front("long long")) {
Type += "O";
ParseType(T);
} else if (T.consume_front("long")) {
Type += "L";
ParseType(T);
} else if (T.consume_front("signed")) {
Type += "S";
ParseType(T);
} else if (T.consume_front("unsigned")) {
Type += "U";
ParseType(T);
} else if (T.consume_front("_Complex")) {
Type += "X";
ParseType(T);
} else if (T.consume_front("_Constant")) {
Type += "I";
ParseType(T);
} else if (T.consume_front("T")) {
if (Substitution.empty())
PrintFatalError(Loc, "Not a template");
ParseType(Substitution);
} else if (auto IsExt = T.consume_front("_ExtVector");
IsExt || T.consume_front("_Vector")) {
// Clang extended vector types are mangled as follows:
//
// '_ExtVector<' <lanes> ',' <scalar type> '>'
// Before parsing T(=<scalar type>), make sure the syntax of
// `_ExtVector<N, T>` is correct...
if (!T.consume_front("<"))
PrintFatalError(Loc, "Expected '<' after '_ExtVector'");
unsigned long long Lanes;
if (consumeUnsignedInteger(T, 10, Lanes))
PrintFatalError(Loc, "Expected number of lanes after '_ExtVector<'");
Type += (IsExt ? "E" : "V") + std::to_string(Lanes);
if (!T.consume_front(","))
PrintFatalError(Loc,
"Expected ',' after number of lanes in '_ExtVector<'");
if (!T.consume_back(">"))
PrintFatalError(
Loc, "Expected '>' after scalar type in '_ExtVector<N, type>'");
// ...all good, we can check if we have a valid `<scalar type>`.
ParseType(T);
} else {
auto ReturnTypeVal = StringSwitch<std::string>(T)
.Case("__builtin_va_list_ref", "A")
.Case("__builtin_va_list", "a")
.Case("__float128", "LLd")
.Case("__fp16", "h")
.Case("__hlsl_resource_t", "Qr")
.Case("__amdgpu_buffer_rsrc_t", "Qb")
.Case("__amdgpu_texture_t", "Qt")
.Case("__int128_t", "LLLi")
.Case("_Float16", "x")
.Case("__bf16", "y")
.Case("bool", "b")
.Case("char", "c")
.Case("constant_CFString", "F")
.Case("double", "d")
.Case("FILE", "P")
.Case("float", "f")
.Case("id", "G")
.Case("int", "i")
.Case("int32_t", "Zi")
.Case("int64_t", "Wi")
.Case("jmp_buf", "J")
.Case("msint32_t", "Ni")
.Case("msuint32_t", "UNi")
.Case("objc_super", "M")
.Case("pid_t", "p")
.Case("ptrdiff_t", "Y")
.Case("SEL", "H")
.Case("short", "s")
.Case("sigjmp_buf", "SJ")
.Case("size_t", "z")
.Case("ucontext_t", "K")
.Case("uint32_t", "UZi")
.Case("uint64_t", "UWi")
.Case("void", "v")
.Case("wchar_t", "w")
.Case("...", ".")
.Default("error");
if (ReturnTypeVal == "error")
PrintFatalError(Loc, "Unknown Type: " + T);
Type += ReturnTypeVal;
}
}
SMLoc Loc;
StringRef Substitution;
bool EnableOpenCLLong;
std::string Type;
};
std::string renderAttributes(const Record *Builtin, BuiltinType BT) {
std::string Attributes;
raw_string_ostream OS(Attributes);
if (Builtin->isSubClassOf("LibBuiltin")) {
if (BT == BuiltinType::LibBuiltin) {
OS << 'f';
} else {
OS << 'F';
if (Builtin->getValueAsBit("OnlyBuiltinPrefixedAliasIsConstexpr"))
OS << 'E';
}
}
if (auto NS = Builtin->getValueAsOptionalString("Namespace")) {
if (NS != "std")
PrintFatalError(Builtin->getFieldLoc("Namespace"), "Unknown namespace: ");
OS << "z";
}
for (const auto *Attr : Builtin->getValueAsListOfDefs("Attributes")) {
OS << Attr->getValueAsString("Mangling");
if (Attr->isSubClassOf("IndexedAttribute")) {
OS << ':' << Attr->getValueAsInt("Index") << ':';
} else if (Attr->isSubClassOf("MultiIndexAttribute")) {
OS << '<';
llvm::ListSeparator Sep(",");
for (int64_t Index : Attr->getValueAsListOfInts("Indices"))
OS << Sep << Index;
OS << '>';
}
}
return Attributes;
}
Builtin buildBuiltin(StringRef Substitution, const Record *BuiltinRecord,
Twine Spelling, BuiltinType BT) {
Builtin B;
B.BT = BT;
B.Name = Spelling.str();
B.Type = PrototypeParser(Substitution, BuiltinRecord).takeTypeString();
B.Attributes = renderAttributes(BuiltinRecord, BT);
B.BuiltinRecord = BuiltinRecord;
return B;
}
struct TemplateInsts {
std::vector<std::string> Substitution;
std::vector<std::string> Affix;
bool IsPrefix;
};
TemplateInsts getTemplateInsts(const Record *R) {
TemplateInsts temp;
auto Substitutions = R->getValueAsListOfStrings("Substitutions");
auto Affixes = R->getValueAsListOfStrings("Affixes");
temp.IsPrefix = R->getValueAsBit("AsPrefix");
if (Substitutions.size() != Affixes.size())
PrintFatalError(R->getLoc(), "Substitutions and affixes "
"don't have the same lengths");
for (auto [Affix, Substitution] : zip(Affixes, Substitutions)) {
temp.Substitution.emplace_back(Substitution);
temp.Affix.emplace_back(Affix);
}
return temp;
}
void collectBuiltins(const Record *BuiltinRecord,
SmallVectorImpl<Builtin> &Builtins) {
TemplateInsts Templates = {};
if (BuiltinRecord->isSubClassOf("Template")) {
Templates = getTemplateInsts(BuiltinRecord);
} else {
Templates.Affix.emplace_back();
Templates.Substitution.emplace_back();
}
for (auto [Substitution, Affix] :
zip(Templates.Substitution, Templates.Affix)) {
for (StringRef Spelling :
BuiltinRecord->getValueAsListOfStrings("Spellings")) {
auto FullSpelling =
(Templates.IsPrefix ? Affix + Spelling : Spelling + Affix).str();
BuiltinType BT = BuiltinType::Builtin;
if (BuiltinRecord->isSubClassOf("AtomicBuiltin")) {
BT = BuiltinType::AtomicBuiltin;
} else if (BuiltinRecord->isSubClassOf("LangBuiltin")) {
BT = BuiltinType::LangBuiltin;
} else if (BuiltinRecord->isSubClassOf("TargetLibBuiltin")) {
BT = BuiltinType::TargetLibBuiltin;
} else if (BuiltinRecord->isSubClassOf("TargetBuiltin")) {
BT = BuiltinType::TargetBuiltin;
} else if (BuiltinRecord->isSubClassOf("LibBuiltin")) {
BT = BuiltinType::LibBuiltin;
if (BuiltinRecord->getValueAsBit("AddBuiltinPrefixedAlias"))
Builtins.push_back(buildBuiltin(
Substitution, BuiltinRecord,
std::string("__builtin_") + FullSpelling, BuiltinType::Builtin));
}
Builtins.push_back(
buildBuiltin(Substitution, BuiltinRecord, FullSpelling, BT));
}
}
}
} // namespace
void clang::EmitClangBuiltins(const RecordKeeper &Records, raw_ostream &OS) {
emitSourceFileHeader("List of builtins that Clang recognizes", OS);
SmallVector<Builtin> Builtins;
// AtomicBuiltins are order dependent. Emit them first to make manual checking
// easier and so we can build a special atomic builtin X-macro.
for (const auto *BuiltinRecord :
Records.getAllDerivedDefinitions("AtomicBuiltin"))
collectBuiltins(BuiltinRecord, Builtins);
unsigned NumAtomicBuiltins = Builtins.size();
for (const auto *BuiltinRecord :
Records.getAllDerivedDefinitions("Builtin")) {
if (BuiltinRecord->isSubClassOf("AtomicBuiltin"))
continue;
// Prefixed builtins are also special and we emit them last so they can have
// their own representation that skips the prefix.
if (BuiltinRecord->getValueAsOptionalDef("RequiredNamePrefix"))
continue;
collectBuiltins(BuiltinRecord, Builtins);
}
// Now collect (and count) the prefixed builtins.
unsigned NumPrefixedBuiltins = Builtins.size();
const Record *FirstPrefix = nullptr;
for (const auto *BuiltinRecord :
Records.getAllDerivedDefinitions("Builtin")) {
auto *Prefix = BuiltinRecord->getValueAsOptionalDef("RequiredNamePrefix");
if (!Prefix)
continue;
if (!FirstPrefix)
FirstPrefix = Prefix;
assert(Prefix == FirstPrefix &&
"Multiple distinct prefixes which is not currently supported!");
assert(!BuiltinRecord->isSubClassOf("AtomicBuiltin") &&
"Cannot require a name prefix for an atomic builtin.");
collectBuiltins(BuiltinRecord, Builtins);
}
NumPrefixedBuiltins = Builtins.size() - NumPrefixedBuiltins;
auto AtomicBuiltins = ArrayRef(Builtins).slice(0, NumAtomicBuiltins);
auto UnprefixedBuiltins = ArrayRef(Builtins).drop_back(NumPrefixedBuiltins);
auto PrefixedBuiltins = ArrayRef(Builtins).take_back(NumPrefixedBuiltins);
// Collect strings into a table.
StringToOffsetTable Table;
Table.GetOrAddStringOffset("");
for (const auto &B : Builtins) {
Table.GetOrAddStringOffset(B.Name);
Table.GetOrAddStringOffset(B.Type);
Table.GetOrAddStringOffset(B.Attributes);
if (B.BT == BuiltinType::TargetBuiltin)
Table.GetOrAddStringOffset(B.BuiltinRecord->getValueAsString("Features"));
}
// Emit enumerators.
OS << R"c++(
#ifdef GET_BUILTIN_ENUMERATORS
)c++";
for (const auto &B : Builtins)
B.EmitEnumerator(OS);
OS << R"c++(
#endif // GET_BUILTIN_ENUMERATORS
)c++";
// Emit a string table that can be referenced for these builtins.
OS << R"c++(
#ifdef GET_BUILTIN_STR_TABLE
)c++";
Table.EmitStringTableDef(OS, "BuiltinStrings");
OS << R"c++(
#endif // GET_BUILTIN_STR_TABLE
)c++";
// Emit a direct set of `Builtin::Info` initializers, first for the unprefixed
// builtins and then for the prefixed builtins.
OS << R"c++(
#ifdef GET_BUILTIN_INFOS
)c++";
for (const auto &B : UnprefixedBuiltins)
B.EmitInfo(OS, Table);
OS << R"c++(
#endif // GET_BUILTIN_INFOS
)c++";
OS << R"c++(
#ifdef GET_BUILTIN_PREFIXED_INFOS
)c++";
for (const auto &B : PrefixedBuiltins)
B.EmitInfo(OS, Table);
OS << R"c++(
#endif // GET_BUILTIN_PREFIXED_INFOS
)c++";
// Emit X-macros for the atomic builtins to support various custome patterns
// used exclusively with those builtins.
//
// FIXME: We should eventually move this to a separate file so that users
// don't need to include the full set of builtins.
OS << R"c++(
#ifdef ATOMIC_BUILTIN
)c++";
for (const auto &Builtin : AtomicBuiltins) {
Builtin.EmitXMacro(OS);
}
OS << R"c++(
#endif // ATOMIC_BUILTIN
#undef ATOMIC_BUILTIN
)c++";
}