diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2017-10-12 13:53:42 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-10-12 13:53:42 -0700 |
| commit | 575230b93370fea86ecccb53fba73927280e917b (patch) | |
| tree | 3f4a3402d435418663edd4a963ed3245b81a4526 /source | |
| parent | 9a231a5efb0ddce635e7e40c2d5b086ff4bd389a (diff) | |
Work towards target-specific function overloads (#210)
* Checkpoint: interface conformance work
- Add explicit definition of `saturate` for the GLSL target, which calls through to `clamp`
- Needed to add explicit initializer to `__BuiltinFloatingPointType` to allow initialization from a single `float`, so that the `saturate` implementation can be sure that it can initialize a `T` from `0.0` or `1.0`.
- This triggered errors in overload resolution, because the logic in place could not figure out that the `T` of the outer generic (`saturate<T>()`) conformed to the interface required by the callee.
At this point I have the call to the scalar `clamp()` getting past type-checking, but not the vector or matrix cases.
* More fixups for overload resolution inside generics
- Make sure value parameters are treated the same as type parameters: we only want to solve for the parameters of the generic actually being applied, and not accidentally generate constraints for outer generics (e.g., when checking the body of a generic function).
- Make sure that the diagnostics stuff uses the correct source manager when expanding the location of a builtin.
* Fixes for function redeclaration
- Handle case of redeclaring a generic function
- Enumerate siblings in the parent of the *generic* not the parent of the *function*
- Add logic to compare generic signatures
- When generic signatures match, specialize functions to compatible generic arguments before comparing the function signatures
- Fix redeclaration logic to *not* detect prefix/postifx operators as redeclarations of one another
- Build an explicit representation of function redeclaration groups
- First declaration is the "primary" and others are stored in a linked list
- Make overload resolution handle redeclared functions
- Only consider the primary declaration and skip others
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 570 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 10 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 10 | ||||
| -rw-r--r-- | source/slang/decl-defs.h | 16 | ||||
| -rw-r--r-- | source/slang/diagnostics.cpp | 2 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang | 31 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang.h | 31 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 7 | ||||
| -rw-r--r-- | source/slang/modifier-defs.h | 9 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 12 |
10 files changed, 631 insertions, 67 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index b35dd2662..e8fd16b94 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -872,7 +872,7 @@ namespace Slang overloadContext.baseExpr = nullptr; overloadContext.mode = OverloadResolveContext::Mode::JustTrying; - AddTypeOverloadCandidates(toType, overloadContext); + AddTypeOverloadCandidates(toType, overloadContext, toType); if(overloadContext.bestCandidates.Count() != 0) { @@ -1491,18 +1491,133 @@ namespace Slang } } + void getGenericParams( + GenericDecl* decl, + List<Decl*>& outParams, + List<GenericTypeConstraintDecl*> outConstraints) + { + for (auto dd : decl->Members) + { + if (dd == decl->inner) + continue; + + if (auto typeParamDecl = dd.As<GenericTypeParamDecl>()) + outParams.Add(typeParamDecl); + else if (auto valueParamDecl = dd.As<GenericValueParamDecl>()) + outParams.Add(valueParamDecl); + else if (auto constraintDecl = dd.As<GenericTypeConstraintDecl>()) + outConstraints.Add(constraintDecl); + } + } + + bool doGenericSignaturesMatch( + GenericDecl* fst, + GenericDecl* snd) + { + // First we'll extract the parameters and constraints + // in each generic signature. We will consider parameters + // and constraints separately so that we are independent + // of the order in which constraints are given (that is, + // a constraint like `<T : IFoo>` whould be considered + // the same as `<T>` with a later `where T : IFoo`. + + List<Decl*> fstParams; + List<GenericTypeConstraintDecl*> fstConstraints; + getGenericParams(fst, fstParams, fstConstraints); + + List<Decl*> sndParams; + List<GenericTypeConstraintDecl*> sndConstraints; + getGenericParams(snd, sndParams, sndConstraints); + + // For there to be any hope of a match, the + // two need to have the same number of parameters. + UInt paramCount = fstParams.Count(); + if (paramCount != sndParams.Count()) + return false; + + // Now we'll walk through the parameters. + for (UInt pp = 0; pp < paramCount; ++pp) + { + Decl* fstParam = fstParams[pp]; + Decl* sndParam = sndParams[pp]; + + if (auto fstTypeParam = dynamic_cast<GenericTypeParamDecl*>(fstParam)) + { + if (auto sndTypeParam = dynamic_cast<GenericTypeParamDecl*>(sndParam)) + { + // TODO: is there any validation that needs to be peformed here? + } + else + { + // Type and non-type parameters can't match. + return false; + } + } + else if (auto fstValueParam = dynamic_cast<GenericValueParamDecl*>(fstParam)) + { + if (auto sndValueParam = dynamic_cast<GenericValueParamDecl*>(sndParam)) + { + // Need to check that the parameters have the same type. + // + // Note: We are assuming here that the type of a value + // parameter cannot be dependent on any of the type + // parameters in the same signature. This is a reasonable + // assumption for now, but could get thorny down the road. + if (!fstValueParam->getType()->Equals(sndValueParam->getType())) + { + // Type mismatch. + return false; + } + + // TODO: This is not the right place to check on default + // values for the parameter, because they won't affect + // the signature, but we should make sure to do validation + // later on (e.g., that only one declaration can/should + // be allowed to provide a default). + } + else + { + // Value and non-value parameters can't match. + return false; + } + } + } + + // If we got this far, then it means the parameter signatures *seem* + // to match up all right, but now we need to check that the constraints + // placed on those parameters are also consistent. + // + // For now I'm going to assume/require that all declarations must + // declare the signature in a way that matches exactly. + UInt constraintCount = fstConstraints.Count(); + if(constraintCount != sndConstraints.Count()) + return false; + + for (UInt cc = 0; cc < constraintCount; ++cc) + { + auto fstConstraint = fstConstraints[cc]; + auto sndConstraint = sndConstraints[cc]; + + // TODO: the challenge here is that the + // constraints are going to be expressed + // in terms of the parameters, which means + // we need to be doing substitution here. + } + + // HACK: okay, we'll just assume things match for now. + return true; + } + // Check if two functions have the same signature for the purposes // of overload resolution. - bool DoFunctionSignaturesMatch( - FuncDecl* fst, - FuncDecl* snd) + bool doFunctionSignaturesMatch( + DeclRef<FuncDecl> fst, + DeclRef<FuncDecl> snd) { - // TODO(tfoley): This function won't do anything sensible for generics, - // so we need to figure out a plan for that... // TODO(tfoley): This copies the parameter array, which is bad for performance. - auto fstParams = fst->GetParameters().ToArray(); - auto sndParams = snd->GetParameters().ToArray(); + auto fstParams = GetParameters(fst).ToArray(); + auto sndParams = GetParameters(snd).ToArray(); // If the functions have different numbers of parameters, then // their signatures trivially don't match. @@ -1517,14 +1632,14 @@ namespace Slang auto sndParam = sndParams[ii]; // If a given parameter type doesn't match, then signatures don't match - if (!fstParam->type.Equals(sndParam->type)) + if (!GetType(fstParam)->Equals(GetType(sndParam))) return false; // If one parameter is `out` and the other isn't, then they don't match // // Note(tfoley): we don't consider `out` and `inout` as distinct here, // because there is no way for overload resolution to pick between them. - if (fstParam->HasModifier<OutModifier>() != sndParam->HasModifier<OutModifier>()) + if (fstParam.getDecl()->HasModifier<OutModifier>() != sndParam.getDecl()->HasModifier<OutModifier>()) return false; } @@ -1534,18 +1649,66 @@ namespace Slang return true; } + RefPtr<Substitutions> createDummySubstitutions( + GenericDecl* genericDecl) + { + RefPtr<Substitutions> subst = new Substitutions(); + subst->genericDecl = genericDecl; + for (auto dd : genericDecl->Members) + { + if (dd == genericDecl->inner) + continue; + + if (auto typeParam = dd.As<GenericTypeParamDecl>()) + { + auto type = DeclRefType::Create(getSession(), + makeDeclRef(typeParam.Ptr())); + subst->args.Add(type); + } + else if (auto valueParam = dd.As<GenericValueParamDecl>()) + { + auto val = new GenericParamIntVal( + makeDeclRef(valueParam.Ptr())); + subst->args.Add(val); + } + // TODO: need to handle constraints here? + } + return subst; + } + void ValidateFunctionRedeclaration(FuncDecl* funcDecl) { auto parentDecl = funcDecl->ParentDecl; SLANG_RELEASE_ASSERT(parentDecl); if (!parentDecl) return; + Decl* childDecl = funcDecl; + + // If this is a generic function (that is, its parent + // declaration is a generic), then we need to look + // for sibling declarations of the parent. + auto genericDecl = dynamic_cast<GenericDecl*>(parentDecl); + if (genericDecl) + { + parentDecl = genericDecl->ParentDecl; + childDecl = genericDecl; + } + // Look at previously-declared functions with the same name, // in the same container + // + // Note: there is an assumption here that declarations that + // occur earlier in the program text will be *later* in + // the linked list of declarations with the same name. + // We are also assuming/requiring that the check here is + // symmetric, in that it is okay to test (A,B) or (B,A), + // and there is no need to test both. + // buildMemberDictionary(parentDecl); - - for (auto prevDecl = funcDecl->nextInContainerWithSameName; prevDecl; prevDecl = prevDecl->nextInContainerWithSameName) + for (auto pp = childDecl->nextInContainerWithSameName; pp; pp = pp->nextInContainerWithSameName) { + auto prevDecl = pp; + // Look through generics to the declaration underneath auto prevGenericDecl = dynamic_cast<GenericDecl*>(prevDecl); if (prevGenericDecl) @@ -1558,18 +1721,121 @@ namespace Slang if (!prevFuncDecl) continue; + // If one declaration is a prefix/postfix operator, and the + // other is not a matching operator, then don't consider these + // to be redeclarations. + // + // Note(tfoley): Any attempt to call such an operator using + // ordinary function-call syntax (if we decided to allow it) + // would be ambiguous in such a case, of course. + // + if (funcDecl->HasModifier<PrefixModifier>() != prevDecl->HasModifier<PrefixModifier>()) + continue; + if (funcDecl->HasModifier<PostfixModifier>() != prevDecl->HasModifier<PostfixModifier>()) + continue; + + // If one is generic and the other isn't, then there is no match. + if ((genericDecl != nullptr) != (prevGenericDecl != nullptr)) + continue; + + // We are going to be comparing the signatures of the + // two functions, but if they are *generic* functions + // then we will need to compare them with consistent + // specializations in place. + // + // We'll go ahead and create some (unspecialized) declaration + // references here, just to be prepared. + DeclRef<FuncDecl> funcDeclRef(funcDecl, nullptr); + DeclRef<FuncDecl> prevFuncDeclRef(prevFuncDecl, nullptr); + + // If we are working with generic functions, then we need to + // consider if their generic signatures match. + if (genericDecl) + { + assert(prevGenericDecl); + if (!doGenericSignaturesMatch(genericDecl, prevGenericDecl)) + continue; + + // Now we need specialize the declaration references + // consistently, so that we can compare. + // + // First we create a "dummy" set of substitutions that + // just reference the parameters of the first generic. + auto subst = createDummySubstitutions(genericDecl); + // + // Then we use those parameters to specialize the *other* + // generic. + // + subst->genericDecl = prevGenericDecl; + prevFuncDeclRef.substitutions = subst; + // + // One way to think about it is that if we have these + // declarations (ignore the name differences...): + // + // // prevFuncDecl: + // void foo1<T>(T x); + // + // // funcDecl: + // void foo2<U>(U x); + // + // Then we will compare `foo2` against `foo1<U>`. + } + // If the parameter signatures don't match, then don't worry - if (!DoFunctionSignaturesMatch(funcDecl, prevFuncDecl)) + if (!doFunctionSignaturesMatch(funcDeclRef, prevFuncDeclRef)) continue; // If we get this far, then we've got two declarations in the same - // scope, with the same name and signature. + // scope, with the same name and signature, so they appear + // to be redeclarations. // - // They might just be redeclarations, which we would want to allow. - - // First, check if the return types match. - // TODO(tfolye): this code won't work for generics - if (!funcDecl->ReturnType.Equals(prevFuncDecl->ReturnType)) + // We will track that redeclaration occured, so that we can + // take it into account for overload resolution. + // + // A huge complication that we'll need to deal with is that + // multiple declarations might introduce default values for + // (different) parameters, and we might need to merge across + // all of them (which could get complicated if defaults for + // parameters can reference earlier parameters). + + // If the previous declaration wasn't already recorded + // as being part of a redeclaration family, then make + // it the primary declaration of a new family. + if (!prevFuncDecl->primaryDecl) + { + prevFuncDecl->primaryDecl = prevFuncDecl; + } + + // The new declaration will belong to the family of + // the previous one, and so it will share the same + // primary declaration. + funcDecl->primaryDecl = prevFuncDecl->primaryDecl; + funcDecl->nextDecl = nullptr; + + // Next we want to chain the new declaration onto + // the linked list of redeclarations. + auto link = &prevFuncDecl->nextDecl; + while (*link) + link = &(*link)->nextDecl; + *link = funcDecl; + + // Now that we've added things to a group of redeclarations, + // we can do some additional validation. + + // First, we will ensure that the return types match + // between the declarations, so that they are truly + // interchangeable. + // + // Note(tfoley): If we ever decide to add a beefier type + // system to Slang, we might allow overloads like this, + // so long as the desired result type can be disambiguated + // based on context at the call type. In that case we would + // consider result types earlier, as part of the signature + // matching step. + // + auto resultType = GetResultType(funcDeclRef); + auto prevResultType = GetResultType(prevFuncDeclRef); + if (!resultType->Equals(prevResultType)) { // Bad dedeclaration if (!isRewriteMode()) @@ -1581,11 +1847,28 @@ namespace Slang break; } - // TODO(tfoley): track the fact that there is redeclaration going on, - // so that we can detect it and react accordingly during overload resolution - // (e.g., by only considering one declaration as the canonical one...) + // Note(tfoley): several of the following checks should + // really be looping over all the previous declarations + // in the same group, and not just the one previous + // declaration we found just now. + + // TODO: Enforce that the new declaration had better + // not specify a default value for any parameter that + // already had a default value in a prior declaration. + + // We are going to want to enforce that we cannot have + // two declarations of a function both specify bodies. + // Before we make that check, however, we need to deal + // with the case where the two function declarations + // might represent different target-specific versions + // of a function. + // + // TODO: if the two declarations are specialized for + // different targets, then skip the body checks below. - // If both have a body, then there is trouble + // If both of the declarations have a body, then there + // is trouble, because we wouldn't know which one to + // use during code generation. if (funcDecl->Body && prevFuncDecl->Body) { // Redefinition @@ -1598,8 +1881,10 @@ namespace Slang break; } - // TODO(tfoley): If both specific default argument expressions - // for the same value, then that is an error too... + // At this point we've processed the redeclaration and + // put it into a group, so there is no reason to keep + // looping and looking at prior declarations. + return; } } @@ -2673,6 +2958,12 @@ namespace Slang // in order for checking to suceed. struct ConstraintSystem { + // The generic declaration whose parameters we + // are trying to solve for. + RefPtr<GenericDecl> genericDecl; + + // Constraints we have accumulated, which constrain + // the possible arguments for those parameters. List<Constraint> constraints; }; @@ -2702,17 +2993,58 @@ namespace Slang // for now look up a conformance member... if(auto declRefType = type->As<DeclRefType>()) { - if( auto aggTypeDeclRef = declRefType->declRef.As<AggTypeDecl>() ) + auto declRef = declRefType->declRef; + + // Easy case: a type conforms to itself. + // + // TODO: This is actually a bit more complicated, as + // the interface needs to be "object-safe" for us to + // really make this determination... + if(declRef == interfaceDeclRef) + return true; + + if( auto aggTypeDeclRef = declRef.As<AggTypeDecl>() ) { for( auto inheritanceDeclRef : getMembersOfType<InheritanceDecl>(aggTypeDeclRef)) { EnsureDecl(inheritanceDeclRef.getDecl()); - auto inheritedDeclRefType = getBaseType(inheritanceDeclRef)->As<DeclRefType>(); - if (!inheritedDeclRefType) + // Here we will recursively look up conformance on the type + // that is being inherited from. This is dangerous because + // it might lead to infinite loops. + // + // TODO: A better appraoch would be to create a linearized list + // of all the interfaces that a given type direclty or indirectly + // inheirts, and store it with the type, so that we don't have + // to recurse in places like this (and can maybe catch infinite + // loops better). This would also help avoid checking multiply-inherited + // conformances multiple times. + + auto inheritedType = getBaseType(inheritanceDeclRef); + if(DoesTypeConformToInterface(inheritedType, interfaceDeclRef)) + return true; + } + } + else if( auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>() ) + { + // We need to enumerate the constraints placed on this type by its outer + // generic declaration, and see if any of them guarantees that we + // satisfy the given interface.. + auto genericDeclRef = genericTypeParamDeclRef.GetParent().As<GenericDecl>(); + SLANG_ASSERT(genericDeclRef); + + for( auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef) ) + { + auto sub = GetSub(constraintDeclRef); + auto sup = GetSup(constraintDeclRef); + + auto subDeclRef = sub->As<DeclRefType>(); + if(!subDeclRef) + continue; + if(subDeclRef->declRef != genericTypeParamDeclRef) continue; - if(interfaceDeclRef.Equals(inheritedDeclRefType->declRef)) + if(DoesTypeConformToInterface(sup, interfaceDeclRef)) return true; } } @@ -2986,7 +3318,7 @@ namespace Slang RefPtr<Type> resultType; // A system for tracking constraints introduced on generic parameters - ConstraintSystem constraintSystem; +// ConstraintSystem constraintSystem; // How much conversion cost should be considered for this overload, // when ranking candidates. @@ -3603,7 +3935,29 @@ namespace Slang DeclRef<CallableDecl> funcDeclRef, OverloadResolveContext& context) { - EnsureDecl(funcDeclRef.getDecl()); + auto funcDecl = funcDeclRef.getDecl(); + EnsureDecl(funcDecl); + + // If this function is a redeclaration, + // then we don't want to include it multiple times, + // and mistakenly think we have an ambiguous call. + // + // Instead, we will carefully consider only the + // "primary" declaration of any callable. + if (auto primaryDecl = funcDecl->primaryDecl) + { + if (funcDecl != primaryDecl) + { + // This is a redeclaration, so we don't + // want to consider it. The primary + // declaration should also get considered + // for the call site and it will match + // anything this declaration would have + // matched. + return; + } + } + OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Func; @@ -3635,11 +3989,14 @@ namespace Slang #endif } + // Add a candidate callee for overload resolution, based on + // calling a particular `ConstructorDecl`. void AddCtorOverloadCandidate( - LookupResultItem typeItem, - RefPtr<Type> type, - DeclRef<ConstructorDecl> ctorDeclRef, - OverloadResolveContext& context) + LookupResultItem typeItem, + RefPtr<Type> type, + DeclRef<ConstructorDecl> ctorDeclRef, + OverloadResolveContext& context, + RefPtr<Type> resultType) { EnsureDecl(ctorDeclRef.getDecl()); @@ -3654,7 +4011,7 @@ namespace Slang OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Func; candidate.item = ctorItem; - candidate.resultType = type; + candidate.resultType = resultType; AddOverloadCandidate(context, candidate); } @@ -3702,13 +4059,18 @@ namespace Slang auto fstParam = fstInt.As<GenericParamIntVal>(); auto sndParam = sndInt.As<GenericParamIntVal>(); + bool okay = false; if (fstParam) - TryUnifyIntParam(constraints, fstParam->declRef, sndInt); + { + if(TryUnifyIntParam(constraints, fstParam->declRef, sndInt)) + okay = true; + } if (sndParam) - TryUnifyIntParam(constraints, sndParam->declRef, fstInt); - - if (fstParam || sndParam) - return true; + { + if(TryUnifyIntParam(constraints, sndParam->declRef, fstInt)) + okay = true; + } + return okay; } } @@ -3768,6 +4130,14 @@ namespace Slang RefPtr<GenericValueParamDecl> paramDecl, RefPtr<IntVal> val) { + // We only want to accumulate constraints on + // the parameters of the declarations being + // specialized (don't accidentially constrain + // parameters of a generic function based on + // calls in its body). + if(paramDecl->ParentDecl != constraints.genericDecl) + return false; + // We want to constrain the given parameter to equal the given value. Constraint constraint; constraint.decl = paramDecl.Ptr(); @@ -3856,7 +4226,10 @@ namespace Slang auto fstDeclRef = fstDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(fstDeclRef.getDecl())) - return TryUnifyTypeParam(constraints, typeParamDecl, snd); + { + if(typeParamDecl->ParentDecl == constraints.genericDecl ) + return TryUnifyTypeParam(constraints, typeParamDecl, snd); + } } if (auto sndDeclRefType = snd->As<DeclRefType>()) @@ -3864,7 +4237,10 @@ namespace Slang auto sndDeclRef = sndDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(sndDeclRef.getDecl())) - return TryUnifyTypeParam(constraints, typeParamDecl, fst); + { + if(typeParamDecl->ParentDecl == constraints.genericDecl ) + return TryUnifyTypeParam(constraints, typeParamDecl, fst); + } } // If we can unify the types structurally, then we are golden @@ -3911,6 +4287,7 @@ namespace Slang if (auto extGenericDecl = GetOuterGeneric(extDecl)) { ConstraintSystem constraints; + constraints.genericDecl = extGenericDecl; if (!TryUnifyTypes(constraints, extDecl->targetType.Ptr(), type)) return DeclRef<Decl>().As<ExtensionDecl>(); @@ -3962,6 +4339,7 @@ namespace Slang OverloadResolveContext& context) { ConstraintSystem constraints; + constraints.genericDecl = genericDeclRef.getDecl(); // Construct a reference to the inner declaration that has any generic // parameter substitutions in place already, but *not* any substutions @@ -4032,15 +4410,16 @@ namespace Slang } void AddAggTypeOverloadCandidates( - LookupResultItem typeItem, - RefPtr<Type> type, - DeclRef<AggTypeDecl> aggTypeDeclRef, - OverloadResolveContext& context) + LookupResultItem typeItem, + RefPtr<Type> type, + DeclRef<AggTypeDecl> aggTypeDeclRef, + OverloadResolveContext& context, + RefPtr<Type> resultType) { for (auto ctorDeclRef : getMembersOfType<ConstructorDecl>(aggTypeDeclRef)) { // now work through this candidate... - AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context); + AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType); } // Now walk through any extensions we can find for this types @@ -4055,7 +4434,7 @@ namespace Slang // TODO(tfoley): `typeItem` here should really reference the extension... // now work through this candidate... - AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context); + AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType); } // Also check for generic constructors @@ -4069,7 +4448,7 @@ namespace Slang DeclRef<ConstructorDecl> innerCtorRef = innerRef.As<ConstructorDecl>(); - AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context); + AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType); // TODO(tfoley): need a way to do the solving step for the constraint system } @@ -4077,15 +4456,62 @@ namespace Slang } } + void addGenericTypeParamOverloadCandidates( + DeclRef<GenericTypeParamDecl> typeDeclRef, + OverloadResolveContext& context, + RefPtr<Type> resultType) + { + // We need to look for any constraints placed on the generic + // type parameter, since they will give us information on + // interfaces that the type must conform to. + + // We expect the parent of the generic type parameter to be a generic... + auto genericDeclRef = typeDeclRef.GetParent().As<GenericDecl>(); + assert(genericDeclRef); + + for(auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef)) + { + // Does this constraint pertain to the type we are working on? + // + // We want constraints of the form `T : Foo` where `T` is the + // generic parameter in question, and `Foo` is whatever we are + // constraining it to. + auto subType = GetSub(constraintDeclRef); + auto subDeclRefType = subType->As<DeclRefType>(); + if(!subDeclRefType) + continue; + if(!subDeclRefType->declRef.Equals(typeDeclRef)) + continue; + + // The super-type in the constraint (e.g., `Foo` in `T : Foo`) + // will tell us a type we should use for lookup. + auto bound = GetSup(constraintDeclRef); + + // Go ahead and use the target type: + // + // TODO: Need to consider case where this might recurse infinitely. + AddTypeOverloadCandidates(bound, context, resultType); + } + } + void AddTypeOverloadCandidates( - RefPtr<Type> type, - OverloadResolveContext& context) + RefPtr<Type> type, + OverloadResolveContext& context, + RefPtr<Type> resultType) { if (auto declRefType = type->As<DeclRefType>()) { - if (auto aggTypeDeclRef = declRefType->declRef.As<AggTypeDecl>()) + auto declRef = declRefType->declRef; + if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>()) + { + AddAggTypeOverloadCandidates(LookupResultItem(aggTypeDeclRef), type, aggTypeDeclRef, context, resultType); + } + else if(auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>()) { - AddAggTypeOverloadCandidates(LookupResultItem(aggTypeDeclRef), type, aggTypeDeclRef, context); + addGenericTypeParamOverloadCandidates( + genericTypeParamDeclRef, + context, + resultType); } } } @@ -4105,7 +4531,7 @@ namespace Slang auto type = DeclRefType::Create( getSession(), aggTypeDeclRef); - AddAggTypeOverloadCandidates(item, type, aggTypeDeclRef, context); + AddAggTypeOverloadCandidates(item, type, aggTypeDeclRef, context, type); } else if (auto genericDeclRef = item.declRef.As<GenericDecl>()) { @@ -4138,7 +4564,15 @@ namespace Slang } else if( auto typeDefDeclRef = item.declRef.As<TypeDefDecl>() ) { - AddTypeOverloadCandidates(GetType(typeDefDeclRef), context); + auto type = getNamedType(getSession(), typeDefDeclRef); + AddTypeOverloadCandidates(GetType(typeDefDeclRef), context, type); + } + else if( auto genericTypeParamDeclRef = item.declRef.As<GenericTypeParamDecl>() ) + { + auto type = DeclRefType::Create( + getSession(), + genericTypeParamDeclRef); + addGenericTypeParamOverloadCandidates(genericTypeParamDeclRef, context, type); } else { @@ -4152,10 +4586,12 @@ namespace Slang { auto funcExprType = funcExpr->type; - if (auto funcDeclRefExpr = funcExpr.As<DeclRefExpr>()) + if (auto declRefExpr = funcExpr.As<DeclRefExpr>()) { - // The expression referenced a function declaration - AddDeclRefOverloadCandidates(LookupResultItem(funcDeclRefExpr->declRef), context); + // The expression directly referenced a declaration, + // so we can use that declaration directly to look + // for anything applicable. + AddDeclRefOverloadCandidates(LookupResultItem(declRefExpr->declRef), context); } else if (auto funcType = funcExprType->As<FuncType>()) { @@ -4179,7 +4615,8 @@ namespace Slang // // TODO(tfoley): are there any meaningful types left // that aren't declaration references? - AddTypeOverloadCandidates(typeType->type, context); + auto type = typeType->type; + AddTypeOverloadCandidates(type, context, type); return; } } @@ -4400,6 +4837,7 @@ namespace Slang { // There were multple equally-good candidates, but none actually usable. // We will construct a diagnostic message to help out. + if (funcName) { if (!isRewriteMode()) @@ -4444,7 +4882,17 @@ namespace Slang { String declString = getDeclSignatureString(candidate.item); - declString = declString + "[" + String(candidate.conversionCostSum) + "]"; +// declString = declString + "[" + String(candidate.conversionCostSum) + "]"; + + if (auto decl = dynamic_cast<CallableDecl*>(candidate.item.declRef.decl)) + { + char buffer[1024]; + sprintf(buffer, "[this:%p, primary:%p, next:%p]", + decl, + decl->primaryDecl, + decl->nextDecl); + declString.append(buffer); + } getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString); diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 3f1a33edc..b792a74a3 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -10,13 +10,19 @@ interface __BuiltinArithmeticType : __BuiltinType {} interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {} // A type that can represent integers -interface __BuiltinIntegerType : __BuiltinArithmeticType {} +interface __BuiltinIntegerType : __BuiltinArithmeticType +{} // A type that can represent non-integers interface __BuiltinRealType : __BuiltinArithmeticType {} // A type that uses a floating-point representation -interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType {} +interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType +{ + // A builtin floating-point type must have an initializer that takes + // a floating-point value... + __init(float value); +} __generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right); diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index 900d05b61..e47e2a90b 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -10,13 +10,19 @@ sb << "// A type that logically has a sign (positive/negative/zero)\n"; sb << "interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {}\n"; sb << "\n"; sb << "// A type that can represent integers\n"; -sb << "interface __BuiltinIntegerType : __BuiltinArithmeticType {}\n"; +sb << "interface __BuiltinIntegerType : __BuiltinArithmeticType\n"; +sb << "{}\n"; sb << "\n"; sb << "// A type that can represent non-integers\n"; sb << "interface __BuiltinRealType : __BuiltinArithmeticType {}\n"; sb << "\n"; sb << "// A type that uses a floating-point representation\n"; -sb << "interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType {}\n"; +sb << "interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType\n"; +sb << "{\n"; +sb << " // A builtin floating-point type must have an initializer that takes\n"; +sb << " // a floating-point value...\n"; +sb << " __init(float value);\n"; +sb << "}\n"; sb << "\n"; sb << "__generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right);\n"; sb << "\n"; diff --git a/source/slang/decl-defs.h b/source/slang/decl-defs.h index f27ab9ba6..c9860f9bf 100644 --- a/source/slang/decl-defs.h +++ b/source/slang/decl-defs.h @@ -127,6 +127,22 @@ ABSTRACT_SYNTAX_CLASS(CallableDecl, ContainerDecl) }) SYNTAX_FIELD(TypeExp, ReturnType) + + // Fields related to redeclaration, so that we + // can support multiple specialized varaitions + // of the "same" logical function. + // + // This should also help us to support redeclaration + // of functions when handling HLSL/GLSL. + + // The "primary" declaration of the function, which will + // be used whenever we need to unique things. + FIELD_INIT(CallableDecl*, primaryDecl, nullptr) + + // The next declaration of the "same" function (that is, + // with the same `primaryDecl`). + FIELD_INIT(CallableDecl*, nextDecl, nullptr); + END_SYNTAX_CLASS() // Base class for callable things that may also have a body that is evaluated to produce their result diff --git a/source/slang/diagnostics.cpp b/source/slang/diagnostics.cpp index da703e1b9..f08554b52 100644 --- a/source/slang/diagnostics.cpp +++ b/source/slang/diagnostics.cpp @@ -155,7 +155,7 @@ static void formatDiagnostic( auto sourceManager = sink->sourceManager; auto expandedLoc = sourceManager->expandSourceLoc(diagnostic.loc); - auto humaneLoc = sourceManager->getHumaneLoc(expandedLoc); + auto humaneLoc = expandedLoc.sourceManager->getHumaneLoc(expandedLoc); sb << humaneLoc.getPath(); sb << "("; diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 442903e6f..81e9931e8 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -842,6 +842,37 @@ __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> __target_intrinsic(glsl, "clamp($0, 0, 1)") matrix<T,N,M> saturate(matrix<T,N,M> x); +__generic<T : __BuiltinFloatingPointType> +__specialized_for_target(glsl) +T saturate(T x) +{ + return clamp(x, T(0), T(1)); +} + +__generic<T : __BuiltinFloatingPointType, let N : int> +__specialized_for_target(glsl) +vector<T,N> saturate(vector<T,N> x) +{ + return clamp(x, + vector<T,N>(T(0)), + vector<T,N>(T(1))); +} + +// HACK: need a helper to turn a scalar into a matrix, +// because GLSL and HLSL disagree on the semantics of +// constructing a matrix from a single scalar. +__generic<T, let N : int, let M : int> +matrix<T,N,M> __scalarToMatrix(T value); + +__generic<T : __BuiltinFloatingPointType, let N : int, let M : int> +__specialized_for_target(glsl) +matrix<T,N,M> saturate(matrix<T,N,M> x) +{ + return clamp(x, + __scalarToMatrix<T,N,M>(T(0)), + __scalarToMatrix<T,N,M>(T(1))); +} + // Extract sign of value __generic<T : __BuiltinSignedArithmeticType> __intrinsic_op int sign(T x); diff --git a/source/slang/hlsl.meta.slang.h b/source/slang/hlsl.meta.slang.h index 84f5e89ed..eccb12f8d 100644 --- a/source/slang/hlsl.meta.slang.h +++ b/source/slang/hlsl.meta.slang.h @@ -844,6 +844,37 @@ sb << "__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n"; sb << "__target_intrinsic(glsl, \"clamp($0, 0, 1)\")\n"; sb << "matrix<T,N,M> saturate(matrix<T,N,M> x);\n"; sb << "\n"; +sb << "__generic<T : __BuiltinFloatingPointType>\n"; +sb << "__specialized_for_target(glsl)\n"; +sb << "T saturate(T x)\n"; +sb << "{\n"; +sb << " return clamp(x, T(0), T(1));\n"; +sb << "}\n"; +sb << "\n"; +sb << "__generic<T : __BuiltinFloatingPointType, let N : int>\n"; +sb << "__specialized_for_target(glsl)\n"; +sb << "vector<T,N> saturate(vector<T,N> x)\n"; +sb << "{\n"; +sb << " return clamp(x,\n"; +sb << " vector<T,N>(T(0)),\n"; +sb << " vector<T,N>(T(1)));\n"; +sb << "}\n"; +sb << "\n"; +sb << "// HACK: need a helper to turn a scalar into a matrix,\n"; +sb << "// because GLSL and HLSL disagree on the semantics of\n"; +sb << "// constructing a matrix from a single scalar.\n"; +sb << "__generic<T, let N : int, let M : int>\n"; +sb << "matrix<T,N,M> __scalarToMatrix(T value);\n"; +sb << "\n"; +sb << "__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n"; +sb << "__specialized_for_target(glsl)\n"; +sb << "matrix<T,N,M> saturate(matrix<T,N,M> x)\n"; +sb << "{\n"; +sb << " return clamp(x,\n"; +sb << " __scalarToMatrix<T,N,M>(T(0)),\n"; +sb << " __scalarToMatrix<T,N,M>(T(1)));\n"; +sb << "}\n"; +sb << "\n"; sb << "\n"; sb << "// Extract sign of value\n"; sb << "__generic<T : __BuiltinSignedArithmeticType> __intrinsic_op int sign(T x);\n"; diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 2f3456d0c..e0dbd5dae 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -785,7 +785,12 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower LoweredTypeInfo visitType(Type* type) { - SLANG_UNIMPLEMENTED_X("type lowering"); + // TODO(tfoley): Now that we use the AST types directly in the IR, there + // isn't much to do in the "lowering" step. Still, there might be cases + // where certain kinds of legalization need to take place, so this + // visitor setup might still be needed in the long run. + return LoweredTypeInfo(type); +// SLANG_UNIMPLEMENTED_X("type lowering"); } LoweredTypeInfo visitFuncType(FuncType* type) diff --git a/source/slang/modifier-defs.h b/source/slang/modifier-defs.h index 9a16a0bdd..c563d35cf 100644 --- a/source/slang/modifier-defs.h +++ b/source/slang/modifier-defs.h @@ -49,6 +49,15 @@ SYNTAX_CLASS(TargetIntrinsicModifier, Modifier) FIELD(Token, definitionToken) END_SYNTAX_CLASS() +// A modifier that marks a declaration as representing a +// specialization that should be preferred on a particular +// target. +SYNTAX_CLASS(SpecializedForTargetModifier, Modifier) + // Token that names the target that the operation + // has been specialized for. + FIELD(Token, targetToken) +END_SYNTAX_CLASS() + // A modifier to tag something as an intrinsic that requires // a certain GLSL extension to be enabled when used SYNTAX_CLASS(RequiredGLSLExtensionModifier, Modifier) diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index a0e07ea12..2b6535bef 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -3842,6 +3842,17 @@ namespace Slang return modifier; } + static RefPtr<RefObject> parseSpecializedForTargetModifier(Parser* parser, void* /*userData*/) + { + auto modifier = new SpecializedForTargetModifier(); + if (AdvanceIf(parser, TokenType::LParent)) + { + modifier->targetToken = parser->ReadToken(TokenType::Identifier); + parser->ReadToken(TokenType::RParent); + } + return modifier; + } + static RefPtr<RefObject> parseGLSLExtensionModifier(Parser* parser, void* /*userData*/) { auto modifier = new RequiredGLSLExtensionModifier(); @@ -4057,6 +4068,7 @@ namespace Slang MODIFIER(__intrinsic_op, parseIntrinsicOpModifier); MODIFIER(__target_intrinsic, parseTargetIntrinsicModifier); + MODIFIER(__specialized_for_target, parseSpecializedForTargetModifier); MODIFIER(__glsl_extension, parseGLSLExtensionModifier); MODIFIER(__glsl_version, parseGLSLVersionModifier); |
