Skip to content

Commit 167d857

Browse files
author
Tim Foley
authored
Initial support for enum declarations (shader-slang#599)
Slang `enum` declarations will always be scoped, e.g.: ```hlsl enum Color { Red, Green = 2, Blue, } Color c = Color.Red; // Not just `Red` ``` A user can write `enum class` as a placebo for now (to ease sharing of headers with C++). Slang does not currently support the `::` operator for static member lookup, so it must be `Color.Green` and not `Color::Green`. Support for `::` as an alternate syntax could be added later if there is strong user demand. An `enum` type can have a declared "tag type" using syntax like C++ `enum class`: ```hlsl enum MyThings : uint { First = 0, // ... } ``` The `enum` cases will store their values using that type. An `enum` that doesn't declare a tag type will use the type `int` by default. Enum cases are assigned values just like in C/C++: cases can have explicit values, but otherwise default to one more than the previous case, or zero for the first case. All `enum` types will automatically conform to a standard-library `interface` called `__EnumType`, which is used so that basic operators like equality testing can be defined generically for all `enum` types. This change only adds one operator at first (the `==` comparison), but other should be added later. An `enum` case needs to be explicitly converted to an integer where needed (e.g., `int(Color.Red)`). This is implemented by having the main integer types (`int` and `uint`) support built-in initializers that can work for *any* `enum` type (or rather, anything conforming to `__EnumType`). Eventually these will be restricted so that an `enum` type can only be converted to its associated tag type. IR code generation completely eliminates `enum` types and their cases. The `enum` type will be replaced with its tag type, and the cases will be replaced with the tag values. Currently this could leave some mess in the IR where cast operations are applied between values that actually have the same type.
1 parent 7852a2b commit 167d857

14 files changed

+936
-41
lines changed

source/slang/check.cpp

+493-11
Large diffs are not rendered by default.

source/slang/compiler.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ namespace Slang
113113

114114
// The name of the entry point function (e.g., `main`)
115115
Name* name;
116-
117-
// The type names we want to substitute into the
116+
117+
// The type names we want to substitute into the
118118
// global generic type parameters
119119
List<String> genericParameterTypeNames;
120120

@@ -373,7 +373,7 @@ namespace Slang
373373
~CompileRequest();
374374

375375
RefPtr<Expr> parseTypeString(TranslationUnitRequest * translationUnit, String typeStr, RefPtr<Scope> scope);
376-
376+
377377
Type* getTypeFromString(String typeStr);
378378

379379
void parseTranslationUnit(
@@ -526,6 +526,8 @@ namespace Slang
526526
Type* getErrorType();
527527
Type* getStringType();
528528

529+
Type* getEnumTypeType();
530+
529531
// Construct the type `Ptr<valueType>`, where `Ptr`
530532
// is looked up as a builtin type.
531533
RefPtr<PtrType> getPtrType(RefPtr<Type> valueType);
@@ -572,4 +574,4 @@ namespace Slang
572574

573575
}
574576

575-
#endif
577+
#endif

source/slang/core.meta.slang

+45
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithme
2828
__init(float value);
2929
}
3030

31+
// A type resulting from an `enum` declaration.
32+
__magic_type(EnumTypeType)
33+
interface __EnumType
34+
{
35+
// The type of tags for this `enum`
36+
//
37+
// Note: using `__Tag` instead of `Tag` to avoid any
38+
// conflict if a user had an `enum` case called `Tag`
39+
associatedtype __Tag : __BuiltinIntegerType;
40+
};
41+
42+
// A type resulting from an `enum` declaration
43+
// with the `[flags]` attribute.
44+
interface __FlagsEnumType : __EnumType
45+
{
46+
};
47+
3148
__generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right);
3249

3350
__generic<T> __intrinsic_op(select) T operator?:(bool condition, T ifTrue, T ifFalse);
@@ -92,6 +109,28 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt)
92109
sb << "__init(" << kBaseTypes[ss].name << " value);\n";
93110
}
94111

