Skip to content

Commit fae3d9a

Browse files
author
Tim Foley
authored
Initial work for "global generic value parameters" (shader-slang#1127)
* Initial work for "global generic value parameters" The main new feature here is support for the `__generic_value_param` keyword, which introduces a *global generic value parameter*. For example: __generic_value_param kOffset : uint = 0; This declaration introduces a global generic value parameter `kOffset` of type `uint` that has a nominal default value of zero. The broad strokes of how this feature was added are as follows: * A new `GlobalGenericValueParamDecl` AST node type is introduces in `slang-decl-defs.h` * A new `parseGlobalGenericValueParamDecl` subroutine is added to `slang-parser.cpp`, and is added to the list of declaration cases as the callback for the `__generic_value_param` name. * Cases for `GlobalGenericValueParamDecl` are added to the declaration checking passes in `slang-check-decl.cpp`, mirroring what is done for other variable declaration cases. * A case for `GlobalGenericValueParamDecl` is aded to the `Module::_collectShaderParams` function, so that it is recognized as a kind of specialization parameter. This introduces a specialization parameter of flavor `SpecializationParam::Flavor::GenericValue` (which was already defined before this change, although it was unused). * A case for `SpecializationParam::Flavor::GenericValue` is added in `Module::_validateSpecializationArgsImpl` to check that a specialization argument represents a compile-time-constant value (not a type). * A case for `GlobalGenericValueParmDecl` is introduced in `slang-lower-to-ir.cpp` that introduces a global generic parameter in the IR * The `IRBuilder` is extended to support creating `IRGlobalGenericParam`s for the distinct cases of type, witness-table, and value parameters. The same IR instruction type/opcode is used for all cases, and only the type of the IR instruction differs. * The existing mechanisms for lowering specialization arguments to the IR, and doing specialization on the IR itself Just Work with global generic value parameters since they already support value parameters on explicit generic declarations. That's the santized version of things, but there were also a bunch of cleanups and tweaks required along the way: * The `SpecializationParam` type was extended to also track a `SourceLoc` to help in diagnostic messages, which meant some churn in the code that collects specialization parameters. * The `_extractSpecializationArgs` function is tweaked to support any kind of "term" as a specialization argument (either a type or a value). * To allow *parsing* specialization arguments that can't possibly be types (e.g., integer literals) we replace the existing `parseTypeString` routine with `parseTermString` and then in `parseTermFromSourceFile` call through to a general case of expression parsing (which can also parse types) rather than only parsing types directly. * Right before doing back-end code generation, we check if the program we are going to emit has remaining (unspecialized) parameters, in which case we emit a diagnostic message for the parameters that haven't been specialized rather than go on to emit code that will fail to compile downstream. * Within the `render-test` tool we collapse down the arrays that held both "generic" and "existential" specialization arguments, so that we just have *global* and *entry-point* specialization argument lists. This mirrors how Slang has worked internally for a while, but the difference hasn't been important to the test tool because no tests currently mix generic and existential specialization. The logic for parsing `TEST_INPUT` lines has been streamlined down to just the global and entry-point cases, but the pre-existing keywords are still allowed so that I don't have to tweak any test cases. There are several significant caveats for this feature, which mean that it isn't really ready for users to hammer on just yet: * There is no support for `Val`s of anything but integers, so there is no way to meaningfully have a generic value param with a type other than `int` or `uint`. * We allow for a default-value expression on global generic parameters, but do not actually make use of that value for anything (e.g., to allow a programmer to omit specialization arguments), nor check that it meets the constraints of being compile-time constant. * Global generic value parameters are *not* currently being treated the same as explicit generic parameters in terms of how they can be used for things like array sizes or other things that require constants. This will probably be relaxed at some point, but allowing a global generic to be used to size an array creates questions around layout. * The IR optimization passes in Slang currently won't eliminate entire blocks of code based on constant values, so using a global generic value parameter to enable/disable features will *not* currently lead to us outputting drastically different HLSL or GLSL. That said, we expect most downstream compilers to be able to handle an `if(0)` well. * Fix regression for tagged union types The change that made specialization arguments be parsed as "terms" first, and then coerced to types meant that any special-case logic that is specific to the parsing of types would be bypassed and thus not apply. Most of that special-case logic isn't wanted for specialization arguments, since it pertains to cases were we want to, e.g, declare a `struct` type while also declaring a variable of that type. The one special case that *is* useful is the `__TaggedUnion(...)` syntax, which is the only way to introduce a tagged union type right now. In order to get that case working again, all I had to do was register the existing logic for parsing `__TaggedUnion` as an expression keyword with the right callback, and the existing logic in expression parsing kicks in (that logic was already handling expression keywords like `this` and `true`). I left in the existing logic for handling `__TaggedUnion` directly where types get parsed, rather than try to unify things. A better long-term fix is to make the base case for type parsing route into `parseAtomicExpr` so that the two paths share the core logic. That change should probably come as its own refactoring/cleanup, because it creates the potential for some subtle breakage. * fixup: typo
1 parent dd43551 commit fae3d9a

20 files changed

+294
-105
lines changed

source/slang/slang-check-decl.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ namespace Slang
4949
checkVarDeclCommon(varDecl);
5050
}
5151

