summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2024-09-18 13:46:20 -0700
committerGitHub <noreply@github.com>2024-09-18 13:46:20 -0700
commit2d83875f4b376f047c4541a6f6c13d36e5aa228b (patch)
treeba41c6faa6e97b6821ac8ea635f2ae52ca8d1f4c /source/slang
parent85b996a75683b5364456d731a9cb4aee5c3fada2 (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.slang25
-rw-r--r--source/slang/hlsl.meta.slang15
-rw-r--r--source/slang/slang-ast-base.h2
-rw-r--r--source/slang/slang-ast-val.cpp4
-rw-r--r--source/slang/slang-check-decl.cpp782
-rw-r--r--source/slang/slang-check-expr.cpp8
-rw-r--r--source/slang/slang-check-impl.h24
-rw-r--r--source/slang/slang-check-overload.cpp1
-rw-r--r--source/slang/slang-ir-use-uninitialized-values.cpp52
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++;
}
}