diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-03-16 07:01:35 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-03-16 07:01:35 -0700 |
| commit | 93ac152bf8283eff1d1b2ec0f7df897a4e96464f (patch) | |
| tree | c0f1b432e9a47ef4bf746c070785036e5c558fe0 /source | |
| parent | e69c0bd31c9d6af4937e8f9ffdb9c1bef4bd9d66 (diff) | |
Overhaul implementation of [attributes] (#443)
The existing code parsed all of the square-bracket `[attributes]` into `HLSLUncheckedAttribute`, and then went on to hand-convert some of them to specialized subclasses of `HLSLAttribute`. When attributes didn't check, they were left as-is, and no error message was issued, because at the time the compiler was focused on accepting arbitrary input.
This change greatly overhauls the handling of `[attributes]`. Attributes are now declared in the stdlib, with declarations like:
```hlsl
__attributeTarget(LoopStmt)
attribute_syntax [unroll(count: int = 0)] : UnrollAttribute;
```
In this syntax, the `unroll` part is giving the attribute name (the `[]` are just for flavor, to make the declaration look like a use site; we could drop it if we don't like the clutter), the `count` is a parameter of the attribute, which we expect to be of type `int`, and which has a default value of `0` if unspecified.
The `: UnrollAttribute` part specifies the meta-level C++ class that will implement this attribute (and corresponds to a class in `modifier-defs.h`). This syntax is similar to our current `syntax` declarations. I'm starting to think we should change it to something like a `__meta_class(UnrollAttribute)` modifier, and then use that uniformly across all cases (e.g., also replacing the curreent `__magic_type(Foo)` syntax).
The `__attributeTarget(LoopStmt)` is a modifier that specifies the meta-level C++ class for syntax that this attribute is allowed to attach to. It is legal to have more than one of these.
Attributes continue to be parsed in an unchecked form, so that we don't tie up semantic analysis and parsing more than necessary. During checking, we look up the attribute name in the current scope, and then replace the unchecked attribute with a more specific one *if* the checking passes.
Checking proceeds in generic and attribute-specific phases. The generic phase includes checking the number of arguments against those specified in the attribute declaration (I don't currently check types, or handle default arguments), and then checking that at least one `__attributeTarget(...)` modifier applies to the syntax node being modified.
The attribute-specific phase then applies to the specialized C++ subclass of `Attribute`, and does the actual checking right now (e.g., that step is responsible for actually type-checking things at present). This can obviously be improved over time.
With this support I went ahead and added declarations for all the HLSL attributes I could find documented on MSDN. I also added a provisional declaration for the `[shader(...)]` attribute that has been added to dxc, but which is not yet documented.
One important detail here is that lookup of attribute names needs to be done carefully, so that we don't let, e.g., local variables shadow an attribute declaration:
```hlsl
int unroll = 5;
// This attribute should *not* get confused by the local variable `unroll`
[unroll] for(...) { .. }
```
The lookup logic already has a notion of a `LookupMask` that can be used to filter declarations out of the result. In this change I surfaced that mask through the main lookup API (rather than requiring a second pass to "refine" lookup results), and made is so that the default lookup mask does *not* include attributes, while an explicit mask can be used to look up *only* attributes.
(An alternatie design we discussed was to follow the approach of C# and have the declaration of an attribute like `[unroll]` actually be `unrollAttribute`, with a suffix. I decided not to follow that approach for now because it seemed like printing good error messages in that case could require us to carefully trim the `Attribute` suffix off of names at times, and using the existing mask behavior seemed simpler.)
To verify that the shadowing behavior is indeed correct, I modified the `loop-unroll.slang` test case.
Smaller notes:
* Removed the `HLSL` prefix from several of the C++ attribute classes
* Made sure to actually validate the modifiers on statements
* Special-cased checking for `ParamDecl` with a null type, because I'm re-using `ParamDecl` for attribute parameters, but can't give a concrete type to some of them right now
* Deleting some old, dead emit-from-AST logic around attributes, rather than try to "fix" code that doesn't run (a more complete scrub of that code is still needed)
* Fixed AST inheritance hierarchy so that a `Modifier` is a `SyntaxNode` rather than a `SyntaxNodeBase`. I have *no* idea why we have both of those, and we need to clean that up soon.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 263 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 68 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 68 | ||||
| -rw-r--r-- | source/slang/decl-defs.h | 7 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 5 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 48 | ||||
| -rw-r--r-- | source/slang/lookup.cpp | 27 | ||||
| -rw-r--r-- | source/slang/lookup.h | 9 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 18 | ||||
| -rw-r--r-- | source/slang/modifier-defs.h | 72 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 131 | ||||
| -rw-r--r-- | source/slang/reflection.cpp | 2 | ||||
| -rw-r--r-- | source/slang/syntax-base-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/syntax.h | 5 |
14 files changed, 591 insertions, 134 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index f7cb3b575..1af91e9fd 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -1412,6 +1412,11 @@ namespace Slang // These are only used in the stdlib, so no checking is needed } + void visitAttributeDecl(AttributeDecl*) + { + // These are only used in the stdlib, so no checking is needed + } + void visitGenericTypeParamDecl(GenericTypeParamDecl*) { // These are only used in the stdlib, so no checking is needed for now @@ -1427,71 +1432,209 @@ namespace Slang // Do nothing with modifiers for now } - RefPtr<Modifier> checkModifier( - RefPtr<Modifier> m, - Decl* /*decl*/) + AttributeDecl* lookUpAttributeDecl(Name* attributeName, Scope* scope) { - if(auto hlslUncheckedAttribute = m.As<HLSLUncheckedAttribute>()) + // Look up the name and see what we find. + // + // TODO: This needs to have some special filtering or naming + // rules to keep us from seeing shadowing variable declarations. + auto lookupResult = lookUp(getSession(), this, attributeName, scope, LookupMask::Attribute); + + // If we didn't find anything, or the result was overloaded, + // then we aren't going to be able to extract a single decl. + if(!lookupResult.isValid() || lookupResult.isOverloaded()) + return nullptr; + + auto decl = lookupResult.item.declRef.getDecl(); + if( auto attributeDecl = dynamic_cast<AttributeDecl*>(decl) ) { - // We have an HLSL `[name(arg,...)]` attribute, and we'd like - // to check that it is provides all the expected arguments - // - // For now we will do this in a completely ad hoc fashion, - // but it would be nice to have some generic routine to - // do the needed type checking/coercion. - auto attribText = getText(hlslUncheckedAttribute->getName()); - - if(attribText == "numthreads") - { - if(hlslUncheckedAttribute->args.Count() != 3) - return m; + return attributeDecl; + } + else + { + return nullptr; + } + } - auto xVal = checkConstantIntVal(hlslUncheckedAttribute->args[0]); - auto yVal = checkConstantIntVal(hlslUncheckedAttribute->args[1]); - auto zVal = checkConstantIntVal(hlslUncheckedAttribute->args[2]); + bool validateAttribute(RefPtr<Attribute> attr) + { + if(auto numThreadsAttr = attr.As<NumThreadsAttribute>()) + { + SLANG_ASSERT(attr->args.Count() == 3); + auto xVal = checkConstantIntVal(attr->args[0]); + auto yVal = checkConstantIntVal(attr->args[1]); + auto zVal = checkConstantIntVal(attr->args[2]); - if(!xVal) return m; - if(!yVal) return m; - if(!zVal) return m; + if(!xVal) return false; + if(!yVal) return false; + if(!zVal) return false; - auto hlslNumThreadsAttribute = new HLSLNumThreadsAttribute(); + numThreadsAttr->x = (int32_t) xVal->value; + numThreadsAttr->y = (int32_t) yVal->value; + numThreadsAttr->z = (int32_t) zVal->value; + } + else if (auto maxVertexCountAttr = attr.As<MaxVertexCountAttribute>()) + { + SLANG_ASSERT(attr->args.Count() == 1); + auto val = checkConstantIntVal(attr->args[0]); - hlslNumThreadsAttribute->loc = hlslUncheckedAttribute->loc; - hlslNumThreadsAttribute->name = hlslUncheckedAttribute->getName(); - hlslNumThreadsAttribute->args = hlslUncheckedAttribute->args; - hlslNumThreadsAttribute->x = (int32_t) xVal->value; - hlslNumThreadsAttribute->y = (int32_t) yVal->value; - hlslNumThreadsAttribute->z = (int32_t) zVal->value; + maxVertexCountAttr->value = (int32_t)val->value; + } + else if(auto instanceAttr = attr.As<InstanceAttribute>()) + { + SLANG_ASSERT(attr->args.Count() == 1); + auto val = checkConstantIntVal(attr->args[0]); - return hlslNumThreadsAttribute; + instanceAttr->value = (int32_t)val->value; + } + else + { + if(attr->args.Count() == 0) + { + // If the attribute took no arguments, then we will + // assume it is valid as written. + } + else + { + // We should be special-casing the checking of any attribute + // with a non-zero number of arguments. + SLANG_DIAGNOSE_UNEXPECTED(getSink(), attr, "unhandled attribute"); + return false; + } } - else if (attribText == "maxvertexcount") + + return true; + } + + RefPtr<AttributeBase> checkAttribute( + UncheckedAttribute* uncheckedAttr, + ModifiableSyntaxNode* attrTarget) + { + auto attrName = uncheckedAttr->getName(); + auto attrDecl = lookUpAttributeDecl( + attrName, + uncheckedAttr->scope); + + if(!attrDecl) + { + getSink()->diagnose(uncheckedAttr, Diagnostics::unknownAttributeName, attrName); + return uncheckedAttr; + } + + if(!attrDecl->syntaxClass.isSubClassOf<Attribute>()) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute declaration does not reference an attribute class"); + return uncheckedAttr; + } + + RefPtr<RefObject> attrObj = attrDecl->syntaxClass.createInstance(); + auto attr = attrObj.As<Attribute>(); + if(!attr) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute class did not yield an attribute object"); + return uncheckedAttr; + } + + // We are going to replace the unchecked attribute with the checked one. + + // First copy all of the state over from the original attribute. + attr->name = uncheckedAttr->name; + attr->args = uncheckedAttr->args; + attr->loc = uncheckedAttr->loc; + + // We will start with checking steps that can be applied independent + // of the concrete attribute type that was selected. These only need + // us to look at the attribute declaration itself. + // + // Start by doing argument/parameter matching + UInt argCount = attr->args.Count(); + UInt paramCounter = 0; + bool mismatch = false; + for(auto paramDecl : attrDecl->getMembersOfType<ParamDecl>()) + { + UInt paramIndex = paramCounter++; + if( paramIndex < argCount ) { - if (hlslUncheckedAttribute->args.Count() != 1) - return m; - auto val = checkConstantIntVal(hlslUncheckedAttribute->args[0]); - auto hlslMaxVertexCountAttrib = new HLSLMaxVertexCountAttribute(); + auto arg = attr->args[paramIndex]; + + // TODO: support checking the argument against the declared + // type for the parameter. - hlslMaxVertexCountAttrib->loc = hlslUncheckedAttribute->loc; - hlslMaxVertexCountAttrib->name = hlslUncheckedAttribute->getName(); - hlslMaxVertexCountAttrib->args = hlslUncheckedAttribute->args; - hlslMaxVertexCountAttrib->value = (int32_t)val->value; - return hlslMaxVertexCountAttrib; } - else if (attribText == "instance") + else { - if (hlslUncheckedAttribute->args.Count() != 1) - return m; - auto val = checkConstantIntVal(hlslUncheckedAttribute->args[0]); - auto attrib = new HLSLInstanceAttribute(); + // We didn't have enough arguments for the + // number of parameters declared. + if(auto defaultArg = paramDecl->initExpr) + { + // The attribute declaration provided a default, + // so we should use that. + // + // TODO: we need to figure out how to hook up + // default arguments as needed. + } + else + { + mismatch = true; + } + } + } + UInt paramCount = paramCounter; + + if(mismatch) + { + getSink()->diagnose(attr, Diagnostics::attributeArgumentCountMismatch, attrName, paramCount, argCount); + return uncheckedAttr; + } - attrib->loc = hlslUncheckedAttribute->loc; - attrib->name = hlslUncheckedAttribute->getName(); - attrib->args = hlslUncheckedAttribute->args; - attrib->value = (int32_t)val->value; - return attrib; + // The next bit of validation that we can apply semi-generically + // is to validate that the target for this attribute is a valid + // one for the chosen attribute. + // + // The attribute declaration will have one or more `AttributeTargetModifier`s + // that each specify a syntax class that the attribute can be applied to. + // If any of these match `attrTarget`, then we are good. + // + bool validTarget = false; + for(auto attrTargetMod : attrDecl->GetModifiersOfType<AttributeTargetModifier>()) + { + if(attrTarget->getClass().isSubClassOf(attrTargetMod->syntaxClass)) + { + validTarget = true; + break; } } + if(!validTarget) + { + getSink()->diagnose(attr, Diagnostics::attributeNotApplicable, attrName); + return uncheckedAttr; + } + + // Now apply type-specific validation to the attribute. + if(!validateAttribute(attr)) + { + return uncheckedAttr; + } + + + return attr; + } + + RefPtr<Modifier> checkModifier( + RefPtr<Modifier> m, + ModifiableSyntaxNode* syntaxNode) + { + if(auto hlslUncheckedAttribute = m.As<UncheckedAttribute>()) + { + // We have an HLSL `[name(arg,...)]` attribute, and we'd like + // to check that it is provides all the expected arguments + // + // First, look up the attribute name in the current scope to find + // the right syntax class to instantiate. + // + + return checkAttribute(hlslUncheckedAttribute, syntaxNode); + } // Default behavior is to leave things as they are, // and assume that modifiers are mostly already checked. // @@ -1505,7 +1648,7 @@ namespace Slang } - void checkModifiers(Decl* decl) + void checkModifiers(ModifiableSyntaxNode* syntaxNode) { // TODO(tfoley): need to make sure this only // performs semantic checks on a `SharedModifier` once... @@ -1516,7 +1659,7 @@ namespace Slang RefPtr<Modifier> resultModifiers; RefPtr<Modifier>* resultModifierLink = &resultModifiers; - RefPtr<Modifier> modifier = decl->modifiers.first; + RefPtr<Modifier> modifier = syntaxNode->modifiers.first; while(modifier) { // Because we are rewriting the list in place, we need to extract @@ -1528,7 +1671,7 @@ namespace Slang // be to return a single unlinked modifier. modifier->next = nullptr; - auto checkedModifier = checkModifier(modifier, decl); + auto checkedModifier = checkModifier(modifier, syntaxNode); if(checkedModifier) { // If checking gave us a modifier to add, then we @@ -1552,7 +1695,7 @@ namespace Slang // Whether we actually re-wrote anything or note, lets // install the new list of modifiers on the declaration - decl->modifiers.first = resultModifiers; + syntaxNode->modifiers.first = resultModifiers; } void visitModuleDecl(ModuleDecl* programNode) @@ -2182,6 +2325,7 @@ namespace Slang { if (!stmt) return; StmtVisitor::dispatch(stmt); + checkModifiers(stmt); } void visitFuncDecl(FuncDecl *functionNode) @@ -2610,11 +2754,14 @@ namespace Slang { // TODO: This needs to bottleneck through the common variable checks - para->type = CheckUsableType(para->type); - - if (para->type.Equals(getSession()->getVoidType())) + if(para->type.exp) { - getSink()->diagnose(para, Diagnostics::parameterCannotBeVoid); + para->type = CheckUsableType(para->type); + + if (para->type.Equals(getSession()->getVoidType())) + { + getSink()->diagnose(para, Diagnostics::parameterCannotBeVoid); + } } } diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index b0b233b1d..f105b98aa 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -1028,3 +1028,71 @@ for (auto op : binaryOps) } } }}}} + +// Statement Attributes + +__attributeTarget(LoopStmt) +attribute_syntax [unroll(count: int = 0)] : UnrollAttribute; + +__attributeTarget(LoopStmt) +attribute_syntax [loop] : LoopAttribute; + +__attributeTarget(LoopStmt) +attribute_syntax [fastopt] : FastOptAttribute; + +__attributeTarget(LoopStmt) +attribute_syntax [allow_uav_condition] : AllowUAVConditionAttribute; + +__attributeTarget(IfStmt) +attribute_syntax [flatten] : FlattenAttribute; + +__attributeTarget(IfStmt) +__attributeTarget(SwitchStmt) +attribute_syntax [branch] : BranchAttribute; + +__attributeTarget(SwitchStmt) +attribute_syntax [forcecase] : ForceCaseAttribute; + +__attributeTarget(SwitchStmt) +attribute_syntax [call] : CallAttribute; + +// Entry-point Attributes + +// All Stages +__attributeTarget(FuncDecl) +attribute_syntax [shader(stage)] : EntryPointAttribute; + +// Hull Shader +__attributeTarget(FuncDecl) +attribute_syntax [maxtessfactor(factor: float)] : MaxTessFactorAttribute; + +__attributeTarget(FuncDecl) +attribute_syntax [outputcontrolpoints(count: int)] : OutputControlPointsAttribute; + +__attributeTarget(FuncDecl) +attribute_syntax [outputtopology(topology)] : OuptutTopologyAttribute; + +__attributeTarget(FuncDecl) +attribute_syntax [partitioning(mode)] : PartitioningAttribute; + +__attributeTarget(FuncDecl) +attribute_syntax [patchconstantfunc(name)] : PatchConstantFuncAttribute; + +// Hull/Domain Shader +__attributeTarget(FuncDecl) +attribute_syntax [domain(domain)] : DomainAttribute; + +// Geometry Shader +__attributeTarget(FuncDecl) +attribute_syntax [maxvertexcount(count: int)] : MaxVertexCountAttribute; + +__attributeTarget(FuncDecl) +attribute_syntax [instance(count: int)] : InstanceAttribute; + +// Fragment ("Pixel") Shader +__attributeTarget(FuncDecl) +attribute_syntax [earlydepthstencil] : EarlyDepthStencilAttribute; + +// Compute Shader +__attributeTarget(FuncDecl) +attribute_syntax [numthreads(x: int, y: int = 1, z: int = 1)] : NumThreadsAttribute; diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index 974607a75..5e1f2ec89 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -1028,3 +1028,71 @@ for (auto op : binaryOps) } } SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW("// Statement Attributes\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(LoopStmt)\n") +SLANG_RAW("attribute_syntax [unroll(count: int = 0)] : UnrollAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(LoopStmt)\n") +SLANG_RAW("attribute_syntax [loop] : LoopAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(LoopStmt)\n") +SLANG_RAW("attribute_syntax [fastopt] : FastOptAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(LoopStmt)\n") +SLANG_RAW("attribute_syntax [allow_uav_condition] : AllowUAVConditionAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(IfStmt)\n") +SLANG_RAW("attribute_syntax [flatten] : FlattenAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(IfStmt)\n") +SLANG_RAW("__attributeTarget(SwitchStmt)\n") +SLANG_RAW("attribute_syntax [branch] : BranchAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(SwitchStmt)\n") +SLANG_RAW("attribute_syntax [forcecase] : ForceCaseAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(SwitchStmt)\n") +SLANG_RAW("attribute_syntax [call] : CallAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Entry-point Attributes\n") +SLANG_RAW("\n") +SLANG_RAW("// All Stages\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [shader(stage)] : EntryPointAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Hull Shader\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [maxtessfactor(factor: float)] : MaxTessFactorAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [outputcontrolpoints(count: int)] : OutputControlPointsAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [outputtopology(topology)] : OuptutTopologyAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [partitioning(mode)] : PartitioningAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [patchconstantfunc(name)] : PatchConstantFuncAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Hull/Domain Shader\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [domain(domain)] : DomainAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Geometry Shader\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [maxvertexcount(count: int)] : MaxVertexCountAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [instance(count: int)] : InstanceAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Fragment (\"Pixel\") Shader\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [earlydepthstencil] : EarlyDepthStencilAttribute;\n") +SLANG_RAW("\n") +SLANG_RAW("// Compute Shader\n") +SLANG_RAW("__attributeTarget(FuncDecl)\n") +SLANG_RAW("attribute_syntax [numthreads(x: int, y: int = 1, z: int = 1)] : NumThreadsAttribute;\n") diff --git a/source/slang/decl-defs.h b/source/slang/decl-defs.h index 8e1985e3f..76480e64b 100644 --- a/source/slang/decl-defs.h +++ b/source/slang/decl-defs.h @@ -266,3 +266,10 @@ SYNTAX_CLASS(SyntaxDecl, Decl) FIELD(SyntaxParseCallback, parseCallback) FIELD(void*, parseUserData) END_SYNTAX_CLASS() + +// A declaration of an attribute to be used with `[name(...)]` syntax. +// +SYNTAX_CLASS(AttributeDecl, ContainerDecl) + // What type of syntax node will be produced to represent this attribute. + FIELD(SyntaxClass<RefObject>, syntaxClass) +END_SYNTAX_CLASS() diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 220ad44ff..ae815cd40 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -197,6 +197,11 @@ DIAGNOSTIC(30051, Error, invalidValueForArgument, "invalid value for argument '$ DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on type '$1'") DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.") +// Attributes +DIAGNOSTIC(31000, Error, unknownAttributeName, "unknown attribute '$0'") +DIAGNOSTIC(31001, Error, attributeArgumentCountMismatch, "attribute '$0' expects $1 arguments ($2 provided)") +DIAGNOSTIC(31001, Error, attributeNotApplicable, "attribute '$0' is not valid here") + // 303xx: interfaces and associated types DIAGNOSTIC(30300, Error, assocTypeInInterfaceOnly, "'associatedtype' can only be defined in an 'interface'.") DIAGNOSTIC(30301, Error, globalGenParamInGlobalScopeOnly, "'__generic_param' can only be defined global scope.") diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 58c917fe7..aee19018a 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -2809,23 +2809,7 @@ struct EmitVisitor void EmitLoopAttributes(RefPtr<Stmt> decl) { - // Don't emit these attributes for GLSL, because it doesn't understand them - if (context->shared->target == CodeGenTarget::GLSL) - return; - - // TODO(tfoley): There really ought to be a semantic checking step for attributes, - // that turns abstract syntax into a concrete hierarchy of attribute types (e.g., - // a specific `LoopModifier` or `UnrollModifier`). - - for(auto attr : decl->GetModifiersOfType<HLSLUncheckedAttribute>()) - { - // Emit whatever attributes the user might have attached, - // whether or not we think they make semantic sense. - // - Emit("["); - emit(attr->getName()); - Emit("]"); - } + // NOTE: Emit-from-AST is gone, so this code is doing nothing. } void EmitUnparsedStmt(RefPtr<UnparsedStmt> stmt) @@ -3134,6 +3118,7 @@ struct EmitVisitor // Only used by stdlib IGNORED(SyntaxDecl) + IGNORED(AttributeDecl) // Don't emit generic decls directly; we will only // ever emit particular instantiations of them. @@ -3346,27 +3331,6 @@ struct EmitVisitor } } - // TODO: eventually we should be checked these modifiers, but for - // now we can emit them unchecked, I guess - else if (auto uncheckedAttr = mod.As<HLSLAttribute>()) - { - Emit("["); - emit(uncheckedAttr->getNameAndLoc()); - auto& args = uncheckedAttr->args; - auto argCount = args.Count(); - if (argCount != 0) - { - Emit("("); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(", "); - EmitExpr(args[aa]); - } - Emit(")"); - } - Emit("]"); - } - else if(auto simpleModifier = mod.As<SimpleModifier>()) { emit(simpleModifier->getNameAndLoc()); @@ -6756,13 +6720,13 @@ emitDeclImpl(decl, nullptr); break; case Stage::Geometry: { - if (auto attrib = entryPointLayout->entryPoint->FindModifier<HLSLMaxVertexCountAttribute>()) + if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>()) { emit("[maxvertexcount("); Emit(attrib->value); emit(")]\n"); } - if (auto attrib = entryPointLayout->entryPoint->FindModifier<HLSLInstanceAttribute>()) + if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) { emit("[instance("); Emit(attrib->value); @@ -6813,13 +6777,13 @@ emitDeclImpl(decl, nullptr); break; case Stage::Geometry: { - if (auto attrib = entryPointLayout->entryPoint->FindModifier<HLSLMaxVertexCountAttribute>()) + if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>()) { emit("layout(max_vertices = "); Emit(attrib->value); emit(") out;\n"); } - if (auto attrib = entryPointLayout->entryPoint->FindModifier<HLSLInstanceAttribute>()) + if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) { emit("layout(invocations = "); Emit(attrib->value); diff --git a/source/slang/lookup.cpp b/source/slang/lookup.cpp index e5ffa00f7..eebef6503 100644 --- a/source/slang/lookup.cpp +++ b/source/slang/lookup.cpp @@ -96,6 +96,11 @@ bool DeclPassesLookupMask(Decl* decl, LookupMask mask) { return (int(mask) & int(LookupMask::Function)) != 0; } + // attribute declaration + else if( auto attrDecl = dynamic_cast<AttributeDecl*>(decl) ) + { + return (int(mask) & int(LookupMask::Attribute)) != 0; + } // default behavior is to assume a value declaration // (no overloading allowed) @@ -423,11 +428,13 @@ LookupResult lookUp( Session* session, SemanticsVisitor* semantics, Name* name, - RefPtr<Scope> scope) + RefPtr<Scope> scope, + LookupMask mask) { LookupRequest request; request.semantics = semantics; request.scope = scope; + request.mask = mask; return DoLookup(session, name, request); } @@ -437,10 +444,12 @@ LookupResult lookUpLocal( Session* session, SemanticsVisitor* semantics, Name* name, - DeclRef<ContainerDecl> containerDeclRef) + DeclRef<ContainerDecl> containerDeclRef, + LookupMask mask) { LookupRequest request; request.semantics = semantics; + request.mask = mask; LookupResult result; DoLocalLookupImpl(session, name, containerDeclRef, request, result, nullptr); @@ -448,12 +457,13 @@ LookupResult lookUpLocal( } void lookUpMemberImpl( - Session* session, + Session* session, SemanticsVisitor* semantics, Name* name, Type* type, LookupResult& ioResult, - BreadcrumbInfo* inBreadcrumbs) + BreadcrumbInfo* inBreadcrumbs, + LookupMask mask) { if (auto declRefType = type->As<DeclRefType>()) { @@ -475,7 +485,7 @@ void lookUpMemberImpl( breadcrumb.declRef = constraintDeclRef; // TODO: Need to consider case where this might recurse infinitely. - lookUpMemberImpl(session, semantics, name, bound, ioResult, &breadcrumb); + lookUpMemberImpl(session, semantics, name, bound, ioResult, &breadcrumb, mask); } } else if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>()) @@ -517,7 +527,7 @@ void lookUpMemberImpl( breadcrumb.declRef = constraintDeclRef; // TODO: Need to consider case where this might recurse infinitely. - lookUpMemberImpl(session, semantics, name, bound, ioResult, &breadcrumb); + lookUpMemberImpl(session, semantics, name, bound, ioResult, &breadcrumb, mask); } } @@ -529,10 +539,11 @@ LookupResult lookUpMember( Session* session, SemanticsVisitor* semantics, Name* name, - Type* type) + Type* type, + LookupMask mask) { LookupResult result; - lookUpMemberImpl(session, semantics, name, type, result, nullptr); + lookUpMemberImpl(session, semantics, name, type, result, nullptr, mask); return result; } diff --git a/source/slang/lookup.h b/source/slang/lookup.h index 473ecaf96..37ab5cf06 100644 --- a/source/slang/lookup.h +++ b/source/slang/lookup.h @@ -21,7 +21,8 @@ LookupResult lookUp( Session* session, SemanticsVisitor* semantics, Name* name, - RefPtr<Scope> scope); + RefPtr<Scope> scope, + LookupMask mask = LookupMask::Default); // perform lookup within the context of a particular container declaration, // and do *not* look further up the chain @@ -29,14 +30,16 @@ LookupResult lookUpLocal( Session* session, SemanticsVisitor* semantics, Name* name, - DeclRef<ContainerDecl> containerDeclRef); + DeclRef<ContainerDecl> containerDeclRef, + LookupMask mask = LookupMask::Default); // Perform member lookup in the context of a type LookupResult lookUpMember( Session* session, SemanticsVisitor* semantics, Name* name, - Type* type); + Type* type, + LookupMask mask = LookupMask::Default); // TODO: this belongs somewhere else diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index cfd1c89a4..428044830 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -2004,17 +2004,12 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> IRInst* inst, Stmt* stmt) { - for(auto attr : stmt->GetModifiersOfType<HLSLUncheckedAttribute>()) + if( stmt->FindModifier<UnrollAttribute>() ) { - // TODO: We should actually catch these attributes during - // semantic checking, so that they have a strongly-typed - // representation in the AST. - if(getText(attr->getName()) == "unroll") - { - auto decoration = getBuilder()->addDecoration<IRLoopControlDecoration>(inst); - decoration->mode = kIRLoopControl_Unroll; - } + auto decoration = getBuilder()->addDecoration<IRLoopControlDecoration>(inst); + decoration->mode = kIRLoopControl_Unroll; } + // TODO: handle other cases here } void visitForStmt(ForStmt* stmt) @@ -2794,6 +2789,11 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> return LoweredValInfo(); } + LoweredValInfo visitAttributeDecl(AttributeDecl* /*decl*/) + { + return LoweredValInfo(); + } + LoweredValInfo visitTypeDefDecl(TypeDefDecl * decl) { return LoweredValInfo::simple(context->irBuilder->getTypeVal(decl->type.type)); diff --git a/source/slang/modifier-defs.h b/source/slang/modifier-defs.h index 725629d35..fdbb05748 100644 --- a/source/slang/modifier-defs.h +++ b/source/slang/modifier-defs.h @@ -288,34 +288,88 @@ SIMPLE_SYNTAX_CLASS(HLSLUniformModifier, Modifier) // HLSL `volatile` modifier (ignored) SIMPLE_SYNTAX_CLASS(HLSLVolatileModifier, Modifier) -// An HLSL `[name(arg0, ...)]` style attribute. -SYNTAX_CLASS(HLSLAttribute, Modifier) +SYNTAX_CLASS(AttributeTargetModifier, Modifier) + // A class to which the declared attribute type is applicable + FIELD(SyntaxClass<RefObject>, syntaxClass) +END_SYNTAX_CLASS() + +// Base class for checked and unchecked `[name(arg0, ...)]` style attribute. +SYNTAX_CLASS(AttributeBase, Modifier) SYNTAX_FIELD(List<RefPtr<Expr>>, args) END_SYNTAX_CLASS() -// An HLSL `[name(...)]` attribute that hasn't undergone -// any semantic analysis. -// After analysis, this might be transformed into a more specific case. -SIMPLE_SYNTAX_CLASS(HLSLUncheckedAttribute, HLSLAttribute) +// A `[name(...)]` attribute that hasn't undergone any semantic analysis. +// After analysis, this will be transformed into a more specific case. +SYNTAX_CLASS(UncheckedAttribute, AttributeBase) + FIELD(RefPtr<Scope>, scope) +END_SYNTAX_CLASS() + +// A `[name(arg0, ...)]` style attribute that has been validated. +SYNTAX_CLASS(Attribute, AttributeBase) +END_SYNTAX_CLASS() + +// An `[unroll]` or `[unroll(count)]` attribute +SYNTAX_CLASS(UnrollAttribute, Attribute) + RAW(IntegerLiteralValue getCount();) +END_SYNTAX_CLASS() + +SIMPLE_SYNTAX_CLASS(LoopAttribute, Attribute) // `[loop]` +SIMPLE_SYNTAX_CLASS(FastOptAttribute, Attribute) // `[fastopt]` +SIMPLE_SYNTAX_CLASS(AllowUAVConditionAttribute, Attribute) // `[allow_uav_condition]` +SIMPLE_SYNTAX_CLASS(BranchAttribute, Attribute) // `[branch]` +SIMPLE_SYNTAX_CLASS(FlattenAttribute, Attribute) // `[flatten]` +SIMPLE_SYNTAX_CLASS(ForceCaseAttribute, Attribute) // `[forcecase]` +SIMPLE_SYNTAX_CLASS(CallAttribute, Attribute) // `[call]` + +// TODO: for attributes that take arguments, the syntax node +// classes should provide accessors for the values of those arguments. + +SIMPLE_SYNTAX_CLASS(MaxTessFactorAttribute, Attribute) +SIMPLE_SYNTAX_CLASS(OutputControlPointsAttribute, Attribute) +SIMPLE_SYNTAX_CLASS(OuptutTopologyAttribute, Attribute) +SIMPLE_SYNTAX_CLASS(PartitioningAttribute, Attribute) +SIMPLE_SYNTAX_CLASS(PatchConstantFuncAttribute, Attribute) +SIMPLE_SYNTAX_CLASS(DomainAttribute, Attribute) + +SIMPLE_SYNTAX_CLASS(EarlyDepthStencilAttribute, Attribute) // An HLSL `[numthreads(x,y,z)]` attribute -SYNTAX_CLASS(HLSLNumThreadsAttribute, HLSLAttribute) +SYNTAX_CLASS(NumThreadsAttribute, Attribute) // The number of threads to use along each axis + // + // TODO: These should be accessors that use the + // ordinary `args` list, rather than side data. FIELD(int32_t, x) FIELD(int32_t, y) FIELD(int32_t, z) END_SYNTAX_CLASS() -SYNTAX_CLASS(HLSLMaxVertexCountAttribute, HLSLAttribute) +SYNTAX_CLASS(MaxVertexCountAttribute, Attribute) // The number of max vertex count for geometry shader + // + // TODO: This should be an accessor that uses the + // ordinary `args` list, rather than side data. FIELD(int32_t, value) END_SYNTAX_CLASS() -SYNTAX_CLASS(HLSLInstanceAttribute, HLSLAttribute) +SYNTAX_CLASS(InstanceAttribute, Attribute) // The number of instances to run for geometry shader + // + // TODO: This should be an accessor that uses the + // ordinary `args` list, rather than side data. FIELD(int32_t, value) END_SYNTAX_CLASS() +// A `[shader("stageName")]` attribute, which marks an entry point +// to be compiled, and specifies the stage for that entry point +SYNTAX_CLASS(EntryPointAttribute, Attribute) + // The resolved stage that the entry point is targetting. + // + // TODO: This should be an accessor that uses the + // ordinary `args` list, rather than side data. + FIELD(Stage, stage); +END_SYNTAX_CLASS() + // HLSL modifiers for geometry shader input topology SIMPLE_SYNTAX_CLASS(HLSLGeometryShaderInputPrimitiveTypeModifier, Modifier) SIMPLE_SYNTAX_CLASS(HLSLPointModifier , HLSLGeometryShaderInputPrimitiveTypeModifier) diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index 82f79af17..5fb7445e9 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -602,10 +602,20 @@ namespace Slang parser->ReadToken(TokenType::LBracket); for(;;) { + // Note: When parsing we just construct an AST node for an + // "unchecked" attribute, and defer all detailed semantic + // checking until later. + // + // An alternative would be to perform lookup of an `AttributeDecl` + // at this point, similar to what we do for `SyntaxDecl`, but it + // seems better to not complicate the parsing process any more. + // + auto nameToken = parser->ReadToken(TokenType::Identifier); - RefPtr<HLSLUncheckedAttribute> modifier = new HLSLUncheckedAttribute(); + RefPtr<UncheckedAttribute> modifier = new UncheckedAttribute(); modifier->name = nameToken.getName(); modifier->loc = nameToken.getLoc(); + modifier->scope = parser->currentScope; if (AdvanceIf(parser, TokenType::LParent)) { @@ -2500,6 +2510,107 @@ namespace Slang return syntaxDecl; } + // A parameter declaration in an attribute declaration. + // + // We are going to use `name: type` syntax just for simplicty, and let the type + // be optional, because we don't actually need it in all cases. + // + static RefPtr<ParamDecl> parseAttributeParamDecl(Parser* parser) + { + auto nameAndLoc = expectIdentifier(parser); + + RefPtr<ParamDecl> paramDecl = new ParamDecl(); + paramDecl->nameAndLoc = nameAndLoc; + + if(AdvanceIf(parser, TokenType::Colon)) + { + paramDecl->type = parser->ParseTypeExp(); + } + + if(AdvanceIf(parser, TokenType::OpAssign)) + { + paramDecl->initExpr = parser->ParseInitExpr(); + } + + return paramDecl; + } + + // Parse declaration of a name to be used for resolving `[attribute(...)]` style modifiers. + // + // These are distinct from `syntax` declarations, because their names don't get added + // to the current scope using their default name. + // + // Also, attribute-specific code doesn't get invokved during parsing. We always parse + // using the default attribute-parsing logic and then all specialized behavior takes + // place during semantic checking. + // + static RefPtr<RefObject> parseAttributeSyntaxDecl(Parser* parser, void* /*userData*/) + { + // Right now the basic form is: + // + // attribute_syntax <name:id> : <syntaxClass:id>; + // + // - `name` gives the name of the attribute to define. + // - `syntaxClass` is the name of an AST node class that we expect + // this attribute to create when checked. + // - `existingKeyword` is the name of an existing keyword that + // the new syntax should be an alias for. + + expect(parser, TokenType::LBracket); + + // First we parse the attribute name. + auto nameAndLoc = expectIdentifier(parser); + + RefPtr<AttributeDecl> attrDecl = new AttributeDecl(); + if(AdvanceIf(parser, TokenType::LParent)) + { + while(!AdvanceIfMatch(parser, TokenType::RParent)) + { + auto param = parseAttributeParamDecl(parser); + + AddMember(attrDecl, param); + + if(AdvanceIfMatch(parser, TokenType::RParent)) + break; + + expect(parser, TokenType::Comma); + } + } + + expect(parser, TokenType::RBracket); + + // TODO: we should allow parameters to be specified here, to cut down + // on the amount of per-attribute-type logic that has to occur later. + + // Next we look for a clause that specified the AST node class. + SyntaxClass<RefObject> syntaxClass; + if (AdvanceIf(parser, TokenType::Colon)) + { + // User is specifying the class that should be construted + auto classNameAndLoc = expectIdentifier(parser); + + syntaxClass = parser->getSession()->findSyntaxClass(classNameAndLoc.name); + } + else + { + // For now we don't support the alternative approach where + // an existing piece of syntax is named to provide the parsing + // support. + + // TODO: diagnose: a syntax class must be specified. + } + + expect(parser, TokenType::Semicolon); + + // TODO: skip creating the declaration if anything failed, just to not screw things + // up for downstream code? + + attrDecl->nameAndLoc = nameAndLoc; + attrDecl->loc = nameAndLoc.loc; + attrDecl->syntaxClass = syntaxClass; + return attrDecl; + } + // Finish up work on a declaration that was parsed static void CompleteDecl( Parser* /*parser*/, @@ -4182,6 +4293,20 @@ namespace Slang return modifier; } + static RefPtr<RefObject> parseAttributeTargetModifier(Parser* parser, void* /*userData*/) + { + expect(parser, TokenType::LParent); + auto syntaxClassNameAndLoc = expectIdentifier(parser); + expect(parser, TokenType::RParent); + + auto syntaxClass = parser->getSession()->findSyntaxClass(syntaxClassNameAndLoc.name); + + RefPtr<AttributeTargetModifier> modifier = new AttributeTargetModifier(); + modifier->syntaxClass = syntaxClass; + + return modifier; + } + RefPtr<ModuleDecl> populateBaseLanguageModule( Session* session, RefPtr<Scope> scope) @@ -4205,6 +4330,7 @@ namespace Slang DECL(__subscript, ParseSubscriptDecl); DECL(interface, parseInterfaceDecl); DECL(syntax, parseSyntaxDecl); + DECL(attribute_syntax,parseAttributeSyntaxDecl); DECL(__import, parseImportDecl); DECL(import, parseImportDecl); @@ -4280,6 +4406,9 @@ namespace Slang MODIFIER(__intrinsic_type, parseIntrinsicTypeModifier); MODIFIER(__implicit_conversion, parseImplicitConversionModifier); + MODIFIER(__attributeTarget, parseAttributeTargetModifier); + + #undef MODIFIER // Add syntax for expression keywords diff --git a/source/slang/reflection.cpp b/source/slang/reflection.cpp index 708b98f2b..65901d6af 100644 --- a/source/slang/reflection.cpp +++ b/source/slang/reflection.cpp @@ -961,7 +961,7 @@ SLANG_API void spReflectionEntryPoint_getComputeThreadGroupSize( SlangUInt sizeAlongAxis[3] = { 1, 1, 1 }; // First look for the HLSL case, where we have an attribute attached to the entry point function - auto numThreadsAttribute = entryPointFunc->FindModifier<HLSLNumThreadsAttribute>(); + auto numThreadsAttribute = entryPointFunc->FindModifier<NumThreadsAttribute>(); if (numThreadsAttribute) { sizeAlongAxis[0] = numThreadsAttribute->x; diff --git a/source/slang/syntax-base-defs.h b/source/slang/syntax-base-defs.h index 2c15f7215..4fded014e 100644 --- a/source/slang/syntax-base-defs.h +++ b/source/slang/syntax-base-defs.h @@ -240,7 +240,7 @@ END_SYNTAX_CLASS() // (that is, we don't use a bitfield, even for simple/common flags). // This ensures that we can track source locations for all modifiers. // -ABSTRACT_SYNTAX_CLASS(Modifier, SyntaxNodeBase) +ABSTRACT_SYNTAX_CLASS(Modifier, SyntaxNode) RAW(typedef IModifierVisitor Visitor;) RAW(virtual void accept(IModifierVisitor* visitor, void* extra) = 0;) diff --git a/source/slang/syntax.h b/source/slang/syntax.h index 0dbdd8238..0f23492d6 100644 --- a/source/slang/syntax.h +++ b/source/slang/syntax.h @@ -805,8 +805,9 @@ namespace Slang type = 0x1, Function = 0x2, Value = 0x4, + Attribute = 0x8, - All = type | Function | Value, + Default = type | Function | Value, }; // Represents one item found during lookup @@ -1000,7 +1001,7 @@ namespace Slang RefPtr<Scope> scope = nullptr; RefPtr<Scope> endScope = nullptr; - LookupMask mask = LookupMask::All; + LookupMask mask = LookupMask::Default; }; // Generate class definition for all syntax classes |