52+
void visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
53+
{
54+
checkVarDeclCommon(decl);
55+
}
56+
5257
void visitImportDecl(ImportDecl* decl);
5358

5459
void visitGenericTypeParamDecl(GenericTypeParamDecl* decl);
@@ -114,6 +119,11 @@ namespace Slang
114119
checkVarDeclCommon(varDecl);
115120
}
116121

122+
void visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
123+
{
124+
checkVarDeclCommon(decl);
125+
}
126+
117127
void visitEnumCaseDecl(EnumCaseDecl* decl);
118128

119129
void visitEnumDecl(EnumDecl* decl);

source/slang/slang-check-shader.cpp

+64-22
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ namespace Slang
5252
/// Recursively walk `type` and add any existential/interface specialization parameters to `ioSpecializationParams`.
5353
static void _collectExistentialSpecializationParamsRec(
5454
SpecializationParams& ioSpecializationParams,
55-
Type* type)
55+
Type* type,
56+
SourceLoc loc)
5657
{
5758
// Whether or not something is an array does not affect
5859
// the number of existential slots it introduces.
@@ -66,7 +67,8 @@ namespace Slang
6667
{
6768
_collectExistentialSpecializationParamsRec(
6869
ioSpecializationParams,
69-
parameterGroupType->getElementType());
70+
parameterGroupType->getElementType(),
71+
loc);
7072
return;
7173
}
7274

@@ -81,6 +83,7 @@ namespace Slang
8183
//
8284
SpecializationParam specializationParam;
8385
specializationParam.flavor = SpecializationParam::Flavor::ExistentialType;
86+
specializationParam.loc = loc;
8487
specializationParam.object = type;
8588
ioSpecializationParams.add(specializationParam);
8689
}
@@ -112,7 +115,8 @@ namespace Slang
112115
{
113116
_collectExistentialSpecializationParamsRec(
114117
ioSpecializationParams,
115-
GetType(paramDeclRef));
118+
GetType(paramDeclRef),
119+
paramDeclRef.getLoc());
116120
}
117121

118122

