[Attributes][HLSL] Teach EnumArgument to refer to an external enum (#70835)

Rather than write a bunch of logic to shepherd between enums with the
same sets of values, add the ability for EnumArgument to refer to an
external enum in the first place.
This commit is contained in:
Justin Bogner 2023-11-01 11:24:48 -07:00 committed by GitHub
parent f2c24cceed
commit 1c6c01fbde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 154 deletions

View File

@ -322,7 +322,7 @@ std::error_code ModularizeUtilities::loadModuleMap(
// Walks the modules and collects referenced headers into
// HeaderFileNames.
bool ModularizeUtilities::collectModuleMapHeaders(clang::ModuleMap *ModMap) {
SmallVector<std::pair<StringRef, const Module *>, 0> Vec;
SmallVector<std::pair<StringRef, const clang::Module *>, 0> Vec;
for (auto &M : ModMap->modules())
Vec.emplace_back(M.first(), M.second);
llvm::sort(Vec, llvm::less_first());
@ -349,14 +349,14 @@ bool ModularizeUtilities::collectModuleHeaders(const clang::Module &Mod) {
for (auto *Submodule : Mod.submodules())
collectModuleHeaders(*Submodule);
if (std::optional<Module::Header> UmbrellaHeader =
if (std::optional<clang::Module::Header> UmbrellaHeader =
Mod.getUmbrellaHeaderAsWritten()) {
std::string HeaderPath = getCanonicalPath(UmbrellaHeader->Entry.getName());
// Collect umbrella header.
HeaderFileNames.push_back(HeaderPath);
// FUTURE: When needed, umbrella header header collection goes here.
} else if (std::optional<Module::DirectoryName> UmbrellaDir =
} else if (std::optional<clang::Module::DirectoryName> UmbrellaDir =
Mod.getUmbrellaDirAsWritten()) {
// If there normal headers, assume these are umbrellas and skip collection.
if (Mod.Headers->size() == 0) {

View File

@ -19,11 +19,12 @@
#include "clang/AST/Type.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/AttributeCommonInfo.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/OpenMPKinds.h"
#include "clang/Basic/Sanitizers.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/Frontend/HLSL/HLSLResource.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/raw_ostream.h"

View File

@ -277,23 +277,28 @@ class DefaultIntArgument<string name, int default> : IntArgument<name, 1> {
int Default = default;
}
// This argument is more complex, it includes the enumerator type name,
// a list of strings to accept, and a list of enumerators to map them to.
// This argument is more complex, it includes the enumerator type
// name, whether the enum type is externally defined, a list of
// strings to accept, and a list of enumerators to map them to.
class EnumArgument<string name, string type, list<string> values,
list<string> enums, bit opt = 0, bit fake = 0>
list<string> enums, bit opt = 0, bit fake = 0,
bit isExternalType = 0>
: Argument<name, opt, fake> {
string Type = type;
list<string> Values = values;
list<string> Enums = enums;
bit IsExternalType = isExternalType;
}
// FIXME: There should be a VariadicArgument type that takes any other type
// of argument and generates the appropriate type.
class VariadicEnumArgument<string name, string type, list<string> values,
list<string> enums> : Argument<name, 1> {
list<string> enums, bit isExternalType = 0>
: Argument<name, 1> {
string Type = type;
list<string> Values = values;
list<string> Enums = enums;
bit IsExternalType = isExternalType;
}
// This handles one spelling of an attribute.
@ -4182,26 +4187,26 @@ def HLSLResource : InheritableAttr {
let Spellings = [];
let Subjects = SubjectList<[Struct]>;
let LangOpts = [HLSL];
let Args = [EnumArgument<"ResourceType", "ResourceClass",
let Args = [EnumArgument<"ResourceClass", "llvm::hlsl::ResourceClass",
["SRV", "UAV", "CBuffer", "Sampler"],
["SRV", "UAV", "CBuffer", "Sampler"]
>,
EnumArgument<"ResourceShape", "ResourceKind",
["SRV", "UAV", "CBuffer", "Sampler"],
/*opt=*/0, /*fake=*/0, /*isExternalType=*/1>,
EnumArgument<"ResourceKind", "llvm::hlsl::ResourceKind",
["Texture1D", "Texture2D", "Texture2DMS",
"Texture3D", "TextureCube", "Texture1DArray",
"Texture2DArray", "Texture2DMSArray",
"TextureCubeArray", "TypedBuffer", "RawBuffer",
"StructuredBuffer", "CBufferKind", "SamplerKind",
"TBuffer", "RTAccelerationStructure", "FeedbackTexture2D",
"FeedbackTexture2DArray"],
"StructuredBuffer", "CBuffer", "Sampler",
"TBuffer", "RTAccelerationStructure",
"FeedbackTexture2D", "FeedbackTexture2DArray"],
["Texture1D", "Texture2D", "Texture2DMS",
"Texture3D", "TextureCube", "Texture1DArray",
"Texture2DArray", "Texture2DMSArray",
"TextureCubeArray", "TypedBuffer", "RawBuffer",
"StructuredBuffer", "CBufferKind", "SamplerKind",
"TBuffer", "RTAccelerationStructure", "FeedbackTexture2D",
"FeedbackTexture2DArray"]
>
"StructuredBuffer", "CBuffer", "Sampler",
"TBuffer", "RTAccelerationStructure",
"FeedbackTexture2D", "FeedbackTexture2DArray"],
/*opt=*/0, /*fake=*/0, /*isExternalType=*/1>
];
let Documentation = [InternalOnly];
}

View File

@ -223,56 +223,6 @@ void CGHLSLRuntime::addBufferResourceAnnotation(llvm::GlobalVariable *GV,
ResourceMD->addOperand(Res.getMetadata());
}
static llvm::hlsl::ResourceKind
castResourceShapeToResourceKind(HLSLResourceAttr::ResourceKind RK) {
switch (RK) {
case HLSLResourceAttr::ResourceKind::Texture1D:
return llvm::hlsl::ResourceKind::Texture1D;
case HLSLResourceAttr::ResourceKind::Texture2D:
return llvm::hlsl::ResourceKind::Texture2D;
case HLSLResourceAttr::ResourceKind::Texture2DMS:
return llvm::hlsl::ResourceKind::Texture2DMS;
case HLSLResourceAttr::ResourceKind::Texture3D:
return llvm::hlsl::ResourceKind::Texture3D;
case HLSLResourceAttr::ResourceKind::TextureCube:
return llvm::hlsl::ResourceKind::TextureCube;
case HLSLResourceAttr::ResourceKind::Texture1DArray:
return llvm::hlsl::ResourceKind::Texture1DArray;
case HLSLResourceAttr::ResourceKind::Texture2DArray:
return llvm::hlsl::ResourceKind::Texture2DArray;
case HLSLResourceAttr::ResourceKind::Texture2DMSArray:
return llvm::hlsl::ResourceKind::Texture2DMSArray;
case HLSLResourceAttr::ResourceKind::TextureCubeArray:
return llvm::hlsl::ResourceKind::TextureCubeArray;
case HLSLResourceAttr::ResourceKind::TypedBuffer:
return llvm::hlsl::ResourceKind::TypedBuffer;
case HLSLResourceAttr::ResourceKind::RawBuffer:
return llvm::hlsl::ResourceKind::RawBuffer;
case HLSLResourceAttr::ResourceKind::StructuredBuffer:
return llvm::hlsl::ResourceKind::StructuredBuffer;
case HLSLResourceAttr::ResourceKind::CBufferKind:
return llvm::hlsl::ResourceKind::CBuffer;
case HLSLResourceAttr::ResourceKind::SamplerKind:
return llvm::hlsl::ResourceKind::Sampler;
case HLSLResourceAttr::ResourceKind::TBuffer:
return llvm::hlsl::ResourceKind::TBuffer;
case HLSLResourceAttr::ResourceKind::RTAccelerationStructure:
return llvm::hlsl::ResourceKind::RTAccelerationStructure;
case HLSLResourceAttr::ResourceKind::FeedbackTexture2D:
return llvm::hlsl::ResourceKind::FeedbackTexture2D;
case HLSLResourceAttr::ResourceKind::FeedbackTexture2DArray:
return llvm::hlsl::ResourceKind::FeedbackTexture2DArray;
}
// Make sure to update HLSLResourceAttr::ResourceKind when add new Kind to
// hlsl::ResourceKind. Assume FeedbackTexture2DArray is the last enum for
// HLSLResourceAttr::ResourceKind.
static_assert(
static_cast<uint32_t>(
HLSLResourceAttr::ResourceKind::FeedbackTexture2DArray) ==
(static_cast<uint32_t>(llvm::hlsl::ResourceKind::NumEntries) - 2));
llvm_unreachable("all switch cases should be covered");
}
void CGHLSLRuntime::annotateHLSLResource(const VarDecl *D, GlobalVariable *GV) {
const Type *Ty = D->getType()->getPointeeOrArrayElementType();
if (!Ty)
@ -284,15 +234,12 @@ void CGHLSLRuntime::annotateHLSLResource(const VarDecl *D, GlobalVariable *GV) {
if (!Attr)
return;
HLSLResourceAttr::ResourceClass RC = Attr->getResourceType();
llvm::hlsl::ResourceKind RK =
castResourceShapeToResourceKind(Attr->getResourceShape());
llvm::hlsl::ResourceClass RC = Attr->getResourceClass();
llvm::hlsl::ResourceKind RK = Attr->getResourceKind();
QualType QT(Ty, 0);
BufferResBinding Binding(D->getAttr<HLSLResourceBindingAttr>());
addBufferResourceAnnotation(GV, QT.getAsString(),
static_cast<llvm::hlsl::ResourceClass>(RC), RK,
Binding);
addBufferResourceAnnotation(GV, QT.getAsString(), RC, RK, Binding);
}
CGHLSLRuntime::BufferResBinding::BufferResBinding(

View File

@ -115,9 +115,8 @@ struct BuiltinTypeDeclBuilder {
return addMemberVariable("h", Ty, Access);
}
BuiltinTypeDeclBuilder &
annotateResourceClass(HLSLResourceAttr::ResourceClass RC,
HLSLResourceAttr::ResourceKind RK) {
BuiltinTypeDeclBuilder &annotateResourceClass(ResourceClass RC,
ResourceKind RK) {
if (Record->isCompleteDefinition())
return *this;
Record->addAttr(
@ -503,7 +502,6 @@ void HLSLExternalSemaSource::completeBufferType(CXXRecordDecl *Record) {
.addHandleMember()
.addDefaultHandleConstructor(*SemaPtr, ResourceClass::UAV)
.addArraySubscriptOperators()
.annotateResourceClass(HLSLResourceAttr::UAV,
HLSLResourceAttr::TypedBuffer)
.annotateResourceClass(ResourceClass::UAV, ResourceKind::TypedBuffer)
.completeDefinition();
}

View File

@ -118,7 +118,7 @@ public:
if (!ID)
return true;
Module *M = ID->getImportedModule();
clang::Module *M = ID->getImportedModule();
assert(M);
if (M->Name != "R")
return true;

View File

@ -898,15 +898,25 @@ namespace {
}
class EnumArgument : public Argument {
std::string type;
std::string fullType;
StringRef shortType;
std::vector<StringRef> values, enums, uniques;
bool isExternal;
public:
EnumArgument(const Record &Arg, StringRef Attr)
: Argument(Arg, Attr), type(std::string(Arg.getValueAsString("Type"))),
values(Arg.getValueAsListOfStrings("Values")),
: Argument(Arg, Attr), values(Arg.getValueAsListOfStrings("Values")),
enums(Arg.getValueAsListOfStrings("Enums")),
uniques(uniqueEnumsInOrder(enums)) {
uniques(uniqueEnumsInOrder(enums)),
isExternal(Arg.getValueAsBit("IsExternalType")) {
StringRef Type = Arg.getValueAsString("Type");
shortType = isExternal ? Type.rsplit("::").second : Type;
// If shortType didn't contain :: at all rsplit will give us an empty
// string.
if (shortType.empty())
shortType = Type;
fullType = isExternal ? Type : (getAttrName() + "Attr::" + Type).str();
// FIXME: Emit a proper error
assert(!uniques.empty());
}
@ -914,7 +924,7 @@ namespace {
bool isEnumArg() const override { return true; }
void writeAccessors(raw_ostream &OS) const override {
OS << " " << type << " get" << getUpperName() << "() const {\n";
OS << " " << fullType << " get" << getUpperName() << "() const {\n";
OS << " return " << getLowerName() << ";\n";
OS << " }";
}
@ -930,30 +940,32 @@ namespace {
OS << getLowerName() << "(" << getUpperName() << ")";
}
void writeCtorDefaultInitializers(raw_ostream &OS) const override {
OS << getLowerName() << "(" << type << "(0))";
OS << getLowerName() << "(" << fullType << "(0))";
}
void writeCtorParameters(raw_ostream &OS) const override {
OS << type << " " << getUpperName();
OS << fullType << " " << getUpperName();
}
void writeDeclarations(raw_ostream &OS) const override {
auto i = uniques.cbegin(), e = uniques.cend();
// The last one needs to not have a comma.
--e;
if (!isExternal) {
auto i = uniques.cbegin(), e = uniques.cend();
// The last one needs to not have a comma.
--e;
OS << "public:\n";
OS << " enum " << shortType << " {\n";
for (; i != e; ++i)
OS << " " << *i << ",\n";
OS << " " << *e << "\n";
OS << " };\n";
}
OS << "public:\n";
OS << " enum " << type << " {\n";
for (; i != e; ++i)
OS << " " << *i << ",\n";
OS << " " << *e << "\n";
OS << " };\n";
OS << "private:\n";
OS << " " << type << " " << getLowerName() << ";";
OS << " " << fullType << " " << getLowerName() << ";";
}
void writePCHReadDecls(raw_ostream &OS) const override {
OS << " " << getAttrName() << "Attr::" << type << " " << getLowerName()
<< "(static_cast<" << getAttrName() << "Attr::" << type
<< ">(Record.readInt()));\n";
OS << " " << fullType << " " << getLowerName() << "(static_cast<"
<< fullType << ">(Record.readInt()));\n";
}
void writePCHReadArgs(raw_ostream &OS) const override {
@ -961,46 +973,50 @@ namespace {
}
void writePCHWrite(raw_ostream &OS) const override {
OS << "Record.push_back(SA->get" << getUpperName() << "());\n";
OS << "Record.push_back(static_cast<uint64_t>(SA->get" << getUpperName()
<< "()));\n";
}
void writeValue(raw_ostream &OS) const override {
// FIXME: this isn't 100% correct -- some enum arguments require printing
// as a string literal, while others require printing as an identifier.
// Tablegen currently does not distinguish between the two forms.
OS << "\\\"\" << " << getAttrName() << "Attr::Convert" << type << "ToStr(get"
<< getUpperName() << "()) << \"\\\"";
OS << "\\\"\" << " << getAttrName() << "Attr::Convert" << shortType
<< "ToStr(get" << getUpperName() << "()) << \"\\\"";
}
void writeDump(raw_ostream &OS) const override {
OS << " switch(SA->get" << getUpperName() << "()) {\n";
for (const auto &I : uniques) {
OS << " case " << getAttrName() << "Attr::" << I << ":\n";
OS << " case " << fullType << "::" << I << ":\n";
OS << " OS << \" " << I << "\";\n";
OS << " break;\n";
}
if (isExternal) {
OS << " default:\n";
OS << " llvm_unreachable(\"Invalid attribute value\");\n";
}
OS << " }\n";
}
void writeConversion(raw_ostream &OS, bool Header) const {
if (Header) {
OS << " static bool ConvertStrTo" << type << "(StringRef Val, " << type
<< " &Out);\n";
OS << " static const char *Convert" << type << "ToStr(" << type
<< " Val);\n";
OS << " static bool ConvertStrTo" << shortType << "(StringRef Val, "
<< fullType << " &Out);\n";
OS << " static const char *Convert" << shortType << "ToStr("
<< fullType << " Val);\n";
return;
}
OS << "bool " << getAttrName() << "Attr::ConvertStrTo" << type
<< "(StringRef Val, " << type << " &Out) {\n";
OS << " std::optional<" << type
<< "> R = llvm::StringSwitch<std::optional<";
OS << type << ">>(Val)\n";
OS << "bool " << getAttrName() << "Attr::ConvertStrTo" << shortType
<< "(StringRef Val, " << fullType << " &Out) {\n";
OS << " std::optional<" << fullType << "> "
<< "R = llvm::StringSwitch<std::optional<" << fullType << ">>(Val)\n";
for (size_t I = 0; I < enums.size(); ++I) {
OS << " .Case(\"" << values[I] << "\", ";
OS << getAttrName() << "Attr::" << enums[I] << ")\n";
OS << fullType << "::" << enums[I] << ")\n";
}
OS << " .Default(std::optional<" << type << ">());\n";
OS << " .Default(std::optional<" << fullType << ">());\n";
OS << " if (R) {\n";
OS << " Out = *R;\n return true;\n }\n";
OS << " return false;\n";
@ -1010,14 +1026,17 @@ namespace {
// trivial because some enumeration values have multiple named
// enumerators, such as type_visibility(internal) and
// type_visibility(hidden) both mapping to TypeVisibilityAttr::Hidden.
OS << "const char *" << getAttrName() << "Attr::Convert" << type
<< "ToStr(" << type << " Val) {\n"
OS << "const char *" << getAttrName() << "Attr::Convert" << shortType
<< "ToStr(" << fullType << " Val) {\n"
<< " switch(Val) {\n";
SmallDenseSet<StringRef, 8> Uniques;
for (size_t I = 0; I < enums.size(); ++I) {
if (Uniques.insert(enums[I]).second)
OS << " case " << getAttrName() << "Attr::" << enums[I]
<< ": return \"" << values[I] << "\";\n";
OS << " case " << fullType << "::" << enums[I] << ": return \""
<< values[I] << "\";\n";
}
if (isExternal) {
OS << " default: llvm_unreachable(\"Invalid attribute value\");\n";
}
OS << " }\n"
<< " llvm_unreachable(\"No enumerator with that value\");\n"
@ -1026,27 +1045,36 @@ namespace {
};
class VariadicEnumArgument: public VariadicArgument {
std::string type, QualifiedTypeName;
std::string fullType;
StringRef shortType;
std::vector<StringRef> values, enums, uniques;
bool isExternal;
protected:
void writeValueImpl(raw_ostream &OS) const override {
// FIXME: this isn't 100% correct -- some enum arguments require printing
// as a string literal, while others require printing as an identifier.
// Tablegen currently does not distinguish between the two forms.
OS << " OS << \"\\\"\" << " << getAttrName() << "Attr::Convert" << type
<< "ToStr(Val)" << "<< \"\\\"\";\n";
OS << " OS << \"\\\"\" << " << getAttrName() << "Attr::Convert"
<< shortType << "ToStr(Val)"
<< "<< \"\\\"\";\n";
}
public:
VariadicEnumArgument(const Record &Arg, StringRef Attr)
: VariadicArgument(Arg, Attr,
std::string(Arg.getValueAsString("Type"))),
type(std::string(Arg.getValueAsString("Type"))),
values(Arg.getValueAsListOfStrings("Values")),
enums(Arg.getValueAsListOfStrings("Enums")),
uniques(uniqueEnumsInOrder(enums)) {
QualifiedTypeName = getAttrName().str() + "Attr::" + type;
uniques(uniqueEnumsInOrder(enums)),
isExternal(Arg.getValueAsBit("IsExternalType")) {
StringRef Type = Arg.getValueAsString("Type");
shortType = isExternal ? Type.rsplit("::").second : Type;
// If shortType didn't contain :: at all rsplit will give us an empty
// string.
if (shortType.empty())
shortType = Type;
fullType = isExternal ? Type : (getAttrName() + "Attr::" + Type).str();
// FIXME: Emit a proper error
assert(!uniques.empty());
@ -1055,16 +1083,18 @@ namespace {
bool isVariadicEnumArg() const override { return true; }
void writeDeclarations(raw_ostream &OS) const override {
auto i = uniques.cbegin(), e = uniques.cend();
// The last one needs to not have a comma.
--e;
if (!isExternal) {
auto i = uniques.cbegin(), e = uniques.cend();
// The last one needs to not have a comma.
--e;
OS << "public:\n";
OS << " enum " << type << " {\n";
for (; i != e; ++i)
OS << " " << *i << ",\n";
OS << " " << *e << "\n";
OS << " };\n";
OS << "public:\n";
OS << " enum " << shortType << " {\n";
for (; i != e; ++i)
OS << " " << *i << ",\n";
OS << " " << *e << "\n";
OS << " };\n";
}
OS << "private:\n";
VariadicArgument::writeDeclarations(OS);
@ -1076,7 +1106,7 @@ namespace {
<< getLowerName() << "_end(); I != E; ++I) {\n";
OS << " switch(*I) {\n";
for (const auto &UI : uniques) {
OS << " case " << getAttrName() << "Attr::" << UI << ":\n";
OS << " case " << fullType << "::" << UI << ":\n";
OS << " OS << \" " << UI << "\";\n";
OS << " break;\n";
}
@ -1086,13 +1116,13 @@ namespace {
void writePCHReadDecls(raw_ostream &OS) const override {
OS << " unsigned " << getLowerName() << "Size = Record.readInt();\n";
OS << " SmallVector<" << QualifiedTypeName << ", 4> " << getLowerName()
OS << " SmallVector<" << fullType << ", 4> " << getLowerName()
<< ";\n";
OS << " " << getLowerName() << ".reserve(" << getLowerName()
<< "Size);\n";
OS << " for (unsigned i = " << getLowerName() << "Size; i; --i)\n";
OS << " " << getLowerName() << ".push_back(" << "static_cast<"
<< QualifiedTypeName << ">(Record.readInt()));\n";
OS << " " << getLowerName() << ".push_back("
<< "static_cast<" << fullType << ">(Record.readInt()));\n";
}
void writePCHWrite(raw_ostream &OS) const override {
@ -1100,42 +1130,42 @@ namespace {
OS << " for (" << getAttrName() << "Attr::" << getLowerName()
<< "_iterator i = SA->" << getLowerName() << "_begin(), e = SA->"
<< getLowerName() << "_end(); i != e; ++i)\n";
OS << " " << WritePCHRecord(QualifiedTypeName, "(*i)");
OS << " " << WritePCHRecord(fullType, "(*i)");
}
void writeConversion(raw_ostream &OS, bool Header) const {
if (Header) {
OS << " static bool ConvertStrTo" << type << "(StringRef Val, " << type
<< " &Out);\n";
OS << " static const char *Convert" << type << "ToStr(" << type
<< " Val);\n";
OS << " static bool ConvertStrTo" << shortType << "(StringRef Val, "
<< fullType << " &Out);\n";
OS << " static const char *Convert" << shortType << "ToStr("
<< fullType << " Val);\n";
return;
}
OS << "bool " << getAttrName() << "Attr::ConvertStrTo" << type
OS << "bool " << getAttrName() << "Attr::ConvertStrTo" << shortType
<< "(StringRef Val, ";
OS << type << " &Out) {\n";
OS << " std::optional<" << type
OS << fullType << " &Out) {\n";
OS << " std::optional<" << fullType
<< "> R = llvm::StringSwitch<std::optional<";
OS << type << ">>(Val)\n";
OS << fullType << ">>(Val)\n";
for (size_t I = 0; I < enums.size(); ++I) {
OS << " .Case(\"" << values[I] << "\", ";
OS << getAttrName() << "Attr::" << enums[I] << ")\n";
OS << fullType << "::" << enums[I] << ")\n";
}
OS << " .Default(std::optional<" << type << ">());\n";
OS << " .Default(std::optional<" << fullType << ">());\n";
OS << " if (R) {\n";
OS << " Out = *R;\n return true;\n }\n";
OS << " return false;\n";
OS << "}\n\n";
OS << "const char *" << getAttrName() << "Attr::Convert" << type
<< "ToStr(" << type << " Val) {\n"
OS << "const char *" << getAttrName() << "Attr::Convert" << shortType
<< "ToStr(" << fullType << " Val) {\n"
<< " switch(Val) {\n";
SmallDenseSet<StringRef, 8> Uniques;
for (size_t I = 0; I < enums.size(); ++I) {
if (Uniques.insert(enums[I]).second)
OS << " case " << getAttrName() << "Attr::" << enums[I]
<< ": return \"" << values[I] << "\";\n";
OS << " case " << fullType << "::" << enums[I] << ": return \""
<< values[I] << "\";\n";
}
OS << " }\n"
<< " llvm_unreachable(\"No enumerator with that value\");\n"