112+
// If this is a basic integer type, then define explicit
113+
// initializers that take a value of an `enum` type.
114+
//
115+
// TODO: This should actually be restricted, so that this
116+
// only applies `where T.__Tag == Self`, but we don't have
117+
// the needed features in our type system to implement
118+
// that constraint right now.
119+
//
120+
switch (kBaseTypes[tt].tag)
121+
{
122+
case BaseType::Int:
123+
case BaseType::UInt:
124+
}}}}
125+
__generic<T:__EnumType>
126+
__init(T value);
127+
${{{{
128+
break;
129+
130+
default:
131+
break;
132+
}
133+
95134
sb << "};\n";
96135
}
97136

@@ -1050,6 +1089,12 @@ for (auto op : binaryOps)
10501089
}
10511090
}}}}
10521091

1092+
// Operators to apply to `enum` types
1093+
1094+
__generic<E : __EnumType>
1095+
__intrinsic_op($(kIROp_Eql))
1096+
bool operator==(E left, E right);
1097+
10531098
// Statement Attributes
10541099

10551100
__attributeTarget(LoopStmt)

source/slang/core.meta.slang.h

+48
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ SLANG_RAW(" // a floating-point value...\n")
2828
SLANG_RAW(" __init(float value);\n")
2929
SLANG_RAW("}\n")
3030
SLANG_RAW("\n")
31+
SLANG_RAW("// A type resulting from an `enum` declaration.\n")
32+
SLANG_RAW("__magic_type(EnumTypeType)\n")
33+
SLANG_RAW("interface __EnumType\n")
34+
SLANG_RAW("{\n")
35+
SLANG_RAW(" // The type of tags for this `enum`\n")
36+
SLANG_RAW(" //\n")
37+
SLANG_RAW(" // Note: using `__Tag` instead of `Tag` to avoid any\n")
38+
SLANG_RAW(" // conflict if a user had an `enum` case called `Tag`\n")
39+
SLANG_RAW(" associatedtype __Tag : __BuiltinIntegerType;\n")
40+
SLANG_RAW("};\n")
41+
SLANG_RAW("\n")
42+
SLANG_RAW("// A type resulting from an `enum` declaration\n")
43+
SLANG_RAW("// with the `[flags]` attribute.\n")
44+
SLANG_RAW("interface __FlagsEnumType : __EnumType\n")
45+
SLANG_RAW("{\n")
46+
SLANG_RAW("};\n")
47+
SLANG_RAW("\n")
3148
SLANG_RAW("__generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right);\n")
3249
SLANG_RAW("\n")
3350
SLANG_RAW("__generic<T> __intrinsic_op(select) T operator?:(bool condition, T ifTrue, T ifFalse);\n")
@@ -92,6 +109,28 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt)
92109
sb << "__init(" << kBaseTypes[ss].name << " value);\n";
93110
}
94111

112+
// If this is a basic integer type, then define explicit
113+
// initializers that take a value of an `enum` type.
114+
//
115+
// TODO: This should actually be restricted, so that this
116+
// only applies `where T.__Tag == Self`, but we don't have
117+
// the needed features in our type system to implement
118+
// that constraint right now.
119+
//
120+
switch (kBaseTypes[tt].tag)
121+
{
122+
case BaseType::Int:
123+
case BaseType::UInt:
124+
SLANG_RAW("\n")
125+
SLANG_RAW(" __generic<T:__EnumType>\n")
126+
SLANG_RAW(" __init(T value);\n")
127+
128+
break;
129+
130+
default:
131+
break;
132+
}
133+
95134
sb << "};\n";
96135
}
97136

@@ -1065,6 +1104,15 @@ for (auto op : binaryOps)
10651104
}
10661105
SLANG_RAW("\n")
10671106
SLANG_RAW("\n")
1107+
SLANG_RAW("// Operators to apply to `enum` types\n")
1108+
SLANG_RAW("\n")
1109+
SLANG_RAW("__generic<E : __EnumType>\n")
1110+
SLANG_RAW("__intrinsic_op(")
1111+
SLANG_SPLICE(kIROp_Eql
1112+
)
1113+
SLANG_RAW(")\n")
1114+
SLANG_RAW("bool operator==(E left, E right);\n")
1115+
SLANG_RAW("\n")
10681116
SLANG_RAW("// Statement Attributes\n")
10691117
SLANG_RAW("\n")
10701118
SLANG_RAW("__attributeTarget(LoopStmt)\n")

