summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/core.meta.slang5
-rw-r--r--source/slang/hlsl.meta.slang2
-rw-r--r--source/slang/slang-ast-decl.h8
-rw-r--r--source/slang/slang-ast-modifier.h9
-rw-r--r--source/slang/slang-ast-support-types.h2
-rw-r--r--source/slang/slang-check-decl.cpp210
-rw-r--r--source/slang/slang-check-expr.cpp6
-rw-r--r--source/slang/slang-diagnostic-defs.h13
-rw-r--r--source/slang/slang-lookup.cpp12
-rw-r--r--source/slang/slang-lower-to-ir.cpp1481
-rw-r--r--source/slang/slang-parser.cpp80
-rw-r--r--source/slang/slang-syntax.h5
12 files changed, 1152 insertions, 681 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index 288786a67..a6a969bed 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -1120,7 +1120,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt)
}
// Set
- sb << " set;\n";
+ sb << " [nonmutating] set;\n";
}
// !!!!!!!!!!!!!!!!!! ref !!!!!!!!!!!!!!!!!!!!!!!!!
@@ -1918,6 +1918,9 @@ attribute_syntax [__vulkanHitAttributes] : VulkanHitAttributesAttribute;
__attributeTarget(FunctionDeclBase)
attribute_syntax [mutating] : MutatingAttribute;
+__attributeTarget(SetterDecl)
+attribute_syntax [nonmutating] : NonmutatingAttribute;
+
/// Indicates that a function computes its result as a function of its arguments without loading/storing any memory or other state.
///
/// This is equivalent to the LLVM `readnone` function attribute.
diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang
index ac49402a1..d8e6cfce3 100644
--- a/source/slang/hlsl.meta.slang
+++ b/source/slang/hlsl.meta.slang
@@ -3658,7 +3658,7 @@ for (int aa = 0; aa < kBaseBufferAccessLevelCount; ++aa)
if (access != SLANG_RESOURCE_ACCESS_READ)
{
- sb << "__target_intrinsic(glsl, \"imageStore($0, int($1), $V2)\") set;\n";
+ sb << "__target_intrinsic(glsl, \"imageStore($0, int($1), $V2)\") [nonmutating] set;\n";
sb << "__intrinsic_op(" << int(kIROp_ImageSubscript) << ") ref;\n";
}
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h
index 428ea8dc3..b3dbbef58 100644
--- a/source/slang/slang-ast-decl.h
+++ b/source/slang/slang-ast-decl.h
@@ -309,6 +309,14 @@ class SubscriptDecl : public CallableDecl
SLANG_CLASS(SubscriptDecl)
};
+ /// A property declaration that abstracts over storage with a getter/setter/etc.
+class PropertyDecl : public ContainerDecl
+{
+ SLANG_CLASS(PropertyDecl)
+
+ TypeExp type;
+};
+
// An "accessor" for a subscript or property
class AccessorDecl : public FunctionDeclBase
{
diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h
index 1cbfab7c3..1f4f6b33f 100644
--- a/source/slang/slang-ast-modifier.h
+++ b/source/slang/slang-ast-modifier.h
@@ -770,6 +770,15 @@ class MutatingAttribute : public Attribute
SLANG_CLASS(MutatingAttribute)
};
+// A `[nonmutating]` attribute, which indicates that a
+// `set` accessor does not need to modify anything through
+// its `this` parameter.
+//
+class NonmutatingAttribute : public Attribute
+{
+ SLANG_CLASS(NonmutatingAttribute)
+};
+
// A `[__readNone]` attribute, which indicates that a function
// computes its results strictly based on argument values, without
diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h
index 6ab9167f2..8143d4e17 100644
--- a/source/slang/slang-ast-support-types.h
+++ b/source/slang/slang-ast-support-types.h
@@ -838,7 +838,7 @@ namespace Slang
// TODO(tfoley): It is ugly to have these.
// We should probably fix the call sites instead.
- const RefPtr<T>& getFirst() { return *begin(); }
+ T* getFirst() { return *begin(); }
Index getCount() { return getFilterCount<T>(m_filterStyle, m_begin, m_end); }
T* operator[](Index index) const
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index 600d456b8..e4cf40122 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -78,9 +78,23 @@ namespace Slang
void visitConstructorDecl(ConstructorDecl* decl);
+ void visitAbstractStorageDeclCommon(ContainerDecl* decl);
+
void visitSubscriptDecl(SubscriptDecl* decl);
+ void visitPropertyDecl(PropertyDecl* decl);
+
+
+ /// Get the type of the storage accessed by an accessor.
+ ///
+ /// The type of storage is determined by the parent declaration.
+ Type* _getAccessorStorageType(AccessorDecl* decl);
+
+ /// Perform checks common to all types of accessors.
+ void _visitAccessorDeclCommon(AccessorDecl* decl);
+
void visitAccessorDecl(AccessorDecl* decl);
+ void visitSetterDecl(SetterDecl* decl);
};
struct SemanticsDeclRedeclarationVisitor
@@ -379,6 +393,30 @@ namespace Slang
qualType.isLeftValue = isLValue;
return qualType;
}
+ else if( auto propertyDeclRef = declRef.as<PropertyDecl>() )
+ {
+ // Access to a declared `property` is similar to
+ // access to a variable/field, except that it
+ // is mediated through accessors (getters, seters, etc.).
+
+ QualType qualType;
+ qualType.type = getType(astBuilder, propertyDeclRef);
+
+ bool isLValue = false;
+
+ // If the property has any declared accessors that
+ // can be used to set the property, then the resulting
+ // expression behaves as an l-value.
+ //
+ if(propertyDeclRef.getDecl()->getMembersOfType<SetterDecl>().isNonEmpty())
+ isLValue = true;
+ if(propertyDeclRef.getDecl()->getMembersOfType<RefAccessorDecl>().isNonEmpty())
+ isLValue = true;
+
+ qualType.isLeftValue = isLValue;
+ return qualType;
+
+ }
else if( auto enumCaseDeclRef = declRef.as<EnumCaseDecl>() )
{
QualType qualType;
@@ -3652,19 +3690,19 @@ namespace Slang
checkCallableDeclCommon(decl);
}
- void SemanticsDeclHeaderVisitor::visitSubscriptDecl(SubscriptDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitAbstractStorageDeclCommon(ContainerDecl* decl)
{
- decl->returnType = CheckUsableType(decl->returnType);
-
- // If we have a subscript declaration with no accessor declarations,
+ // If we have a subscript or property declaration with no accessor declarations,
// then we should create a single `GetterDecl` to represent
// the implicit meaning of their declaration, so:
//
// subscript(uint index) -> T;
+ // property x : Y;
//
// becomes:
//
// subscript(uint index) -> T { get; }
+ // property x : Y { get; }
//
bool anyAccessors = decl->getMembersOfType<AccessorDecl>().isNonEmpty();
@@ -3677,29 +3715,173 @@ namespace Slang
getterDecl->parentDecl = decl;
decl->members.add(getterDecl);
}
+ }
+
+ void SemanticsDeclHeaderVisitor::visitSubscriptDecl(SubscriptDecl* decl)
+ {
+ decl->returnType = CheckUsableType(decl->returnType);
+
+ visitAbstractStorageDeclCommon(decl);
checkCallableDeclCommon(decl);
}
- void SemanticsDeclHeaderVisitor::visitAccessorDecl(AccessorDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitPropertyDecl(PropertyDecl* decl)
{
- // An accessor must appear nested inside a subscript declaration (today),
- // or a property declaration (when we add them). It will derive
- // its return type from the outer declaration, so we handle both
- // of these checks at the same place.
- auto parent = decl->parentDecl;
- if (auto parentSubscript = as<SubscriptDecl>(parent))
+ decl->type = CheckUsableType(decl->type);
+ visitAbstractStorageDeclCommon(decl);
+ }
+
+ Type* SemanticsDeclHeaderVisitor::_getAccessorStorageType(AccessorDecl* decl)
+ {
+ auto parentDecl = decl->parentDecl;
+ if (auto parentSubscript = as<SubscriptDecl>(parentDecl))
{
ensureDecl(parentSubscript, DeclCheckState::CanUseTypeOfValueDecl);
- decl->returnType = parentSubscript->returnType;
+ return parentSubscript->returnType;
+ }
+ else if (auto parentProperty = as<PropertyDecl>(parentDecl))
+ {
+ ensureDecl(parentProperty, DeclCheckState::CanUseTypeOfValueDecl);
+ return parentProperty->type.type;
}
- // TODO: when we add "property" declarations, check for them here
+ else
+ {
+ return getASTBuilder()->getErrorType();
+ }
+ }
+
+ void SemanticsDeclHeaderVisitor::_visitAccessorDeclCommon(AccessorDecl* decl)
+ {
+ // An accessor must appear nested inside a subscript or property declaration.
+ //
+ auto parentDecl = decl->parentDecl;
+ if (as<SubscriptDecl>(parentDecl))
+ {}
+ else if (as<PropertyDecl>(parentDecl))
+ {}
else
{
getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty);
}
+ }
- checkCallableDeclCommon(decl);
+ void SemanticsDeclHeaderVisitor::visitAccessorDecl(AccessorDecl* decl)
+ {
+ _visitAccessorDeclCommon(decl);
+
+ // Note: This subroutine is used by both `get`
+ // and `ref` accessors, but is bypassed by
+ // `set` accessors (which use `visitSetterDecl`
+ // intead).
+
+ // Accessors (other than setters) don't support
+ // parameters.
+ //
+ if( decl->getParameters().getCount() != 0 )
+ {
+ getSink()->diagnose(decl, Diagnostics::nonSetAccessorMustNotHaveParams);
+ }
+
+ // By default, the return type of an accessor is treated as
+ // the type of the abstract storage location being accessed.
+ //
+ // A `ref` accessor currently relies on this logic even though
+ // it isn't quite correct, because we don't have support
+ // for by-reference return values today. This is a non-issue
+ // for now because we don't support user-defined `ref`
+ // accessors yet.
+ //
+ // TODO: Once we can support the by-reference return value
+ // correctly *or* we can move to something like a coroutine-based
+ // `modify` accessor (a la Swift), we should split out
+ // handling of `RefAccessorDecl` and only use this routine
+ // for `GetterDecl`s.
+ //
+ decl->returnType.type = _getAccessorStorageType(decl);
+ }
+
+ void SemanticsDeclHeaderVisitor::visitSetterDecl(SetterDecl* decl)
+ {
+ // Make sure to invoke the common checking logic for all accessors.
+ _visitAccessorDeclCommon(decl);
+
+ // A `set` accessor always returns `void`.
+ //
+ decl->returnType.type = getASTBuilder()->getVoidType();
+
+ // A setter always receives a single value representing
+ // the new value to set into the storage.
+ //
+ // The user may declare that parameter explicitly and
+ // thereby control its name, or they can declare no
+ // parmaeters and allow the compiler to synthesize one
+ // names `newValue`.
+ //
+ ParamDecl* newValueParam = nullptr;
+ auto params = decl->getParameters();
+ if( params.getCount() >= 1 )
+ {
+ // If the user declared an explicit parameter
+ // then that is the one that will represent
+ // the new value.
+ //
+ newValueParam = params.getFirst();
+
+ if( params.getCount() > 1 )
+ {
+ // If the user declared more than one explicit
+ // parameter, then that is an error.
+ //
+ getSink()->diagnose(params[1], Diagnostics::setAccessorMayNotHaveMoreThanOneParam);
+ }
+ }
+ else
+ {
+ // If the user didn't declare any explicit parameters,
+ // then we create an implicit one and add it into
+ // the AST.
+ //
+ newValueParam = m_astBuilder->create<ParamDecl>();
+ newValueParam->nameAndLoc.name = getName("newValue");
+ newValueParam->nameAndLoc.loc = decl->loc;
+
+ newValueParam->parentDecl = decl;
+ decl->members.add(newValueParam);
+ }
+
+ // The new-value parameter is expected to have the
+ // same type as the abstract storage that the
+ // accessor is setting.
+ //
+ auto newValueType = _getAccessorStorageType(decl);
+
+ // It is allowed and encouraged for the programmer
+ // to leave off the type on the new-value parameter,
+ // in which case we will set it to the expected
+ // type automatically.
+ //
+ if( !newValueParam->type.exp )
+ {
+ newValueParam->type.type = newValueType;
+ }
+ else
+ {
+ // If the user *did* give the new-value parameter
+ // an explicit type, then we need to check it
+ // and then enforce that it matches what we expect.
+ //
+ auto actualType = CheckProperType(newValueParam->type);
+
+ if(as<ErrorType>(actualType))
+ {}
+ else if(actualType->equals(newValueType))
+ {}
+ else
+ {
+ getSink()->diagnose(newValueParam, Diagnostics::setAccessorParamWrongType, newValueParam, actualType, newValueType);
+ }
+ }
}
GenericDecl* SemanticsVisitor::GetOuterGeneric(Decl* decl)
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index d2e5afd85..42842995d 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -1992,7 +1992,11 @@ namespace Slang
{
auto containerDecl = scope->containerDecl;
- if( auto funcDeclBase = as<FunctionDeclBase>(containerDecl) )
+ if( auto setterDecl = as<SetterDecl>(containerDecl) )
+ {
+ expr->type.isLeftValue = true;
+ }
+ else if( auto funcDeclBase = as<FunctionDeclBase>(containerDecl) )
{
if( funcDeclBase->hasModifier<MutatingAttribute>() )
{
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 5349f0e30..5a841dfc9 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -331,6 +331,18 @@ DIAGNOSTIC(30821, Error, tagTypeMustBeListedFirst, "an unum type may only have a
DIAGNOSTIC(30820, Error, cannotInheritFromExplicitlySealedDeclarationInAnotherModule, "cannot inherit from type '$0' marked 'sealed' in module '$1'")
DIAGNOSTIC(30821, Error, cannotInheritFromImplicitlySealedDeclarationInAnotherModule, "cannot inherit from type '$0' in module '$1' because it is implicitly 'sealed'; mark the base type 'open' to allow inheritance across modules")
+// 309xx: subscripts
+
+// 310xx: properties
+
+// 311xx: accessors
+
+DIAGNOSTIC(31100, Error, accessorMustBeInsideSubscriptOrProperty, "an accessor declaration is only allowed inside a subscript or property declaration")
+
+DIAGNOSTIC(31101, Error, nonSetAccessorMustNotHaveParams, "accessors other than 'set' must not have parameters")
+DIAGNOSTIC(31102, Error, setAccessorMayNotHaveMoreThanOneParam, "a 'set' accessor may not have more than one parameter")
+DIAGNOSTIC(31102, Error, setAccessorParamWrongType, "'set' parameter '$0' has type '$1' which does not match the expected type '$2'")
+
// 39999 waiting to be placed in the right range
DIAGNOSTIC(39999, Error, expectedIntegerConstantWrongType, "expected integer constant (found: '$0')")
@@ -393,7 +405,6 @@ DIAGNOSTIC(38009, Error, expectedValueOfTypeForSpecializationArg, "expected a co
DIAGNOSTIC(38100, Error, typeDoesntImplementInterfaceRequirement, "type '$0' does not provide required interface member '$1'")
DIAGNOSTIC(38101, Error, thisExpressionOutsideOfTypeDecl, "'this' expression can only be used in members of an aggregate type")
DIAGNOSTIC(38102, Error, initializerNotInsideType, "an 'init' declaration is only allowed inside a type or 'extension' declaration")
-DIAGNOSTIC(38102, Error, accessorMustBeInsideSubscriptOrProperty, "an accessor declaration is only allowed inside a subscript or property declaration")
DIAGNOSTIC(38103, Error, thisTypeOutsideOfTypeDecl, "'This' type can only be used inside of an aggregate type")
DIAGNOSTIC(38020, Error, mismatchEntryPointTypeArgument, "expecting $0 entry-point type arguments, provided $1.")
diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp
index 855497d00..25e1eedce 100644
--- a/source/slang/slang-lookup.cpp
+++ b/source/slang/slang-lookup.cpp
@@ -738,6 +738,18 @@ static void _lookUpInScopes(
//
thisParameterMode = LookupResultItem::Breadcrumb::ThisParameterMode::MutableValue;
}
+ else if( containerDeclRef.is<SetterDecl>() )
+ {
+ // In the context of a `set` accessor, the members of the
+ // surrounding type are accessible through a mutable `this`.
+ //
+ // TODO: At some point we may want a way to opt out of this
+ // behavior; it is possible to have a setter on a `struct`
+ // that actually just sets data into a buffer that is
+ // referenced by one of the `struct`'s fields.
+ //
+ thisParameterMode = LookupResultItem::Breadcrumb::ThisParameterMode::MutableValue;
+ }
else if( auto funcDeclRef = containerDeclRef.as<FunctionDeclBase>() )
{
// The implicit `this`/`This` for a function-like declaration
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index 58f23b0c8..a744d9f38 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -71,34 +71,12 @@ struct SubscriptInfo : ExtendedValueInfo
DeclRef<SubscriptDecl> declRef;
};
-// This case is used to indicate a reference to an AST-level
-// subscript operation bound to particular arguments.
-//
-// For example in a case like this:
-//
-// RWStructuredBuffer<Foo> gBuffer;
-// ... gBuffer[someIndex] ...
-//
-// the expression `gBuffer[someIndex]` will be lowered to
-// a value that references `RWStructureBuffer<Foo>::operator[]`
-// with arguments `(gBuffer, someIndex)`.
-//
-// Such a value can be an l-value, and depending on the context
-// where it is used, can lower into a call to either the getter
-// or setter operations of the subscript.
-//
-struct BoundSubscriptInfo : ExtendedValueInfo
-{
- DeclRef<SubscriptDecl> declRef;
- IRType* type;
- List<IRInst*> args;
-};
-
// Some cases of `ExtendedValueInfo` need to
// recursively contain `LoweredValInfo`s, and
// so we forward declare them here and fill
// them in later.
//
+struct BoundStorageInfo;
struct BoundMemberInfo;
struct SwizzledLValueInfo;
@@ -130,7 +108,7 @@ struct LoweredValInfo
// An AST-level subscript operation bound to a particular
// object and arguments.
- BoundSubscript,
+ BoundStorage,
// The result of applying swizzling to an l-value
SwizzledLValue,
@@ -189,13 +167,13 @@ struct LoweredValInfo
return (SubscriptInfo*)ext;
}
- static LoweredValInfo boundSubscript(
- BoundSubscriptInfo* boundSubscriptInfo);
+ static LoweredValInfo boundStorage(
+ BoundStorageInfo* boundStorageInfo);
- BoundSubscriptInfo* getBoundSubscriptInfo()
+ BoundStorageInfo* getBoundStorageInfo()
{
- SLANG_ASSERT(flavor == Flavor::BoundSubscript);
- return (BoundSubscriptInfo*)ext;
+ SLANG_ASSERT(flavor == Flavor::BoundStorage);
+ return (BoundStorageInfo*)ext;
}
static LoweredValInfo swizzledLValue(
@@ -208,6 +186,49 @@ struct LoweredValInfo
}
};
+// This case is used to indicate a reference to an AST-level
+// operation that accesses abstract storage.
+//
+// This could be an invocation of a `subscript` declaration,
+// with argument representing an index or indices:
+//
+// RWStructuredBuffer<Foo> gBuffer;
+// ... gBuffer[someIndex] ...
+//
+// the expression `gBuffer[someIndex]` will be lowered to
+// a value that references `RWStructureBuffer<Foo>::operator[]`
+// with arguments `(gBuffer, someIndex)`.
+//
+// This could also be an reference to a `property` declaration,
+// with no arguments:
+//
+// struct Sphere { property radius : int { get { ... } } }
+// Sphere sphere;
+// ... sphere.radius ...
+//
+// the expression `sphere.radius` will be lowered to a value
+// that references `Sphere::radius` with arguments `(sphere)`.
+//
+// Such a value can be an l-value, and depending on the context
+// where it is used, can lower into a call to either the getter
+// or setter operations of the storage.
+//
+struct BoundStorageInfo : ExtendedValueInfo
+{
+ /// The declaration of the abstract storage (subscript or property)
+ DeclRef<ContainerDecl> declRef;
+
+ /// The IR-level type of the stored value
+ IRType* type;
+
+ /// The base value/object on which storage is being accessed
+ LoweredValInfo base;
+
+ /// Additional arguments required to reify a reference to the storage
+ List<IRInst*> additionalArgs;
+};
+
+
// Represents some declaration bound to a particular
// object. For example, if we had `obj.f` where `f`
// is a member function, we'd use a `BoundMemberInfo`
@@ -268,12 +289,12 @@ LoweredValInfo LoweredValInfo::subscript(
return info;
}
-LoweredValInfo LoweredValInfo::boundSubscript(
- BoundSubscriptInfo* boundSubscriptInfo)
+LoweredValInfo LoweredValInfo::boundStorage(
+ BoundStorageInfo* boundStorageInfo)
{
LoweredValInfo info;
- info.flavor = Flavor::BoundSubscript;
- info.ext = boundSubscriptInfo;
+ info.flavor = Flavor::BoundStorage;
+ info.ext = boundStorageInfo;
return info;
}
@@ -585,67 +606,9 @@ LoweredValInfo emitCallToDeclRef(
UInt argCount,
IRInst* const* args)
{
- auto builder = context->irBuilder;
-
-
- if (auto subscriptDeclRef = funcDeclRef.as<SubscriptDecl>())
- {
- // A reference to a subscript declaration is a special case,
- // because it is not possible to call a subscript directly;
- // we must call one of its accessors.
- //
- // TODO: everything here will also apply to propery declarations
- // once we have them, so some of this code might be shared
- // some day.
-
- DeclRef<GetterDecl> getterDeclRef;
- bool justAGetter = true;
- for (auto accessorDeclRef : getMembersOfType<AccessorDecl>(subscriptDeclRef, MemberFilterStyle::Instance))
- {
- // We want to track whether this subscript has any accessors other than
- // `get` (assuming that everything except `get` can be used for setting...).
-
- if (auto foundGetterDeclRef = accessorDeclRef.as<GetterDecl>())
- {
- // We found a getter.
- getterDeclRef = foundGetterDeclRef;
- }
- else
- {
- // There was something other than a getter, so we can't
- // invoke an accessor just now.
- justAGetter = false;
- }
- }
-
- if (!justAGetter || !getterDeclRef)
- {
- // We can't perform an actual call right now, because
- // this expression might appear in an r-value or l-value
- // position (or *both* if it is being passed as an argument
- // for an `in out` parameter!).
- //
- // Instead, we will construct a special-case value to
- // represent the latent subscript operation (abstractly
- // this is a reference to a storage location).
-
- // The abstract storage location will need to include
- // all the arguments being passed to the subscript operation.
-
- RefPtr<BoundSubscriptInfo> boundSubscript = new BoundSubscriptInfo();
- boundSubscript->declRef = subscriptDeclRef;
- boundSubscript->type = type;
- boundSubscript->args.addRange(args, argCount);
-
- context->shared->extValues.add(boundSubscript);
+ SLANG_ASSERT(funcType);
- return LoweredValInfo::boundSubscript(boundSubscript);
- }
-
- // Otherwise we are just call the getter, and so that
- // is what we need to be emitting a call to...
- funcDeclRef = getterDeclRef;
- }
+ auto builder = context->irBuilder;
auto funcDecl = funcDeclRef.getDecl();
if(auto intrinsicOpModifier = funcDecl->findModifier<IntrinsicOpModifier>())
@@ -685,22 +648,6 @@ LoweredValInfo emitCallToDeclRef(
// Fallback case is to emit an actual call.
//
- // TODO: We are constructing a type that we expect the function
- // being called to have here, but that type doesn't account
- // for `in` vs. `out`/`inout` parameters, so it could easily
- // be wrong. We should sort out why this path in the code
- // even needs to be computing a type (rather than taking
- // it directly from the declaration).
- //
- if(!funcType)
- {
- List<IRType*> argTypes;
- for(UInt ii = 0; ii < argCount; ++ii)
- {
- argTypes.add(args[ii]->getDataType());
- }
- funcType = builder->getFuncType(argCount, argTypes.getBuffer(), type);
- }
LoweredValInfo funcVal = emitDeclRef(context, funcDeclRef, funcType);
return emitCallToVal(context, type, funcVal, argCount, args);
}
@@ -715,6 +662,111 @@ LoweredValInfo emitCallToDeclRef(
return emitCallToDeclRef(context, type, funcDeclRef, funcType, args.getCount(), args.getBuffer());
}
+ /// Represents the "direction" that a parameter is being passed (e.g., `in` or `out`
+enum ParameterDirection
+{
+ kParameterDirection_In, ///< Copy in
+ kParameterDirection_Out, ///< Copy out
+ kParameterDirection_InOut, ///< Copy in, copy out
+ kParameterDirection_Ref, ///< By-reference
+};
+
+
+ /// Emit a call to the given `accessorDeclRef`.
+ ///
+ /// The `base` value represents the object on which the accessor is being invoked.
+ /// The `args` represent any additional arguments to the accessor. This could be
+ /// because we are invoking a subscript accessor (so the args include any index value(s)),
+ /// and/or because we are invoking a setter (so that the args include the new value
+ /// to be set).
+ ///
+static LoweredValInfo _emitCallToAccessor(
+ IRGenContext* context,
+ IRType* type,
+ DeclRef<AccessorDecl> accessorDeclRef,
+ LoweredValInfo base,
+ UInt argCount,
+ IRInst* const* args);
+
+static LoweredValInfo _emitCallToAccessor(
+ IRGenContext* context,
+ IRType* type,
+ DeclRef<AccessorDecl> accessorDeclRef,
+ LoweredValInfo base,
+ List<IRInst*> const& args)
+{
+ return _emitCallToAccessor(context, type, accessorDeclRef, base, args.getCount(), args.getBuffer());
+}
+
+ /// Lower a reference to abstract storage (a property or subscript).
+ ///
+ /// The given `storageDeclRef` is being accessed on some `base` value,
+ /// to yield a value of some expected `type`. The additional `args`
+ /// are only needed in the case of a subscript declaration (for
+ /// a property, `argCount` should be zero).
+ ///
+ /// In the case where there is only a `get` accessor, this function
+ /// will go ahead and invoke it to produce a value here and now.
+ /// Otherwise, it will produce an abstract `LoweredValInfo` that
+ /// encapsulates the reference to the storage so that downstream
+ /// code can decide which accessor(s) to invoke.
+ ///
+static LoweredValInfo lowerStorageReference(
+ IRGenContext* context,
+ IRType* type,
+ DeclRef<ContainerDecl> storageDeclRef,
+ LoweredValInfo base,
+ UInt argCount,
+ IRInst* const* args)
+{
+ DeclRef<GetterDecl> getterDeclRef;
+ bool justAGetter = true;
+ for (auto accessorDeclRef : getMembersOfType<AccessorDecl>(storageDeclRef, MemberFilterStyle::Instance))
+ {
+ // We want to track whether this storage has any accessors other than
+ // `get` (assuming that everything except `get` can be used for setting...).
+
+ if (auto foundGetterDeclRef = accessorDeclRef.as<GetterDecl>())
+ {
+ // We found a getter.
+ getterDeclRef = foundGetterDeclRef;
+ }
+ else
+ {
+ // There was something other than a getter, so we can't
+ // invoke an accessor just now.
+ justAGetter = false;
+ }
+ }
+
+ if (!justAGetter || !getterDeclRef)
+ {
+ // We can't perform an actual call right now, because
+ // this expression might appear in an r-value or l-value
+ // position (or *both* if it is being passed as an argument
+ // for an `in out` parameter!).
+ //
+ // Instead, we will construct a special-case value to
+ // represent the latent access operation (abstractly
+ // this is a reference to a storage location).
+
+ // The abstract storage location will need to include
+ // all the arguments being passed in the case of a subscript operation.
+
+ RefPtr<BoundStorageInfo> boundStorage = new BoundStorageInfo();
+ boundStorage->declRef = storageDeclRef;
+ boundStorage->type = type;
+ boundStorage->base = base;
+ boundStorage->additionalArgs.addRange(args, argCount);
+
+ context->shared->extValues.add(boundStorage);
+
+ return LoweredValInfo::boundStorage(boundStorage);
+ }
+
+ return _emitCallToAccessor(context, type, getterDeclRef, base, argCount, args);
+}
+
IRInst* getFieldKey(
IRGenContext* context,
DeclRef<Decl> field)
@@ -744,7 +796,7 @@ LoweredValInfo extractField(
break;
case LoweredValInfo::Flavor::BoundMember:
- case LoweredValInfo::Flavor::BoundSubscript:
+ case LoweredValInfo::Flavor::BoundStorage:
{
// The base value is one that is trying to defer a get-vs-set
// decision, so we will need to do the same.
@@ -791,9 +843,9 @@ top:
case LoweredValInfo::Flavor::Ptr:
return lowered;
- case LoweredValInfo::Flavor::BoundSubscript:
+ case LoweredValInfo::Flavor::BoundStorage:
{
- auto boundSubscriptInfo = lowered.getBoundSubscriptInfo();
+ auto boundStorageInfo = lowered.getBoundStorageInfo();
// We are being asked to extract a value from a subscript call
// (e.g., `base[index]`). We will first check if the subscript
@@ -804,31 +856,34 @@ top:
// in case the `get` operation has a natural translation for
// a target, while the general `ref` case does not...)
- auto getters = getMembersOfType<GetterDecl>(boundSubscriptInfo->declRef, MemberFilterStyle::Instance);
+ auto getters = getMembersOfType<GetterDecl>(boundStorageInfo->declRef, MemberFilterStyle::Instance);
if (getters.getCount())
{
- lowered = emitCallToDeclRef(
+ auto getter = *getters.begin();
+ lowered = _emitCallToAccessor(
context,
- boundSubscriptInfo->type,
- *getters.begin(),
- nullptr,
- boundSubscriptInfo->args);
+ boundStorageInfo->type,
+ getter,
+ boundStorageInfo->base,
+ boundStorageInfo->additionalArgs);
goto top;
}
- auto refAccessors = getMembersOfType<RefAccessorDecl>(boundSubscriptInfo->declRef, MemberFilterStyle::Instance);
+ auto refAccessors = getMembersOfType<RefAccessorDecl>(boundStorageInfo->declRef, MemberFilterStyle::Instance);
if(refAccessors.getCount())
{
+ auto refAccessor = *refAccessors.begin();
+
// The `ref` accessor will return a pointer to the value, so
// we need to reflect that in the type of our `call` instruction.
- IRType* ptrType = context->irBuilder->getPtrType(boundSubscriptInfo->type);
+ IRType* ptrType = context->irBuilder->getPtrType(boundStorageInfo->type);
- LoweredValInfo refVal = emitCallToDeclRef(
+ LoweredValInfo refVal = _emitCallToAccessor(
context,
ptrType,
- *refAccessors.begin(),
- nullptr,
- boundSubscriptInfo->args);
+ refAccessor,
+ boundStorageInfo->base,
+ boundStorageInfo->additionalArgs);
// The result from the call needs to be implicitly dereferenced,
// so that it can work as an l-value of the desired result type.
@@ -1827,28 +1882,186 @@ LoweredValInfo createVar(
return LoweredValInfo::ptr(irAlloc);
}
-void addArgs(
+ /// Add a single `in` argument value to a list of arguments
+void addInArg(
IRGenContext* context,
- List<IRInst*>* ioArgs,
- LoweredValInfo argInfo)
+ List<IRInst*>* ioArgs,
+ LoweredValInfo argVal)
{
auto& args = *ioArgs;
- switch( argInfo.flavor )
+ switch( argVal.flavor )
{
case LoweredValInfo::Flavor::Simple:
case LoweredValInfo::Flavor::Ptr:
case LoweredValInfo::Flavor::SwizzledLValue:
- case LoweredValInfo::Flavor::BoundSubscript:
+ case LoweredValInfo::Flavor::BoundStorage:
case LoweredValInfo::Flavor::BoundMember:
- args.add(getSimpleVal(context, argInfo));
+ args.add(getSimpleVal(context, argVal));
break;
default:
- SLANG_UNIMPLEMENTED_X("addArgs case");
+ SLANG_UNIMPLEMENTED_X("addInArg case");
break;
}
}
+// After a call to a function with `out` or `in out`
+// parameters, we may need to copy data back into
+// the l-value locations used for output arguments.
+//
+// During lowering of the argument list, we build
+// up a list of these "fixup" assignments that need
+// to be performed.
+struct OutArgumentFixup
+{
+ LoweredValInfo dst;
+ LoweredValInfo src;
+};
+
+ /// Apply any fixups that have been created for `out` and `inout` arguments.
+static void applyOutArgumentFixups(
+ IRGenContext* context,
+ List<OutArgumentFixup> const& fixups)
+{
+ for (auto fixup : fixups)
+ {
+ assign(context, fixup.dst, fixup.src);
+ }
+}
+
+ /// Add one argument value to the argument list for a call being constructed
+void addArg(
+ IRGenContext* context,
+ List<IRInst*>* ioArgs, //< The argument list being built
+ List<OutArgumentFixup>* ioFixups, //< "Fixup" logic to apply for `out` or `inout` arguments
+ LoweredValInfo argVal, //< The lowered value of the argument to add
+ IRType* paramType, //< The type of the corresponding parameter
+ ParameterDirection paramDirection, //< The direction of the parameter (`in`, `out`, etc.)
+ SourceLoc loc) //< A location to use if we need to report an error
+{
+ switch(paramDirection)
+ {
+ case kParameterDirection_Ref:
+ {
+ // According to our "calling convention" we need to
+ // pass a pointer into the callee. Unlike the case for
+ // `out` and `inout` below, it is never valid to do
+ // copy-in/copy-out for a `ref` parameter, so we just
+ // pass in the actual pointer.
+ //
+ IRInst* argPtr = getAddress(context, argVal, loc);
+ addInArg(context, ioArgs, LoweredValInfo::simple(argPtr));
+ }
+ break;
+
+ case kParameterDirection_Out:
+ case kParameterDirection_InOut:
+ {
+ // According to our "calling convention" we need to
+ // pass a pointer into the callee.
+ //
+ // A naive approach would be to just take the address
+ // of `loweredArg` above and pass it in, but that
+ // has two issues:
+ //
+ // 1. The l-value might not be something that has a single
+ // well-defined "address" (e.g., `foo.xzy`).
+ //
+ // 2. The l-value argument might actually alias some other
+ // storage that the callee will access (e.g., we are
+ // passing in a global variable, or two `out` parameters
+ // are being passed the same location in an array).
+ //
+ // In each of these cases, the safe option is to create
+ // a temporary variable to use for argument-passing,
+ // and then do copy-in/copy-out around the call.
+ //
+ // TODO: We should consider ruling out case (2) as undefined
+ // behavior, and specify that whether `inout` and `out` are
+ // handled via copy-in-copy-out or by-reference parameter
+ // passing is an implementation detail. That would allow
+ // us to avoid introducing a copy except where it is required
+ // for the semantics of (1).
+ //
+ // TODO: We should confirm whether such a change will make
+ // it harder to create SSA values for variables that get
+ // used with `out` or `inout` parameters.
+
+ LoweredValInfo tempVar = createVar(context, paramType);
+
+ // If the parameter is `in out` or `inout`, then we need
+ // to ensure that we pass in the original value stored
+ // in the argument, which we accomplish by assigning
+ // from the l-value to our temp.
+ if(paramDirection == kParameterDirection_InOut)
+ {
+ assign(context, tempVar, argVal);
+ }
+
+ // Now we can pass the address of the temporary variable
+ // to the callee as the actual argument for the `in out`
+ SLANG_ASSERT(tempVar.flavor == LoweredValInfo::Flavor::Ptr);
+ IRInst* tempPtr = getAddress(context, tempVar, loc);
+ addInArg(context, ioArgs, LoweredValInfo::simple(tempPtr));
+
+ // Finally, after the call we will need
+ // to copy in the other direction: from our
+ // temp back to the original l-value.
+ OutArgumentFixup fixup;
+ fixup.src = tempVar;
+ fixup.dst = argVal;
+
+ (*ioFixups).add(fixup);
+
+ }
+ break;
+
+ default:
+ addInArg(context, ioArgs, argVal);
+ break;
+ }
+}
+
+ /// Add argument(s) corresponding to one parameter to a call
+ ///
+ /// The `argExpr` is the AST-level expression being passed as an argument to the call.
+ /// The `paramType` and `paramDirection` represent what is known about the receiving
+ /// parameter of the callee (e.g., if the parameter `in`, `inout`, etc.).
+ /// The `ioArgs` array receives the IR-level argument(s) that are added for the given
+ /// argument expression.
+ /// The `ioFixups` array receives any "fixup" code that needs to be run *after* the
+ /// call completes (e.g., to move from a scratch variable used for an `inout` argument back
+ /// into the original location).
+ ///
+void addCallArgsForParam(
+ IRGenContext* context,
+ IRType* paramType,
+ ParameterDirection paramDirection,
+ Expr* argExpr,
+ List<IRInst*>* ioArgs,
+ List<OutArgumentFixup>* ioFixups)
+{
+ switch(paramDirection)
+ {
+ case kParameterDirection_Ref:
+ case kParameterDirection_Out:
+ case kParameterDirection_InOut:
+ {
+ LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr);
+ addArg(context, ioArgs, ioFixups, loweredArg, paramType, paramDirection, argExpr->loc);
+ }
+ break;
+
+ default:
+ {
+ LoweredValInfo loweredArg = lowerRValueExpr(context, argExpr);
+ addInArg(context, ioArgs, loweredArg);
+ }
+ break;
+ }
+}
+
+
//
// When we try to turn a `LoweredValInfo` into an address of some temporary storage,
@@ -1885,15 +2098,6 @@ LoweredValInfo tryGetAddress(
LoweredValInfo const& inVal,
TryGetAddressMode mode);
- /// Represents the "direction" that a parameter is being passed (e.g., `in` or `out`
-enum ParameterDirection
-{
- kParameterDirection_In, ///< Copy in
- kParameterDirection_Out, ///< Copy out
- kParameterDirection_InOut, ///< Copy in, copy out
- kParameterDirection_Ref, ///< By-reference
-};
-
/// Compute the direction for a parameter based on its declaration
ParameterDirection getParameterDirection(VarDeclBase* paramDecl)
{
@@ -1924,7 +2128,11 @@ ParameterDirection getParameterDirection(VarDeclBase* paramDecl)
}
/// Compute the direction for a `this` parameter based on the declaration of its parent function
-ParameterDirection getThisParamDirection(Decl* parentDecl)
+ ///
+ /// If the given declaration doesn't care about the direction of a `this` parameter, then
+ /// it will return the provided `defaultDirection` instead.
+ ///
+ParameterDirection getThisParamDirection(Decl* parentDecl, ParameterDirection defaultDirection)
{
// Applications can opt in to a mutable `this` parameter,
// by applying the `[mutating]` attribute to their
@@ -1935,11 +2143,32 @@ ParameterDirection getThisParamDirection(Decl* parentDecl)
return kParameterDirection_InOut;
}
- // TODO: If/when we support user-defined subscripts or properties,
- // we should probably make the `set` accessor on those default to
- // `[mutating]` rather than require users to specify it. There
- // might need to be a `[nonmutating]` modifier for the rare case
- // where a user wants to opt out.
+ // A `set` accessor on a property or subscript declaration
+ // defaults to a mutable `this` parameter, but the programmer
+ // can opt out of this behavior using `[nonmutating]`
+ //
+ if( parentDecl->hasModifier<NonmutatingAttribute>() )
+ {
+ return kParameterDirection_In;
+ }
+ else if( as<SetterDecl>(parentDecl) )
+ {
+ return kParameterDirection_InOut;
+ }
+
+ // Declarations that represent abstract storage (a property
+ // or subscript) do not want to dictate anything about
+ // the direction of an outer `this` parameter, since that
+ // should be determined by their inner accessors.
+ //
+ if( as<PropertyDecl>(parentDecl) )
+ {
+ return defaultDirection;
+ }
+ if( as<SubscriptDecl>(parentDecl) )
+ {
+ return defaultDirection;
+ }
// For now we make any `this` parameter default to `in`.
//
@@ -2003,6 +2232,346 @@ Type* getThisParamTypeForCallable(
return getThisParamTypeForContainer(context, parentDeclRef);
}
+// When lowering something callable (most commonly a function declaration),
+// we need to construct an appropriate parameter list for the IR function
+// that folds in any contributions from both the declaration itself *and*
+// its parent declaration(s).
+//
+// For example, given code like:
+//
+// struct Foo { int bar(float y) { ... } };
+//
+// we need to generate IR-level code something like:
+//
+// func Foo_bar(Foo this, float y) -> int;
+//
+// that is, the `this` parameter has become explicit.
+//
+// The same applies to generic parameters, and these
+// should apply even if the nested declaration is `static`:
+//
+// struct Foo<T> { static int bar(T y) { ... } };
+//
+// becomes:
+//
+// func Foo_bar<T>(T y) -> int;
+//
+// In order to implement this, we are going to do a recursive
+// walk over a declaration and its parents, collecting separate
+// lists of ordinary and generic parameters that will need
+// to be included in the final declaration's parameter list.
+//
+// When doing code generation for an ordinary value parameter,
+// we mostly care about its type, and then also its "direction"
+// (`in`, `out`, `in out`). We sometimes need acess to the
+// original declaration so that we can inspect it for meta-data,
+// but in some cases there is no such declaration (e.g., a `this`
+// parameter doesn't get an explicit declaration in the AST).
+// To handle this we break out the relevant data into derived
+// structures:
+//
+struct IRLoweringParameterInfo
+{
+ // This AST-level type of the parameter
+ Type* type = nullptr;
+
+ // The direction (`in` vs `out` vs `in out`)
+ ParameterDirection direction;
+
+ // The variable/parameter declaration for
+ // this parameter (if any)
+ VarDeclBase* decl = nullptr;
+
+ // Is this the representation of a `this` parameter?
+ bool isThisParam = false;
+};
+//
+// We need a way to be able to create a `IRLoweringParameterInfo` given the declaration
+// of a parameter:
+//
+IRLoweringParameterInfo getParameterInfo(
+ IRGenContext* context,
+ DeclRef<VarDeclBase> const& paramDecl)
+{
+ IRLoweringParameterInfo info;
+ info.type = getType(context->astBuilder, paramDecl);
+ info.decl = paramDecl;
+ info.direction = getParameterDirection(paramDecl);
+ info.isThisParam = false;
+ return info;
+}
+//
+
+// Here's the declaration for the type to hold the lists:
+struct ParameterLists
+{
+ List<IRLoweringParameterInfo> params;
+};
+//
+// Because there might be a `static` declaration somewhere
+// along the lines, we need to be careful to prohibit adding
+// non-generic parameters in some cases.
+enum ParameterListCollectMode
+{
+ // Collect everything: ordinary and generic parameters.
+ kParameterListCollectMode_Default,
+
+
+ // Only collect generic parameters.
+ kParameterListCollectMode_Static,
+};
+//
+// We also need to be able to detect whether a declaration is
+// either explicitly or implicitly treated as `static`:
+ParameterListCollectMode getModeForCollectingParentParameters(
+ Decl* decl,
+ ContainerDecl* parentDecl)
+{
+ // If we have a `static` parameter, then it is obvious
+ // that we should use the `static` mode
+ if(isEffectivelyStatic(decl, parentDecl))
+ return kParameterListCollectMode_Static;
+
+ // Otherwise, let's default to collecting everything
+ return kParameterListCollectMode_Default;
+}
+//
+// When dealing with a member function, we need to be able to add the `this`
+// parameter for the enclosing type:
+//
+void addThisParameter(
+ ParameterDirection direction,
+ Type* type,
+ ParameterLists* ioParameterLists)
+{
+ IRLoweringParameterInfo info;
+ info.type = type;
+ info.decl = nullptr;
+ info.direction = direction;
+ info.isThisParam = true;
+
+ ioParameterLists->params.add(info);
+}
+//
+// And here is our function that will do the recursive walk:
+void collectParameterLists(
+ IRGenContext* context,
+ DeclRef<Decl> const& declRef,
+ ParameterLists* ioParameterLists,
+ ParameterListCollectMode mode,
+ ParameterDirection thisParamDirection)
+{
+ // The parameters introduced by any "parent" declarations
+ // will need to come first, so we'll deal with that
+ // logic here.
+ if( auto parentDeclRef = declRef.getParent() )
+ {
+ // Compute the mode to use when collecting parameters from
+ // the outer declaration. The most important question here
+ // is whether parameters of the outer declaration should
+ // also count as parameters of the inner declaration.
+ ParameterListCollectMode innerMode = getModeForCollectingParentParameters(declRef, parentDeclRef);
+
+ // Don't down-grade our `static`-ness along the chain.
+ if(innerMode < mode)
+ innerMode = mode;
+
+ ParameterDirection innerThisParamDirection = getThisParamDirection(declRef, thisParamDirection);
+
+
+ // Now collect any parameters from the parent declaration itself
+ collectParameterLists(context, parentDeclRef, ioParameterLists, innerMode, innerThisParamDirection);
+
+ // We also need to consider whether the inner declaration needs to have a `this`
+ // parameter corresponding to the outer declaration.
+ if( innerMode != kParameterListCollectMode_Static )
+ {
+ auto thisType = getThisParamTypeForContainer(context, parentDeclRef);
+ if(thisType)
+ {
+ addThisParameter(innerThisParamDirection, thisType, ioParameterLists);
+ }
+ }
+ }
+
+ // Once we've added any parameters based on parent declarations,
+ // we can see if this declaration itself introduces parameters.
+ //
+ if( auto callableDeclRef = declRef.as<CallableDecl>() )
+ {
+ // Don't collect parameters from the outer scope if
+ // we are in a `static` context.
+ if( mode == kParameterListCollectMode_Default )
+ {
+ for( auto paramDeclRef : getParameters(callableDeclRef) )
+ {
+ ioParameterLists->params.add(getParameterInfo(context, paramDeclRef));
+ }
+ }
+ }
+}
+
+bool isConstExprVar(Decl* decl)
+{
+ if( decl->hasModifier<ConstExprModifier>() )
+ {
+ return true;
+ }
+ else if(decl->hasModifier<HLSLStaticModifier>() && decl->hasModifier<ConstModifier>())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+IRType* maybeGetConstExprType(
+ IRBuilder* builder,
+ IRType* type,
+ Decl* decl)
+{
+ if(isConstExprVar(decl))
+ {
+ return builder->getRateQualifiedType(
+ builder->getConstExprRate(),
+ type);
+ }
+
+ return type;
+}
+
+
+struct FuncDeclBaseTypeInfo
+{
+ IRType* type;
+ IRType* resultType;
+ ParameterLists parameterLists;
+ List<IRType*> paramTypes;
+};
+
+void _lowerFuncDeclBaseTypeInfo(
+ IRGenContext* context,
+ DeclRef<FunctionDeclBase> declRef,
+ FuncDeclBaseTypeInfo& outInfo)
+{
+ auto builder = context->irBuilder;
+
+ // Collect the parameter lists we will use for our new function.
+ auto& parameterLists = outInfo.parameterLists;
+ collectParameterLists(
+ context,
+ declRef,
+ &parameterLists, kParameterListCollectMode_Default, kParameterDirection_In);
+
+ auto& paramTypes = outInfo.paramTypes;
+
+ for( auto paramInfo : parameterLists.params )
+ {
+ IRType* irParamType = lowerType(context, paramInfo.type);
+
+ switch( paramInfo.direction )
+ {
+ case kParameterDirection_In:
+ // Simple case of a by-value input parameter.
+ break;
+
+ // If the parameter is declared `out` or `inout`,
+ // then we will represent it with a pointer type in
+ // the IR, but we will use a specialized pointer
+ // type that encodes the parameter direction information.
+ case kParameterDirection_Out:
+ irParamType = builder->getOutType(irParamType);
+ break;
+ case kParameterDirection_InOut:
+ irParamType = builder->getInOutType(irParamType);
+ break;
+ case kParameterDirection_Ref:
+ irParamType = builder->getRefType(irParamType);
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unknown parameter direction");
+ break;
+ }
+
+ // If the parameter was explicitly marked as being a compile-time
+ // constant (`constexpr`), then attach that information to its
+ // IR-level type explicitly.
+ if( paramInfo.decl )
+ {
+ irParamType = maybeGetConstExprType(builder, irParamType, paramInfo.decl);
+ }
+
+ paramTypes.add(irParamType);
+ }
+
+ auto& irResultType = outInfo.resultType;
+ irResultType = lowerType(context, getResultType(context->astBuilder, declRef));
+
+ if (auto setterDeclRef = declRef.as<SetterDecl>())
+ {
+ // A `set` accessor always returns `void`
+ //
+ // TODO: We should handle this by making the result
+ // type of a `set` accessor be represented accurately
+ // at the AST level (ditto for the `ref` case below).
+ //
+ irResultType = builder->getVoidType();
+ }
+
+ if( auto refAccessorDeclRef = declRef.as<RefAccessorDecl>() )
+ {
+ // A `ref` accessor needs to return a *pointer* to the value
+ // being accessed, rather than a simple value.
+ irResultType = builder->getPtrType(irResultType);
+ }
+
+ outInfo.type = builder->getFuncType(
+ paramTypes.getCount(),
+ paramTypes.getBuffer(),
+ irResultType);
+}
+
+static LoweredValInfo _emitCallToAccessor(
+ IRGenContext* context,
+ IRType* type,
+ DeclRef<AccessorDecl> accessorDeclRef,
+ LoweredValInfo base,
+ UInt argCount,
+ IRInst* const* args)
+{
+ FuncDeclBaseTypeInfo info;
+ _lowerFuncDeclBaseTypeInfo(context, accessorDeclRef, info);
+
+ List<IRInst*> allArgs;
+
+ List<OutArgumentFixup> fixups;
+ if(base.flavor != LoweredValInfo::Flavor::None)
+ {
+ SLANG_ASSERT(info.parameterLists.params.getCount() >= 1);
+ SLANG_ASSERT(info.parameterLists.params[0].isThisParam);
+
+ auto thisParam = info.parameterLists.params[0];
+ auto thisParamType = lowerType(context, thisParam.type);
+
+ addArg(context, &allArgs, &fixups, base, thisParamType, thisParam.direction, SourceLoc());
+ }
+
+ allArgs.addRange(args, argCount);
+
+ LoweredValInfo result = emitCallToDeclRef(
+ context,
+ type,
+ accessorDeclRef,
+ info.type,
+ allArgs.getCount(),
+ allArgs.getBuffer());
+
+ applyOutArgumentFixups(context, fixups);
+
+ return result;
+}
//
@@ -2061,7 +2630,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
LoweredValInfo visitMemberExpr(MemberExpr* expr)
{
auto loweredType = lowerType(context, expr->type);
- auto loweredBase = lowerRValueExpr(context, expr->baseExpression);
+ auto loweredBase = lowerSubExpr(expr->baseExpression);
auto declRef = expr->declRef;
if (auto fieldDeclRef = declRef.as<VarDecl>())
@@ -2102,8 +2671,16 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
// for a cast here (that could become a no-op later).
return loweredBase;
}
+ else if(auto propertyDeclRef = declRef.as<PropertyDecl>())
+ {
+ // A reference to a property is a special case, because
+ // we must translate the reference to the property
+ // into a reference to one of its accessors.
+ //
+ return lowerStorageReference(context, loweredType, propertyDeclRef, loweredBase, 0, nullptr);
+ }
- SLANG_UNIMPLEMENTED_X("codegen for subscript expression");
+ SLANG_UNIMPLEMENTED_X("codegen for member expression");
UNREACHABLE_RETURN(LoweredValInfo());
}
@@ -2422,131 +2999,6 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
UNREACHABLE_RETURN(LoweredValInfo());
}
- // After a call to a function with `out` or `in out`
- // parameters, we may need to copy data back into
- // the l-value locations used for output arguments.
- //
- // During lowering of the argument list, we build
- // up a list of these "fixup" assignments that need
- // to be performed.
- struct OutArgumentFixup
- {
- LoweredValInfo dst;
- LoweredValInfo src;
- };
-
- /// Add argument(s) corresponding to one parameter to a call
- ///
- /// The `argExpr` is the AST-level expression being passed as an argument to the call.
- /// The `paramType` and `paramDirection` represent what is known about the receiving
- /// parameter of the callee (e.g., if the parameter `in`, `inout`, etc.).
- /// The `ioArgs` array receives the IR-level argument(s) that are added for the given
- /// argument expression.
- /// The `ioFixups` array receives any "fixup" code that needs to be run *after* the
- /// call completes (e.g., to move from a scratch variable used for an `inout` argument back
- /// into the original location).
- ///
- void addCallArgsForParam(
- IRType* paramType,
- ParameterDirection paramDirection,
- Expr* argExpr,
- List<IRInst*>* ioArgs,
- List<OutArgumentFixup>* ioFixups)
- {
- switch(paramDirection)
- {
- case kParameterDirection_Ref:
- {
- // A `ref` qualified parameter must be implemented with by-reference
- // parameter passing, so the argument value should be lowered as
- // an l-value.
- //
- LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr);
-
- // According to our "calling convention" we need to
- // pass a pointer into the callee. Unlike the case for
- // `out` and `inout` below, it is never valid to do
- // copy-in/copy-out for a `ref` parameter, so we just
- // pass in the actual pointer.
- //
- IRInst* argPtr = getAddress(context, loweredArg, argExpr->loc);
- (*ioArgs).add(argPtr);
- }
- break;
-
- case kParameterDirection_Out:
- case kParameterDirection_InOut:
- {
- // This is a `out` or `inout` parameter, and so
- // the argument must be lowered as an l-value.
-
- LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr);
-
- // According to our "calling convention" we need to
- // pass a pointer into the callee.
- //
- // A naive approach would be to just take the address
- // of `loweredArg` above and pass it in, but that
- // has two issues:
- //
- // 1. The l-value might not be something that has a single
- // well-defined "address" (e.g., `foo.xzy`).
- //
- // 2. The l-value argument might actually alias some other
- // storage that the callee will access (e.g., we are
- // passing in a global variable, or two `out` parameters
- // are being passed the same location in an array).
- //
- // In each of these cases, the safe option is to create
- // a temporary variable to use for argument-passing,
- // and then do copy-in/copy-out around the call.
- //
- // TODO: We should consider ruling out case (2) as undefined
- // behavior, and specify that whether `inout` and `out` are
- // handled via copy-in-copy-out or by-reference parameter
- // passing is an implementation detail. That would allow
- // us to avoid introducing a copy except where it is required
- // for the semantics of (1).
-
- LoweredValInfo tempVar = createVar(context, paramType);
-
- // If the parameter is `in out` or `inout`, then we need
- // to ensure that we pass in the original value stored
- // in the argument, which we accomplish by assigning
- // from the l-value to our temp.
- if(paramDirection == kParameterDirection_InOut)
- {
- assign(context, tempVar, loweredArg);
- }
-
- // Now we can pass the address of the temporary variable
- // to the callee as the actual argument for the `in out`
- SLANG_ASSERT(tempVar.flavor == LoweredValInfo::Flavor::Ptr);
- (*ioArgs).add(tempVar.val);
-
- // Finally, after the call we will need
- // to copy in the other direction: from our
- // temp back to the original l-value.
- OutArgumentFixup fixup;
- fixup.src = tempVar;
- fixup.dst = loweredArg;
-
- (*ioFixups).add(fixup);
-
- }
- break;
-
- default:
- {
- // This is a pure input parameter, and so we will
- // pass it as an r-value.
- LoweredValInfo loweredArg = lowerRValueExpr(context, argExpr);
- addArgs(context, ioArgs, loweredArg);
- }
- break;
- }
- }
-
void addDirectCallArgs(
InvokeExpr* expr,
DeclRef<CallableDecl> funcDeclRef,
@@ -2594,7 +3046,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
// make a conscious decision at some point.
}
- addCallArgsForParam(paramType, paramDirection, argExpr, ioArgs, ioFixups);
+ addCallArgsForParam(context, paramType, paramDirection, argExpr, ioArgs, ioFixups);
}
}
@@ -2627,14 +3079,6 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
}
- void applyOutArgumentFixups(List<OutArgumentFixup> const& fixups)
- {
- for (auto fixup : fixups)
- {
- assign(context, fixup.dst, fixup.src);
- }
- }
-
struct ResolvedCallInfo
{
DeclRef<Decl> funcDeclRef;
@@ -2741,6 +3185,50 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
auto funcDeclRef = resolvedInfo.funcDeclRef;
auto baseExpr = resolvedInfo.baseExpr;
+ // If the thing being invoked is a subscript operation,
+ // then we need to handle multiple extra details
+ // that don't arise for other kinds of calls.
+ //
+ // TODO: subscript operations probably deserve to
+ // be handled on their own path for this reason...
+ //
+ if (auto subscriptDeclRef = funcDeclRef.template as<SubscriptDecl>())
+ {
+ // A reference to a subscript declaration is a special case,
+ // because it is not possible to call a subscript directly;
+ // we must call one of its accessors.
+ //
+ auto loweredBase = lowerSubExpr(baseExpr);
+ addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
+ auto result = lowerStorageReference(context, type, subscriptDeclRef, loweredBase, irArgs.getCount(), irArgs.getBuffer());
+
+ // TODO: Applying the fixups for arguments to the subscript at this point
+ // won't technically be correct, since the call to the subscript may
+ // not have occured at this point.
+ //
+ // It seems like we need to either:
+ //
+ // * Capture the arguments to the subscript as `LoweredValInfo` instead of `IRInst*`
+ // so that we can deal with everything related to fixups around the actual call
+ // site.
+ //
+ // OR
+ //
+ // * Handle everything to do with "fixups" differently, by treating them as deferred
+ // actions that gert queued up on the context itself and then flushed at certain
+ // well-defined points, so that we don't have to be as careful around them.
+ //
+ // OR
+ //
+ // * Switch to a more "destination-driven" approach to code generation, where we
+ // can determine on entry to the lowering of a sub-expression whether it will be
+ // used for read, write, or read/write, and resolve things like the choice of
+ // accessor at that point instead.
+ //
+ applyOutArgumentFixups(context, argFixups);
+ return result;
+ }
+
// First comes the `this` argument if we are calling
// a member function:
if( baseExpr )
@@ -2748,8 +3236,9 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
auto thisType = getThisParamTypeForCallable(context, funcDeclRef);
auto irThisType = lowerType(context, thisType);
addCallArgsForParam(
+ context,
irThisType,
- getThisParamDirection(funcDeclRef.getDecl()),
+ getThisParamDirection(funcDeclRef.getDecl(), kParameterDirection_In),
baseExpr,
&irArgs,
&argFixups);
@@ -2767,7 +3256,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
funcDeclRef,
funcType,
irArgs);
- applyOutArgumentFixups(argFixups);
+ applyOutArgumentFixups(context, argFixups);
return result;
}
@@ -4076,13 +4565,13 @@ LoweredValInfo tryGetAddress(
// the address of our value. Easy!
return val;
- case LoweredValInfo::Flavor::BoundSubscript:
+ case LoweredValInfo::Flavor::BoundStorage:
{
// If we are are trying to turn a subscript operation like `buffer[index]`
// into a pointer, then we need to find a `ref` accessor declared
// as part of the subscript operation being referenced.
//
- auto subscriptInfo = val.getBoundSubscriptInfo();
+ auto subscriptInfo = val.getBoundStorageInfo();
// We don't want to immediately bind to a `ref` accessor if there is
// a `set` accessor available, unless we are in an "aggressive" mode
@@ -4099,16 +4588,18 @@ LoweredValInfo tryGetAddress(
auto refAccessors = getMembersOfType<RefAccessorDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
if(refAccessors.isNonEmpty())
{
+ auto refAccessor = *refAccessors.begin();
+
// The `ref` accessor will return a pointer to the value, so
// we need to reflect that in the type of our `call` instruction.
IRType* ptrType = context->irBuilder->getPtrType(subscriptInfo->type);
- LoweredValInfo refVal = emitCallToDeclRef(
+ LoweredValInfo refVal = _emitCallToAccessor(
context,
ptrType,
- *refAccessors.begin(),
- nullptr,
- subscriptInfo->args);
+ refAccessor,
+ subscriptInfo->base,
+ subscriptInfo->additionalArgs);
// The result from the call should be a pointer, and it
// is the address that we wanted in the first place.
@@ -4129,7 +4620,7 @@ LoweredValInfo tryGetAddress(
// to a single field in something, but for whatever reason the
// higher-level logic was not able to turn it into a pointer
// already (maybe the base value for the field reference is
- // a `BoundSubscript`, etc.).
+ // a `BoundStorage`, etc.).
//
// We need to read the entire base value out, modify the field
// we care about, and then write it back.
@@ -4307,7 +4798,7 @@ top:
}
break;
- case LoweredValInfo::Flavor::BoundSubscript:
+ case LoweredValInfo::Flavor::BoundStorage:
{
// The `left` value refers to a subscript operation on
// a resource type, bound to particular arguments, e.g.:
@@ -4318,20 +4809,31 @@ top:
// is one, and then fall back to a `ref` accessor if
// there is no setter.
//
- auto subscriptInfo = left.getBoundSubscriptInfo();
+ auto subscriptInfo = left.getBoundStorageInfo();
// Search for an appropriate "setter" declaration
auto setters = getMembersOfType<SetterDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
if (setters.isNonEmpty())
{
- auto allArgs = subscriptInfo->args;
- addArgs(context, &allArgs, right);
+ auto setter = *setters.begin();
+
+ auto allArgs = subscriptInfo->additionalArgs;
- emitCallToDeclRef(
+ // Note: here we are assuming that all setters take
+ // the new-value parameter as an `in` rather than
+ // as any kind of reference.
+ //
+ // TODO: If we add support for something like `const&`
+ // for input parameters, we might have to deal with
+ // that here.
+ //
+ addInArg(context, &allArgs, right);
+
+ _emitCallToAccessor(
context,
builder->getVoidType(),
- *setters.begin(),
- nullptr,
+ setter,
+ subscriptInfo->base,
allArgs);
return;
}
@@ -4339,16 +4841,18 @@ top:
auto refAccessors = getMembersOfType<RefAccessorDecl>(subscriptInfo->declRef, MemberFilterStyle::Instance);
if(refAccessors.isNonEmpty())
{
+ auto refAccessor = *refAccessors.begin();
+
// The `ref` accessor will return a pointer to the value, so
// we need to reflect that in the type of our `call` instruction.
IRType* ptrType = context->irBuilder->getPtrType(subscriptInfo->type);
- LoweredValInfo refVal = emitCallToDeclRef(
+ LoweredValInfo refVal = _emitCallToAccessor(
context,
ptrType,
- *refAccessors.begin(),
- nullptr,
- subscriptInfo->args);
+ refAccessor,
+ subscriptInfo->base,
+ subscriptInfo->additionalArgs);
// The result from the call needs to be implicitly dereferenced,
// so that it can work as an l-value of the desired result type.
@@ -4736,7 +5240,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo();
}
- LoweredValInfo visitSubscriptDecl(SubscriptDecl* decl)
+ LoweredValInfo visitStorageDeclCommon(ContainerDecl* decl)
{
// A subscript operation may encompass one or more
// accessors, and these are what should actually
@@ -4760,6 +5264,16 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo();
}
+ LoweredValInfo visitSubscriptDecl(SubscriptDecl* decl)
+ {
+ return visitStorageDeclCommon(decl);
+ }
+
+ LoweredValInfo visitPropertyDecl(PropertyDecl* decl)
+ {
+ return visitStorageDeclCommon(decl);
+ }
+
bool isGlobalVarDecl(VarDecl* decl)
{
auto parent = decl->parentDecl;
@@ -5588,208 +6102,15 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
- // When lowering something callable (most commonly a function declaration),
- // we need to construct an appropriate parameter list for the IR function
- // that folds in any contributions from both the declaration itself *and*
- // its parent declaration(s).
- //
- // For example, given code like:
- //
- // struct Foo { int bar(float y) { ... } };
- //
- // we need to generate IR-level code something like:
- //
- // func Foo_bar(Foo this, float y) -> int;
- //
- // that is, the `this` parameter has become explicit.
- //
- // The same applies to generic parameters, and these
- // should apply even if the nested declaration is `static`:
- //
- // struct Foo<T> { static int bar(T y) { ... } };
- //
- // becomes:
- //
- // func Foo_bar<T>(T y) -> int;
- //
- // In order to implement this, we are going to do a recursive
- // walk over a declaration and its parents, collecting separate
- // lists of ordinary and generic parameters that will need
- // to be included in the final declaration's parameter list.
- //
- // When doing code generation for an ordinary value parameter,
- // we mostly care about its type, and then also its "direction"
- // (`in`, `out`, `in out`). We sometimes need acess to the
- // original declaration so that we can inspect it for meta-data,
- // but in some cases there is no such declaration (e.g., a `this`
- // parameter doesn't get an explicit declaration in the AST).
- // To handle this we break out the relevant data into derived
- // structures:
- //
- struct ParameterInfo
- {
- // This AST-level type of the parameter
- Type* type = nullptr;
-
- // The direction (`in` vs `out` vs `in out`)
- ParameterDirection direction;
-
- // The variable/parameter declaration for
- // this parameter (if any)
- VarDeclBase* decl = nullptr;
-
- // Is this the representation of a `this` parameter?
- bool isThisParam = false;
- };
- //
- // We need a way to be able to create a `ParameterInfo` given the declaration
- // of a parameter:
- //
- ParameterInfo getParameterInfo(VarDeclBase* paramDecl)
- {
- ParameterInfo info;
- info.type = paramDecl->getType();
- info.decl = paramDecl;
- info.direction = getParameterDirection(paramDecl);
- info.isThisParam = false;
- return info;
- }
- //
-
- // Here's the declaration for the type to hold the lists:
- struct ParameterLists
- {
- List<ParameterInfo> params;
- };
- //
- // Because there might be a `static` declaration somewhere
- // along the lines, we need to be careful to prohibit adding
- // non-generic parameters in some cases.
- enum ParameterListCollectMode
- {
- // Collect everything: ordinary and generic parameters.
- kParameterListCollectMode_Default,
-
-
- // Only collect generic parameters.
- kParameterListCollectMode_Static,
- };
- //
- // We also need to be able to detect whether a declaration is
- // either explicitly or implicitly treated as `static`:
- ParameterListCollectMode getModeForCollectingParentParameters(
- Decl* decl,
- ContainerDecl* parentDecl)
- {
- // If we have a `static` parameter, then it is obvious
- // that we should use the `static` mode
- if(isEffectivelyStatic(decl, parentDecl))
- return kParameterListCollectMode_Static;
-
- // Otherwise, let's default to collecting everything
- return kParameterListCollectMode_Default;
- }
- //
- // When dealing with a member function, we need to be able to add the `this`
- // parameter for the enclosing type:
- //
- void addThisParameter(
- ParameterDirection direction,
- Type* type,
- ParameterLists* ioParameterLists)
- {
- ParameterInfo info;
- info.type = type;
- info.decl = nullptr;
- info.direction = direction;
- info.isThisParam = true;
-
- ioParameterLists->params.add(info);
- }
- //
- // And here is our function that will do the recursive walk:
- void collectParameterLists(
- Decl* decl,
- ParameterLists* ioParameterLists,
- ParameterListCollectMode mode)
- {
- // The parameters introduced by any "parent" declarations
- // will need to come first, so we'll deal with that
- // logic here.
- if( auto parentDecl = decl->parentDecl )
- {
- // Compute the mode to use when collecting parameters from
- // the outer declaration. The most important question here
- // is whether parameters of the outer declaration should
- // also count as parameters of the inner declaration.
- ParameterListCollectMode innerMode = getModeForCollectingParentParameters(decl, parentDecl);
-
- // Don't down-grade our `static`-ness along the chain.
- if(innerMode < mode)
- innerMode = mode;
-
- // Now collect any parameters from the parent declaration itself
- collectParameterLists(parentDecl, ioParameterLists, innerMode);
-
- // We also need to consider whether the inner declaration needs to have a `this`
- // parameter corresponding to the outer declaration.
- if( innerMode != kParameterListCollectMode_Static )
- {
- ParameterDirection direction = getThisParamDirection(decl);
- auto thisType = getThisParamTypeForContainer(context, createDefaultSpecializedDeclRef(context, parentDecl));
- if(thisType)
- {
- addThisParameter(direction, thisType, ioParameterLists);
- }
- }
- }
-
- // Once we've added any parameters based on parent declarations,
- // we can see if this declaration itself introduces parameters.
- //
- if( auto callableDecl = as<CallableDecl>(decl) )
- {
- // Don't collect parameters from the outer scope if
- // we are in a `static` context.
- if( mode == kParameterListCollectMode_Default )
- {
- for( auto paramDecl : callableDecl->getParameters() )
- {
- ioParameterLists->params.add(getParameterInfo(paramDecl));
- }
- }
- }
- }
bool isImportedDecl(Decl* decl)
{
return Slang::isImportedDecl(context, decl);
}
- bool isConstExprVar(Decl* decl)
- {
- if( decl->hasModifier<ConstExprModifier>() )
- {
- return true;
- }
- else if(decl->hasModifier<HLSLStaticModifier>() && decl->hasModifier<ConstModifier>())
- {
- return true;
- }
-
- return false;
- }
-
IRType* maybeGetConstExprType(IRType* type, Decl* decl)
{
- if(isConstExprVar(decl))
- {
- return getBuilder()->getRateQualifiedType(
- getBuilder()->getConstExprRate(),
- type);
- }
-
- return type;
+ return Slang::maybeGetConstExprType(getBuilder(), type, decl);
}
IRGeneric* emitOuterGeneric(
@@ -6054,7 +6375,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
getBuilder()->addTargetIntrinsicDecoration(irInst, UnownedStringSlice(), definition.getUnownedSlice());
}
- void addParamNameHint(IRInst* inst, ParameterInfo info)
+ void addParamNameHint(IRInst* inst, IRLoweringParameterInfo const& info)
{
if(auto decl = info.decl)
{
@@ -6087,124 +6408,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return as<IRStringLit>(builder->getStringValue(stringLitExpr->value.getUnownedSlice()));
}
- void _lowerFuncResultAndParameterTypes(
- ParameterLists& parameterLists,
- List<IRType*>& paramTypes,
- IRType*& irResultType,
- IRBuilder* subBuilder,
- IRGenContext* subContext,
- FunctionDeclBase* decl)
- {
- // Collect the parameter lists we will use for our new function.
- collectParameterLists(decl, &parameterLists, kParameterListCollectMode_Default);
-
- // In most cases the return type for a declaration can be read off the declaration
- // itself, but things get a bit more complicated when we have to deal with
- // accessors for subscript declarations (and eventually for properties).
- //
- // We compute a declaration to use for looking up the return type here:
- CallableDecl* declForReturnType = decl;
- if (auto accessorDecl = as<AccessorDecl>(decl))
- {
- // We are some kind of accessor, so the parent declaration should
- // know the correct return type to expose.
- //
- auto parentDecl = accessorDecl->parentDecl;
- if (auto subscriptDecl = as<SubscriptDecl>(parentDecl))
- {
- declForReturnType = subscriptDecl;
- }
- }
-
- for( auto paramInfo : parameterLists.params )
- {
- IRType* irParamType = lowerType(subContext, paramInfo.type);
-
- switch( paramInfo.direction )
- {
- case kParameterDirection_In:
- // Simple case of a by-value input parameter.
- break;
-
- // If the parameter is declared `out` or `inout`,
- // then we will represent it with a pointer type in
- // the IR, but we will use a specialized pointer
- // type that encodes the parameter direction information.
- case kParameterDirection_Out:
- irParamType = subBuilder->getOutType(irParamType);
- break;
- case kParameterDirection_InOut:
- irParamType = subBuilder->getInOutType(irParamType);
- break;
- case kParameterDirection_Ref:
- irParamType = subBuilder->getRefType(irParamType);
- break;
-
- default:
- SLANG_UNEXPECTED("unknown parameter direction");
- break;
- }
-
- // If the parameter was explicitly marked as being a compile-time
- // constant (`constexpr`), then attach that information to its
- // IR-level type explicitly.
- if( paramInfo.decl )
- {
- irParamType = maybeGetConstExprType(irParamType, paramInfo.decl);
- }
-
- paramTypes.add(irParamType);
- }
-
- irResultType = lowerType(subContext, declForReturnType->returnType);
-
- if (auto setterDecl = as<SetterDecl>(decl))
- {
- // We are lowering a "setter" accessor inside a subscript
- // declaration, which means we don't want to *return* the
- // stated return type of the subscript, but instead take
- // it as a parameter.
- //
- IRType* irParamType = irResultType;
- paramTypes.add(irParamType);
-
- // Instead, a setter always returns `void`
- //
- irResultType = subBuilder->getVoidType();
- }
-
- if( auto refAccessorDecl = as<RefAccessorDecl>(decl) )
- {
- // A `ref` accessor needs to return a *pointer* to the value
- // being accessed, rather than a simple value.
- irResultType = subBuilder->getPtrType(irResultType);
- }
- }
-
- IRFuncType* _lowerFuncTypeImpl(
- ParameterLists& parameterLists,
- List<IRType*>& paramTypes,
- IRType*& irResultType,
- IRBuilder* builder,
- IRGenContext* irGenContext,
- FunctionDeclBase* decl)
- {
- _lowerFuncResultAndParameterTypes(
- parameterLists,
- paramTypes,
- irResultType,
- builder,
- irGenContext,
- decl);
-
- auto irFuncType = builder->getFuncType(
- paramTypes.getCount(),
- paramTypes.getBuffer(),
- irResultType);
-
- return irFuncType;
- }
-
IRInst* lowerFuncType(FunctionDeclBase* decl)
{
NestedContext nestedContextFuncType(this);
@@ -6213,16 +6416,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
auto outerGenerics = emitOuterGenerics(funcTypeContext, decl, decl);
- ParameterLists parameterLists;
- List<IRType*> paramTypes;
- IRType* irResultType = nullptr;
- auto irFuncType = _lowerFuncTypeImpl(
- parameterLists,
- paramTypes,
- irResultType,
- funcTypeBuilder,
+ FuncDeclBaseTypeInfo info;
+ _lowerFuncDeclBaseTypeInfo(
funcTypeContext,
- decl);
+ createDefaultSpecializedDeclRef(funcTypeContext, decl),
+ info);
+
+ auto irFuncType = info.type;
return finishOuterGenerics(funcTypeBuilder, irFuncType, outerGenerics);
}
@@ -6244,16 +6444,17 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
addNameHint(context, irFunc, decl);
addLinkageDecoration(context, irFunc, decl);
- ParameterLists parameterLists;
- List<IRType*> paramTypes;
- IRType* irResultType = nullptr;
- auto irFuncType = _lowerFuncTypeImpl(
- parameterLists,
- paramTypes,
- irResultType,
- subBuilder,
+ FuncDeclBaseTypeInfo info;
+ _lowerFuncDeclBaseTypeInfo(
subContext,
- decl);
+ createDefaultSpecializedDeclRef(context, decl),
+ info);
+
+ auto irFuncType = info.type;
+ auto& irResultType = info.resultType;
+ auto& parameterLists = info.parameterLists;
+ auto& paramTypes = info.paramTypes;
+
irFunc->setFullType(irFuncType);
subBuilder->setInsertInto(irFunc);
@@ -6396,18 +6597,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
}
}
- if (auto setterDecl = as<SetterDecl>(decl))
- {
- // Add the IR parameter for the new value
- IRType* irParamType = irResultType;
- auto irParam = subBuilder->emitParam(irParamType);
- addNameHint(context, irParam, "newValue");
-
- // TODO: we need some way to wire this up to the `newValue`
- // or whatever name we give for that parameter inside
- // the setter body.
- }
-
{
auto attr = decl->findModifier<PatchConstantFuncAttribute>();
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index c939e0a38..ed20e0d2e 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -208,6 +208,10 @@ namespace Slang
Parser* parser,
ContainerDecl* containerDecl);
+ static void parseModernParamList(
+ Parser* parser,
+ CallableDecl* decl);
+
//
static void Unexpected(
@@ -1208,7 +1212,7 @@ namespace Slang
}
static void parseParameterList(
- Parser* parser,
+ Parser* parser,
CallableDecl* decl)
{
parser->ReadToken(TokenType::LParent);
@@ -2645,6 +2649,7 @@ namespace Slang
Modifiers modifiers = ParseModifiers(parser);
AccessorDecl* decl = nullptr;
+ auto loc = peekToken(parser).loc;
if( AdvanceIf(parser, "get") )
{
decl = parser->astBuilder->create<GetterDecl>();
@@ -2662,9 +2667,24 @@ namespace Slang
Unexpected(parser);
return nullptr;
}
+ decl->loc = loc;
AddModifiers(decl, modifiers.first);
+ parser->PushScope(decl);
+
+ // A `set` declaration should support declaring an explicit
+ // name for the parameter representing the new value.
+ //
+ // We handle this by supporting an arbitrary parameter list
+ // on any accessor, and then assume that semantic checking
+ // will diagnose any cases that aren't allowed.
+ //
+ if(parser->tokenReader.peekTokenType() == TokenType::LParent)
+ {
+ parseModernParamList(parser, decl);
+ }
+
if( parser->tokenReader.peekTokenType() == TokenType::LBrace )
{
decl->body = parser->parseBlockStatement();
@@ -2674,25 +2694,14 @@ namespace Slang
parser->ReadToken(TokenType::Semicolon);
}
+ parser->PopScope();
+
+
return decl;
}
- static NodeBase* ParseSubscriptDecl(Parser* parser, void* /*userData*/)
+ static void parseStorageDeclBody(Parser* parser, ContainerDecl* decl)
{
- SubscriptDecl* decl = parser->astBuilder->create<SubscriptDecl>();
- parser->FillPosition(decl);
- parser->PushScope(decl);
-
- // TODO: the use of this name here is a bit magical...
- decl->nameAndLoc.name = getName(parser, "operator[]");
-
- parseParameterList(parser, decl);
-
- if( AdvanceIf(parser, TokenType::RightArrow) )
- {
- decl->returnType = parser->ParseTypeExp();
- }
-
if( AdvanceIf(parser, TokenType::LBrace) )
{
// We want to parse nested "accessor" declarations
@@ -2708,6 +2717,25 @@ namespace Slang
// empty body should be treated like `{ get; }`
}
+ }
+
+ static NodeBase* ParseSubscriptDecl(Parser* parser, void* /*userData*/)
+ {
+ SubscriptDecl* decl = parser->astBuilder->create<SubscriptDecl>();
+ parser->FillPosition(decl);
+ parser->PushScope(decl);
+
+ // TODO: the use of this name here is a bit magical...
+ decl->nameAndLoc.name = getName(parser, "operator[]");
+
+ parseParameterList(parser, decl);
+
+ if( AdvanceIf(parser, TokenType::RightArrow) )
+ {
+ decl->returnType = parser->ParseTypeExp();
+ }
+
+ parseStorageDeclBody(parser, decl);
parser->PopScope();
return decl;
@@ -2718,6 +2746,25 @@ namespace Slang
return parser->ReadToken(tokenType).type == tokenType;
}
+ static NodeBase* ParsePropertyDecl(Parser* parser, void* /*userData*/)
+ {
+ PropertyDecl* decl = parser->astBuilder->create<PropertyDecl>();
+ parser->FillPosition(decl);
+ parser->PushScope(decl);
+
+ decl->nameAndLoc = expectIdentifier(parser);
+
+ if( expect(parser, TokenType::Colon) )
+ {
+ decl->type = parser->ParseTypeExp();
+ }
+
+ parseStorageDeclBody(parser, decl);
+
+ parser->PopScope();
+ return decl;
+ }
+
static void parseModernVarDeclBaseCommon(
Parser* parser,
VarDeclBase* decl)
@@ -5254,6 +5301,7 @@ namespace Slang
DECL(extension, ParseExtensionDecl);
DECL(__init, parseConstructorDecl);
DECL(__subscript, ParseSubscriptDecl);
+ DECL(property, ParsePropertyDecl);
DECL(interface, parseInterfaceDecl);
DECL(syntax, parseSyntaxDecl);
DECL(attribute_syntax,parseAttributeSyntaxDecl);
diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h
index 37901e447..81c191ccf 100644
--- a/source/slang/slang-syntax.h
+++ b/source/slang/slang-syntax.h
@@ -105,6 +105,11 @@ namespace Slang
return declRef.substitute(astBuilder, declRef.getDecl()->initExpr);
}
+ inline Type* getType(ASTBuilder* astBuilder, DeclRef<PropertyDecl> const& declRef)
+ {
+ return declRef.substitute(astBuilder, declRef.getDecl()->type.Ptr());
+ }
+
inline Type* getType(ASTBuilder* astBuilder, DeclRef<EnumCaseDecl> const& declRef)
{
return declRef.substitute(astBuilder, declRef.getDecl()->type.Ptr());