From 618acb24fdd8e3c447e4ab8826ea9a4eb117885b Mon Sep 17 00:00:00 2001 From: Theresa Foley Date: Tue, 25 Jan 2022 12:59:52 -0800 Subject: Add support for HLSL unorm/snorm (#2095) Read/write resource types (what D3D/HLSL often refer to as UAVs) can be broadly categorized based on whether they require an underlying format (e.g., a `DXGI_FORMAT`) for reads, or not. D3D refers to the ones that require a format as "typed" UAVs (even though a `RWStructuredBuffer` is clearly "typed" at the HLSL level). Vulkan refers to these cases as "storage images" and "storage texel buffers." Under the D3D model, an application does not have to specify the exact format for a formatted/"typed" UAV in order for loads to work, but it *does* need to specify if an HLSL resource with a declared `float` or vector-of-`float` element type will be backed by data with a `*_UNORM` or `*_SNORM` format. This is where the `unorm` and `snorm` type modifiers come in. Superficially, it might seem that adding this feature to the Slang compiler is "just" a matter of adding the two modifiers, which is easily done with a pair of one-line `syntax` declarations in `core.meta.slang` plus the corresponding AST node types. Unfortunately the superficial view misses the detail that, to date, Slang has not had any support for *type modifiers* at all, and has only supported *declaration modifiers*. The distinction has so far not mattered, even with modifiers like `const` because, e.g., the difference between a "`const` array of `float`" and an "array of `const float`" doesn't really matter. So, adding these two modifiers required introducing a lot of infrastructure along the way. Let's walk through what needed to happen: * As described above, the actual `syntax` was added easily in the Slang stdlib * I added a new subclass of `Modifier` for `TypeModifier`s in the AST, and added the AST nodes for `unorm` and `snorm` as subclasses of that. * In order to syntactically support modifiers applied to types (e.g., `unorm float`), I needed to add a `ModifiedTypeExpr` subclass of `Expr` that represents a base type expression with one or more modifiers applied * The parser needed some subtle new logic. There are two main cases where type modifiers will come up: 1. In contexts where we might be parsing a declaration (e.g., `const unorm float a`), we need to support a list of modifiers that might freely mix type modifiers and "declaration modifiers" which are not intended to apply to types. In this case we need to split the lis tof modifiers into the type-related ones and the declaration-related ones, and attach each subset to the appropriate place. This is very important for features like C-style pointers, where in `static const float* a;`, the `static` modifier applies to the entire declaration of `a`, but the `const` modifier *only* applies to the `float` type specifier, and *not* to the outer pointer type (the actual type of `a`). 2. In contexts where we are not parsing a declaration (e.g., a generic type argument), we need to support a list of modifiers and appy them *all* to the type specifier being parsed, even if some of them might not be appropriate. * While working in the parser I implemented a certain amount of unrelated cleanup for code that was using raw `Modifier*`s to represent lists of modifiers, instead of the purpose-built `Modifiers` type. * The `_parseGenericArg` case needed specific work, because it is an important case in the grammar where we need to parse *either* a type expression or a value exprssion, but cannot easily predict which we will see. The fix implemented for now is to always try to parse modifiers and, if we see any, to assume we are in the type case. Because of the rules for how modifiers in a C-like language inhere to the type specifier (and not necessarily the entire type), we need to refactor some of the type expression parsing routines to support parsing a "suffix" of a type expression. * Note: I decided to be conservative and only make these changes in `_parseGenericArg` because that is place that is *needed* in order for user code with `unorm`/`snorm` to work, but in practice a user could still confuse our parser by using type modifiers as part of a cast (e.g., `x = (unorm float)y;`). While there is currently no reason why a user should want to do this, it *does* suggest that we need to be prepared to see type modifiers in other ambiguous "expression or type?" contexts. We have so far preferred to avoid looking up built-in syntax declarations like modifiers in expression contexts, because we want to allow users to create variable names that might conflict with some of the more surprising modifier keywords in HLSL (e.g., both `triangle` and `sample` are modifier keyword). A nuanced strategy may be required when we get around to closing this gap (which will be needed around when we want full pointer support, since a cast like `(const SomeType*)somePtr` is pretty common). * In semantic checking, we now need a `visitModifiedTypeExpr`, which visits the base expression to produce a `Type` and then checks each of the `Modifier`s attached to it. During this process we need to translate the AST-level `Modifier`s into something that can exist properly in the universe of `Type`s. We introduce a `ModifiedType` subclass of `Type`, distinct from the `ModifiedTypeExpr` subclass of `Expr`. Furthermore, we introduce a `ModifierVal` subclass of `Val`, distinct from `Modifier`/`TypeModifier`. * One unfortunate thing here is that it means we have both, e.g., `UNormModifier` to represent the parsed syntax, and `UNormModifierVal` to represent the `Type`/`Val`-level representation of the same concept. It is quite likely that we are near the point where we can/should consider having two distinct AST representations: one for freshly-parsed ASTs and one for semantically-checked ASTs. The `Type`/`Val` hierarchy clearly belongs to the latter. * No actual semantic checking is currently being applied to the `unorm` and `snorm` modifiers, although we should in principle check that they are only being applied to `float` and vector-of-`float` types. * In an attempt to simplify some of the creation logic and build a tiny bit of reusable infrastructure, I went ahead and added the skeleton of a dedupe-caching system in `ASTBuilder` so that we can easily ensure only a single `UNormModifierVal` and a single `SNormModifierVal` ever get created inside the scope of a single builder. * TODO: Thinking about this, I'm now worried the deduplication does not mean I can make the simplifications I currently do in semantic checking by assuming that any two `UNormModifierVal`s will be pointer-identical. This is because we do not currently (IIRC) have the required "bottleneck" in the compiler where all ASTs get serialized after initial checking, and then deserialized when `import`ed into a downstream module, so that every AST node during a checking step comes from a single `ASTBuilder`. Hmm... * If we can rely on deduplication to do its thing, then the `Val` and `Type` implementations of modifiers can be relatively simple. * TODO: One issue here is that the equality comparison for `ModifiedType` currently checks for the same base type and the same modifiers in the same order. This works for now when we only have a small number of type modifiers and any given type will hae at most one, but in the longer run it relies on us to implement some kind of canonicalization scheme, which would both ensure that between `Modified(T, {A, B})` and `Modified(T, {B, A})` only one is allowed (that is, a canonical ordering on modifiers), and that we do not allow `Modified(Modified(T, {A}), {B})`. * TODO: One other issues is that the `ModifiedType` case does not currently interact correctly with the `as()`-based casting for types (whereas that operation *does* interact in a semantically-correct fashion with `typedef`s). Fixing this issue in a robust way really depends on us re-architecting the `Type` system so that *any* `Type` can have modifiers attached, with modifiers affecting type identity/deduplication. * The key place where `ModifiedType` creates a complication in semantic checking is type conversion/coercion. A user is likely to declare a `RWTexture2D`, fetch from it (producing a value of type `unorm float`) and then assign the result to a `float` variable, prompting for a conversion from `unorm float` to `float` (because they are distinct `Type`s). * We handle this case in the core `_coerce()` operation by checking if either `toType` or `fromType` is a `ModifiedType`. If *either* one is a modified type, we apply logic to check for modifiers that are present on one and not the other. Basically we check which modifiers need to be "dropped" and which need to be "added" during conversion, and validate that these modifiers *can* be dropped/added without creating a semantic error. The only type modifiers we support right now *can* be dropped/added like this, so we are fine. * TODO: When we add more complete pointer support, we could need logic here to validate when casts between, e.g., `const int*` and `int*` should/shouldn't be allowed. * Note: Even opening the door to type modifiers at all creates the same kind of challenges for user-defined generic types (and functions!) since `MyType` and `MyType` are distinct instantiations in a future where we support `const` as a type modifier. We *may* need to plan to restrict where modified types can be used, so that certain built-in generic types support modified types as arguments, but user-defined types don't (or at least might need to opt-in to get support). * The result of a `_coerce()` that drops/adds modifiers is a `ModifierCastExpr`, which is a kind of no-op AST node that merely expresses that the conversion is allowed and valid. * In IR lowering we currently do the simple thing and translate a `ModifiedType` to a distinct IR node called `AttributedType`. * The change in terminology from "modifier" to "attribute" is to follow the way that these kinds of modifiers best map to the `IRAttr` case in the IR (rather than the `IRDecoration` case). We probably ought to do a careful terminology scrub here, because having this terminology mismatch between IR and AST could be a source of confusion. * TODO: In principle, using `IRAttributedType` creates the same basic problems as using `ModifiedType`: code that is usin `as()` or similar operations to check for a specific subclass of `IRType` may not see the case they were looking for due to use of `IRAttributedType`. * Initially I had hoped to avoid the problem by having the `IRAttr`s be attached directly as operands to an otherwise-ordinary `IRType`. E.g., a lowered `unorm float4` would be an `IRVectorType` with an "extra" operand that is an `IRUNormAttr`, something like: `Vector`. This sounds great (and looks great!), but runs into the problem that it is incompatible with the way we currently represent things like generic type parameters. A generic type parameter `T` is represented as an `IRParam`, and it does *not* make sense to have an additional `IRParam` to represent `const T` or `unorm T`, etc. * The Right Way to solve this stuff at both the AST and IR levels is to avoid passing around bare `Type*` or `IRType*` in general, and instead use a value type that implements the needed policy more directly: something like a `TypeHolder` or `IRTypeHolder` (placeholder name). The `*Holder` type would abstract over the various "wrapper" nodes required to store all the additional data like attributes but, importantly, would *not* allow that extra information to be dropped or lost during operations like casting (e.g., note how the current `Type` implementation of `as()` loses information on `typedef` names, making our error messages slightly worse). This is actually quite similar to how we currently use the `DeclRef` system to allow working with what is *usually* a `T*` under the hood, but in a way that ensures we don't lose track of any generic substitution information. * During C-like code emit we have a process that turns an `IRType` into a chain of declarators as needed to emit a C-like declaration with pointers, arrays, etc. The `IRAttributedType` case needs to get folded into this logic. Basically, when we see an `IRAttributedType` we immediately emit any modifiers that are required to be in a prefix position, then recursively emit the underlying type with an extra layer of declarator that tracks the modifiers, so that we can emit any modifiers that should be placed in a postfix position *after* the type. As a specific example, our C/C++ back-end would want to use the postifx option to handle `const`, because then it can properly emit stuff like `int const * const *` and not the incorrect `const const int**`. * The HLSL emit logic overrides the prefix case for handling type attributes, and uses it to emit `unorm` and `snorm` where they occur. * One unfortunate detail is that (apparently) some downstream HLSL compilers do not allow the `unorm`/`snorm` modifiers to apply to `vector` types, even though that should be semantically valid. Instead, they only support `float`, `float2`, `float3`, and `float4` explicitly. To work around this issue, we go ahead and change our HLSL emit logic so that when we encountered 1-to-4 component vectors of `float`, `int`, or `uint` we emit the type name using the typical HLSL shorthand. This is actually a signficicant change in our HLSL output, but it both seemed like a good fix to have anyway, and was also the only obvious way to address the downstream parser shortcomings without a massive kludge. * As a result of this change the `half-texture.slang` test broke, since it was using raw HLSL as the expected output. I changed the test to do a DXIL comparison instead, which is our preferred way of testing cross-compilation behavior (since it is more robust in the face of small changes to our source output). --- source/core/slang-common.h | 2 + source/slang/core.meta.slang | 36 +++ source/slang/slang-ast-builder.cpp | 72 +++++ source/slang/slang-ast-builder.h | 65 +++++ source/slang/slang-ast-expr.h | 26 +- source/slang/slang-ast-modifier.h | 40 +++ source/slang/slang-ast-type.cpp | 97 +++++++ source/slang/slang-ast-type.h | 15 + source/slang/slang-ast-val.cpp | 46 +++ source/slang/slang-ast-val.h | 35 +++ source/slang/slang-check-conversion.cpp | 145 ++++++++++ source/slang/slang-check-expr.cpp | 49 ++++ source/slang/slang-check-impl.h | 9 + source/slang/slang-emit-c-like.cpp | 56 +++- source/slang/slang-emit-c-like.h | 16 +- source/slang/slang-emit-hlsl.cpp | 37 ++- source/slang/slang-emit-hlsl.h | 1 + source/slang/slang-ir-inst-defs.h | 3 + source/slang/slang-ir-insts.h | 24 ++ source/slang/slang-ir.cpp | 25 +- source/slang/slang-ir.h | 10 +- source/slang/slang-lower-to-ir.cpp | 40 +++ source/slang/slang-parser.cpp | 310 ++++++++++++++++++--- tests/compute/half-texture.slang | 2 +- tests/compute/half-texture.slang.1.expected | 45 --- tests/compute/half-texture.slang.hlsl | 26 ++ .../types/modifiers/snorm-modifier.slang | 18 ++ .../types/modifiers/unorm-modifier.slang | 19 ++ 28 files changed, 1163 insertions(+), 106 deletions(-) delete mode 100644 tests/compute/half-texture.slang.1.expected create mode 100644 tests/compute/half-texture.slang.hlsl create mode 100644 tests/language-feature/types/modifiers/snorm-modifier.slang create mode 100644 tests/language-feature/types/modifiers/unorm-modifier.slang diff --git a/source/core/slang-common.h b/source/core/slang-common.h index 6f91da873..f17660e0d 100644 --- a/source/core/slang-common.h +++ b/source/core/slang-common.h @@ -36,6 +36,8 @@ namespace Slang // Type used for indexing, in arrays/views etc. Signed. typedef Int Index; typedef UInt UIndex; + typedef Int Count; + typedef UInt UCount; static const Index kMaxIndex = kMaxInt; diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index d2f574ef8..ca07b299d 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -27,6 +27,42 @@ syntax globallycoherent : GloballyCoherentModifier; /// syntax pervertex : PerVertexModifier; +/// Modifier to indicate a buffer or texture element type is +/// backed by data in an unsigned normalized format. +/// +/// The `unorm` modifier is only valid on `float` and `vector`s +/// with `float` elements. +/// +/// This modifier does not affect the semantics of any variable, +/// parameter, or field that uses it. The semantics of a `float` +/// or vector are the same with or without `unorm`. +/// +/// The `unorm` modifier can be used for the element type of a +/// buffer or texture, to indicate that the data that is bound +/// to that buffer or texture is in a matching normalized format. +/// Some platforms may require a `unorm` qualifier for such buffers +/// and textures, and others may operate correctly without it. +/// +syntax unorm : UNormModifier; + +/// Modifier to indicate a buffer or texture element type is +/// backed by data in an signed normalized format. +/// +/// The `snorm` modifier is only valid on `float` and `vector`s +/// with `float` elements. +/// +/// This modifier does not affect the semantics of any variable, +/// parameter, or field that uses it. The semantics of a `float` +/// or vector are the same with or without `snorm`. +/// +/// The `snorm` modifier can be used for the element type of a +/// buffer or texture, to indicate that the data that is bound +/// to that buffer or texture is in a matching normalized format. +/// Some platforms may require a `unorm` qualifier for such buffers +/// and textures, and others may operate correctly without it. +/// +syntax snorm : SNormModifier; + /// A type that can be used as an operand for builtins [sealed] [builtin] diff --git a/source/slang/slang-ast-builder.cpp b/source/slang/slang-ast-builder.cpp index 4948a68a6..8e1813df3 100644 --- a/source/slang/slang-ast-builder.cpp +++ b/source/slang/slang-ast-builder.cpp @@ -288,10 +288,82 @@ Type* ASTBuilder::getAndType(Type* left, Type* right) return type; } +Type* ASTBuilder::getModifiedType(Type* base, Count modifierCount, Val* const* modifiers) +{ + auto type = create(); + type->base = base; + type->modifiers.addRange(modifiers, modifierCount); + return type; +} + +NodeBase* ASTBuilder::_getOrCreateImpl(NodeDesc const& desc, NodeCreateFunc createFunc, void* createFuncUserData) +{ + if(auto found = m_cachedNodes.TryGetValue(desc)) + return *found; + + auto node = createFunc(this, desc, createFuncUserData); + + auto operandCount = desc.operandCount; + NodeBase** operandsCopy = m_arena.allocateAndZeroArray(desc.operandCount); + for(Index i = 0; i < operandCount; ++i) + operandsCopy[i] = desc.operands[i]; + + NodeDesc descCopy = desc; + descCopy.operands = operandsCopy; + m_cachedNodes.Add(descCopy, node); + + return node; +} + + +Val* ASTBuilder::getUNormModifierVal() +{ + return _getOrCreate(); +} + +Val* ASTBuilder::getSNormModifierVal() +{ + return _getOrCreate(); +} + TypeType* ASTBuilder::getTypeType(Type* type) { return create(type); } +bool ASTBuilder::NodeDesc::operator==(NodeDesc const& that) const +{ + if(type != that.type) return false; + if(operandCount != that.operandCount) return false; + for(Index i = 0; i < operandCount; ++i) + { + // Note: we are comparing the operands directly for identity + // (pointer equality) rather than doing the `Val`-level + // equality check. + // + // The rationale here is that nodes that will be created + // via a `NodeDesc` *should* all be going through the + // deduplication path anyway, as should their operands. + // + if(operands[i] != that.operands[i]) return false; + } + return true; +} +HashCode ASTBuilder::NodeDesc::getHashCode() const +{ + Hasher hasher; + hasher.hashValue(Int(type)); + hasher.hashValue(operandCount); + for(Index i = 0; i < operandCount; ++i) + { + // Note: we are hashing the raw pointer value rather + // than the content of the value node. This is done + // to match the semantics implemented for `==` on + // `NodeDesc`. + // + hasher.hashValue((void*) operands[i]); + } + return hasher.getResult(); +} } // namespace Slang diff --git a/source/slang/slang-ast-builder.h b/source/slang/slang-ast-builder.h index 2838230bd..7af5ae710 100644 --- a/source/slang/slang-ast-builder.h +++ b/source/slang/slang-ast-builder.h @@ -157,6 +157,14 @@ public: Type* getAndType(Type* left, Type* right); + Type* getModifiedType(Type* base, Count modifierCount, Val* const* modifiers); + Type* getModifiedType(Type* base, List const& modifiers) + { + return getModifiedType(base, modifiers.getCount(), modifiers.getBuffer()); + } + Val* getUNormModifierVal(); + Val* getSNormModifierVal(); + TypeType* getTypeType(Type* type); /// Helpers to get type info from the SharedASTBuilder @@ -208,6 +216,63 @@ protected: SharedASTBuilder* m_sharedASTBuilder; MemoryArena m_arena; + + struct NodeDesc + { + ASTNodeType type; + Count operandCount = 0; + NodeBase* const* operands = nullptr; + + bool operator==(NodeDesc const& that) const; + HashCode getHashCode() const; + }; + + /// A cache for AST nodes that are entirely defined by their node type, with + /// no need for additional state. + Dictionary m_cachedNodes; + + + typedef NodeBase* (*NodeCreateFunc)(ASTBuilder* astBuilder, NodeDesc const& desc, void* userData); + + NodeBase* _getOrCreateImpl(NodeDesc const& desc, NodeCreateFunc createFunc, void* createFuncUserData); + + template + SLANG_FORCE_INLINE T* _getOrCreate(Count operandCount, NodeBase* const* operands) + { + SLANG_COMPILE_TIME_ASSERT(IsValidType::Value); + + struct Helper + { + static NodeBase* create(ASTBuilder* astBuilder, NodeDesc const& desc, void* /*userData*/) + { + return astBuilder->create(desc.operandCount, desc.operands); + } + }; + + NodeDesc desc; + desc.type = T::kType; + desc.operandCount = operandCount; + desc.operands = operands; + return (T*) _getOrCreateImpl(desc, &Helper::create, nullptr); + } + + template + SLANG_FORCE_INLINE T* _getOrCreate() + { + SLANG_COMPILE_TIME_ASSERT(IsValidType::Value); + + struct Helper + { + static NodeBase* create(ASTBuilder* astBuilder, NodeDesc const& /*desc*/, void* /*userData*/) + { + return astBuilder->create(); + } + }; + + NodeDesc desc; + desc.type = T::kType; + return (T*) _getOrCreateImpl(desc, &Helper::create, nullptr); + } }; } // namespace Slang diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index 81bed9264..b73e07042 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -240,7 +240,7 @@ class CastToSuperTypeExpr: public Expr /// The value being cast to a super type /// - /// The type being case from is `valueArg->type`. + /// The type being cast from is `valueArg->type`. /// Expr* valueArg = nullptr; @@ -248,6 +248,21 @@ class CastToSuperTypeExpr: public Expr Val* witnessArg = nullptr; }; + /// A cast of a value to the same type, with different modifiers. + /// + /// The type being cast to is stored as this expression's `type`. + /// +class ModifierCastExpr : public Expr +{ + SLANG_AST_CLASS(ModifierCastExpr) + + /// The value being cast. + /// + /// The type being cast from is `valueArg->type`. + /// + Expr* valueArg = nullptr; +}; + class SelectExpr: public OperatorExpr { SLANG_AST_CLASS(SelectExpr) @@ -340,4 +355,13 @@ class AndTypeExpr : public Expr TypeExp right; }; + /// A type exprssion that applies one or more modifiers to another type +class ModifiedTypeExpr : public Expr +{ + SLANG_AST_CLASS(ModifiedTypeExpr); + + Modifiers modifiers; + TypeExp base; +}; + } // namespace Slang diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h index 2929a53c2..2558d7c4b 100644 --- a/source/slang/slang-ast-modifier.h +++ b/source/slang/slang-ast-modifier.h @@ -983,4 +983,44 @@ class PayloadAttribute : public Attribute SLANG_AST_CLASS(PayloadAttribute) }; + /// A modifier that applies to types rather than declarations. + /// + /// In most cases, the Slang compiler assumes that a modifier should + /// inhere to a declaration. Given input like: + /// + /// mod1 mod2 int myVar = ...; + /// + /// The default assumption is that `mod1` and `mod2` apply to `myVar` + /// and *not* to the `int` type. + /// + /// In order to allow modifiers to inhere to the type instead, we introduce + /// a base class for modifiers that really don't want to belong to the declaration, + /// and instead want to belong to the type (or rather the type *specifier* + /// from a parsing standpoint). + /// +class TypeModifier : public Modifier +{ + SLANG_AST_CLASS(TypeModifier) +}; + + /// A modifier that applies to a type and implies information about the + /// underlying format of a resource that uses that type as its element type. + /// +class ResourceElementFormatModifier : public TypeModifier +{ + SLANG_AST_CLASS(ResourceElementFormatModifier) +}; + + /// HLSL `unorm` modifier +class UNormModifier : public ResourceElementFormatModifier +{ + SLANG_AST_CLASS(UNormModifier) +}; + + /// HLSL `snorm` modifier +class SNormModifier : public ResourceElementFormatModifier +{ + SLANG_AST_CLASS(SNormModifier) +}; + } // namespace Slang diff --git a/source/slang/slang-ast-type.cpp b/source/slang/slang-ast-type.cpp index 45a736f23..b6bc1f170 100644 --- a/source/slang/slang-ast-type.cpp +++ b/source/slang/slang-ast-type.cpp @@ -1053,5 +1053,102 @@ Val* AndType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet su return substType; } +// ModifiedType + + +void ModifiedType::_toTextOverride(StringBuilder& out) +{ + for( auto modifier : modifiers ) + { + modifier->toText(out); + out.appendChar(' '); + } + base->toText(out); +} + +bool ModifiedType::_equalsImplOverride(Type* type) +{ + auto other = as(type); + if(!other) + return false; + + if(!base->equals(other->base)) + return false; + + // TODO: Eventually we need to put the `modifiers` into + // a canonical ordering as part of creation of a `ModifiedType`, + // so that two instances that apply the same modifiers to + // the same type will have those modifiers in a matching order. + // + // The simplest way to achieve that ordering *for now* would + // be to sort the array by the integer AST node type tag. + // That approach would of course not scale to modifiers that + // have any operands of their own. + // + // Note that we would *also* need the logic that creates a + // `ModifiedType` to detect when the base type is itself a + // `ModifiedType` and produce a single `ModifiedType` with + // a combined list of modifiers and a non-`ModifiedType` as + // its base type. + // + auto modifierCount = modifiers.getCount(); + if(modifierCount != other->modifiers.getCount()) + return false; + + for( Index i = 0; i < modifierCount; ++i ) + { + auto thisModifier = this->modifiers[i]; + auto otherModifier = other->modifiers[i]; + if(!thisModifier->equalsVal(otherModifier)) + return false; + } + return true; +} + +HashCode ModifiedType::_getHashCodeOverride() +{ + Hasher hasher; + hasher.hashObject(base); + for( auto modifier : modifiers ) + { + hasher.hashObject(modifier); + } + return hasher.getResult(); +} + +Type* ModifiedType::_createCanonicalTypeOverride() +{ + ModifiedType* canonical = m_astBuilder->create(); + canonical->base = base->getCanonicalType(); + for( auto modifier : modifiers ) + { + canonical->modifiers.add(modifier); + } + return canonical; +} + +Val* ModifiedType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) +{ + int diff = 0; + Type* substBase = as(base->substituteImpl(astBuilder, subst, &diff)); + + List substModifiers; + for( auto modifier : modifiers ) + { + auto substModifier = modifier->substituteImpl(astBuilder, subst, &diff); + substModifiers.add(substModifier); + } + + if(!diff) + return this; + + *ioDiff = 1; + + ModifiedType* substType = m_astBuilder->create(); + substType->base = substBase; + substType->modifiers = _Move(substModifiers); + return substType; +} + } // namespace Slang diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h index 5b44a5087..f919e7bc2 100644 --- a/source/slang/slang-ast-type.h +++ b/source/slang/slang-ast-type.h @@ -729,4 +729,19 @@ class AndType : public Type Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); }; +class ModifiedType : public Type +{ + SLANG_AST_CLASS(ModifiedType) + + Type* base; + List modifiers; + + // Overrides should be public so base classes can access + void _toTextOverride(StringBuilder& out); + bool _equalsImplOverride(Type* type); + HashCode _getHashCodeOverride(); + Type* _createCanonicalTypeOverride(); + Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); +}; + } // namespace Slang diff --git a/source/slang/slang-ast-val.cpp b/source/slang/slang-ast-val.cpp index 97d79e290..0f3bd2b3a 100644 --- a/source/slang/slang-ast-val.cpp +++ b/source/slang/slang-ast-val.cpp @@ -528,6 +528,52 @@ Val* TaggedUnionSubtypeWitness::_substituteImplOverride(ASTBuilder* astBuilder, return substWitness; } +// ModifierVal +bool ModifierVal::_equalsValOverride(Val* val) +{ + // TODO: This is assuming we can fully deduplicate the values that represent + // modifiers, which may not actually be the case if there are multiple modules + // being combined that use different `ASTBuilder`s. + // + return this == val; +} + +HashCode ModifierVal::_getHashCodeOverride() +{ + Hasher hasher; + hasher.hashValue((void*) this); + return hasher.getResult(); +} + +// UNormModifierVal + +void UNormModifierVal::_toTextOverride(StringBuilder& out) +{ + out.append("unorm"); +} + +Val* UNormModifierVal::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) +{ + SLANG_UNUSED(astBuilder); + SLANG_UNUSED(subst); + SLANG_UNUSED(ioDiff); + return this; +} + +// SNormModifierVal + +void SNormModifierVal::_toTextOverride(StringBuilder& out) +{ + out.append("snorm"); +} + +Val* SNormModifierVal::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) +{ + SLANG_UNUSED(astBuilder); + SLANG_UNUSED(subst); + SLANG_UNUSED(ioDiff); + return this; +} } // namespace Slang diff --git a/source/slang/slang-ast-val.h b/source/slang/slang-ast-val.h index b76d6325f..5fd3e54f5 100644 --- a/source/slang/slang-ast-val.h +++ b/source/slang/slang-ast-val.h @@ -241,4 +241,39 @@ class ExtractFromConjunctionSubtypeWitness : public SubtypeWitness int indexInConjunction; }; + /// A value that represents a modifier attached to some other value +class ModifierVal : public Val +{ + SLANG_AST_CLASS(ModifierVal) + + bool _equalsValOverride(Val* val); + HashCode _getHashCodeOverride(); +}; + +class TypeModifierVal : public ModifierVal +{ + SLANG_AST_CLASS(TypeModifierVal) +}; + +class ResourceFormatModifierVal : public TypeModifierVal +{ + SLANG_AST_CLASS(ResourceFormatModifierVal) +}; + +class UNormModifierVal : public ResourceFormatModifierVal +{ + SLANG_AST_CLASS(UNormModifierVal) + + void _toTextOverride(StringBuilder& out); + Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); +}; + +class SNormModifierVal : public ResourceFormatModifierVal +{ + SLANG_AST_CLASS(SNormModifierVal) + + void _toTextOverride(StringBuilder& out); + Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); +}; + } // namespace Slang diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index b6c7069a2..855590472 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -539,6 +539,68 @@ namespace Slang return false; } + /// Do the `left` and `right` modifiers represent the same thing? + static bool _doModifiersMatch(Val* left, Val* right) + { + if( left == right ) + return true; + + if( left->equalsVal(right) ) + return true; + + return false; + } + + /// Does `type` have a modifier that matches `modifier`? + static bool _hasMatchingModifier(ModifiedType* type, Val* modifier) + { + if(!type) return false; + + for( auto m : type->modifiers ) + { + if(_doModifiersMatch(m, modifier)) + return true; + } + + return false; + } + + /// Can `modifier` be added to a type as part of a coercion? + /// + /// For example, it is generally safe to convert from a value + /// of type `T` to a value of type `const T` in C/C++. + /// + static bool _canModifierBeAddedDuringCoercion(Val* modifier) + { + switch( modifier->astNodeType ) + { + default: + return false; + + case ASTNodeType::UNormModifierVal: + case ASTNodeType::SNormModifierVal: + return true; + } + } + + /// Can `modifier` be dropped from a type as part of a coercion? + /// + /// For example, it is generally safe to convert from a value + /// of type `const T` to a value of type `T` in C/C++. + /// + static bool _canModifierBeDroppedDuringCoercion(Val* modifier) + { + switch( modifier->astNodeType ) + { + default: + return false; + + case ASTNodeType::UNormModifierVal: + case ASTNodeType::SNormModifierVal: + return true; + } + } + bool SemanticsVisitor::_coerce( Type* toType, Expr** outToExpr, @@ -592,6 +654,77 @@ namespace Slang return true; } + { + // It is possible that one or more of the types involved might have modifiers + // on it, but the underlying types are otherwise the same. + // + auto toModified = as(toType); + auto toBase = toModified ? toModified->base : toType; + // + auto fromModified = as(fromType); + auto fromBase = fromModified ? fromModified->base : fromType; + + + if((toModified || fromModified) && toBase->equals(fromBase)) + { + // We need to check each modifier present on either `toType` + // or `fromType`. For each modifier, it will either be: + // + // * Present on both types; these are a non-issue + // * Present only on `toType` + // * Present only on `fromType` + // + if( toModified ) + { + for( auto modifier : toModified->modifiers ) + { + if(_hasMatchingModifier(fromModified, modifier)) + continue; + + // If `modifier` is present on `toType`, but not `fromType`, + // then we need to know whether this modifier can be added + // to the type of an expression as part of coercion. + // + if( !_canModifierBeAddedDuringCoercion(modifier) ) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + } + } + if( fromModified ) + { + for( auto modifier : fromModified->modifiers ) + { + if(_hasMatchingModifier(toModified, modifier)) + continue; + + // If `modifier` is present on `fromType`, but not `toType`, + // then we need to know whether this modifier can be dropped + // to the type of an expression as part of coercion. + // + if( !_canModifierBeDroppedDuringCoercion(modifier) ) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + } + } + + // If all the modifiers were okay, we can convert. + + // TODO: we may need a cost to allow disambiguation of overloads based on modifiers? + if(outCost) + { + *outCost = kConversionCost_None; + } + if( outToExpr ) + { + *outToExpr = createModifierCastExpr(toType, fromExpr); + } + + return true; + } + } + // Coercion from an initializer list is allowed for many types, // so we will farm that out to its own subroutine. // @@ -931,6 +1064,18 @@ namespace Slang return expr; } + Expr* SemanticsVisitor::createModifierCastExpr( + Type* toType, + Expr* fromExpr) + { + ModifierCastExpr* expr = m_astBuilder->create(); + expr->loc = fromExpr->loc; + expr->type = QualType(toType); + expr->valueArg = fromExpr; + return expr; + } + + Expr* SemanticsVisitor::coerce( Type* toType, Expr* fromExpr) diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index e8384e0ab..906a21d53 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -2142,4 +2142,53 @@ namespace Slang return expr; } + Expr* SemanticsExprVisitor::visitModifiedTypeExpr(ModifiedTypeExpr* expr) + { + // The base type should be a proper type (not an expression, generic, etc.) + // + expr->base = CheckProperType(expr->base); + auto baseType = expr->base.type; + + // We will check the modifiers that were applied to the type expression + // one by one, and collect a list of the ones that should modify the + // resulting `Type`. + // + List modifierVals; + for( auto modifier : expr->modifiers ) + { + auto modifierVal = checkTypeModifier(modifier, baseType); + if(!modifierVal) + continue; + modifierVals.add(modifierVal); + } + + auto modifiedType = m_astBuilder->getModifiedType(baseType, modifierVals); + expr->type = m_astBuilder->getTypeType(modifiedType); + + return expr; + } + + Val* SemanticsExprVisitor::checkTypeModifier(Modifier* modifier, Type* type) + { + SLANG_UNUSED(type); + + if( auto unormModifier = as(modifier) ) + { + // TODO: validate that `type` is either `float` or a vector of `float`s + return m_astBuilder->getUNormModifierVal(); + + } + else if( auto snormModifier = as(modifier) ) + { + // TODO: validate that `type` is either `float` or a vector of `float`s + return m_astBuilder->getSNormModifierVal(); + } + else + { + // TODO: more complete error message here + getSink()->diagnose(modifier, Diagnostics::unexpected, "unknown type modifier in semantic checking"); + return nullptr; + } + } + } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index bd2392c67..8ea693104 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1154,6 +1154,10 @@ namespace Slang Expr* fromExpr, Val* witness); + Expr* createModifierCastExpr( + Type* toType, + Expr* fromExpr); + /// Does there exist an implicit conversion from `fromType` to `toType`? bool canConvertImplicitly( Type* toType, @@ -1573,6 +1577,7 @@ namespace Slang CASE(OverloadedExpr2) CASE(AggTypeCtorExpr) CASE(CastToSuperTypeExpr) + CASE(ModifierCastExpr) CASE(LetExpr) CASE(ExtractExistentialValueExpr) @@ -1587,6 +1592,10 @@ namespace Slang Expr* visitThisExpr(ThisExpr* expr); Expr* visitThisTypeExpr(ThisTypeExpr* expr); Expr* visitAndTypeExpr(AndTypeExpr* expr); + Expr* visitModifiedTypeExpr(ModifiedTypeExpr* expr); + + /// Perform semantic checking on a `modifier` that is being applied to the given `type` + Val* checkTypeModifier(Modifier* modifier, Type* type); }; struct SemanticsStmtVisitor diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index 06d34c260..e04759773 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -170,6 +170,18 @@ void CLikeSourceEmitter::emitDeclarator(DeclaratorInfo* declarator) } break; + case DeclaratorInfo::Flavor::Attributed: + { + auto attributedDeclarator = (AttributedDeclaratorInfo*)declarator; + auto instWithAttributes = attributedDeclarator->instWithAttributes; + for(auto attr : instWithAttributes->getAllAttrs()) + { + _emitPostfixTypeAttr(attr); + } + emitDeclarator(attributedDeclarator->next); + } + break; + default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor"); @@ -249,16 +261,24 @@ List CLikeSourceEmitter::getSortedWitnessTableEntries(IRWi return sortedWitnessTableEntries; } -void CLikeSourceEmitter::_emitArrayType(IRArrayType* arrayType, DeclaratorInfo* declarator) +void CLikeSourceEmitter::_emitPrefixTypeAttr(IRAttr* attr) { - SizedArrayDeclaratorInfo arrayDeclarator(declarator, arrayType->getElementCount()); - _emitType(arrayType->getElementType(), &arrayDeclarator); + SLANG_UNUSED(attr); + + // By defualt we will not emit any attributes. + // + // TODO: If `const` ever surfaces as a type attribute in our IR, + // we may need to handle it here. } -void CLikeSourceEmitter::_emitUnsizedArrayType(IRUnsizedArrayType* arrayType, DeclaratorInfo* declarator) +void CLikeSourceEmitter::_emitPostfixTypeAttr(IRAttr* attr) { - UnsizedArrayDeclaratorInfo arrayDeclarator(declarator); - _emitType(arrayType->getElementType(), &arrayDeclarator); + SLANG_UNUSED(attr); + + // By defualt we will not emit any attributes. + // + // TODO: If `const` ever surfaces as a type attribute in our IR, + // we may need to handle it here. } void CLikeSourceEmitter::_emitType(IRType* type, DeclaratorInfo* declarator) @@ -278,11 +298,31 @@ void CLikeSourceEmitter::_emitType(IRType* type, DeclaratorInfo* declarator) break; case kIROp_ArrayType: - _emitArrayType(cast(type), declarator); + { + auto arrayType = cast(type); + SizedArrayDeclaratorInfo arrayDeclarator(declarator, arrayType->getElementCount()); + _emitType(arrayType->getElementType(), &arrayDeclarator); + } break; case kIROp_UnsizedArrayType: - _emitUnsizedArrayType(cast(type), declarator); + { + auto arrayType = cast(type); + UnsizedArrayDeclaratorInfo arrayDeclarator(declarator); + _emitType(arrayType->getElementType(), &arrayDeclarator); + } + break; + + case kIROp_AttributedType: + { + auto attributedType = cast(type); + for(auto attr : attributedType->getAllAttrs()) + { + _emitPrefixTypeAttr(attr); + } + AttributedDeclaratorInfo attributedDeclarator(declarator, attributedType); + _emitType(attributedType->getBaseType(), &attributedDeclarator); + } break; } diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h index 08d24ef04..ed0f69a2c 100644 --- a/source/slang/slang-emit-c-like.h +++ b/source/slang/slang-emit-c-like.h @@ -80,6 +80,7 @@ public: SizedArray, UnsizedArray, LiteralSizedArray, + Attributed, }; Flavor flavor; @@ -145,6 +146,16 @@ public: {} }; + struct AttributedDeclaratorInfo : ChainedDeclaratorInfo + { + AttributedDeclaratorInfo(DeclaratorInfo* next, IRInst* instWithAttributes) + : ChainedDeclaratorInfo(Flavor::Attributed, next) + , instWithAttributes(instWithAttributes) + {} + + IRInst* instWithAttributes; + }; + struct ComputeEmitActionsContext; // An action to be performed during code emit. @@ -449,11 +460,12 @@ public: virtual void emitPostKeywordTypeAttributesImpl(IRInst* inst) { SLANG_UNUSED(inst); } - void _emitArrayType(IRArrayType* arrayType, DeclaratorInfo* declarator); - void _emitUnsizedArrayType(IRUnsizedArrayType* arrayType, DeclaratorInfo* declarator); void _emitType(IRType* type, DeclaratorInfo* declarator); void _emitInst(IRInst* inst); + virtual void _emitPrefixTypeAttr(IRAttr* attr); + virtual void _emitPostfixTypeAttr(IRAttr* attr); + // Emit the argument list (including paranthesis) in a `CallInst` void _emitCallArgList(IRCall* call); diff --git a/source/slang/slang-emit-hlsl.cpp b/source/slang/slang-emit-hlsl.cpp index dd5c18315..09749546a 100644 --- a/source/slang/slang-emit-hlsl.cpp +++ b/source/slang/slang-emit-hlsl.cpp @@ -681,7 +681,30 @@ void HLSLSourceEmitter::emitLayoutDirectivesImpl(TargetRequest* targetReq) void HLSLSourceEmitter::emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) { - // TODO(tfoley) : should really emit these with sugar + // In some cases we *need* to use the built-in syntax sugar for vector types, + // so we will try to emit those whenever possible. + // + if( elementCount >= 1 && elementCount <= 4 ) + { + switch( elementType->getOp() ) + { + case kIROp_FloatType: + case kIROp_IntType: + case kIROp_UIntType: + // TODO: There are more types that need to be covered here + emitType(elementType); + m_writer->emit(elementCount); + return; + + default: + break; + } + } + + // As a fallback, we will use the `vector<...>` type constructor, + // although we should not expect to run into types that don't + // have a sugared form. + // m_writer->emit("vector<"); emitType(elementType); m_writer->emit(","); @@ -973,6 +996,18 @@ void HLSLSourceEmitter::emitPostKeywordTypeAttributesImpl(IRInst* inst) } } +void HLSLSourceEmitter::_emitPrefixTypeAttr(IRAttr* attr) +{ + switch( attr->getOp() ) + { + default: + Super::_emitPrefixTypeAttr(attr); + break; + + case kIROp_UNormAttr: m_writer->emit("unorm "); break; + case kIROp_SNormAttr: m_writer->emit("snorm "); break; + } +} void HLSLSourceEmitter::emitSimpleFuncParamImpl(IRParam* param) { diff --git a/source/slang/slang-emit-hlsl.h b/source/slang/slang-emit-hlsl.h index 59449e519..eeda7c0b7 100644 --- a/source/slang/slang-emit-hlsl.h +++ b/source/slang/slang-emit-hlsl.h @@ -55,6 +55,7 @@ protected: virtual void emitPostKeywordTypeAttributesImpl(IRInst* inst) SLANG_OVERRIDE; + void _emitPrefixTypeAttr(IRAttr* attr) SLANG_OVERRIDE; // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`) diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index 8e8a243a0..9fa5a2e9d 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -52,6 +52,7 @@ INST(Nop, nop, 0, 0) INST(TaggedUnionType, TaggedUnion, 0, 0) INST(ConjunctionType, Conjunction, 0, 0) + INST(AttributedType, Attributed, 0, 0) /* BindExistentialsTypeBase */ @@ -695,6 +696,8 @@ INST_RANGE(Layout, VarLayout, EntryPointLayout) INST(StageAttr, stage, 1, 0) INST(StructFieldLayoutAttr, fieldLayout, 2, 0) INST(CaseTypeLayoutAttr, caseLayout, 1, 0) + INST(UNormAttr, unorm, 0, 0) + INST(SNormAttr, snorm, 0, 0) /* SemanticAttr */ INST(UserSemanticAttr, userSemantic, 2, 0) INST(SystemValueSemanticAttr, systemValueSemantic, 2, 0) diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index cbcefa56a..7ef31d71a 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -2113,6 +2113,18 @@ public: return getConjunctionType(2, types); } + IRType* getAttributedType( + IRType* baseType, + UInt attributeCount, + IRAttr* const* attributes); + + IRType* getAttributedType( + IRType* baseType, + List attributes) + { + return getAttributedType(baseType, attributes.getCount(), attributes.getBuffer()); + } + // Set the data type of an instruction, while preserving // its rate, if any. void setDataType(IRInst* inst, IRType* dataType); @@ -2643,6 +2655,18 @@ public: IRStageAttr* getStageAttr(Stage stage); + IRAttr* getAttr(IROp op, UInt operandCount, IRInst* const* operands); + + IRAttr* getAttr(IROp op, List const& operands) + { + return getAttr(op, operands.getCount(), operands.getBuffer()); + } + + IRAttr* getAttr(IROp op) + { + return getAttr(op, 0, nullptr); + } + IRTypeLayout* getTypeLayout(IROp op, List const& operands); IRVarLayout* getVarLayout(List const& operands); IREntryPointLayout* getEntryPointLayout( diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index 27eb1adfb..f7ebfdb64 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -200,7 +200,7 @@ namespace Slang return nullptr; } - IROperandListBase IRInst::getAllAttrs() + IROperandList IRInst::getAllAttrs() { // We assume as an invariant that all attributes appear at the end of the operand // list, after all the non-attribute operands. @@ -215,7 +215,7 @@ namespace Slang while(cursor != end && !as(cursor->get())) cursor++; - return IROperandListBase(cursor, end); + return IROperandList(cursor, end); } // IRConstant @@ -2766,6 +2766,17 @@ namespace Slang return getType(kIROp_ConjunctionType, typeCount, (IRInst* const*)types); } + IRType* IRBuilder::getAttributedType( + IRType* baseType, + UInt attributeCount, + IRAttr* const* attributes) + { + List operands; + operands.add(baseType); + for(UInt i = 0; i < attributeCount; ++i) + operands.add(attributes[i]); + return getType(kIROp_AttributedType, operands.getCount(), operands.getBuffer()); + } void IRBuilder::setDataType(IRInst* inst, IRType* dataType) @@ -4258,6 +4269,16 @@ namespace Slang operands)); } + IRAttr* IRBuilder::getAttr(IROp op, UInt operandCount, IRInst* const* operands) + { + return cast(findOrEmitHoistableInst( + getVoidType(), + op, + operandCount, + operands)); + } + + IRTypeLayout* IRBuilder::getTypeLayout(IROp op, List const& operands) { diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index 4d1c153b4..04fd6b1c9 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -125,6 +125,7 @@ struct IRBlock; struct IRDecoration; struct IRRate; struct IRType; +struct IRAttr; // A double-linked list of instruction struct IRInstListBase @@ -492,7 +493,7 @@ struct IRInst T* findDecoration(); /// Get all the attributes attached to this instruction. - IROperandListBase getAllAttrs(); + IROperandList getAllAttrs(); /// Find the first attribute of type `T` attached to this instruction. template @@ -1428,6 +1429,13 @@ struct IRConjunctionType : IRType IRType* getCaseType(Int index) { return (IRType*) getOperand(index); } }; +struct IRAttributedType : IRType +{ + IR_LEAF_ISA(AttributedType) + + IRType* getBaseType() { return (IRType*) getOperand(0); } +}; + /// Represents a tuple. Tuples are created by `IRMakeTuple` and its elements /// are accessed via `GetTupleElement(tupleValue, IRIntLit)`. struct IRTupleType : IRType diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 40c88a0bc..b47448ae1 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -1777,6 +1777,34 @@ struct ValLoweringVisitor : ValVisitorbase); + + List irAttrs; + for(auto astModifier : astType->modifiers) + { + IRAttr* irAttr = (IRAttr*) lowerSimpleVal(context, astModifier); + if(irAttr) + irAttrs.add(irAttr); + } + + auto irType = getBuilder()->getAttributedType(irBase, irAttrs); + return LoweredValInfo::simple(irType); + } + + LoweredValInfo visitUNormModifierVal(UNormModifierVal* astVal) + { + SLANG_UNUSED(astVal); + return LoweredValInfo::simple(getBuilder()->getAttr(kIROp_UNormAttr)); + } + + LoweredValInfo visitSNormModifierVal(SNormModifierVal* astVal) + { + SLANG_UNUSED(astVal); + return LoweredValInfo::simple(getBuilder()->getAttr(kIROp_SNormAttr)); + } + // We do not expect to encounter the following types in ASTs that have // passed front-end semantic checking. #define UNEXPECTED_CASE(NAME) IRType* visit##NAME(NAME*) { SLANG_UNEXPECTED(#NAME); UNREACHABLE_RETURN(nullptr); } @@ -3620,6 +3648,12 @@ struct ExprLoweringVisitorBase : ExprVisitor UNREACHABLE_RETURN(LoweredValInfo()); } + LoweredValInfo visitModifierCastExpr( + ModifierCastExpr* expr) + { + return this->dispatch(expr->valueArg); + } + LoweredValInfo subscriptValue( IRType* type, LoweredValInfo baseVal, @@ -3704,6 +3738,12 @@ struct ExprLoweringVisitorBase : ExprVisitor UNREACHABLE_RETURN(LoweredValInfo()); } + LoweredValInfo visitModifiedTypeExpr(ModifiedTypeExpr* /*expr*/) + { + SLANG_UNIMPLEMENTED_X("type expression during code generation"); + UNREACHABLE_RETURN(LoweredValInfo()); + } + LoweredValInfo visitAssocTypeDecl(AssocTypeDecl* decl) { SLANG_UNIMPLEMENTED_X("associatedtype expression during code generation"); diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index d9bbaaace..961691c9a 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -213,10 +213,10 @@ namespace Slang static Decl* parseEnumDecl(Parser* parser); - static Modifier* ParseOptSemantics( + static Modifiers _parseOptSemantics( Parser* parser); - static void ParseOptSemantics( + static void _parseOptSemantics( Parser* parser, Decl* decl); @@ -234,6 +234,8 @@ namespace Slang static TokenType peekTokenType(Parser* parser); + static Expr* _parseGenericArg(Parser* parser); + // static void Unexpected( @@ -1228,7 +1230,7 @@ namespace Slang { Expr* typeSpec = nullptr; NameLoc nameAndLoc; - Modifier* semantics = nullptr; + Modifiers semantics; Expr* initializer = nullptr; }; @@ -1483,7 +1485,7 @@ namespace Slang parser->PushScope(decl); parseParameterList(parser, decl); - ParseOptSemantics(parser, decl); + _parseOptSemantics(parser, decl); decl->body = parseOptBody(parser); parser->PopScope(); @@ -1511,9 +1513,9 @@ namespace Slang } // Add modifiers to the end of the modifier list for a declaration - void AddModifiers(Decl* decl, Modifier* modifiers) + static void _addModifiers(Decl* decl, Modifiers const& modifiers) { - if (!modifiers) + if (!modifiers.first) return; Modifier** link = &decl->modifiers.first; @@ -1521,7 +1523,7 @@ namespace Slang { link = &(*link)->next; } - *link = modifiers; + *link = modifiers.first; } static Name* generateName(Parser* parser, String const& base) @@ -1556,7 +1558,7 @@ namespace Slang } decl->type = TypeExp(declaratorInfo.typeSpec); - AddModifiers(decl, declaratorInfo.semantics); + _addModifiers(decl, declaratorInfo.semantics); decl->initExpr = declaratorInfo.initializer; } @@ -1695,7 +1697,7 @@ namespace Slang struct InitDeclarator { RefPtr declarator; - Modifier* semantics = nullptr; + Modifiers semantics; Expr* initializer = nullptr; }; @@ -1706,7 +1708,7 @@ namespace Slang { InitDeclarator result; result.declarator = parseDeclarator(parser, options); - result.semantics = ParseOptSemantics(parser); + result.semantics = _parseOptSemantics(parser); return result; } @@ -1822,12 +1824,6 @@ namespace Slang } }; - // Pares an argument to an application of a generic - Expr* ParseGenericArg(Parser* parser) - { - return parser->ParseArgExpr(); - } - // Create a type expression that will refer to the given declaration static Expr* createDeclRefType(Parser* parser, Decl* decl) @@ -1866,10 +1862,10 @@ namespace Slang parser->ReadToken(TokenType::OpLess); parser->genericDepth++; // For now assume all generics have at least one argument - genericApp->arguments.add(ParseGenericArg(parser)); + genericApp->arguments.add(_parseGenericArg(parser)); while (AdvanceIf(parser, TokenType::Comma)) { - genericApp->arguments.add(ParseGenericArg(parser)); + genericApp->arguments.add(_parseGenericArg(parser)); } parser->genericDepth--; @@ -2016,7 +2012,143 @@ namespace Slang return parseThisTypeExpr(parser); } - static TypeSpec parseTypeSpec(Parser* parser) + /// Apply the given `modifiers` (if any) to the given `typeExpr` + static Expr* _applyModifiersToTypeExpr(Parser* parser, Expr* typeExpr, Modifiers const& modifiers) + { + if(modifiers.first) + { + // Currently, we represent a type with modifiers applied to it as + // an AST node of the `ModifiedTypeExpr` class. We will create + // one here and make it be the home for our `typeModifiers`. + // + ModifiedTypeExpr* modifiedTypeExpr = parser->astBuilder->create(); + modifiedTypeExpr->base.exp = typeExpr; + modifiedTypeExpr->modifiers = modifiers; + return modifiedTypeExpr; + } + else + { + // If none of the modifiers were type modifiers, we can leave + // the existing type expression alone. + return typeExpr; + } + } + + /// Apply any type modifier in `ioBaseModifiers` to the given `typeExpr`. + /// + /// If any type modifiers were present, `ioBaseModifiers` will be updated + /// to only include those modifiers that were not type modifiers (if any). + /// + /// If no type modifiers were present, `ioBaseModifiers` will remain unchanged. + /// + static Expr* _applyTypeModifiersToTypeExpr(Parser* parser, Expr* typeExpr, Modifiers& ioBaseModifiers) + { + // The `Modifiers` that were passed in as `ioBaseModifiers` comprise + // a singly-linked list of `Modifier` nodes. + // + // It is possible that some of these modifiers represent type modifiers and, + // if so, we want to transfer those modifiers to apply to the type given + // by `typeExpr`. Any remaining modifiers that are not type modifiers will + // be left in the `ioBaseModifiers` list. + // + // The type modifiers will be collected into their own `Modifiers` list, + // and we will retain a poiner to the final pointer in the linked list + // (the one that is null), so that we can append to the end. + // + Modifiers typeModifiers; + Modifier** typeModifierLink = &typeModifiers.first; + + // While iterating over the base modifiers, we need to be able to remove + // a linked-list node while inspecting it, so we will similarly keep a "link" + // variable that points at whatever location points to the current node + // (either the head of the list, or the `next` pointer in the previous modifier) + // + Modifier** baseModifierLink = &ioBaseModifiers.first; + while(auto baseModifier = *baseModifierLink) + { + // We want to detect whether we have a type modifier or not. + // + auto typeModifier = as(baseModifier); + + // The easy case is when we *don't* have a type modifier. + // + if(!typeModifier) + { + // We want to leave the modifier where it is (in the list + // of "base" modifiers), and advance to the next one in order. + // + baseModifierLink = &baseModifier->next; + } + else + { + // If we have a type modifier, we need to graft it onto + // the list of type modifiers. This is done by writing + // a pointer to the type modifier into the "link" for + // the type modifier list, and updating the link to point + // to the `next` field of the current modifier (since that + // fill be the location any further type modifiers need + // to be linked). + // + *typeModifierLink = typeModifier; + typeModifierLink = &typeModifier->next; + + // The above logic puts `typeModifier` into the type modifer + // list, but it doesn't remove it from the base modifier list. + // In order to do that we must replace the pointer to `typeModifer` + // with a pointer to whatever is next in the base list, and also + // null out the `next` field of `typeModifier` so that it no + // longer points to the base modifiers that come after it. + // + *baseModifierLink = typeModifier->next; + typeModifier->next = nullptr; + + // Note: We do *not* need to update `baseModifierLink` before + // the next loop iteration, because `*baseModifierLink` has + // already been updated so that it points to the next node + // we want to visit. + } + } + + // If we ended up finding any type modifiers, we want to apply them + // to the type expression. + // + return _applyModifiersToTypeExpr(parser, typeExpr, typeModifiers); + } + + static TypeSpec _applyModifiersToTypeSpec(Parser* parser, TypeSpec typeSpec, Modifiers const& inModifiers) + { + // It is possible that the form of the type specifier will have + // included a declaration directly (e.g., using `struct { ... }` + // as a type specifier to declare both a type and value(s) of that + // type in one go). + // + if(auto decl = typeSpec.decl) + { + // In the case where there *is* a declaration, we want to apply + // any modifiers that logically belong to the type to the type, + // and any modifiers that logically belong to the declaration to + // the declaration. + // + Modifiers modifiers = inModifiers; + typeSpec.expr = _applyTypeModifiersToTypeExpr(parser, typeSpec.expr, modifiers); + + // Any remaining modifiers should instead be applied to the declaration. + _addModifiers(decl, modifiers); + } + else + { + // If there are modifiers, then we apply *all* of them to the type expression. + // This may result in modifiers being applied that do not belong on a type; + // in that case we rely on downstream semantic checking to diagnose any error. + // + typeSpec.expr = _applyModifiersToTypeExpr(parser, typeSpec.expr, inModifiers); + } + + return typeSpec; + } + + /// Parse a type specifier, without dealing with modifiers. + static TypeSpec _parseSimpleTypeSpec(Parser* parser) { TypeSpec typeSpec; @@ -2110,13 +2242,56 @@ namespace Slang return typeSpec; } + /// Parse a type specifier, following the given list of modifiers. + /// + /// If there are any modifiers in `ioModifiers`, this function may modify it + /// by stripping out any type modifiers and attaching them to the `TypeSpec`. + /// Any modifiers that are not type modifiers will be left where they were. + /// + static TypeSpec _parseTypeSpec(Parser* parser, Modifiers& ioModifiers) + { + TypeSpec typeSpec = _parseSimpleTypeSpec(parser); + + // We don't know whether `ioModifiers` has any modifiers in it, + // or which of them might be type modifiers, so we will delegate + // figuring that out to a subroutine. + // + typeSpec.expr = _applyTypeModifiersToTypeExpr(parser, typeSpec.expr, ioModifiers); + + return typeSpec; + } + + /// Parse a type specifier, including any leading modifiers. + /// + /// Note that all the modifiers that precede the type specifier + /// will end up as modifiers for the type specifier even if they + /// should *not* be allowed as modifiers on a type. + /// + /// This function should not be used in contexts where a type specifier + /// is being parsed as part of a declaration, such that a subset of + /// the modifiers might inhere to the declaration rather than the + /// type specifier. + /// + static TypeSpec _parseTypeSpec(Parser* parser) + { + Modifiers modifiers = ParseModifiers(parser); + TypeSpec typeSpec = _parseSimpleTypeSpec(parser); + + typeSpec = _applyModifiersToTypeSpec(parser, typeSpec, modifiers); + + return typeSpec; + } + + static DeclBase* ParseDeclaratorDecl( - Parser* parser, - ContainerDecl* containerDecl) + Parser* parser, + ContainerDecl* containerDecl, + Modifiers const& inModifiers) { SourceLoc startPosition = parser->tokenReader.peekLoc(); - auto typeSpec = parseTypeSpec(parser); + Modifiers modifiers = inModifiers; + auto typeSpec = _parseTypeSpec(parser, modifiers); // We may need to build up multiple declarations in a group, // but the common case will be when we have just a single @@ -2206,7 +2381,7 @@ namespace Slang // Only parse as a function if we didn't already see mutually-exclusive // constructs when parsing the declarator. && !initDeclarator.initializer - && !initDeclarator.semantics) + && !initDeclarator.semantics.first) { // Looks like a function, so parse it like one. UnwrapDeclarator(parser->astBuilder, initDeclarator, &declaratorInfo); @@ -2433,14 +2608,15 @@ namespace Slang // // opt-semantics ::= (':' semantic)* // - static Modifier* ParseOptSemantics( + static Modifiers _parseOptSemantics( Parser* parser) { + Modifiers modifiers; + if (!AdvanceIf(parser, TokenType::Colon)) - return nullptr; + return modifiers; - Modifier* result = nullptr; - Modifier** link = &result; + Modifier** link = &modifiers.first; SLANG_ASSERT(!*link); for (;;) @@ -2470,18 +2646,18 @@ namespace Slang // avoiding an infinite loop here. if (!AdvanceIf(parser, TokenType::Colon)) { - return result; + return modifiers; } } } - static void ParseOptSemantics( + static void _parseOptSemantics( Parser* parser, Decl* decl) { - AddModifiers(decl, ParseOptSemantics(parser)); + _addModifiers(decl, _parseOptSemantics(parser)); } static Decl* ParseHLSLBufferDecl( @@ -2559,7 +2735,7 @@ namespace Slang // Any semantics applied to the buffer declaration are taken as applying // to the variable instead. - ParseOptSemantics(parser, bufferVarDecl); + _parseOptSemantics(parser, bufferVarDecl); // The declarations in the body belong to the data type. parseDeclBody(parser, bufferDataTypeDecl); @@ -2917,7 +3093,7 @@ namespace Slang } decl->loc = loc; - AddModifiers(decl, modifiers.first); + _addModifiers(decl, modifiers); parser->PushScope(decl); @@ -3423,7 +3599,7 @@ namespace Slang Decl* declToModify = decl; if(auto genericDecl = as(decl)) declToModify = genericDecl->inner; - AddModifiers(declToModify, modifiers.first); + _addModifiers(declToModify, modifiers); // Make sure the decl is properly nested inside its lexical parent if (containerDecl) @@ -3435,7 +3611,7 @@ namespace Slang static DeclBase* ParseDeclWithModifiers( Parser* parser, ContainerDecl* containerDecl, - Modifiers modifiers ) + Modifiers modifiers ) { DeclBase* decl = nullptr; @@ -3462,7 +3638,7 @@ namespace Slang // Our final fallback case is to assume that the user is // probably writing a C-style declarator-based declaration. - decl = ParseDeclaratorDecl(parser, containerDecl); + decl = ParseDeclaratorDecl(parser, containerDecl, modifiers); break; } break; @@ -3483,7 +3659,7 @@ namespace Slang // If nothing else matched, we try to parse an "ordinary" declarator-based declaration default: - decl = ParseDeclaratorDecl(parser, containerDecl); + decl = ParseDeclaratorDecl(parser, containerDecl, modifiers); break; } @@ -4299,7 +4475,7 @@ namespace Slang /// static Expr* _parseAtomicTypeExpr(Parser* parser) { - auto typeSpec = parseTypeSpec(parser); + auto typeSpec = _parseTypeSpec(parser); if( typeSpec.decl ) { AddMember(parser->currentScope, typeSpec.decl); @@ -4318,15 +4494,8 @@ namespace Slang return parsePostfixTypeSuffix(parser, typeExpr); } - /// Parse an infix type expression. - /// - /// Currently, the only infix type expression we support is the `&` - /// operator for forming interface conjunctions. - /// - static Expr* _parseInfixTypeExpr(Parser* parser) + static Expr* _parseInfixTypeExprSuffix(Parser* parser, Expr* leftExpr) { - auto leftExpr = _parsePostfixTypeExpr(parser); - for(;;) { // As long as the next token is an `&`, we will try @@ -4349,6 +4518,17 @@ namespace Slang return leftExpr; } + /// Parse an infix type expression. + /// + /// Currently, the only infix type expression we support is the `&` + /// operator for forming interface conjunctions. + /// + static Expr* _parseInfixTypeExpr(Parser* parser) + { + auto leftExpr = _parsePostfixTypeExpr(parser); + return _parseInfixTypeExprSuffix(parser, leftExpr); + } + Expr* Parser::ParseType() { return _parseInfixTypeExpr(this); @@ -5508,6 +5688,46 @@ namespace Slang return parsePrefixExpr(this); } + /// Parse an argument to an application of a generic + static Expr* _parseGenericArg(Parser* parser) + { + // The grammar for generic arguments needs to be a super-set of the + // grammar for types and for expressions, because we do not know + // which to expect at each argument position during parsing. + // + // For the most part the expression grammar is more permissive than + // the type grammar, but types support modifiers that are not + // (currently) allowed in pure expression contexts. + // + // We could in theory allow modifiers to appear in expression contexts + // and deal with the cases where this should not be allowed downstream, + // but doing so runs a high risk of changing the meaning of existing code + // (notably in cases where a user might have used a variable name that + // overlaps with a language modifier keyword). + // + // Instead, we will simply detect the case where modifiers appear on + // a generic argument here, as a special case. + // + Modifiers modifiers = ParseModifiers(parser); + if(modifiers.first) + { + // If there are any modifiers, then we know that we are actually + // in the type case. + // + auto typeSpec = _parseSimpleTypeSpec(parser); + typeSpec = _applyModifiersToTypeSpec(parser, typeSpec, modifiers); + + auto typeExpr = typeSpec.expr; + + typeExpr = parsePostfixTypeSuffix(parser, typeExpr); + typeExpr = _parseInfixTypeExprSuffix(parser, typeExpr); + + return typeExpr; + } + + return parser->ParseArgExpr(); + } + Expr* parseTermFromSourceFile( ASTBuilder* astBuilder, TokenSpan const& tokens, diff --git a/tests/compute/half-texture.slang b/tests/compute/half-texture.slang index 1cb73dadd..6f131c568 100644 --- a/tests/compute/half-texture.slang +++ b/tests/compute/half-texture.slang @@ -1,5 +1,5 @@ //TEST:CROSS_COMPILE: -target spirv -entry computeMain -profile cs_6_2 -//TEST:SIMPLE: -target hlsl -entry computeMain -profile cs_6_2 +//TEST:CROSS_COMPILE: -target dxil-assembly -entry computeMain -profile cs_6_2 //TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], stride=16):out RWStructuredBuffer outputBuffer; diff --git a/tests/compute/half-texture.slang.1.expected b/tests/compute/half-texture.slang.1.expected deleted file mode 100644 index 7dd96403f..000000000 --- a/tests/compute/half-texture.slang.1.expected +++ /dev/null @@ -1,45 +0,0 @@ -result code = 0 -standard error = { -} -standard output = { -#pragma pack_matrix(column_major) - -#line 8 "tests/compute/half-texture.slang" -RWTexture2D halfTexture_0 : register(u1); - -RWTexture2D > halfTexture2_0 : register(u2); - -RWTexture2D > halfTexture4_0 : register(u3); - - -#line 5 -RWStructuredBuffer outputBuffer_0 : register(u0); - - -#line 18 -[shader("compute")][numthreads(4, 4, 1)] -void computeMain(vector dispatchThreadID_0 : SV_DISPATCHTHREADID) -{ - -#line 20 - vector pos_0 = (vector) dispatchThreadID_0.xy; - float _S1 = 1.00000000000000000000 / 3.00000000000000000000; - vector pos2_0 = vector(int(3) - pos_0.y, int(3) - pos_0.x); - -#line 29 - half h_0 = halfTexture_0[(vector) pos2_0]; - vector h2_0 = halfTexture2_0[(vector) pos2_0]; - vector h4_0 = halfTexture4_0[(vector) pos2_0]; - - - - halfTexture_0[(vector) pos_0] = h2_0.x + h2_0.y; - halfTexture2_0[(vector) pos_0] = h4_0.xy; - halfTexture4_0[(vector) pos_0] = vector(h2_0, h_0, h_0); - - int index_0 = pos_0.x + pos_0.y * int(4); - outputBuffer_0[(uint) index_0] = index_0; - return; -} - -} diff --git a/tests/compute/half-texture.slang.hlsl b/tests/compute/half-texture.slang.hlsl new file mode 100644 index 000000000..c606703a4 --- /dev/null +++ b/tests/compute/half-texture.slang.hlsl @@ -0,0 +1,26 @@ +//TEST_IGNORE_FILE: +RWTexture2D halfTexture_0 : register(u1); +RWTexture2D > halfTexture2_0 : register(u2); +RWTexture2D > halfTexture4_0 : register(u3); + +RWStructuredBuffer outputBuffer_0 : register(u0); + +[shader("compute")][numthreads(4, 4, 1)] +void computeMain(uint3 dispatchThreadID_0 : SV_DISPATCHTHREADID) +{ + int2 pos_0 = (int2) dispatchThreadID_0.xy; + float _S1 = 1.00000000000000000000 / 3.00000000000000000000; + int2 pos2_0 = int2(int(3) - pos_0.y, int(3) - pos_0.x); + + half h_0 = halfTexture_0[(uint2) pos2_0]; + vector h2_0 = halfTexture2_0[(uint2) pos2_0]; + vector h4_0 = halfTexture4_0[(uint2) pos2_0]; + + halfTexture_0[(uint2) pos_0] = h2_0.x + h2_0.y; + halfTexture2_0[(uint2) pos_0] = h4_0.xy; + halfTexture4_0[(uint2) pos_0] = vector(h2_0, h_0, h_0); + + int index_0 = pos_0.x + pos_0.y * int(4); + outputBuffer_0[(uint) index_0] = index_0; + return; +} diff --git a/tests/language-feature/types/modifiers/snorm-modifier.slang b/tests/language-feature/types/modifiers/snorm-modifier.slang new file mode 100644 index 000000000..cd8bfb691 --- /dev/null +++ b/tests/language-feature/types/modifiers/snorm-modifier.slang @@ -0,0 +1,18 @@ +// snorm-modifier.slang + +//TEST:COMPARE_HLSL:-profile cs_5_0 -entry main + +#ifndef __SLANG__ +#define input input_0 +#define output output_0 +#endif + +Buffer input; +RWStructuredBuffer output; + +[numthreads(4, 1, 1)] +void main(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + output[tid] = input[tid]; +} diff --git a/tests/language-feature/types/modifiers/unorm-modifier.slang b/tests/language-feature/types/modifiers/unorm-modifier.slang new file mode 100644 index 000000000..8ef851437 --- /dev/null +++ b/tests/language-feature/types/modifiers/unorm-modifier.slang @@ -0,0 +1,19 @@ +// unorm-modifier.slang + +//TEST:COMPARE_HLSL:-profile cs_5_0 -entry main + +#ifndef __SLANG__ +#define input input_0 +#define output output_0 +#endif + + +Buffer input; +RWStructuredBuffer output; + +[numthreads(4, 1, 1)] +void main(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + output[tid] = input[tid]; +} -- cgit v1.2.3