summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-06-30 10:01:09 -0700
committerGitHub <noreply@github.com>2020-06-30 10:01:09 -0700
commitdc44b08ec377106a0c6d1c022e2754d9e11c579f (patch)
tree83c452e6610bf03675d2a1e9160c36b52bb9c620 /source
parent47b43f8b15ef35c520b9b287fd17ff25e36bfe95 (diff)
Initial work on property declarations (#1410)
* Initial work on property declarations Introduction ============ The main feature added here is support for `property` declarations, which provide a nicer experience for working with getter/setter pairs. If existing code had something like this: ```hlsl struct Sphere { float4 centerAndRadius; // xyz: center, w: radius float3 getCenter() { return centerAndRadius.xyz; } void setCenter(float3 newValue) { centerAndRadius.xyz = newValue; } // similarly for radius... } void someFunc(in out Sphere s) { float3 c = s.getCenter(); s.setCenter(c + offset); } ``` It can be expressed instead using a `property` declaration for `center`: ```hlsl struct Sphere { float4 centerAndRadius; // xyz: center, w: radius property center : float3 { get { return centerAndRadius.xyz; } set(newValue) { centerAndRadius.xyz = newValue; } } // similarly for radius... } void someFunc(in out Sphere s) { float3 c = s.center; s.center = c + offset; } ``` The benefits at the declaration site aren't that signficiant (e.g., in the example above we actually have slightly more lines of code), but the improvement in code clarity for users is significant. Having `property` declarations should also make it easier to migrate from a simple field to a property with more complex logic without having to first abstract the use-site code using a getter and setter. An important future benefit of `property` syntax will be if we allow `interface`s to include `property` requirements, and then also allow those requirements to be satisfied by ordinary fields in concrete types. Subscripts ---------- The Slang compiler already has limited (stdlib-use-only) support for `__subscript` declarations, which are conceptually similar to `operator[]` from the C++ world, but are expressed in a way that is more in line with `subscript` declarations in Swift. A `SubscriptDecl` in the AST contains zero or more `AccessorDecl`s, which correspond to the `get` and `set` clauses inside the original declaration (there is also a case for a `__ref` accessor, to handle the case where access needs to return a single address/reference that can be atomically mutated). A major goal of the implementation here is to re-use as much of the infrastructure as possible for `__subscript` declarations when implementing `property` declarations. Nonmutating Setters ------------------- One additional thing added in this change is the ability to mark a `set` accessor on either a subscript or a property as `[nonmutating]`, and indeed all of the existing `set` accessors declared in the stdlib have been marked this way. The need for this modifier is a bit subtle. If we think about a typical subscript or property: ```hlsl struct MyThing { int f; property p : int { get { return f; } set(newValue) { f = newValue; } } } ``` it is clear we want the `set` accessor to translate to output HLSL as something like: ``` void MyThing_p_set(inout MyThing this, int newValue) { this.f = newValue; } ``` Note how the implicit `this` parameter is `inout` even though we didn't mark anything as `[mutating]`. This is the obvious thing a user would expect us to generate given a property declaration. Now consider a case like the following: ```hlsl struct MyThing { RWStructuredBuffer<int> storage; property p : int { get { return storage[0]; } set(newValue) { storage[0] = newValue; } } } ``` This new declaration doesn't require (or want) an `inout` `this` parameter at all: ``` void MyThing_p_set(MyThing this, int newValue) { this.storage[0] = newValue; } ``` In fact, given the limitations in the current Slang compiler around functions that return resource types (or use them for `inout` parameters), we can only support a `set` operation like this if we can ensure that the `this` parameter is considered to be `in` instead of `inout`. This is exactly the behavior we allow users to opt into with a `[nonmutating] set` declaration. All of the subscript operations in the stdlib today have `set` accessors that don't actually change the value of `this` that they act on (e.g., storing into a `RWStructuredBuffer` using its `operator[]` doesn't change the value of the `RWStructuredBuffer` variable -- just its contents). We'd gotten away without this detail so far just because `set` accessors were only being declared in the stdlib and they were all implicitly `[nonmutating]` anyway, so it never surfaced as an issue that the code we generated assumed a setter wouldn't change `this`. Implementation ============== Parser and AST -------------- Adding a new AST node for `PropertyDecl` and the relevant parsing logic was mostly straightforward. The biggest change was allowing a `set` declaration to introduce an explicit name for the parameter that represents the new value to be set. This change also adds a `[nonmutating]` attribute as a dual to `[mutating]`, for reasons I will get to later. Semantic Checking ----------------- The `getTypeForDeclRef` logic was updated to allow references to `property` declarations. Some of the semantic checking work for subscripts was pulled out into re-usable subroutines to allow it to be shared by `__subscript` and `property` declarations. The checking of accessor declarations, which sets their result type based on the type of the outer `__subscript` was changed to also handle an outer `property`. Some special-case logic was added for checking of `set` declarations to make sure that their parameter is given the expected type. Some logic around deciding whether or not `this` is mutable had to be updated to correctly note that `this` should be mutable by default in a `set` accessor, with an explicit `[nonmutating]` modifier required to opt out of this default. (This is the inverse of how a typical method or `get` accessor works). IR Lowering ----------- The good news is that after IR lowering, access to properties turns into ordinary function calls (equivalent to what hand-written getters and setters would produce), so that subsequent compiler steps (including all the target-specific emit logic) doesn't have to care about the new feature. The bad news is that adding `property` declarations has revealed a few holes in how IR lowering was handling `__subscript` declarations and their accessors, so that it didn't trivially work for the new case as-is. The IR lowering pass already has the `LoweredValInfo` type that abstractly represents a value that resulted from lowering some AST code to the IR. One of the cases of `LoweredValInfo` was `BoundSubscript` that represented an expression of the form `baseVal[someIndex]` where the AST-level expression referenced a `__subscript` declaration. The key feature of `BoundSubscript` is that it avoided deciding whether to invoke the getter, the setter, or both "too early" and instead tried to only invoke the expected/required operations on-demand. This change generalizes `BoundSubscript` to handle `property` references as well, so it changes to `BoundStorage`. Making the type handle user-defined property declarations required fixing a bunch of issues: * When building up argument lists in the IR, we need to know whether an argument corresponds to an `in` or an `out`/`inout` parameter, to decide whether to pass the value directly or a pointer to the value. Some of the logic in the lowering pass had been playing fast and loose with this, so this change tries to make sure that whenever we care computing a list of `IRInst*` that represent the arguments to a call we have the information about the corresponding parameter. * Similarly, when emitting a call to an accessor in the IR, the information about the expected type of the callee was missing/unavailable, and the code was incorrectly building up the expected type of the callee based on the types of the arguments at the call site. The logic has been changed so that we can extract the expected signature of an accessor (how it will be translated to the IR) using the same logic that is used to produce the actual `IRFunc` for the accessor (so hopefully both will always agree). * Dealing with `in` vs. `inout` differences around parameters means also dealing with the "fixup" code that is used to assign from the temporary used to pass an `inout` argument back into the actual l-value expression that was used. That logic has all been hoisted out of the expression visitor(s) and into the global scope. Future Work =========== The entire approach to handling l-values in the IR lowering pass is broken, and it is in need a of a complete rewrite based on new first-principles design goals. While something like `LoweredValInfo` is decent for abstracting over the easy cases of r-values, addresses, and a few complicated l-value cases like swizzling, it just doesn't scale to highly abstract l-values like we get from `__subcript` and `property` declarations, nor other corner cases of l-values that we need to handle (e.g., passing an `int` to an `inout float` parameter is allowed in HLSL, and performs conversions in both directions!). It Should be Easy (TM) to extend the logic that tries to synthesize an interface conformance witness method when there isn't an exact match to also support synthesizing a property declaration (plus its accessors) to witness a required property when the type has a field of the same name/type. * fixup: pedantic template parsing error (thanks, clang!) * fixup: cleanups and review feedback * Removed some `#ifdef`'d out code from merge change * Added proper diagnostics for accessor parameter constraints, which led to some fixes/refactorings * Added a test case for the accessor-related diagnostics
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());