source/slang/decl-defs.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ SIMPLE_SYNTAX_CLASS(StructDecl, AggTypeDecl)
8787

8888
SIMPLE_SYNTAX_CLASS(ClassDecl, AggTypeDecl)
8989

90+
// TODO: Is it appropriate to treat an `enum` as an aggregate type?
91+
// Most code that looks for, e.g., conformances assumes user-defined
92+
// types are all `AggTypeDecl`, so this is the right choice for now
93+
// if we want `enum` types to be able to implement interfaces, etc.
94+
//
95+
SYNTAX_CLASS(EnumDecl, AggTypeDecl)
96+
RAW(
97+
RefPtr<Type> tagType;
98+
)
99+
END_SYNTAX_CLASS()
100+
101+
SIMPLE_SYNTAX_CLASS(EnumCaseDecl, VarDeclBase)
102+
90103
// An interface which other types can conform to
91104
SIMPLE_SYNTAX_CLASS(InterfaceDecl, AggTypeDecl)
92105

@@ -135,7 +148,7 @@ END_SYNTAX_CLASS()
135148
SYNTAX_CLASS(AssocTypeDecl, AggTypeDecl)
136149
END_SYNTAX_CLASS()
137150

138-
// A '__generic_param' declaration, which defines a generic
151+
// A '__generic_param' declaration, which defines a generic
139152
// entry-point parameter. Is a container of GenericTypeConstraintDecl
140153
SYNTAX_CLASS(GlobalGenericParamDecl, AggTypeDecl)
141154
END_SYNTAX_CLASS()

