diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/core.meta.slang | 5 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang | 2 | ||||
| -rw-r--r-- | source/slang/slang-ast-decl.h | 8 | ||||
| -rw-r--r-- | source/slang/slang-ast-modifier.h | 9 | ||||
| -rw-r--r-- | source/slang/slang-ast-support-types.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 210 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 13 | ||||
| -rw-r--r-- | source/slang/slang-lookup.cpp | 12 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 1481 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 80 | ||||
| -rw-r--r-- | source/slang/slang-syntax.h | 5 |
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, + ¶meterLists, 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, ¶meterLists, 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()); |
