diff options
| author | Yong He <yonghe@outlook.com> | 2024-09-18 13:46:20 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-18 13:46:20 -0700 |
| commit | 2d83875f4b376f047c4541a6f6c13d36e5aa228b (patch) | |
| tree | ba41c6faa6e97b6821ac8ea635f2ae52ca8d1f4c /source/slang | |
| parent | 85b996a75683b5364456d731a9cb4aee5c3fada2 (diff) | |
Add `IRWArray` interface, and make StructuredBuffer conform to them. (#5097)
* Add `IRWArray` interface, and make StructuredBuffer conform to them.
* Update user guide.
* Fix.
* Fixes.
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/core.meta.slang | 25 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang | 15 | ||||
| -rw-r--r-- | source/slang/slang-ast-base.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-ast-val.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 782 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 24 | ||||
| -rw-r--r-- | source/slang/slang-check-overload.cpp | 1 | ||||
| -rw-r--r-- | source/slang/slang-ir-use-uninitialized-values.cpp | 52 |
9 files changed, 572 insertions, 341 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 03dda0fe5..afcff8e65 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -396,6 +396,15 @@ interface IArray<T> } } +interface IRWArray<T> : IArray<T> +{ + __subscript(int index)->T + { + get; + set; + } +} + // The "comma operator" is effectively just a generic function that returns its second // argument. The left-to-right evaluation order guaranteed by Slang then ensures that // `left` is evaluated before `right`. @@ -1148,21 +1157,15 @@ extension int16_t : IRangedValue __generic<T, let N:int> __magic_type(ArrayExpressionType) -struct Array : IArray<T> +struct Array : IRWArray<T> { __intrinsic_op($(kIROp_GetArrayLength)) int getCount(); - - __subscript(int index) -> T - { - __intrinsic_op($(kIROp_GetElement)) - get; - } } /// An `N` component vector with elements of type `T`. __generic<T = float, let N : int = 4> __magic_type(VectorExpressionType) -struct vector : IArray<T> +struct vector : IRWArray<T> { /// The element type of the vector typedef T Element; @@ -1182,8 +1185,6 @@ struct vector : IArray<T> [ForceInline] int getCount() { return N; } - - __subscript(int index) -> T { __intrinsic_op($(kIROp_GetElement)) get; } } const int kRowMajorMatrixLayout = $(SLANG_MATRIX_LAYOUT_ROW_MAJOR); @@ -1192,7 +1193,7 @@ const int kColumnMajorMatrixLayout = $(SLANG_MATRIX_LAYOUT_COLUMN_MAJOR); /// A matrix with `R` rows and `C` columns, with elements of type `T`. __generic<T = float, let R : int = 4, let C : int = 4, let L : int = $(SLANG_MATRIX_LAYOUT_MODE_UNKNOWN)> __magic_type(MatrixExpressionType) -struct matrix : IArray<vector<T,C>> +struct matrix : IRWArray<vector<T,C>> { __intrinsic_op($(kIROp_MakeMatrixFromScalar)) __implicit_conversion($(kConversionCost_ScalarToMatrix)) @@ -1207,8 +1208,6 @@ struct matrix : IArray<vector<T,C>> [ForceInline] int getCount() { return R; } - - __subscript(int index) -> vector<T,C> { __intrinsic_op($(kIROp_GetElement)) get; } } __intrinsic_op($(kIROp_Eql)) diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 0fd40b9d8..7a7fd8ff0 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -20604,3 +20604,18 @@ uint64_t clockARB() }; } } + +extension<T, L : IBufferDataLayout> StructuredBuffer<T, L> : IArray<T> +{ + int getCount() { uint count; uint stride; this.GetDimensions(count, stride); return count; } +} + +extension<T, L : IBufferDataLayout> RWStructuredBuffer<T, L> : IRWArray<T> +{ + int getCount() { uint count; uint stride; this.GetDimensions(count, stride); return count; } +} + +extension<T, L : IBufferDataLayout> RasterizerOrderedStructuredBuffer<T, L> : IRWArray<T> +{ + int getCount() { uint count; uint stride; this.GetDimensions(count, stride); return count; } +}
\ No newline at end of file diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h index 6f3789c7e..9339f5dee 100644 --- a/source/slang/slang-ast-base.h +++ b/source/slang/slang-ast-base.h @@ -757,6 +757,8 @@ class Expr : public SyntaxNode QualType type; + bool checked = false; + void accept(IExprVisitor* visitor, void* extra); }; diff --git a/source/slang/slang-ast-val.cpp b/source/slang/slang-ast-val.cpp index e8020aa04..68a55e567 100644 --- a/source/slang/slang-ast-val.cpp +++ b/source/slang/slang-ast-val.cpp @@ -461,7 +461,7 @@ Val* DeclaredSubtypeWitness::_resolveImplOverride() ConversionCost DeclaredSubtypeWitness::_getOverloadResolutionCostOverride() { - return kConversionCost_GenericParamUpcast; + return kConversionCost_None; } Val* DeclaredSubtypeWitness::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int * ioDiff) @@ -611,7 +611,7 @@ Val* TransitiveSubtypeWitness::_substituteImplOverride(ASTBuilder* astBuilder, S ConversionCost TransitiveSubtypeWitness::_getOverloadResolutionCostOverride() { - return getSubToMid()->getOverloadResolutionCost() + getMidToSup()->getOverloadResolutionCost(); + return getSubToMid()->getOverloadResolutionCost() + getMidToSup()->getOverloadResolutionCost() + kConversionCost_GenericParamUpcast; } void TransitiveSubtypeWitness::_toTextOverride(StringBuilder& out) diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 02e3241a9..deb8c55eb 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -3865,7 +3865,7 @@ namespace Slang void SemanticsVisitor::addModifiersToSynthesizedDecl( ConformanceCheckingContext* context, DeclRef<Decl> requiredMemberDeclRef, - FunctionDeclBase* synthesized, + CallableDecl* synthesized, ThisExpr*& synThis) { // Required interface methods can be `static` or non-`static`, @@ -4018,13 +4018,13 @@ namespace Slang } } - FunctionDeclBase* SemanticsVisitor::synthesizeMethodSignatureForRequirementWitness( + CallableDecl* SemanticsVisitor::synthesizeMethodSignatureForRequirementWitness( ConformanceCheckingContext* context, - DeclRef<FunctionDeclBase> requiredMemberDeclRef, + DeclRef<CallableDecl> requiredMemberDeclRef, List<Expr*>& synArgs, ThisExpr*& synThis) { - FunctionDeclBase* synFuncDecl = as<FunctionDeclBase>(m_astBuilder->createByNodeType(requiredMemberDeclRef.getDecl()->astNodeType)); + CallableDecl* synFuncDecl = as<CallableDecl>(m_astBuilder->createByNodeType(requiredMemberDeclRef.getDecl()->astNodeType)); SLANG_ASSERT(synFuncDecl); synFuncDecl->ownedScope = m_astBuilder->create<Scope>(); synFuncDecl->ownedScope->containerDecl = synFuncDecl; @@ -4165,8 +4165,8 @@ namespace Slang ThisExpr* synThis = nullptr; List<Expr*> synArgs; - auto synFuncDecl = synthesizeMethodSignatureForRequirementWitness( - context, requiredMemberDeclRef, synArgs, synThis); + auto synFuncDecl = as<FunctionDeclBase>(synthesizeMethodSignatureForRequirementWitness( + context, requiredMemberDeclRef, synArgs, synThis)); auto resultType = synFuncDecl->returnType.type; @@ -4494,6 +4494,7 @@ namespace Slang // Synthesize the property name with a prefix to avoid name clashing. synPropertyDecl->nameAndLoc = requiredMemberDeclRef.getDecl()->nameAndLoc; synPropertyDecl->nameAndLoc.name = getName(String("$syn_property_") + getText(requiredMemberDeclRef.getName())); + synPropertyDecl->parentDecl = context->parentDecl; // The type of our synthesized property will be the expected type @@ -4511,260 +4512,90 @@ namespace Slang auto propertyType = getType(m_astBuilder, requiredMemberDeclRef); synPropertyDecl->type.type = propertyType; - // Our synthesized property will have an accessor declaration for - // each accessor of the requirement. - // - // TODO: If we ever start to support synthesis for subscript requirements, - // then we probably want to factor the accessor-related logic into - // a subroutine so that it can be shared between properties and subscripts. - // - Dictionary<DeclRef<AccessorDecl>, AccessorDecl*> mapRequiredAccessorToSynAccessor; - for( auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(m_astBuilder, requiredMemberDeclRef) ) - { - // The synthesized accessor will be an AST node of the same class as - // the required accessor. - // - auto synAccessorDecl = (AccessorDecl*) m_astBuilder->createByNodeType(requiredAccessorDeclRef.getDecl()->astNodeType); - synAccessorDecl->ownedScope = m_astBuilder->create<Scope>(); - synAccessorDecl->ownedScope->containerDecl = synAccessorDecl; - synAccessorDecl->ownedScope->parent = getScope(context->parentDecl); - - // Whatever the required accessor returns, that is what our synthesized accessor will return. - // - synAccessorDecl->returnType.type = getResultType(m_astBuilder, requiredAccessorDeclRef); - // Similarly, our synthesized accessor will have parameters matching those of the requirement. - // - // Note: in practice we expect that only `set` accessors will have any parameters, - // and they will only have a single parameter. - // - List<Expr*> synArgs; - for( auto requiredParamDeclRef : getParameters(m_astBuilder, requiredAccessorDeclRef) ) - { - auto paramType = getType(m_astBuilder, requiredParamDeclRef); - - // The synthesized parameter will ahve the same name and - // type as the parameter of the requirement. - // - auto synParamDecl = m_astBuilder->create<ParamDecl>(); - synParamDecl->nameAndLoc = requiredParamDeclRef.getDecl()->nameAndLoc; - synParamDecl->type.type = paramType; - - // We need to add the parameter as a child declaration of - // the accessor we are building. - // - synParamDecl->parentDecl = synAccessorDecl; - synAccessorDecl->members.add(synParamDecl); - - // For each paramter, we will create an argument expression - // to represent it in the body of the accessor. - // - auto synArg = m_astBuilder->create<VarExpr>(); - synArg->declRef = makeDeclRef(synParamDecl); - synArg->type = paramType; - synArgs.add(synArg); - } - - // We need to create a `this` expression to be used in the body - // of the synthesized accessor. - // - // TODO: if we ever allow `static` properties or subscripts, - // we will need to handle that case here, by *not* creating - // a `this` expression. - // - ThisExpr* synThis = m_astBuilder->create<ThisExpr>(); - synThis->scope = synAccessorDecl->ownedScope; - - // The type of `this` in our accessor will be the type for - // which we are synthesizing a conformance. - // - synThis->type.type = context->conformingType; - - // A `get` accessor should default to an immutable `this`, - // while other accessors default to mutable `this`. - // - // TODO: If we ever add other kinds of accessors, we will - // need to check that this assumption stays valid. - // - synThis->type.isLeftValue = true; - if(as<GetterDecl>(requiredAccessorDeclRef)) - synThis->type.isLeftValue = false; - - // If the accessor requirement is `[nonmutating]` then our - // synthesized accessor should be too, and also the `this` - // parameter should *not* be an l-value. - // - if( requiredAccessorDeclRef.getDecl()->hasModifier<NonmutatingAttribute>() ) - { - synThis->type.isLeftValue = false; - - auto synAttr = m_astBuilder->create<NonmutatingAttribute>(); - synAccessorDecl->modifiers.first = synAttr; - } - // - // Note: we don't currently support `[mutating] get` accessors, - // but the desired behavior in that case is clear, so we go - // ahead and future-proof this code a bit: - // - else if( requiredAccessorDeclRef.getDecl()->hasModifier<MutatingAttribute>() ) - { - synThis->type.isLeftValue = true; - - auto synAttr = m_astBuilder->create<MutatingAttribute>(); - synAccessorDecl->modifiers.first = synAttr; - } - else if (requiredAccessorDeclRef.getDecl()->hasModifier<RefAttribute>()) - { - synThis->type.isLeftValue = true; - - auto synAttr = m_astBuilder->create<RefAttribute>(); - synAccessorDecl->modifiers.first = synAttr; - } - else if (requiredAccessorDeclRef.getDecl()->hasModifier<ConstRefAttribute>()) - { - auto synAttr = m_astBuilder->create<ConstRefAttribute>(); - synAccessorDecl->modifiers.first = synAttr; - } - // We are going to synthesize an expression and then perform - // semantic checking on it, but if there are semantic errors - // we do *not* want to report them to the user as such, and - // instead want the result to be a failure to synthesize - // a valid witness. - // - // We will buffer up diagnostics into a temporary sink and - // then throw them away when we are done. - // - // TODO: This behavior might be something we want to make - // into a more fundamental capability of `DiagnosticSink` and/or - // `SemanticsVisitor` so that code can push/pop the emission - // of diagnostics more easily. - // - DiagnosticSink tempSink(getSourceManager(), nullptr); - SemanticsVisitor subVisitor(withSink(&tempSink)); - - // We start by constructing an expression that represents - // `this.name` where `name` is the name of the required - // member. The caller already passed in a `lookupResult` - // that should indicate all the declarations found by - // looking up `name`, so we can start with that. - // - // TODO: Note that there are many cases for member lookup - // that are not handled just by using `createLookupResultExpr` - // because they are currently being special-cased (the most - // notable cases are swizzles, as well as lookup of static - // members in types). - // - // The main result here is that we will not be able to synthesize - // a requirement for a built-in scalar/vector/matrix type to - // a property with a name like `.xy` based on the presence of - // swizles, even though it seems like such a thing should Just Work. - // - // If this is important we could "fix" it by allowing this - // code to dispatch to the special-case logic used when doing - // semantic checking for member expressions. - // - // Note: an alternative would be to change the stdlib declarations - // of vectors/matrices so that all the swizzles are defined as - // `property` declarations. There are some C++ math libraries (like GLM) - // that implement swizzle syntax by a similar approach of statically - // enumerating all possible swizzles. The down-side to such an - // approach is that the combinatorial space of swizzles is quite - // large (especially for matrices) so that supporting them via - // general-purpose language features is unlikely to be as efficient - // as special-case logic. - // - auto synMemberRef = subVisitor.createLookupResultExpr( - requiredMemberDeclRef.getName(), - lookupResult, - synThis, - requiredMemberDeclRef.getLoc(), - nullptr); - synMemberRef->loc = requiredMemberDeclRef.getLoc(); - - // The body of the accessor will depend on the class of the accessor - // we are synthesizing (e.g., `get` vs. `set`). - // - Stmt* synBodyStmt = nullptr; - if( as<GetterDecl>(requiredAccessorDeclRef) ) - { - // A `get` accessor will simply perform: - // - // return this.name; - // - // which involves coercing the member access `this.name` to - // the expected type of the property. - // - auto coercedMemberRef = subVisitor.coerce(CoercionSite::Return, propertyType, synMemberRef); - auto synReturn = m_astBuilder->create<ReturnStmt>(); - synReturn->expression = coercedMemberRef; - - synBodyStmt = synReturn; - } - else if( as<SetterDecl>(requiredAccessorDeclRef) ) - { - // We expect all `set` accessors to have a single argument, - // but we will defensively bail out if that is somehow - // not the case. - // - SLANG_ASSERT(synArgs.getCount() == 1); - if(synArgs.getCount() != 1) - return false; - - // A `set` accessor will simply perform: - // - // this.name = newValue; - // - // which involves creating and checking an assignment - // expression. - - auto synAssign = m_astBuilder->create<AssignExpr>(); - synAssign->left = synMemberRef; - synAssign->right = synArgs[0]; - - auto synCheckedAssign = subVisitor.checkAssignWithCheckedOperands(synAssign); - - auto synExprStmt = m_astBuilder->create<ExpressionStmt>(); - synExprStmt->expression = synCheckedAssign; - - synBodyStmt = synExprStmt; - } - else - { - // While there are other kinds of accessors than `get` and `set`, - // those are currently only reserved for stdlib-internal use. - // We will not bother with synthesis for those cases. - // - return false; - } + // We start by constructing an expression that represents + // `this.name` where `name` is the name of the required + // member. The caller already passed in a `lookupResult` + // that should indicate all the declarations found by + // looking up `name`, so we can start with that. + // + // TODO: Note that there are many cases for member lookup + // that are not handled just by using `createLookupResultExpr` + // because they are currently being special-cased (the most + // notable cases are swizzles, as well as lookup of static + // members in types). + // + // The main result here is that we will not be able to synthesize + // a requirement for a built-in scalar/vector/matrix type to + // a property with a name like `.xy` based on the presence of + // swizles, even though it seems like such a thing should Just Work. + // + // If this is important we could "fix" it by allowing this + // code to dispatch to the special-case logic used when doing + // semantic checking for member expressions. + // + // Note: an alternative would be to change the stdlib declarations + // of vectors/matrices so that all the swizzles are defined as + // `property` declarations. There are some C++ math libraries (like GLM) + // that implement swizzle syntax by a similar approach of statically + // enumerating all possible swizzles. The down-side to such an + // approach is that the combinatorial space of swizzles is quite + // large (especially for matrices) so that supporting them via + // general-purpose language features is unlikely to be as efficient + // as special-case logic. + // + // We are going to synthesize an expression and then perform + // semantic checking on it, but if there are semantic errors + // we do *not* want to report them to the user as such, and + // instead want the result to be a failure to synthesize + // a valid witness. + // + // We will buffer up diagnostics into a temporary sink and + // then throw them away when we are done. + // + // TODO: This behavior might be something we want to make + // into a more fundamental capability of `DiagnosticSink` and/or + // `SemanticsVisitor` so that code can push/pop the emission + // of diagnostics more easily. + // + DiagnosticSink tempSink(getSourceManager(), nullptr); + SemanticsVisitor subVisitor(withSink(&tempSink)); - // We bail out if we ran into any errors (meaning that the synthesized - // accessor is not usable). - // - // TODO: If there were *warnings* emitted to the sink, it would probably - // be good to show those warnings to the user, since they might indicate - // real issues. E.g., with the current logic a `float` field could - // satisfying an `int` property requirement, but the user would probably - // want to be warned when they do such a thing. - // - if(tempSink.getErrorCount() != 0) - return false; + // We need to create a `this` expression to be used in the body + // of the synthesized accessor. + // + // TODO: if we ever allow `static` properties or subscripts, + // we will need to handle that case here, by *not* creating + // a `this` expression. + // + ThisExpr* synThis = m_astBuilder->create<ThisExpr>(); + synThis->scope = synPropertyDecl->ownedScope; - synAccessorDecl->body = synBodyStmt; + // The type of `this` in our accessor will be the type for + // which we are synthesizing a conformance. + // + synThis->type.type = context->conformingType; + synThis->type.isLeftValue = true; + auto synMemberRef = subVisitor.createLookupResultExpr( + requiredMemberDeclRef.getName(), + lookupResult, + synThis, + requiredMemberDeclRef.getLoc(), + nullptr); + synMemberRef->loc = requiredMemberDeclRef.getLoc(); - synAccessorDecl->parentDecl = synPropertyDecl; - synPropertyDecl->members.add(synAccessorDecl); + bool canSynAccessors = synthesizeAccessorRequirements( + context, + requiredMemberDeclRef, + propertyType, + synMemberRef, + synPropertyDecl, + witnessTable); + if (!canSynAccessors) + return false; + - // If synthesis of an accessor worked, then we will record it into - // a local dictionary. We do *not* install the accessor into the - // witness table yet, because it is possible that synthesis will - // succeed for some accessors but not others, and we don't want - // to leave the witness table in a state where a requirement is - // "partially satisfied." - // - mapRequiredAccessorToSynAccessor.add(requiredAccessorDeclRef, synAccessorDecl); - } - synPropertyDecl->parentDecl = context->parentDecl; // The visibility of synthesized decl should be the min of the parent decl and the requirement. if (requiredMemberDeclRef.getDecl()->findModifier<VisibilityModifier>()) @@ -4774,21 +4605,6 @@ namespace Slang auto visibility = Math::Min(thisVisibility, requirementVisibility); addVisibilityModifier(m_astBuilder, synPropertyDecl, visibility); } - - // Once our synthesized declaration is complete, we need - // to install it as the witness that satifies the given - // requirement. - // - // Subsequent code generation should not be able to tell the - // difference between our synthetic property and a hand-written - // one with the same behavior. - // - for(auto& [key, value] : mapRequiredAccessorToSynAccessor) - { - witnessTable->add(key.getDecl(), RequirementWitness(makeDeclRef(value))); - } - witnessTable->add(requiredMemberDeclRef.getDecl(), - RequirementWitness(makeDeclRef(synPropertyDecl))); return true; } @@ -5020,6 +4836,412 @@ namespace Slang return true; } + bool SemanticsVisitor::synthesizeAccessorRequirements( + ConformanceCheckingContext* context, + DeclRef<ContainerDecl> requiredMemberDeclRef, + Type* resultType, + Expr* synBoundStorageExpr, + ContainerDecl* synAccesorContainer, + RefPtr<WitnessTable> witnessTable) + { + Dictionary<DeclRef<AccessorDecl>, AccessorDecl*> mapRequiredAccessorToSynAccessor; + for (auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(m_astBuilder, requiredMemberDeclRef)) + { + // The synthesized accessor will be an AST node of the same class as + // the required accessor. + // + auto synAccessorDecl = (AccessorDecl*)m_astBuilder->createByNodeType(requiredAccessorDeclRef.getDecl()->astNodeType); + synAccessorDecl->ownedScope = m_astBuilder->create<Scope>(); + synAccessorDecl->ownedScope->containerDecl = synAccessorDecl; + synAccessorDecl->ownedScope->parent = getScope(context->parentDecl); + + // Whatever the required accessor returns, that is what our synthesized accessor will return. + // + synAccessorDecl->returnType.type = resultType; + + // Similarly, our synthesized accessor will have parameters matching those of the requirement. + // + // Note: in practice we expect that only `set` accessors will have any parameters, + // and they will only have a single parameter. + // + List<Expr*> synArgs; + for (auto requiredParamDeclRef : getParameters(m_astBuilder, requiredAccessorDeclRef)) + { + auto paramType = getType(m_astBuilder, requiredParamDeclRef); + + // The synthesized parameter will ahve the same name and + // type as the parameter of the requirement. + // + auto synParamDecl = m_astBuilder->create<ParamDecl>(); + synParamDecl->nameAndLoc = requiredParamDeclRef.getDecl()->nameAndLoc; + synParamDecl->type.type = paramType; + + // We need to add the parameter as a child declaration of + // the accessor we are building. + // + synParamDecl->parentDecl = synAccessorDecl; + synAccessorDecl->members.add(synParamDecl); + + // For each paramter, we will create an argument expression + // to represent it in the body of the accessor. + // + auto synArg = m_astBuilder->create<VarExpr>(); + synArg->declRef = makeDeclRef(synParamDecl); + synArg->type = paramType; + synArgs.add(synArg); + } + + // We need to create a `this` expression to be used in the body + // of the synthesized accessor. + // + // TODO: if we ever allow `static` properties or subscripts, + // we will need to handle that case here, by *not* creating + // a `this` expression. + // + ThisExpr* synThis = m_astBuilder->create<ThisExpr>(); + synThis->scope = synAccessorDecl->ownedScope; + + // The type of `this` in our accessor will be the type for + // which we are synthesizing a conformance. + // + synThis->type.type = context->conformingType; + + // A `get` accessor should default to an immutable `this`, + // while other accessors default to mutable `this`. + // + // TODO: If we ever add other kinds of accessors, we will + // need to check that this assumption stays valid. + // + synThis->type.isLeftValue = true; + if (as<GetterDecl>(requiredAccessorDeclRef)) + synThis->type.isLeftValue = false; + + // If the accessor requirement is `[nonmutating]` then our + // synthesized accessor should be too, and also the `this` + // parameter should *not* be an l-value. + // + if (requiredAccessorDeclRef.getDecl()->hasModifier<NonmutatingAttribute>()) + { + synThis->type.isLeftValue = false; + + auto synAttr = m_astBuilder->create<NonmutatingAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + // + // Note: we don't currently support `[mutating] get` accessors, + // but the desired behavior in that case is clear, so we go + // ahead and future-proof this code a bit: + // + else if (requiredAccessorDeclRef.getDecl()->hasModifier<MutatingAttribute>()) + { + synThis->type.isLeftValue = true; + + auto synAttr = m_astBuilder->create<MutatingAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + else if (requiredAccessorDeclRef.getDecl()->hasModifier<RefAttribute>()) + { + synThis->type.isLeftValue = true; + + auto synAttr = m_astBuilder->create<RefAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + else if (requiredAccessorDeclRef.getDecl()->hasModifier<ConstRefAttribute>()) + { + auto synAttr = m_astBuilder->create<ConstRefAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + // We are going to synthesize an expression and then perform + // semantic checking on it, but if there are semantic errors + // we do *not* want to report them to the user as such, and + // instead want the result to be a failure to synthesize + // a valid witness. + // + // We will buffer up diagnostics into a temporary sink and + // then throw them away when we are done. + // + // TODO: This behavior might be something we want to make + // into a more fundamental capability of `DiagnosticSink` and/or + // `SemanticsVisitor` so that code can push/pop the emission + // of diagnostics more easily. + // + DiagnosticSink tempSink(getSourceManager(), nullptr); + SemanticsVisitor subVisitor(withSink(&tempSink)); + + // The body of the accessor will depend on the class of the accessor + // we are synthesizing (e.g., `get` vs. `set`). + // + Stmt* synBodyStmt = nullptr; + if (as<GetterDecl>(requiredAccessorDeclRef)) + { + // A `get` accessor will simply perform: + // + // return this.name; + // + // which involves coercing the member access `this.name` to + // the expected type of the property. + // + auto coercedMemberRef = subVisitor.coerce(CoercionSite::Return, resultType, synBoundStorageExpr); + auto synReturn = m_astBuilder->create<ReturnStmt>(); + synReturn->expression = coercedMemberRef; + + synBodyStmt = synReturn; + } + else if (as<SetterDecl>(requiredAccessorDeclRef)) + { + // We expect all `set` accessors to have a single argument, + // but we will defensively bail out if that is somehow + // not the case. + // + SLANG_ASSERT(synArgs.getCount() == 1); + if (synArgs.getCount() != 1) + return false; + + // A `set` accessor will simply perform: + // + // this.name = newValue; + // + // which involves creating and checking an assignment + // expression. + + auto synAssign = m_astBuilder->create<AssignExpr>(); + synAssign->left = synBoundStorageExpr; + synAssign->right = synArgs[0]; + + auto synCheckedAssign = subVisitor.checkAssignWithCheckedOperands(synAssign); + + auto synExprStmt = m_astBuilder->create<ExpressionStmt>(); + synExprStmt->expression = synCheckedAssign; + + synBodyStmt = synExprStmt; + } + else + { + // While there are other kinds of accessors than `get` and `set`, + // those are currently only reserved for stdlib-internal use. + // We will not bother with synthesis for those cases. + // + return false; + } + + // We bail out if we ran into any errors (meaning that the synthesized + // accessor is not usable). + // + // TODO: If there were *warnings* emitted to the sink, it would probably + // be good to show those warnings to the user, since they might indicate + // real issues. E.g., with the current logic a `float` field could + // satisfying an `int` property requirement, but the user would probably + // want to be warned when they do such a thing. + // + if (tempSink.getErrorCount() != 0) + return false; + + synAccessorDecl->body = synBodyStmt; + + synAccessorDecl->parentDecl = synAccesorContainer; + synAccesorContainer->members.add(synAccessorDecl); + + // If synthesis of an accessor worked, then we will record it into + // a local dictionary. We do *not* install the accessor into the + // witness table yet, because it is possible that synthesis will + // succeed for some accessors but not others, and we don't want + // to leave the witness table in a state where a requirement is + // "partially satisfied." + // + mapRequiredAccessorToSynAccessor.add(requiredAccessorDeclRef, synAccessorDecl); + } + + // Once our synthesized declaration is complete, we need + // to install it as the witness that satifies the given + // requirement. + // + // Subsequent code generation should not be able to tell the + // difference between our synthetic property and a hand-written + // one with the same behavior. + // + for (auto& [key, value] : mapRequiredAccessorToSynAccessor) + { + witnessTable->add(key.getDecl(), RequirementWitness(getDefaultDeclRef(value))); + } + witnessTable->add(requiredMemberDeclRef.getDecl(), + RequirementWitness(getDefaultDeclRef(synAccesorContainer))); + return true; + } + + bool SemanticsVisitor::trySynthesizeWrapperTypeSubscriptRequirementWitness( + ConformanceCheckingContext* context, + DeclRef<SubscriptDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable) + { + // We are synthesizing the subscript requirement for a wrapper type: + // struct Wrapper + // { + // Inner inner; + // subscript(int index)->int { get { return inner[index]; } + // set { inner[index] = newValue; } + // } + // } + // + // // Find the witness that FooImpl : IFoo. + auto aggTypeDecl = as<AggTypeDecl>(context->parentDecl); + auto innerType = aggTypeDecl->wrappedType.type; + DeclRef<Decl> innerProperty; + auto innerWitness = tryGetSubtypeWitness(innerType, witnessTable->baseType); + if (!innerWitness) + return false; + // + List<Expr*> synArgs; + ThisExpr* synThis; + auto synSubscriptDecl = synthesizeMethodSignatureForRequirementWitness(context, requiredMemberDeclRef, + synArgs, synThis); + auto declType = getType(m_astBuilder, getDefaultDeclRef(synSubscriptDecl).as<SubscriptDecl>()); + synThis->checked = true; + + // Form a `this[args...]` expression that we will use to coerce from + // in the synthesized subscript accessors. + // + synSubscriptDecl->parentDecl = context->parentDecl; + DiagnosticSink tempSink(getSourceManager(), nullptr); + SemanticsVisitor subVisitor(withSink(&tempSink)); + auto base = m_astBuilder->create<VarExpr>(); + base->scope = synThis->scope; + base->name = getName("inner"); + + IndexExpr* indexExpr = m_astBuilder->create<IndexExpr>(); + indexExpr->baseExpression = base; + indexExpr->indexExprs = _Move(synArgs); + auto synBaseStorageExpr = subVisitor.CheckTerm(indexExpr); + + if (tempSink.getErrorCount() != 0) + return false; + + // Our synthesized subscript will have an accessor declaration for + // each accessor of the requirement. + // + bool canSynAccessors = synthesizeAccessorRequirements( + context, + requiredMemberDeclRef, + declType, + synBaseStorageExpr, + synSubscriptDecl, witnessTable); + if (!canSynAccessors) + return false; + + synSubscriptDecl->parentDecl = context->parentDecl; + + // The visibility of synthesized decl should be the min of the parent decl and the requirement. + if (requiredMemberDeclRef.getDecl()->findModifier<VisibilityModifier>()) + { + auto requirementVisibility = getDeclVisibility(requiredMemberDeclRef.getDecl()); + auto thisVisibility = getDeclVisibility(context->parentDecl); + auto visibility = Math::Min(thisVisibility, requirementVisibility); + addVisibilityModifier(m_astBuilder, synSubscriptDecl, visibility); + } + + return true; + } + + bool SemanticsVisitor::trySynthesizeSubscriptRequirementWitness( + ConformanceCheckingContext* context, + DeclRef<SubscriptDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable) + { + if (isWrapperTypeDecl(context->parentDecl)) + return trySynthesizeWrapperTypeSubscriptRequirementWitness(context, requiredMemberDeclRef, witnessTable); + + // The situation here is that the context of an inheritance + // declaration didn't provide an exact match for a required + // subscript. E.g.: + // + // interface ICell { subscript(int index)->int {get;} } + // struct MyCell : ICell + // { + // subscript(uint index)->int {ref;} + // } + // + // It is clear in this case that the `MyCell` type *can* + // satisfy the signature required by `ICell`, if we consider + // all the allowed type coercion rules, and use `ref` accessor + // to implement `get`. + // + // The approach in this function will be to construct a + // synthesized `subscript` along the lines of: + // + // struct MyCell ... + // { + // ... + // subscript(int index)->int {get;} + // { + // get { return this.origianl_subscript[index]; } + // } + // } + // + // That is, we construct a `subscript` with the correct type + // and with an accessor for each requirement, where the accesors + // all try to dispatch to the original subscript decl. + // + // If those synthesized accessors all type-check, then we can + // say that the type must satisfy the requirement structurally, + // even if there isn't an exact signature match. More + // importantly, the `property` we just synthesized can be + // used as a witness to the fact that the requirement is + // satisfied. + // + // The big-picture flow of the logic here is similar to + // `trySynthesizePropertyRequirementWitness()` above, and we + // will not comment this code as exhaustively, under the + // assumption that readers of the code don't benefit from + // having the exact same information stated twice. + // + + List<Expr*> synArgs; + ThisExpr* synThis; + auto synSubscriptDecl = synthesizeMethodSignatureForRequirementWitness(context, requiredMemberDeclRef, + synArgs, synThis); + synThis->type.isLeftValue = true; + synThis->checked = true; + synSubscriptDecl->parentDecl = context->parentDecl; + + auto declType = getType(m_astBuilder, getDefaultDeclRef(synSubscriptDecl).as<SubscriptDecl>()); + + // Form a `this[args...]` expression that we will use to coerce from + // in the synthesized subscript accessors. + // + DiagnosticSink tempSink(getSourceManager(), nullptr); + SemanticsVisitor subVisitor(withSink(&tempSink)); + IndexExpr* indexExpr = m_astBuilder->create<IndexExpr>(); + indexExpr->baseExpression = synThis; + indexExpr->indexExprs = _Move(synArgs); + auto synBaseStorageExpr = subVisitor.CheckTerm(indexExpr); + + if (tempSink.getErrorCount() != 0) + return false; + + // Our synthesized subscript will have an accessor declaration for + // each accessor of the requirement. + // + bool canSynAccessors = synthesizeAccessorRequirements( + context, + requiredMemberDeclRef, + declType, + synBaseStorageExpr, + synSubscriptDecl, witnessTable); + if (!canSynAccessors) + return false; + + + // The visibility of synthesized decl should be the min of the parent decl and the requirement. + if (requiredMemberDeclRef.getDecl()->findModifier<VisibilityModifier>()) + { + auto requirementVisibility = getDeclVisibility(requiredMemberDeclRef.getDecl()); + auto thisVisibility = getDeclVisibility(context->parentDecl); + auto visibility = Math::Min(thisVisibility, requirementVisibility); + addVisibilityModifier(m_astBuilder, synSubscriptDecl, visibility); + } + + return true; + } + bool SemanticsVisitor::trySynthesizeRequirementWitness( ConformanceCheckingContext* context, LookupResult const& lookupResult, @@ -5098,6 +5320,14 @@ namespace Slang witnessTable); } + if (auto requiredSubscriptDeclRef = requiredMemberDeclRef.as<SubscriptDecl>()) + { + return trySynthesizeSubscriptRequirementWitness( + context, + requiredSubscriptDeclRef, + witnessTable); + } + if (auto requiredAssocTypeDeclRef = requiredMemberDeclRef.as<AssocTypeDecl>()) { if (auto builtinAttr = requiredAssocTypeDeclRef.getDecl()->findModifier<BuiltinRequirementModifier>()) @@ -5635,10 +5865,20 @@ namespace Slang // requirement, it may be possible that we can still synthesis the // implementation if this is one of the known builtin requirements. // Otherwise, report diagnostic now. - if (!requiredMemberDeclRef.getDecl()->hasModifier<BuiltinRequirementModifier>() && - !(requiredMemberDeclRef.as<GenericDecl>() && + + if (requiredMemberDeclRef.getDecl()->hasModifier<BuiltinRequirementModifier>() || + (requiredMemberDeclRef.as<GenericDecl>() && getInner(requiredMemberDeclRef.as<GenericDecl>())->hasModifier<BuiltinRequirementModifier>())) { + } + else if (requiredMemberDeclRef.as<SubscriptDecl>() && + (as<ArrayExpressionType>(context->conformingType) || + as<VectorExpressionType>(context->conformingType) || + as<MatrixExpressionType>(context->conformingType))) + { + } + else + { getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, subType, requiredMemberDeclRef); getSink()->diagnose(requiredMemberDeclRef, Diagnostics::seeDeclarationOf, requiredMemberDeclRef); return false; diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 3414c16b5..8f24ec5b0 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -1401,7 +1401,15 @@ namespace Slang Expr* SemanticsVisitor::CheckTerm(Expr* term) { + // If we have already checked the expr, don't check again. + if (term->checked) + { + return term; + } + auto checkedTerm = _CheckTerm(term); + checkedTerm->checked = true; + // Differentiable type checking. // TODO: This can be super slow. if (this->m_parentFunc && diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index ad3539a21..adb7e81f3 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1735,7 +1735,7 @@ namespace Slang void addModifiersToSynthesizedDecl( ConformanceCheckingContext* context, DeclRef<Decl> requirement, - FunctionDeclBase* synthesized, + CallableDecl* synthesized, ThisExpr* &synThis); void addRequiredParamsToSynthesizedDecl( @@ -1743,9 +1743,9 @@ namespace Slang CallableDecl* synthesized, List<Expr*>& synArgs); - FunctionDeclBase* synthesizeMethodSignatureForRequirementWitness( + CallableDecl* synthesizeMethodSignatureForRequirementWitness( ConformanceCheckingContext* context, - DeclRef<FunctionDeclBase> requiredMemberDeclRef, + DeclRef<CallableDecl> requiredMemberDeclRef, List<Expr*>& synArgs, ThisExpr*& synThis); @@ -1756,6 +1756,14 @@ namespace Slang List<Expr*>& synGenericArgs, ThisExpr*& synThis); + bool synthesizeAccessorRequirements( + ConformanceCheckingContext* context, + DeclRef<ContainerDecl> requiredMemberDeclRef, + Type* resultType, + Expr* synBoundStorageExpr, + ContainerDecl* synAccesorContainer, + RefPtr<WitnessTable> witnessTable); + void _addMethodWitness( WitnessTable* witnessTable, DeclRef<CallableDecl> requirement, @@ -1793,6 +1801,16 @@ namespace Slang DeclRef<PropertyDecl> requiredMemberDeclRef, RefPtr<WitnessTable> witnessTable); + bool trySynthesizeSubscriptRequirementWitness( + ConformanceCheckingContext* context, + DeclRef<SubscriptDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable); + + bool trySynthesizeWrapperTypeSubscriptRequirementWitness( + ConformanceCheckingContext* context, + DeclRef<SubscriptDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable); + bool trySynthesizeAssociatedTypeRequirementWitness( ConformanceCheckingContext* context, LookupResult const& lookupResult, diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index 068a240bb..70eabb4f7 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -1548,6 +1548,7 @@ namespace Slang auto itemDiff = CompareLookupResultItems(left->item, right->item); if(itemDiff) return itemDiff; + auto specificityDiff = compareOverloadCandidateSpecificity(left->item, right->item); if(specificityDiff) return specificityDiff; diff --git a/source/slang/slang-ir-use-uninitialized-values.cpp b/source/slang/slang-ir-use-uninitialized-values.cpp index 8661ba0dc..56b13aa09 100644 --- a/source/slang/slang-ir-use-uninitialized-values.cpp +++ b/source/slang/slang-ir-use-uninitialized-values.cpp @@ -448,21 +448,6 @@ namespace Slang return false; } - static bool isWrittenTo(IRInst* inst) - { - for (auto alias : getAliasableInstructions(inst)) - { - for (auto use = alias->firstUse; use; use = use->nextUse) - { - InstructionUsageType usage = getInstructionUsageType(use->getUser(), alias); - if (usage == Store || usage == StoreParent) - return true; - } - } - - return false; - } - static bool isDirectlyWrittenTo(IRInst* inst) { for (auto use = inst->firstUse; use; use = use->nextUse) @@ -580,36 +565,6 @@ namespace Slang } } - static void checkParameterAsInOut(IRParam* param, IRFunc* func, bool isThis, DiagnosticSink* sink) - { - // If the inout is used for the sake of interface conformance, let it be - for (auto use = func->firstUse; use; use = use->nextUse) - { - if (as<IRWitnessTableEntry>(use->getUser())) - return; - } - - // If there is at least one write... - if (isWrittenTo(param)) - return; - - // ...or if there is an intrinsic_asm instruction - for (const auto& b : func->getBlocks()) - { - for (auto inst = b->getFirstInst(); inst; inst = inst->next) - { - if (as<IRGenericAsm>(inst)) - return; - } - } - - sink->diagnose(param, - isThis - ? Diagnostics::methodNeverMutates - : Diagnostics::inOutNeverStoredInto, - param); - } - static void checkUninitializedValues(IRFunc* func, DiagnosticSink* sink) { // Differentiable functions will generate undefined values @@ -631,22 +586,15 @@ namespace Slang if (auto entry = func->findDecoration<IREntryPointDecoration>()) stage = entry->getProfile().getStage(); - bool structMethod = func->findDecoration<IRMethodDecoration>(); - // Check out parameters if (!isUnmodifying(func)) { int index = 0; for (auto param : firstBlock->getParams()) { - bool isThis = structMethod && (index == 0); - ParameterCheckType checkType = isPotentiallyUnintended(param, stage, index); if (checkType == AsOut) checkParameterAsOut(reachability, func, param, sink); - else if (checkType == AsInOut) - checkParameterAsInOut(param, func, isThis, sink); - index++; } } |