@@ -147,13 +151,15 @@ namespace Slang
147151
{
148152
SpecializationParam param;
149153
param.flavor = SpecializationParam::Flavor::GenericType;
154+
param.loc = genericTypeParam->loc;
150155
param.object = genericTypeParam;
151156
m_genericSpecializationParams.add(param);
152157
}
153158
else if(auto genericValParam = as<GenericValueParamDecl>(m))
154159
{
155160
SpecializationParam param;
156161
param.flavor = SpecializationParam::Flavor::GenericValue;
162+
param.loc = genericValParam->loc;
157163
param.object = genericValParam;
158164
m_genericSpecializationParams.add(param);
159165
}
@@ -1020,9 +1026,21 @@ static bool doesParameterMatch(
10201026
//
10211027
SpecializationParam specializationParam;
10221028
specializationParam.flavor = SpecializationParam::Flavor::GenericType;
1029+
specializationParam.loc = globalGenericParam->loc;
10231030
specializationParam.object = globalGenericParam;
10241031
m_specializationParams.add(specializationParam);
10251032
}
1033+
else if( auto globalGenericValueParam = as<GlobalGenericValueParamDecl>(globalDecl) )
1034+
{
1035+
// A global generic type parameter declaration introduces
1036+
// a suitable specialization parameter.
1037+
//
1038+
SpecializationParam specializationParam;
1039+
specializationParam.flavor = SpecializationParam::Flavor::GenericValue;
1040+
specializationParam.loc = globalGenericValueParam->loc;
1041+
specializationParam.object = globalGenericValueParam;
1042+
m_specializationParams.add(specializationParam);
1043+
}
10261044
else if( auto importDecl = as<ImportDecl>(globalDecl) )
10271045
{
10281046
// An `import` declaration creates a requirement dependency
@@ -1425,16 +1443,20 @@ static bool doesParameterMatch(
14251443
auto& arg = args[ii];
14261444
auto& param = m_specializationParams[ii];
14271445

1428-
auto argType = arg.val.as<Type>();
1429-
SLANG_ASSERT(argType);
1430-
14311446
switch( param.flavor )
14321447
{
14331448
case SpecializationParam::Flavor::GenericType:
14341449
{
14351450
auto genericTypeParamDecl = param.object.as<GlobalGenericParamDecl>();
14361451
SLANG_ASSERT(genericTypeParamDecl);
14371452

1453+
RefPtr<Type> argType = as<Type>(arg.val);
1454+
if(!argType)
1455+
{
1456+
sink->diagnose(param.loc, Diagnostics::expectedTypeForSpecializationArg, genericTypeParamDecl);
1457+
argType = getLinkage()->getSessionImpl()->getErrorType();
1458+
}
1459+
14381460
// TODO: There is a serious flaw to this checking logic if we ever have cases where
14391461
// the constraints on one `type_param` can depend on another `type_param`, e.g.:
14401462
//
@@ -1520,6 +1542,13 @@ static bool doesParameterMatch(
15201542
auto interfaceType = param.object.as<Type>();
15211543
SLANG_ASSERT(interfaceType);
15221544

1545+
RefPtr<Type> argType = as<Type>(arg.val);
1546+
if(!argType)
1547+
{
1548+
sink->diagnose(param.loc, Diagnostics::expectedTypeForSpecializationArg, interfaceType);
1549+
argType = getLinkage()->getSessionImpl()->getErrorType();
1550+
}
1551+
15231552
auto witness = visitor.tryGetSubtypeWitness(argType, interfaceType);
15241553
if (!witness)
15251554
{
@@ -1539,6 +1568,29 @@ static bool doesParameterMatch(
15391568
}
15401569
break;
15411570

1571+
case SpecializationParam::Flavor::GenericValue:
1572+
{
1573+
auto paramDecl = param.object.as<GlobalGenericValueParamDecl>();
1574+
SLANG_ASSERT(paramDecl);
1575+
1576+
// Now we need to check that the argument `Val` has the
1577+
// appropriate type expected by the parameter.
1578+
1579+
RefPtr<IntVal> intVal = as<IntVal>(arg.val);
1580+
if(!intVal)
1581+
{
1582+
sink->diagnose(param.loc, Diagnostics::expectedValueOfTypeForSpecializationArg, paramDecl->getType(), paramDecl);
1583+
intVal = new ConstantIntVal(0);
1584+
}
1585+
1586+
ModuleSpecializationInfo::GenericArgInfo expandedArg;
1587+
expandedArg.paramDecl = paramDecl;
1588+
expandedArg.argVal = intVal;
1589+
1590+
specializationInfo->genericArgs.add(expandedArg);
1591+
}
1592+
break;
1593+
15421594
default:
15431595
SLANG_UNEXPECTED("unhandled specialization parameter flavor");
15441596
}
@@ -1556,27 +1608,17 @@ static bool doesParameterMatch(
15561608
{
15571609
auto linkage = componentType->getLinkage();
15581610

1611+
SharedSemanticsContext semanticsContext(linkage, sink);
1612+
SemanticsVisitor semanticsVisitor(&semanticsContext);
1613+
15591614
auto argCount = argExprs.getCount();
15601615
for(Index ii = 0; ii < argCount; ++ii )
15611616
{
15621617
auto argExpr = argExprs[ii];
15631618
auto paramInfo = componentType->getSpecializationParam(ii);
15641619

1565-
// TODO: We should support non-type arguments here
1566-
1567-
auto argType = checkProperType(linkage, TypeExp(argExpr), sink);
1568-
if( !argType )
1569-
{
1570-
// If no witness was found, then we will be unable to satisfy
1571-
// the conformances required.
1572-
sink->diagnose(argExpr,
1573-
Diagnostics::expectedAType,
1574-
argExpr->type);
1575-
continue;
1576-
}
1577-
15781620
SpecializationArg arg;
1579-
arg.val = argType;
1621+
arg.val = semanticsVisitor.ExtractGenericArgVal(argExpr);
15801622
outArgs.add(arg);
15811623
}
15821624
}
@@ -1757,7 +1799,7 @@ static bool doesParameterMatch(
17571799
RefPtr<Expr> argExpr;
17581800
for (auto & s : scopesToTry)
17591801
{
1760-
argExpr = linkage->parseTypeString(name, s);
1802+
argExpr = linkage->parseTermString(name, s);
17611803
argExpr = semantics.CheckTerm(argExpr);
17621804
if( argExpr )
17631805
{
@@ -1790,7 +1832,7 @@ static bool doesParameterMatch(
17901832
SemanticsVisitor visitor(&sharedSemanticsContext);
17911833

17921834
SpecializationParams specializationParams;
1793-
_collectExistentialSpecializationParamsRec(specializationParams, unspecializedType);
1835+
_collectExistentialSpecializationParamsRec(specializationParams, unspecializedType, SourceLoc());
17941836

17951837
assert(specializationParams.getCount() == argCount);
17961838

source/slang/slang-compiler.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,37 @@ SlangResult dissassembleDXILUsingDXC(
23822382
BackEndCompileRequest* compileRequest,
23832383
EndToEndCompileRequest* endToEndReq)
23842384
{
2385+
// If we are about to generate output code, but we still
2386+
// have unspecialized generic/existential parameters,
2387+
// then there is a problem.
2388+
//
2389+
auto program = compileRequest->getProgram();
2390+
auto specializationParamCount = program->getSpecializationParamCount();
2391+
if( specializationParamCount != 0 )
2392+
{
2393+
auto sink = compileRequest->getSink();
2394+
2395+
for( Index ii = 0; ii < specializationParamCount; ++ii )
2396+
{
2397+
auto specializationParam = program->getSpecializationParam(ii);
2398+
if( auto decl = as<Decl>(specializationParam.object) )
2399+
{
2400+
sink->diagnose(specializationParam.loc, Diagnostics::specializationParameterOfNameNotSpecialized, decl);
2401+
}
2402+
else if( auto type = as<Type>(specializationParam.object) )
2403+
{
2404+
sink->diagnose(specializationParam.loc, Diagnostics::specializationParameterOfNameNotSpecialized, type);
2405+
}
2406+
else
2407+
{
2408+
sink->diagnose(specializationParam.loc, Diagnostics::specializationParameterNotSpecialized);
2409+
}
2410+
}
2411+
2412+
return;
2413+
}
2414+
2415+
23852416
// Go through the code-generation targets that the user
23862417
// has specified, and generate code for each of them.
23872418
//

source/slang/slang-compiler.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,7 @@ namespace Slang
12061206
///
12071207
SlangResult loadFile(String const& path, PathInfo& outPathInfo, ISlangBlob** outBlob);
12081208

1209-
RefPtr<Expr> parseTypeString(String typeStr, RefPtr<Scope> scope);
1209+
RefPtr<Expr> parseTermString(String str, RefPtr<Scope> scope);
12101210

12111211
Type* specializeType(
12121212
Type* unspecializedType,

source/slang/slang-decl-defs.h

+5
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ END_SYNTAX_CLASS()
180180
SYNTAX_CLASS(GlobalGenericParamDecl, AggTypeDecl)
181181
END_SYNTAX_CLASS()
182182

183+
// A `__generic_value_param` declaration, which defines an existential
184+
// value parameter (not a type parameter.
185+
SYNTAX_CLASS(GlobalGenericValueParamDecl, VarDeclBase)
186+
END_SYNTAX_CLASS()
187+
183188
// A scope for local declarations (e.g., as part of a statement)
184189
SIMPLE_SYNTAX_CLASS(ScopeDecl, ContainerDecl)
185190

source/slang/slang-diagnostic-defs.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,16 @@ DIAGNOSTIC(38002, Note, entryPointCandidate, "see candidate declaration for entr
363363
DIAGNOSTIC(38003, Error, entryPointSymbolNotAFunction, "entry point '$0' must be declared as a function")
364364

365365
DIAGNOSTIC(38004, Error, entryPointTypeParameterNotFound, "no type found matching entry-point type parameter name '$0'")
366-
DIAGNOSTIC(38005, Error, globalGenericArgumentNotAType, "argument for global generic parameter '$0' must be a type")
366+
DIAGNOSTIC(38005, Error, expectedTypeForSpecializationArg, "expected a type as argument for specialization parameter '$0'")
367367

368368
DIAGNOSTIC(38006, Warning, specifiedStageDoesntMatchAttribute, "entry point '$0' being compiled for the '$1' stage has a '[shader(...)]' attribute that specifies the '$2' stage")
369369
DIAGNOSTIC(38007, Error, entryPointHasNoStage, "no stage specified for entry point '$0'; use either a '[shader(\"name\")]' function attribute or the '-stage <name>' command-line option to specify a stage")
370370

371+
DIAGNOSTIC(38008, Error, specializationParameterOfNameNotSpecialized, "no specialization argument was provided for specialization parameter '$0'")
372+
DIAGNOSTIC(38008, Error, specializationParameterNotSpecialized, "no specialization argument was provided for specialization parameter")
373+
374+
DIAGNOSTIC(38009, Error, expectedValueOfTypeForSpecializationArg, "expected a constant value of type '$0' as argument for specialization parameter '$1'")
375+
371376
DIAGNOSTIC(38100, Error, typeDoesntImplementInterfaceRequirement, "type '$0' does not provide required interface member '$1'")
372377
DIAGNOSTIC(38101, Error, thisExpressionOutsideOfTypeDecl, "'this' expression can only be used in members of an aggregate type")
373378
DIAGNOSTIC(38102, Error, initializerNotInsideType, "an 'init' declaration is only allowed inside a type or 'extension' declaration")
@@ -388,7 +393,6 @@ DIAGNOSTIC(38025, Error, mismatchSpecializationArguments, "expected $0 specializ
388393
DIAGNOSTIC(38026, Error, globalTypeArgumentDoesNotConformToInterface, "type argument `$1` for global generic parameter `$0` does not conform to interface `$2`.")
389394

390395
DIAGNOSTIC(38027, Error, mismatchExistentialSlotArgCount, "expected $0 existential slot arguments ($1 provided)")
391-
DIAGNOSTIC(38028, Error, existentialSlotArgNotAType, "existential slot argument $0 was not a type")
392396
DIAGNOSTIC(38029, Error,typeArgumentDoesNotConformToInterface, "type argument '$0' does not conform to the required interface '$1'")
393397

394398
DIAGNOSTIC(38200, Error, recursiveModuleImport, "module `$0` recursively imports itself")

source/slang/slang-ir-insts.h

+12-1
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,18 @@ struct IRBuilder
19261926
UInt caseArgCount,
19271927
IRInst* const* caseArgs);
19281928

1929-
IRGlobalGenericParam* emitGlobalGenericParam();
1929+
IRGlobalGenericParam* emitGlobalGenericParam(
1930+
IRType* type);
1931+
1932+
IRGlobalGenericParam* emitGlobalGenericTypeParam()
1933+
{
1934+
return emitGlobalGenericParam(getTypeKind());
1935+
}
1936+
1937+
IRGlobalGenericParam* emitGlobalGenericWitnessTableParam()
1938+
{
1939+
return emitGlobalGenericParam(getWitnessTableType());
1940+
}
19301941

19311942
IRBindGlobalGenericParam* emitBindGlobalGenericParam(
19321943
IRInst* param,

source/slang/slang-ir-link.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ IRGlobalGenericParam* cloneGlobalGenericParamImpl(
552552
IRGlobalGenericParam* originalVal,
553553
IROriginalValuesForClone const& originalValues)
554554
{
555-
auto clonedVal = builder->emitGlobalGenericParam();
555+
auto clonedVal = builder->emitGlobalGenericParam(originalVal->getFullType());
556556
cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
557557
return clonedVal;
558558
}

source/slang/slang-ir.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -3328,12 +3328,13 @@ namespace Slang
33283328
return inst;
33293329
}
33303330

3331-
IRGlobalGenericParam* IRBuilder::emitGlobalGenericParam()
3331+
IRGlobalGenericParam* IRBuilder::emitGlobalGenericParam(
3332+
IRType* type)
33323333
{
33333334
IRGlobalGenericParam* irGenericParam = createInst<IRGlobalGenericParam>(
33343335
this,
33353336
kIROp_GlobalGenericParam,
3336-
nullptr);
3337+
type);
33373338
addGlobalValue(this, irGenericParam);
33383339
return irGenericParam;
33393340
}

source/slang/slang-lower-to-ir.cpp

+12-2
Original file line numberDiff line numberDiff line change
@@ -4211,7 +4211,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
42114211
// This is a constraint on a global generic type parameters,
42124212
// and so it should lower as a parameter of its own.
42134213

4214-
auto inst = getBuilder()->emitGlobalGenericParam();
4214+
auto inst = getBuilder()->emitGlobalGenericWitnessTableParam();
42154215
addLinkageDecoration(context, inst, decl);
42164216
return LoweredValInfo::simple(inst);
42174217
}
@@ -4227,11 +4227,21 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
42274227

42284228
LoweredValInfo visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl)
42294229
{
4230-
auto inst = getBuilder()->emitGlobalGenericParam();
4230+
auto inst = getBuilder()->emitGlobalGenericTypeParam();
42314231
addLinkageDecoration(context, inst, decl);
42324232
return LoweredValInfo::simple(inst);
42334233
}
42344234

4235+
LoweredValInfo visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
4236+
{
4237+
auto builder = getBuilder();
4238+
auto type = lowerType(context, decl->type);
4239+
auto inst = builder->emitGlobalGenericParam(type);
4240+
addLinkageDecoration(context, inst, decl);
4241+
return LoweredValInfo::simple(inst);
4242+
}
4243+
4244+
42354245
void lowerWitnessTable(
42364246
IRGenContext* subContext,
42374247
WitnessTable* astWitnessTable,

0 commit comments

Comments
 (0)