source/slang/diagnostic-defs.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@ DIAGNOSTIC(30049, Note, thisIsImmutableByDefault, "a 'this' parameter is curren
197197

198198
DIAGNOSTIC(30051, Error, invalidValueForArgument, "invalid value for argument '$0'")
199199
DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on type '$1'")
200-
DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.")
201200

201+
DIAGNOSTIC(30100, Error, staticRefToNonStaticMember, "type '$0' cannot be used to refer to non-static member '$1'")
202+
203+
DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.")
202204
DIAGNOSTIC(33071, Error, expectedAStringLiteral, "expected a string literal")
203205

204206
// Attributes
@@ -208,6 +210,14 @@ DIAGNOSTIC(31001, Error, attributeNotApplicable, "attribute '$0' is not valid he
208210

209211
DIAGNOSTIC(31100, Error, unknownStageName, "unknown stage name '$0'")
210212

213+
// Enums
214+
215+
DIAGNOSTIC(32000, Error, invalidEnumTagType, "invalid tag type for 'enum': '$0'")
216+
DIAGNOSTIC(32001, Error, enumTypeAlreadyHasTagType, "'enum' type has already declared a tag type")
217+
DIAGNOSTIC(32002, Note, seePreviousTagType, "see previous tag type declaration")
218+
DIAGNOSTIC(32003, Error, unexpectedEnumTagExpr, "unexpected form for 'enum' tag value expression")
219+
220+
211221

212222
// 303xx: interfaces and associated types
213223
DIAGNOSTIC(30300, Error, assocTypeInInterfaceOnly, "'associatedtype' can only be defined in an 'interface'.")

source/slang/lower-to-ir.cpp

+68-7
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,9 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
26682668
// so we need to track a bit of extra data:
26692669
struct SwitchStmtInfo
26702670
{
2671+
// The block that will be made to contain the `switch` statement
2672+
IRBlock* initialBlock = nullptr;
2673+
26712674
// The label for the `default` case, if any.
26722675
IRBlock* defaultLabel = nullptr;
26732676

@@ -2757,12 +2760,21 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
27572760
// for the best.
27582761
//
27592762
// TODO: figure out something cleaner.
2760-
auto caseVal = getSimpleVal(context, lowerRValueExpr(context, caseStmt->expr));
2763+
2764+
// Actually, one gotcha is that if we ever allow non-constant
2765+
// expressions here (or anything that requires instructions
2766+
// to be emitted to yield its value), then those instructions
2767+
// need to go into an appropriate block.
2768+
2769+
IRGenContext subContext = *context;
2770+
IRBuilder subBuilder = *getBuilder();
2771+
subBuilder.setInsertInto(info->initialBlock);
2772+
subContext.irBuilder = &subBuilder;
2773+
auto caseVal = getSimpleVal(context, lowerRValueExpr(&subContext, caseStmt->expr));
27612774

27622775
// Figure out where we are branching to.
27632776
auto label = getLabelForCase(info);
27642777

2765-
27662778
// Add this `case` to the list for the enclosing `switch`.
27672779
info->cases.Add(caseVal);
27682780
info->cases.Add(label);
@@ -2863,6 +2875,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
28632875
// Iterate over the body of the statement, looking
28642876
// for `case` or `default` statements:
28652877
SwitchStmtInfo info;
2878+
info.initialBlock = initialBlock;
28662879
info.defaultLabel = nullptr;
28672880
lowerSwitchCases(stmt->body, &info);
28682881

@@ -3760,6 +3773,59 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
37603773
setMangledName(inst, context->getSession()->getNameObj(name));
37613774
}
37623775

3776+
LoweredValInfo visitEnumCaseDecl(EnumCaseDecl* decl)
3777+
{
3778+
// A case within an `enum` decl will lower to a value
3779+
// of the `enum`'s "tag" type.
3780+
//
3781+
// TODO: a bit more work will be needed if we allow for
3782+
// enum cases that have payloads, because then we need
3783+
// a function that constructs the value given arguments.
3784+
3785+
IRBuilder subBuilderStorage = *getBuilder();
3786+
IRBuilder* subBuilder = &subBuilderStorage;
3787+
3788+
// Emit any generics that should wrap the actual type.
3789+
emitOuterGenerics(subBuilder, decl, decl);
3790+
3791+
IRGenContext subContextStorage = *context;
3792+
IRGenContext* subContext = &subContextStorage;
3793+
subContext->irBuilder = subBuilder;
3794+
3795+
return lowerRValueExpr(subContext, decl->initExpr);
3796+
}
3797+
3798+
LoweredValInfo visitEnumDecl(EnumDecl* decl)
3799+
{
3800+
// Given a declaration of a type, we need to make sure
3801+
// to output "witness tables" for any interfaces this
3802+
// type has declared conformance to.
3803+
for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
3804+
{
3805+
ensureDecl(context, inheritanceDecl);
3806+
}
3807+
3808+
IRBuilder subBuilderStorage = *getBuilder();
3809+
IRBuilder* subBuilder = &subBuilderStorage;
3810+
emitOuterGenerics(subBuilder, decl, decl);
3811+
3812+
IRGenContext subContextStorage = *context;
3813+
IRGenContext* subContext = &subContextStorage;
3814+
subContext->irBuilder = subBuilder;
3815+
3816+
// An `enum` declaration will currently lower directly to its "tag"
3817+
// type, so that any references to the `enum` become referenes to
3818+
// the tag type instead.
3819+
//
3820+
// TODO: if we ever support `enum` types with payloads, we would
3821+
// need to make the `enum` lower to some kind of custom "tagged union"
3822+
// type.
3823+
3824+
IRType* loweredTagType = lowerType(subContext, decl->tagType);
3825+
3826+
return LoweredValInfo::simple(finishOuterGenerics(subBuilder, loweredTagType));
3827+
}
3828+
37633829
LoweredValInfo visitAggTypeDecl(AggTypeDecl* decl)
37643830
{
37653831
// Don't generate an IR `struct` for intrinsic types
@@ -3768,11 +3834,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
37683834
return LoweredValInfo();
37693835
}
37703836

3771-
if(getMangledName(decl) == "_ST03int")
3772-
{
3773-
decl = decl;
3774-
}
3775-
37763837
// Given a declaration of a type, we need to make sure
37773838
// to output "witness tables" for any interfaces this
37783839
// type has declared conformance to.

0 commit comments

Comments
 (0)