diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2017-10-27 11:22:11 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-10-27 11:22:11 -0700 |
| commit | 4ab545bcd0716cc3f2da432a921c1f53fdce7925 (patch) | |
| tree | 14ff6f0775425b2e6f6571dab7ab63e57382598f | |
| parent | 56bc82656c2b2cd581a430713bc25b409bb4da4f (diff) | |
Initial work on support code generation for generics with constraints (#233)
This change includes a lot of infrastructure work, but the main point is to allow code like the following:
```
// define an interface
interface Helper { float help(); }
// define a generic function that uses the interface
float test<T : Helper>( T t ) { return t.help(); }
// define a type that implements the interface
struct A : Helper { float help() { return 1.0 } }
// define an ordinary function that calls the
// generic function with a concrete type:
float doIt()
{
A a;
return test<A>(a);
}
```
Getting this to generate valid code involves a lot of steps. This change includes the initial version of all of these steps, but leaves a lot of gaps where more complete implementation is required.
The changes include:
- Member lookup on types has been centralized, and now handles the case where the type we are looking for a member in is a generic parameter (e.g., given `t.help()` we can now look up `help` in `Helper` by knowing that `t` is a `T` and `T` conforms to `Helper`).
- There is an obvious cleanup still to be done here where the same exact logic should be used to look up available "constructor" declarations inside a type when the type is used like a function.
- Add a notion of subtype constraint "wittnesses" to the type system. When a generic is declared as taking `<T : Helper>` it really takes two generic parameters: the type `T` and a proof that `T` conforms to `Helper`. The actual arguments to a generic will then include both the type argument and a suitable witness argument (both type-level values).
- As it stands right now, a witness wraps a `DeclRef` to the declaration that represents the appropriate subtype relationship. So if we have `struct A : Helper`, that `: Helper` part turns into an `InheritanceDecl` member, and a reference to that member can serve as a witness to the fact that `A` conforms to `Helper`.
- Make explicit generic application `G<A,B>` synthesize the additional arguments that represent conformances required by the generic.
- This does *not* yet deal with the case where a generic is implicitly specialized as part of an ordinary call `G(a,b)`
- A bug fix to not auto-specialize generics during lookup. The problem here was related to an attempted fix of an earlier issue.
During checking of a method nested in a generic type, we were running into problems where `DeclRefType::create()` was getting called on an un-specialized reference to `vector`, and this was leading to a crash when the code looked for the arguments for the generic. This was worked around by having name lookup automatically specialize any generics it runs into while going through lookup contexts.
That choice creates the problem that in a generic method like this:
```
void test<T>(T val) { ... }
```
any reference to `val` inside the body of `test` will end up getting specialized so that it is effectively `test<T>::val`, when that isn't really needed.
- Add front-end logic to check that when a type claims to conform to an interface it actually must provide the methods required by the interface. The checking process goes ahead and builds a front-end "witness table" that maps declarations in the interface being conformed to over to their concrete implementations for the type.
- At the moment the checking is completely broken and bad: it assumes that *any* member with the right name is an appropriate declaration to satisfy a requirement. That obviously needs to be fixed.
- Add an explicit operation to the IR for lookup of methods: `lookup_interface_method(w, r)` where `w` is a reference to the "witness" value and `r` is an `IRDeclRef` for the member we want to look up.
- Add an explicit notion of witness tables to the IR. These end up being the IR representation of an `InheritanceDecl` in a type, and they are generated by enumerating the members that satisfy the interface requirements (which were handily already enumerated by the front-end checking). The witness table is an explicit IR value, and so it will be referenced/used at the site where conformance is being exploited (e.g., as part of a `specialize` call), so it should be safe to eliminate witness tables that are unused (since they represent conformances that aren't actually exploited). Similarly, the entries in a witness table are uses of the functions that implement interface methods, and so keep those live.
- In order to implement the above, I did a bit of a cleanup pass on the IR representation so that there is an `IRUser` base that `IRInst` inherits from, so that we can have users of values that aren't instructions.
- One annoying thing is that because of how types and generics are handled in the IR, we needed a way to have a type-level `Val` that wraps an IR-level value: e.g., to allow an IR-level witness table to be used as one of the arguments for specialization of a generic. The design I chose here is to have a "proxy" `Val` subclass (`IRProxyVal`) that wraps an `IRValue*`. These should only ever appear as part of types and `DeclRef`s that are used by the IR.
- One annoying bit here is that an IR value might then have a use that is not manifest in the set of IR instructions, and instead only appears as part of a type somewhere.
- I'm not 100% happy with this design, but it seems like we'd have to tackle similar issues if/when we eventually allow functions to have `constexpr` or `@Constant` parameters
- Make generic specialization also propagate witness table arguments through to their use sites (this is mostly just the existing substitution machinery, once we have `IRProxyVal`), and then include logic to specialize `lookup_interface_method` instructions when their first operand is a concrete witness table.
All of this work allows a single limited test using generics with constraints to pass, but more work is needed to make the solution robust.
| -rw-r--r-- | source/slang/bytecode.cpp | 6 | ||||
| -rw-r--r-- | source/slang/check.cpp | 809 | ||||
| -rw-r--r-- | source/slang/decl-defs.h | 9 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 146 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 3 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 45 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 589 | ||||
| -rw-r--r-- | source/slang/ir.h | 103 | ||||
| -rw-r--r-- | source/slang/lookup.cpp | 78 | ||||
| -rw-r--r-- | source/slang/lookup.h | 11 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 339 | ||||
| -rw-r--r-- | source/slang/lower.cpp | 10 | ||||
| -rw-r--r-- | source/slang/mangle.cpp | 38 | ||||
| -rw-r--r-- | source/slang/mangle.h | 4 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 17 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 2 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 92 | ||||
| -rw-r--r-- | source/slang/syntax.h | 93 | ||||
| -rw-r--r-- | source/slang/type-defs.h | 1 | ||||
| -rw-r--r-- | source/slang/val-defs.h | 69 | ||||
| -rw-r--r-- | tests/compute/generics-constrained.slang | 46 | ||||
| -rw-r--r-- | tests/compute/generics-constrained.slang.expected.txt | 4 | ||||
| -rw-r--r-- | tests/ir/loop.slang.expected | 124 |
24 files changed, 2156 insertions, 484 deletions
diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp index 085b7303d..f1b23849d 100644 --- a/source/slang/bytecode.cpp +++ b/source/slang/bytecode.cpp @@ -728,7 +728,7 @@ BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( paramCount++; } - for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { switch( ii->op ) { @@ -792,7 +792,7 @@ BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( // Now loop over the non-parameter instructions and // allocate actual register locations to them. - for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { switch(ii->op) { @@ -857,7 +857,7 @@ BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( UInt blockOffset = subContext->currentBytecode.Count(); blockOffsets.Add( blockOffset ); - for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { // What we do with each instruction depends a bit on the // kind of instruction it is. diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 5ebb22999..a42edc331 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -212,9 +212,22 @@ namespace Slang case LookupResultItem::Breadcrumb::Kind::Member: bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc); break; + case LookupResultItem::Breadcrumb::Kind::Deref: bb = ConstructDerefExpr(bb, loc); break; + + case LookupResultItem::Breadcrumb::Kind::Constraint: + { + // TODO: do we need to make something more + // explicit here? + bb = ConstructDeclRefExpr( + breadcrumb->declRef, + bb, + loc); + } + break; + default: SLANG_UNREACHABLE("all cases handle"); } @@ -425,9 +438,20 @@ namespace Slang // the name of a non-proper type, and then have the compiler fill // in the default values for its type arguments (e.g., a variable // given type `Texture2D` will actually have type `Texture2D<float4>`). - bool CoerceToProperTypeImpl(TypeExp const& typeExp, RefPtr<Type>* outProperType) + bool CoerceToProperTypeImpl( + TypeExp const& typeExp, + RefPtr<Type>* outProperType, + DiagnosticSink* sink) { Type* type = typeExp.type.Ptr(); + if(!type && typeExp.exp) + { + if(auto typeType = typeExp.exp->type.type.As<TypeType>()) + { + type = typeType->type; + } + } + if (auto genericDeclRefType = type->As<GenericDeclRefType>()) { // We are using a reference to a generic declaration as a concrete @@ -447,11 +471,11 @@ namespace Slang { if (!typeParam->initType.exp) { - if (outProperType) + if (sink) { if (!isRewriteMode()) { - getSink()->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); + sink->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); } *outProperType = getSession()->getErrorType(); } @@ -466,11 +490,11 @@ namespace Slang { if (!valParam->initExpr) { - if (outProperType) + if (sink) { if (!isRewriteMode()) { - getSink()->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); + sink->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); } *outProperType = getSession()->getErrorType(); } @@ -509,13 +533,16 @@ namespace Slang TypeExp CoerceToProperType(TypeExp const& typeExp) { TypeExp result = typeExp; - CoerceToProperTypeImpl(typeExp, &result.type); + CoerceToProperTypeImpl(typeExp, &result.type, getSink()); return result; } - bool CanCoerceToProperType(TypeExp const& typeExp) + TypeExp tryCoerceToProperType(TypeExp const& typeExp) { - return CoerceToProperTypeImpl(typeExp, nullptr); + TypeExp result = typeExp; + if(!CoerceToProperTypeImpl(typeExp, &result.type, nullptr)) + return TypeExp(); + return result; } // Check a type, and coerce it to be proper @@ -1156,16 +1183,13 @@ namespace Slang } } + genericDecl->SetCheckState(DeclCheckState::CheckedHeader); + // check the nested declaration // TODO: this needs to be done in an appropriate environment... checkDecl(genericDecl->inner); } - void visitInterfaceDecl(InterfaceDecl* /*decl*/) - { - // TODO: do some actual checking of members here - } - void visitInheritanceDecl(InheritanceDecl* inheritanceDecl) { // check the type being inherited from @@ -1417,19 +1441,6 @@ namespace Slang } } - void visitClassDecl(ClassDecl * classNode) - { - if (classNode->IsChecked(DeclCheckState::Checked)) - return; - classNode->SetCheckState(DeclCheckState::Checked); - - for (auto field : classNode->GetFields()) - { - field->type = CheckUsableType(field->type); - field->SetCheckState(DeclCheckState::Checked); - } - } - void visitStructField(StructField* field) { // TODO: bottleneck through general-case variable checking @@ -1438,16 +1449,298 @@ namespace Slang field->SetCheckState(DeclCheckState::Checked); } - void visitStructDecl(StructDecl * structNode) + bool doesSignatureMatchRequirement( + CallableDecl* memberDecl, + DeclRef<CallableDecl> requiredMemberDeclRef) + { + // TODO: actually implement matching here. For now we'll + // just pretend that things are satisfied in order to make progress. + return true; + } + + // Does the given `memberDecl` work as an implementation + // to satisfy the requirement `requiredMemberDeclRef` + // from an interface? + bool doesMemberSatisfyRequirement( + Decl* memberDecl, + DeclRef<Decl> requiredMemberDeclRef) + { + // At a high level, we want to chack that the + // `memberDecl` and the `requiredMemberDeclRef` + // have the same AST node class, and then also + // check that their signatures match. + // + // There are a bunch of detailed decisions that + // have to be made, though, because we might, e.g., + // allow a function with more general parameter + // types to satisfy a requirement with more + // specific parameter types. + // + // If we ever allow for "property" declarations, + // then we would probably need to allow an + // ordinary field to satisfy a property requirement. + // + // An associated type requirement should be allowed + // to be satisfied by any type declaration: + // a typedef, a `struct`, etc. + + if (auto memberFuncDecl = dynamic_cast<FuncDecl*>(memberDecl)) + { + if (auto requiredFuncDeclRef = requiredMemberDeclRef.As<FuncDecl>()) + { + // Check signature match. + return doesSignatureMatchRequirement( + memberFuncDecl, + requiredFuncDeclRef); + } + } + else if (auto memberInitDecl = dynamic_cast<ConstructorDecl*>(memberDecl)) + { + if (auto requiredInitDecl = requiredMemberDeclRef.As<ConstructorDecl>()) + { + // Check signature match. + return doesSignatureMatchRequirement( + memberInitDecl, + requiredInitDecl); + } + } + + // Default: just assume that thing aren't being satisfied. + return false; + } + + // Find the appropriate member of a declared type to + // satisfy a requirement of an interface the type + // claims to conform to. + // + // The type declaration `typeDecl` has declared that it + // conforms to the interface `interfaceDeclRef`, and + // `requiredMemberDeclRef` is a required member of + // the interface. + RefPtr<Decl> findWitnessForInterfaceRequirement( + AggTypeDecl* typeDecl, + InheritanceDecl* inheritanceDecl, + DeclRef<InterfaceDecl> interfaceDeclRef, + DeclRef<Decl> requiredMemberDeclRef) + { + // We will look up members with the same name, + // since only same-name members will be able to + // satisfy the requirement. + // + // TODO: this won't work right now for members that + // don't have names, which right now includes + // initializers/constructors. + Name* name = requiredMemberDeclRef.GetName(); + + // We are basically looking up members of the + // given type, but we need to be a bit careful. + // We *cannot* perfom lookup "through" inheritance + // declarations for this or other interfaces, + // since that would let us satisfy a requirement + // with itself. + // + // There's also an interesting question of whether + // we can/should support innterface requirements + // being satisfied via `__transparent` members. + // This seems like a "clever" idea rather than + // a useful one, and IR generation would + // need to construct real IR to trampoline over + // to the implementation. + // + // The final case that can't be reduced to just + // "a directly declared member with the same name" + // is the case where the type inherits a member + // that can satisfy the requirement from a base type. + // We are ignoring implementation inheritance for + // now, so we won't worry about this. + + // Make sure that by-name lookup is possible. + buildMemberDictionary(typeDecl); + + Decl* firstMemberOfName = nullptr; + typeDecl->memberDictionary.TryGetValue(name, firstMemberOfName); + + if (!firstMemberOfName) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDecl, requiredMemberDeclRef); + return nullptr; + } + + // Iterate over the members and look for one that matches + // the expected signature for the requirement. + for (auto memberDecl = firstMemberOfName; memberDecl; memberDecl = memberDecl->nextInContainerWithSameName) + { + if (doesMemberSatisfyRequirement(memberDecl, requiredMemberDeclRef)) + return memberDecl; + } + + // No suitable member found, although there were candidates. + // + // TODO: Eventually we might want something akin to the current + // overload resolution logic, where we keep track of a list + // of "candidates" for satisfaction of the requirement, + // and if nothing is found we print the candidates + + getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDecl, requiredMemberDeclRef); + return nullptr; + } + + // Check that the type declaration `typeDecl`, which + // declares conformance to the interface `interfaceDeclRef`, + // (via the given `inheritanceDecl`) actually provides + // members to satisfy all the requirements in the interface. + void checkInterfaceConformance( + AggTypeDecl* typeDecl, + InheritanceDecl* inheritanceDecl, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + // We need to check the declaration of the interface + // before we can check that we conform to it. + EnsureDecl(interfaceDeclRef.getDecl()); + + // TODO: If we ever allow for implementation inheritance, + // then we will need to consider the case where a type + // declares that it conforms to an interface, but one of + // its (non-interface) base types already conforms to + // that interface, so that all of the requirements are + // already satisfied with inherited implementations... + + for (auto requiredMemberDeclRef : getMembers(interfaceDeclRef)) + { + // Some members of the interface don't actually represent + // things that we required of the implementing type. + // For example, when the interface declares that + // it inherits from another interface, we don't look for + // a matching inheritance clause on the type, but + // instead require that it also conforms to that + // interface. + if (auto requiredInheritanceDeclRef = requiredMemberDeclRef.As<InheritanceDecl>()) + { + // Recursively check that the type conforms + // to the inherited interface. + // + // TODO: we *really* need a linearization step here!!!! + checkConformanceToType( + typeDecl, + inheritanceDecl, + getBaseType(requiredInheritanceDeclRef)); + continue; + } + + // Look for a member in the type that can satisfy the + // interface requirement. + auto conformanceWitness = findWitnessForInterfaceRequirement( + typeDecl, + inheritanceDecl, + interfaceDeclRef, + requiredMemberDeclRef); + + if (!conformanceWitness) + continue; + + // Store that witness into a table stored on the `inheritnaceDecl` + // so that it can be used for downstream code generation. + + inheritanceDecl->requirementWitnesses.Add(requiredMemberDeclRef, conformanceWitness); + } + } + + void checkConformanceToType( + AggTypeDecl* typeDecl, + InheritanceDecl* inheritanceDecl, + Type* baseType) + { + if (auto baseDeclRefType = baseType->As<DeclRefType>()) + { + auto baseTypeDeclRef = baseDeclRefType->declRef; + if (auto baseInterfaceDeclRef = baseTypeDeclRef.As<InterfaceDecl>()) + { + // The type is stating that it conforms to an interface. + // We need to check that it provides all of the members + // required by that interface. + checkInterfaceConformance( + typeDecl, + inheritanceDecl, + baseInterfaceDeclRef); + return; + } + } + + getSink()->diagnose(inheritanceDecl, Diagnostics::unimplemented, "type not supported for inheritance"); + } + + // Check that the type declaration `typeDecl`, which + // declares that it inherits from another type via + // `inheritanceDecl` actually does what it needs to + // for that inheritance to be valid. + void checkConformance( + AggTypeDecl* typeDecl, + InheritanceDecl* inheritanceDecl) + { + // Look at the type being inherited from, and validate + // appropriately. + auto baseType = inheritanceDecl->base.type; + checkConformanceToType(typeDecl, inheritanceDecl, baseType); + } + + void visitAggTypeDecl(AggTypeDecl* decl) { - if (structNode->IsChecked(DeclCheckState::Checked)) + if (decl->IsChecked(DeclCheckState::Checked)) return; - structNode->SetCheckState(DeclCheckState::Checked); - for (auto field : structNode->GetFields()) + // TODO: we should check inheritance declarations + // first, since they need to be validated before + // we can make use of the type (e.g., you need + // to know that `A` inherits from `B` in order + // to check an expression like `aValue.bMethod()` + // where `aValue` is of type `A` but `bMethod` + // is defined in type `B`. + // + // TODO: We should also add a pass that takes + // all the stated inheritance relationships, + // expands them to include implicitic inheritance, + // and then linearizes them. This would allow + // later passes that need to know everything + // a type inherits from to proceed linearly + // through the list, rather than having to + // recurse (and potentially see the same interface + // more than once). + + decl->SetCheckState(DeclCheckState::CheckedHeader); + + // Now check all of the member declarations. + for (auto member : decl->Members) { - checkDecl(field); + checkDecl(member); } + + // After we've checked members, we need to go through + // any inheritance clauses on the type itself, and + // confirm that the type actually provides whatever + // those clauses require. + + if (auto interfaceDecl = dynamic_cast<InterfaceDecl*>(decl)) + { + // Don't check that an interface conforms to the + // things it inherits from. + } + else + { + // For non-interface types we need to check conformance. + // + // TODO: Need to figure out what this should do for + // `abstract` types if we ever add them. Should they + // be required to implement all interface requirements, + // just with `abstract` methods that replicate things? + // (That's what C# does). + + for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) + { + checkConformance(decl, inheritanceDecl); + } + } + + decl->SetCheckState(DeclCheckState::Checked); } void visitDeclGroup(DeclGroup* declGroup) @@ -2751,38 +3044,34 @@ namespace Slang // Default behavior is to look at all available `__subscript` // declarations on the type and try to call one of them. - if (auto declRefType = baseType->AsDeclRefType()) { - if (auto aggTypeDeclRef = declRefType->declRef.As<AggTypeDecl>()) + LookupResult lookupResult = lookUpMember( + getSession(), + this, + getName("operator[]"), + baseType); + if (!lookupResult.isValid()) { - // Checking of the type must be complete before we can reference its members safely - EnsureDecl(aggTypeDeclRef.getDecl(), DeclCheckState::Checked); - - // Note(tfoley): The name used for lookup here is a bit magical, since - // it must match what the parser installed in subscript declarations. - LookupResult lookupResult = LookUpLocal( - getSession(), - this, getName("operator[]"), aggTypeDeclRef); - if (!lookupResult.isValid()) - { - goto fail; - } - - RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr( - lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); + goto fail; + } - // Now that we know there is at least one subscript member, - // we will construct a reference to it and try to call it + // Now that we know there is at least one subscript member, + // we will construct a reference to it and try to call it. + // + // Note: the expression may be an `OverloadedExpr`, in which + // case the attempt to call it will trigger overload + // resolution. + RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr( + lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); - RefPtr<InvokeExpr> subscriptCallExpr = new InvokeExpr(); - subscriptCallExpr->loc = subscriptExpr->loc; - subscriptCallExpr->FunctionExpr = subscriptFuncExpr; + RefPtr<InvokeExpr> subscriptCallExpr = new InvokeExpr(); + subscriptCallExpr->loc = subscriptExpr->loc; + subscriptCallExpr->FunctionExpr = subscriptFuncExpr; - // TODO(tfoley): This path can support multiple arguments easily - subscriptCallExpr->Arguments.Add(subscriptExpr->IndexExpression); + // TODO(tfoley): This path can support multiple arguments easily + subscriptCallExpr->Arguments.Add(subscriptExpr->IndexExpression); - return CheckInvokeExprWithCheckedOperands(subscriptCallExpr.Ptr()); - } + return CheckInvokeExprWithCheckedOperands(subscriptCallExpr.Ptr()); } fail: @@ -2987,9 +3276,59 @@ namespace Slang vectorType->elementCount); } - bool DoesTypeConformToInterface( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef) + struct TypeWitnessBreadcrumb + { + TypeWitnessBreadcrumb* prev; + DeclRef<Decl> declRef; + }; + + RefPtr<Val> createTypeWitness( + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef, + TypeWitnessBreadcrumb* inBreadcrumbs) + { + if(!inBreadcrumbs) + { + // We need to construct a witness to the fact + // that `type` has been proven to be equal + // to `interfaceDeclRef`. + // + SLANG_UNEXPECTED("reflexive type witness"); + return nullptr; + } + + auto breadcrumbs = inBreadcrumbs; + + auto bb = breadcrumbs; + breadcrumbs = breadcrumbs->prev; + + if(breadcrumbs) + { + // There are multiple steps in the proof, so + // we need a transitive witness to show that + // because `A : B` and `B : C` then `A : C` + // + SLANG_UNEXPECTED("transitive type witness"); + return nullptr; + } + + // Simple case: we have a single declaration + // that shows that `type` conforms to `interfaceDeclRef`. + // + + RefPtr<DeclaredSubtypeWitness> witness = new DeclaredSubtypeWitness(); + witness->sub = type; + witness->sup = DeclRefType::Create(getSession(), interfaceDeclRef); + witness->declRef = bb->declRef; + return witness; + } + + bool doesTypeConformToInterfaceImpl( + RefPtr<Type> originalType, + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef, + RefPtr<Val>* outWitness, + TypeWitnessBreadcrumb* inBreadcrumbs) { // for now look up a conformance member... if(auto declRefType = type->As<DeclRefType>()) @@ -3002,7 +3341,13 @@ namespace Slang // the interface needs to be "object-safe" for us to // really make this determination... if(declRef == interfaceDeclRef) + { + if(outWitness) + { + *outWitness = createTypeWitness(originalType, interfaceDeclRef, inBreadcrumbs); + } return true; + } if( auto aggTypeDeclRef = declRef.As<AggTypeDecl>() ) { @@ -3022,8 +3367,18 @@ namespace Slang // conformances multiple times. auto inheritedType = getBaseType(inheritanceDeclRef); - if(DoesTypeConformToInterface(inheritedType, interfaceDeclRef)) + + // We need to ensure that the witness that gets created + // is a composite one, reflecting lookup through + // the inheritance declaration. + TypeWitnessBreadcrumb breadcrumb; + breadcrumb.prev = inBreadcrumbs; + breadcrumb.declRef = inheritanceDeclRef; + + if(doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb)) + { return true; + } } } else if( auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>() ) @@ -3045,8 +3400,18 @@ namespace Slang if(subDeclRef->declRef != genericTypeParamDeclRef) continue; - if(DoesTypeConformToInterface(sup, interfaceDeclRef)) + // The witness that we create needs to reflect that + // it found the needed conformance by lookup through + // a generic type constraint. + + TypeWitnessBreadcrumb breadcrumb; + breadcrumb.prev = inBreadcrumbs; + breadcrumb.declRef = constraintDeclRef; + + if(doesTypeConformToInterfaceImpl(originalType, sup, interfaceDeclRef, outWitness, &breadcrumb)) + { return true; + } } } } @@ -3055,6 +3420,22 @@ namespace Slang return false; } + bool DoesTypeConformToInterface( + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + return doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, nullptr, nullptr); + } + + RefPtr<Val> tryGetInterfaceConformanceWitness( + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + RefPtr<Val> result; + doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, &result, nullptr); + return result; + } + RefPtr<Type> TryJoinTypeWithInterface( RefPtr<Type> type, DeclRef<InterfaceDecl> interfaceDeclRef) @@ -3308,6 +3689,7 @@ namespace Slang ArityChecked, FixityChecked, TypeChecked, + DirectionChecked, Appicable, }; Status status = Status::Unchecked; @@ -3324,6 +3706,11 @@ namespace Slang // How much conversion cost should be considered for this overload, // when ranking candidates. ConversionCost conversionCostSum = kConversionCost_None; + + // When required, a candidate can store a pre-checked list of + // arguments so that we don't have to repeat work across checking + // phases. Currently this is only needed for generics. + RefPtr<Substitutions> subst; }; @@ -3541,6 +3928,12 @@ namespace Slang { auto genericDeclRef = candidate.item.declRef.As<GenericDecl>(); + // We will go ahead and hang onto the arguments that we've + // already checked, since downstream validation might need + // them. + candidate.subst = new Substitutions(); + auto& checkedArgs = candidate.subst->args; + int aa = 0; for (auto memberRef : getMembers(genericDeclRef)) { @@ -3548,17 +3941,20 @@ namespace Slang { auto arg = context.getArg(aa++); + TypeExp typeExp; if (context.mode == OverloadResolveContext::Mode::JustTrying) { - if (!CanCoerceToProperType(TypeExp(arg))) + typeExp = tryCoerceToProperType(TypeExp(arg)); + if(!typeExp.type) { return false; } } else { - TypeExp typeExp = CoerceToProperType(TypeExp(arg)); + typeExp = CoerceToProperType(TypeExp(arg)); } + checkedArgs.Add(typeExp.type); } else if (auto valParamRef = memberRef.As<GenericValueParamDecl>()) { @@ -3573,11 +3969,10 @@ namespace Slang } candidate.conversionCostSum += cost; } - else - { - arg = Coerce(GetType(valParamRef), arg); - auto val = ExtractGenericArgInteger(arg); - } + + arg = Coerce(GetType(valParamRef), arg); + auto val = ExtractGenericArgInteger(arg); + checkedArgs.Add(val); } else { @@ -3585,6 +3980,7 @@ namespace Slang } } + // Okay, we've made it! return true; } @@ -3650,6 +4046,100 @@ namespace Slang return true; } + // Create a witness that attests to the fact that `type` + // is equal to itself. + RefPtr<Val> createTypeEqualityWitness( + Type* type) + { + SLANG_UNEXPECTED("unimplemented"); + } + + // If `sub` is a subtype of `sup`, then return a value that + // can serve as a "witness" for that fact. + RefPtr<Val> tryGetSubtypeWitness( + RefPtr<Type> sub, + RefPtr<Type> sup) + { + if(sub->Equals(sup)) + { + // They are the same type, so we just need a witness + // for type equality. + return createTypeEqualityWitness(sub); + } + + if(auto supDeclRefType = sup->As<DeclRefType>()) + { + auto supDeclRef = supDeclRefType->declRef; + if(auto supInterfaceDeclRef = supDeclRef.As<InterfaceDecl>()) + { + if(auto witness = tryGetInterfaceConformanceWitness(sub, supInterfaceDeclRef)) + { + return witness; + } + } + } + + return nullptr; + } + + // In the case where we are explicitly applying a generic + // to arguments (e.g., `G<A,B>`) check that the constraints + // on those parameters are satisfied. + // + // Note: the constraints actually work as additional parameters/arguments + // of the generic, and so we need to reify them into the final + // argument list. + // + bool TryCheckOverloadCandidateConstraints( + OverloadResolveContext& context, + OverloadCandidate const& candidate) + { + // We only need this step for generics, so always succeed on + // everything else. + if(candidate.flavor != OverloadCandidate::Flavor::Generic) + return true; + + auto genericDeclRef = candidate.item.declRef.As<GenericDecl>(); + assert(genericDeclRef); + + // We should have the existing arguments to the generic + // handy, so that we can construct a substitution list. + + RefPtr<Substitutions> subst = candidate.subst; + assert(subst); + + subst->genericDecl = genericDeclRef.getDecl(); + subst->outer = genericDeclRef.substitutions; + + for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType<GenericTypeConstraintDecl>() ) + { + DeclRef<GenericTypeConstraintDecl> constraintDeclRef( + constraintDecl, + subst); + + auto sub = GetSub(constraintDeclRef); + auto sup = GetSup(constraintDeclRef); + + auto subTypeWitness = tryGetSubtypeWitness(sub, sup); + if(subTypeWitness) + { + subst->args.Add(subTypeWitness); + } + else + { + if(context.mode != OverloadResolveContext::Mode::JustTrying) + { + // TODO: diagnose a problem here + getSink()->diagnose(context.loc, Diagnostics::unimplemented, "generic constraint not satisfied"); + } + return false; + } + } + + // Done checking all the constraints, hooray. + return true; + } + // Try to check an overload candidate, but bail out // if any step fails void TryCheckOverloadCandidate( @@ -3671,15 +4161,18 @@ namespace Slang if (!TryCheckOverloadCandidateDirections(context, candidate)) return; + candidate.status = OverloadCandidate::Status::DirectionChecked; + if (!TryCheckOverloadCandidateConstraints(context, candidate)) + return; + candidate.status = OverloadCandidate::Status::Appicable; } // Create the representation of a given generic applied to some arguments - RefPtr<Expr> CreateGenericDeclRef( - RefPtr<Expr> baseExpr, - RefPtr<Expr> originalExpr, - UInt argCount, - RefPtr<Expr> const* args) + RefPtr<Expr> createGenericDeclRef( + RefPtr<Expr> baseExpr, + RefPtr<Expr> originalExpr, + RefPtr<Substitutions> subst) { auto baseDeclRefExpr = baseExpr.As<DeclRefExpr>(); if (!baseDeclRefExpr) @@ -3694,16 +4187,9 @@ namespace Slang return CreateErrorExpr(originalExpr); } - RefPtr<Substitutions> subst = new Substitutions(); subst->genericDecl = baseGenericRef.getDecl(); subst->outer = baseGenericRef.substitutions; - for(UInt aa = 0; aa < argCount; ++aa) - { - auto arg = args[aa]; - subst->args.Add(ExtractGenericArgVal(arg)); - } - DeclRef<Decl> innerDeclRef(GetInner(baseGenericRef), subst); return ConstructDeclRefExpr( @@ -3753,6 +4239,9 @@ namespace Slang if (!TryCheckOverloadCandidateDirections(context, candidate)) goto error; + if (!TryCheckOverloadCandidateConstraints(context, candidate)) + goto error; + { auto baseExpr = ConstructLookupResultExpr( candidate.item, context.baseExpr, context.funcLoc); @@ -3792,9 +4281,10 @@ namespace Slang break; case OverloadCandidate::Flavor::Generic: - return CreateGenericDeclRef(baseExpr, context.originalExpr, - context.argCount, - context.args); + return createGenericDeclRef( + baseExpr, + context.originalExpr, + candidate.subst); break; default: @@ -5144,7 +5634,7 @@ namespace Slang expr->type = QualType(getSession()->getErrorType()); - auto lookupResult = LookUp( + auto lookupResult = lookUp( getSession(), this, expr->name, expr->scope); if (lookupResult.isValid()) @@ -5449,6 +5939,12 @@ namespace Slang // Note: Checking for vector types before declaration-reference types, // because vectors are also declaration reference types... + // + // Also note: the way this is done right now means that the ability + // to swizzle vectors interferes with any chance of looking up + // members via extension, for vector or scalar types. + // + // TODO: Matrix swizzles probably need to be handled at some point. if (auto baseVecType = baseType->AsVectorType()) { return CheckSwizzleExpr( @@ -5473,69 +5969,45 @@ namespace Slang // TODO: this duplicates a *lot* of logic with the case below. // We need to fix that. auto type = typeType->type; - if(auto declRefType = type->AsDeclRefType()) - { - if (auto aggTypeDeclRef = declRefType->declRef.As<AggTypeDecl>()) - { - // Checking of the type must be complete before we can reference its members safely - EnsureDecl(aggTypeDeclRef.getDecl(), DeclCheckState::Checked); - - LookupResult lookupResult = LookUpLocal( - getSession(), - this, expr->name, aggTypeDeclRef); - if (!lookupResult.isValid()) - { - return lookupResultFailure(expr, baseType); - } - // TODO: need to filter for declarations that are valid to refer - // to in this context... - - return createLookupResultExpr( - lookupResult, - expr->BaseExpression, - expr->loc); - } - } - } - else if (auto declRefType = baseType->AsDeclRefType()) - { - if (auto aggTypeDeclRef = declRefType->declRef.As<AggTypeDecl>()) + LookupResult lookupResult = lookUpMember( + getSession(), + this, + expr->name, + type); + if (!lookupResult.isValid()) { - // Checking of the type must be complete before we can reference its members safely - EnsureDecl(aggTypeDeclRef.getDecl(), DeclCheckState::Checked); - - LookupResult lookupResult = LookUpLocal( - getSession(), - this, expr->name, aggTypeDeclRef); - if (!lookupResult.isValid()) - { - return lookupResultFailure(expr, baseType); - } - - return createLookupResultExpr( - lookupResult, - expr->BaseExpression, - expr->loc); + return lookupResultFailure(expr, baseType); } - // catch-all - return lookupResultFailure(expr, baseType); + // TODO: need to filter for declarations that are valid to refer + // to in this context... + + return createLookupResultExpr( + lookupResult, + expr->BaseExpression, + expr->loc); } - // All remaining cases assume we have a `BasicType` - else if (!baseType->AsBasicType()) - expr->type = QualType(getSession()->getErrorType()); else - expr->type = QualType(getSession()->getErrorType()); - if (!baseType->Equals(getSession()->getErrorType()) && - expr->type->Equals(getSession()->getErrorType())) { - if (!isRewriteMode()) + LookupResult lookupResult = lookUpMember( + getSession(), + this, + expr->name, + baseType.Ptr()); + if (!lookupResult.isValid()) { - getSink()->diagnose(expr, Diagnostics::typeHasNoPublicMemberOfName, baseType, expr->name); + return lookupResultFailure(expr, baseType); } + + // TODO: need to filter for declarations that are valid to refer + // to in this context... + + return createLookupResultExpr( + lookupResult, + expr->BaseExpression, + expr->loc); } - return expr; } SemanticsVisitor & operator = (const SemanticsVisitor &) = delete; @@ -5810,6 +6282,14 @@ namespace Slang *outTypeResult = type; return QualType(getTypeType(type)); } + else if (auto constraintDeclRef = declRef.As<GenericTypeConstraintDecl>()) + { + // When we access a constraint or an inheritance decl (as a member), + // we are conceptually performing a "cast" to the given super-type, + // with the declaration showing that such a cast is legal. + auto type = GetSup(constraintDeclRef); + return QualType(type); + } else if (auto funcDeclRef = declRef.As<CallableDecl>()) { auto type = getFuncType(session, funcDeclRef); @@ -5842,4 +6322,57 @@ namespace Slang return semantics->ApplyExtensionToType(extDecl, type); } + // Sometimes we need to refer to a declaration the way that it would be specialized + // inside the context where it is declared (e.g., with generic parameters filled in + // using their archetypes). + // + RefPtr<Substitutions> createDefaultSubstitutions( + Session* session, + Decl* decl, + Substitutions* parentSubst) + { + auto dd = decl->ParentDecl; + if( auto genericDecl = dynamic_cast<GenericDecl*>(dd) ) + { + // We don't want to specialize references to anything + // other than the "inner" declaration itself. + if(decl != genericDecl->inner) + return parentSubst; + + RefPtr<Substitutions> subst = new Substitutions(); + subst->genericDecl = genericDecl; + subst->outer = parentSubst; + + for( auto mm : genericDecl->Members ) + { + if( auto genericTypeParamDecl = mm.As<GenericTypeParamDecl>() ) + { + subst->args.Add(DeclRefType::Create(session, makeDeclRef(genericTypeParamDecl.Ptr()))); + } + else if( auto genericValueParamDecl = mm.As<GenericValueParamDecl>() ) + { + subst->args.Add(new GenericParamIntVal(makeDeclRef(genericValueParamDecl.Ptr()))); + } + } + + // TODO: need to fill in constraints here... + + return subst; + } + return parentSubst; + } + + RefPtr<Substitutions> createDefaultSubstitutions( + Session* session, + Decl* decl) + { + RefPtr<Substitutions> subst; + if( auto parentDecl = decl->ParentDecl ) + { + subst = createDefaultSubstitutions(session, parentDecl); + } + subst = createDefaultSubstitutions(session, decl, subst); + return subst; + } + } diff --git a/source/slang/decl-defs.h b/source/slang/decl-defs.h index c9860f9bf..c96fe6d09 100644 --- a/source/slang/decl-defs.h +++ b/source/slang/decl-defs.h @@ -96,6 +96,15 @@ SIMPLE_SYNTAX_CLASS(InterfaceDecl, AggTypeDecl) SYNTAX_CLASS(InheritanceDecl, Decl) // The type expression as written SYNTAX_FIELD(TypeExp, base) + +RAW( + // After checking, this dictionary will map members + // required by the base type to their concrete + // implementations in the type that contains + // this inheritance declaration. + Dictionary<DeclRef<Decl>, Decl*> requirementWitnesses; +) + END_SYNTAX_CLASS() // TODO: may eventually need sub-classes for explicit/direct vs. implicit/indirect inheritance diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 492c0e084..36e68c853 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -316,6 +316,8 @@ DIAGNOSTIC(38001, Error, ambiguousEntryPoint, "more than one funct DIAGNOSTIC(38002, Note, entryPointCandidate, "see candidate declaration for entry point '$0'") DIAGNOSTIC(38003, Error, entryPointSymbolNotAFunction, "entry point '$0' must be declared as a function") +DIAGNOSTIC(38100, Error, typeDoesntImplementInterfaceRequirement, "type '$0' does not provide required interface member '$1'") + // // 4xxxx - IL code generation. // diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index f76ab1db1..6b411a25d 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -5172,7 +5172,7 @@ emitDeclImpl(decl, nullptr); // Start by emitting the non-terminator instructions in the block. auto terminator = block->getLastInst(); assert(isTerminatorInst(terminator)); - for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->nextInst) + for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst()) { emitIRInst(context, inst); } @@ -5508,6 +5508,26 @@ emitDeclImpl(decl, nullptr); EmitContext* context, IRFunc* func) { + // We don't want to declare generic functions, + // because none of our targets actually support them. + if(func->genericDecl) + return; + + // We also don't want to emit declarations for operations + // that only appear in the IR as stand-ins for built-in + // operations on that target. + if (isTargetIntrinsic(context, func)) + return; + + // Finally, don't emit a declaration for an entry point, + // because it might need meta-data attributes attached + // to it, and the HLSL compiler will get upset if the + // forward declaration doesn't *also* have those + // attributes. + if(asEntryPoint(func)) + return; + + // A function declaration doesn't have any IR basic blocks, // and as a result it *also* doesn't have the IR `param` instructions, // so we need to emit a declaration entirely from the type. @@ -5566,82 +5586,6 @@ emitDeclImpl(decl, nullptr); return nullptr; } -#if 0 - void emitGLSLEntryPointFunc( - EmitContext* context, - IRFunc* func) - { - auto funcType = func->getType(); - auto resultType = func->getResultType(); - - auto entryPointLayout = getEntryPointLayout(context, func); - assert(entryPointLayout); - - // TODO: need to deal with decorations on the entry point - // that should be turned into global-scope `layout` qualifiers. - - // TODO: emit kernel inputs and outputs to globals. - - // Emit a global `out` declaration to hold the output from our shader - // kernel. - // - // TODO: need to generate unique names beter than this - // - // TODO: need to handle the case where the output is - // a structure (should that be fixed up at the IR level, - // or here?). - // Best option might be to translate the entry-point - // result parameter into an `out` parameter, so that - // we can handle those uniformly. - // - String resultName = getIRName(func) + "_result"; - emitGLSLLayoutQualifiers(entryPointLayout->resultLayout); - emit("out "); - emitIRType(context, resultType, resultName); - emit(";\n"); - - // Emit global `in` and/or `out` declarations for the - // parameters of our shader kernel. - // - // TODO: We need to make sure these names don't collide with anything. - // - // TODO: We need to handle scalarization here. - // - auto firstParam = func->getFirstParam(); - for( auto pp = firstParam; pp; pp = pp->getNextParam() ) - { - // TODO: actually handle `out` parameters here. - - auto paramLayout = getVarLayout(context, pp); - auto paramName = getIRName(pp); - emitGLSLLayoutQualifiers(paramLayout); - emit("in "); - emitIRType(context, pp->getType(), paramName); - emit(";\n"); - } - - // Now that we've emitted our parameter declarations, - // we can start to emit the body of the entry point: - // - emit("void main()\n{\n"); - - // We had better not be trying to output an entry - // point from a declaration rather than a definition. - assert(isDefinition(func)); - - // At the most basic, we just want to emit the operations in - // the entry point function directly, but with the small catch - // that if there was a `return` statement in there somewhere, - // we need to turn that into a write to our output variable. - // - // TODO: yeah, that should get cleared up at the IR level... - // - emitIRStmtsForBlocks(context, func->getFirstBlock(), nullptr); - - emit("}\n"); - } -#endif - EntryPointLayout* asEntryPoint(IRFunc* func) { if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>()) @@ -5697,26 +5641,11 @@ emitDeclImpl(decl, nullptr); EmitContext* context, IRFunc* func) { -#if 0 - if( getTarget(context) == CodeGenTarget::GLSL - && isEntryPoint(func) ) - { - // We have a shader entry point, and that - // requires a different strategy for source - // code generation in GLSL, because the - // parameters/result of the entry point - // need to be translated into globals. - // - - emitGLSLEntryPointFunc(context, func); - } - else -#endif if(func->genericDecl) { Emit("/* "); emitIRFuncDecl(context, func); - Emit(" */"); + Emit(" */\n"); return; } @@ -6129,12 +6058,6 @@ emitDeclImpl(decl, nullptr); emitIRGlobalVar(context, (IRGlobalVar*) inst); break; -#if 0 - case kIROp_StructType: - emitIRStruct(context, (IRStructDecl*) inst); - break; -#endif - case kIROp_Var: emitIRVar(context, (IRVar*) inst); break; @@ -6257,7 +6180,7 @@ emitDeclImpl(decl, nullptr); emitIRUsedTypesForValue(context, pp); } - for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { emitIRUsedTypesForValue(context, ii); } @@ -6289,6 +6212,20 @@ emitDeclImpl(decl, nullptr); { emitIRUsedTypesForModule(context, module); + // Before we emit code, we need to forward-declare + // all of our functions so that we don't have to + // sort them by dependencies. + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) + { + if(gv->op != kIROp_Func) + continue; + + auto func = (IRFunc*) gv; + emitIRFuncDecl(context, func); + } + + + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { emitIRGlobalInst(context, gv); @@ -6454,9 +6391,12 @@ String emitEntryPoint( specializeGenerics(lowered); -// fprintf(stderr, "###\n"); -// dumpIR(lowered); -// fprintf(stderr, "###\n"); + // Debugging code for IR transformations... +#if 0 + fprintf(stderr, "###\n"); + dumpIR(lowered); + fprintf(stderr, "###\n"); +#endif // // TODO: Need to decide whether to do these before or after diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h index 636eeec16..d2e2c12e8 100644 --- a/source/slang/ir-inst-defs.h +++ b/source/slang/ir-inst-defs.h @@ -97,6 +97,7 @@ INST(FloatLit, float_constant, 0, 0) INST(decl_ref, decl_ref, 0, 0) INST(specialize, specialize, 2, 0) +INST(lookup_interface_method, lookup_interface_method, 2, 0) INST(Construct, construct, 0, 0) INST(Call, call, 1, 0) @@ -106,6 +107,8 @@ INST(Func, func, 0, PARENT) INST(Block, block, 0, PARENT) INST(global_var, global_var, 0, 0) +INST(witness_table, witness_table, 0, 0) +INST(witness_table_entry, witness_table_entry, 2, 0) INST(Param, param, 0, 0) INST(StructField, field, 0, 0) diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index c8e6ee52b..f7bcc2eb2 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -74,6 +74,16 @@ struct IRSpecialize : IRInst IRUse specDeclRefVal; }; +// An instruction that looks up the implementation +// of an interface operation identified by `requirementDeclRef` +// in the witness table `witnessTable` which should +// hold the conformance information for a specific type. +struct IRLookupWitnessMethod : IRInst +{ + IRUse witnessTable; + IRUse requirementDeclRef; +}; + // struct IRCall : IRInst @@ -251,6 +261,26 @@ struct IRGlobalVar : IRGlobalValue PtrType* getType() { return type.As<PtrType>(); } }; +// An entry in a witness table (see below) +struct IRWitnessTableEntry : IRUser +{ + // The AST-level requirement + IRUse requirementKey; + + // The IR-level value that satisfies the requirement + IRUse satisfyingVal; +}; + +// A witness table is a global value that stores +// information about how a type conforms to some +// interface. It basically takes the form of a +// map from the required members of the interface +// to the IR values that satisfy those requirements. +struct IRWitnessTable : IRGlobalValue +{ + IRValueList<IRWitnessTableEntry> entries; +}; + // Description of an instruction to be used for global value numbering @@ -330,6 +360,16 @@ struct IRBuilder IRValue* genericVal, DeclRef<Decl> specDeclRef); + IRValue* emitLookupInterfaceMethodInst( + IRType* type, + IRValue* witnessTableVal, + IRValue* interfaceMethodVal); + + IRValue* emitLookupInterfaceMethodInst( + IRType* type, + DeclRef<Decl> witnessTableDeclRef, + DeclRef<Decl> interfaceMethodDeclRef); + IRInst* emitCallInst( IRType* type, IRValue* func, @@ -352,6 +392,11 @@ struct IRBuilder IRFunc* createFunc(); IRGlobalVar* createGlobalVar( IRType* valueType); + IRWitnessTable* createWitnessTable(); + IRWitnessTableEntry* createWitnessTableEntry( + IRWitnessTable* witnessTable, + IRValue* requirementKey, + IRValue* satisfyingVal); IRBlock* createBlock(); IRBlock* emitBlock(); diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 14639de7e..5cc73a36b 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -41,7 +41,7 @@ namespace Slang // - void IRUse::init(IRInst* u, IRValue* v) + void IRUse::init(IRUser* u, IRValue* v) { user = u; usedValue = v; @@ -57,7 +57,7 @@ namespace Slang // - IRUse* IRInst::getArgs() + IRUse* IRUser::getArgs() { // We assume that *all* instructions are laid out // in memory such that their arguments come right @@ -154,18 +154,38 @@ namespace Slang return isTerminatorInst(inst->op); } + // + + void IRValueListBase::addImpl(IRValue* parent, IRChildValue* val) + { + val->parent = parent; + val->prev = last; + val->next = nullptr; + + if (last) + { + last->next = val; + } + else + { + first = val; + } + + last = val; + } + // // Add an instruction to a specific parent void IRBuilder::addInst(IRBlock* block, IRInst* inst) { - inst->parentBlock = block; + inst->parent = block; if (!block->firstInst) { - inst->prevInst = nullptr; - inst->nextInst = nullptr; + inst->prev = nullptr; + inst->next = nullptr; block->firstInst = inst; block->lastInst = inst; @@ -174,10 +194,10 @@ namespace Slang { auto prev = block->lastInst; - inst->prevInst = prev; - inst->nextInst = nullptr; + inst->prev = prev; + inst->next = nullptr; - prev->nextInst = inst; + prev->next = inst; block->lastInst = inst; } } @@ -402,7 +422,7 @@ namespace Slang bool operator==(IRInstKey const& left, IRInstKey const& right) { if(left.inst->op != right.inst->op) return false; - if(left.inst->parentBlock != right.inst->parentBlock) return false; + if(left.inst->parent != right.inst->parent) return false; if(left.inst->argCount != right.inst->argCount) return false; auto argCount = left.inst->argCount; @@ -420,7 +440,7 @@ namespace Slang int IRInstKey::GetHashCode() { auto code = Slang::GetHashCode(inst->op); - code = combineHash(code, Slang::GetHashCode(inst->parentBlock)); + code = combineHash(code, Slang::GetHashCode(inst->parent)); code = combineHash(code, Slang::GetHashCode(inst->argCount)); auto argCount = inst->argCount; @@ -487,7 +507,7 @@ namespace Slang // way: we will construct a temporary instruction and // then use it to look up in a cache of instructions. - irValue = createInst<IRConstant>(builder, op, type); + irValue = createValue<IRConstant>(builder, op, type); memcpy(&irValue->u, value, valueSize); key.inst = irValue; @@ -534,7 +554,7 @@ namespace Slang DeclRefBase const& declRef) { // TODO: we should cache these... - auto irValue = createInst<IRDeclRef>( + auto irValue = createValue<IRDeclRef>( this, kIROp_decl_ref, nullptr); @@ -573,6 +593,33 @@ namespace Slang return inst; } + IRValue* IRBuilder::emitLookupInterfaceMethodInst( + IRType* type, + IRValue* witnessTableVal, + IRValue* interfaceMethodVal) + { + auto inst = createInst<IRLookupWitnessMethod>( + this, + kIROp_lookup_interface_method, + type, + witnessTableVal, + interfaceMethodVal); + addInst(inst); + return inst; + } + + IRValue* IRBuilder::emitLookupInterfaceMethodInst( + IRType* type, + DeclRef<Decl> witnessTableDeclRef, + DeclRef<Decl> interfaceMethodDeclRef) + { + auto witnessTableVal = getDeclRefVal(witnessTableDeclRef); + auto interfaceMethodVal = getDeclRefVal(interfaceMethodDeclRef); + return emitLookupInterfaceMethodInst(type, witnessTableVal, interfaceMethodVal); + } + + + IRInst* IRBuilder::emitCallInst( IRType* type, IRValue* func, @@ -792,6 +839,37 @@ namespace Slang return globalVar; } + IRWitnessTable* IRBuilder::createWitnessTable() + { + IRWitnessTable* witnessTable = createValue<IRWitnessTable>( + this, + kIROp_witness_table, + nullptr); + addGlobalValue(getModule(), witnessTable); + return witnessTable; + } + + IRWitnessTableEntry* IRBuilder::createWitnessTableEntry( + IRWitnessTable* witnessTable, + IRValue* requirementKey, + IRValue* satisfyingVal) + { + IRWitnessTableEntry* entry = createInst<IRWitnessTableEntry>( + this, + kIROp_witness_table_entry, + nullptr, + requirementKey, + satisfyingVal); + + if (witnessTable) + { + witnessTable->entries.add(witnessTable, entry); + } + + return entry; + } + + IRBlock* IRBuilder::createBlock() { return createValue<IRBlock>( @@ -1284,6 +1362,8 @@ namespace Slang switch(inst->op) { case kIROp_Func: + case kIROp_global_var: + case kIROp_witness_table: { auto irFunc = (IRFunc*) inst; dump(context, "@"); @@ -1312,6 +1392,10 @@ namespace Slang IRDumpContext* context, IRType* type); + static void dumpDeclRef( + IRDumpContext* context, + DeclRef<Decl> const& declRef); + static void dumpOperand( IRDumpContext* context, IRValue* inst) @@ -1341,6 +1425,12 @@ namespace Slang dumpType(context, (IRType*)inst); return; + case kIROp_decl_ref: + dump(context, "$\""); + dumpDeclRef(context, ((IRDeclRef*)inst)->declRef); + dump(context, "\""); + return; + default: break; } @@ -1355,11 +1445,6 @@ namespace Slang dump(context, getText(name).Buffer()); } - - static void dumpDeclRef( - IRDumpContext* context, - DeclRef<Decl> const& declRef); - static void dumpVal( IRDumpContext* context, Val* val) @@ -1376,6 +1461,20 @@ namespace Slang { dumpDeclRef(context, genericParamVal->declRef); } + else if(auto declaredSubtypeWitness = dynamic_cast<DeclaredSubtypeWitness*>(val)) + { + dump(context, "DeclaredSubtypeWitness("); + dumpType(context, declaredSubtypeWitness->sub); + dump(context, ", "); + dumpType(context, declaredSubtypeWitness->sup); + dump(context, ", "); + dumpDeclRef(context, declaredSubtypeWitness->declRef); + dump(context, ")"); + } + else if (auto proxyVal = dynamic_cast<IRProxyVal*>(val)) + { + dumpOperand(context, proxyVal->inst); + } else { dump(context, "???"); @@ -1417,6 +1516,20 @@ namespace Slang dump(context, "."); } dump(context, decl->getName()); + if (auto genericTypeConstraintDecl = dynamic_cast<GenericTypeConstraintDecl*>(decl)) + { + dump(context, "{"); + dumpType(context, genericTypeConstraintDecl->sub); + dump(context, " : "); + dumpType(context, genericTypeConstraintDecl->sup); + dump(context, "}"); + } + else if (auto inheritanceDecl = dynamic_cast<InheritanceDecl*>(decl)) + { + dump(context, "{ _ : "); + dumpType(context, inheritanceDecl->base); + dump(context, "}"); + } if(genericParentDeclRef) { @@ -1538,7 +1651,7 @@ namespace Slang IRDumpContext* context, IRBlock* block) { - for (auto ii = block->firstInst; ii; ii = ii->nextInst) + for (auto ii = block->firstInst; ii; ii = ii->getNextInst()) { dumpInst(context, ii); } @@ -1775,15 +1888,16 @@ namespace Slang bool first = true; for (auto mm : genericDecl->Members) { - if (!first) dump(context, ", "); if( auto typeParamDecl = mm.As<GenericTypeParamDecl>() ) { + if (!first) dump(context, ", "); dumpDeclRef(context, makeDeclRef(typeParamDecl.Ptr())); first = false; } else if( auto valueParamDecl = mm.As<GenericTypeParamDecl>() ) { + if (!first) dump(context, ", "); dumpDeclRef(context, makeDeclRef(valueParamDecl.Ptr())); first = false; } @@ -1791,11 +1905,11 @@ namespace Slang first = true; for (auto mm : genericDecl->Members) { - if (!first) dump(context, ", "); - else dump(context, " where "); - if( auto constraintDecl = mm.As<GenericTypeConstraintDecl>() ) { + if (!first) dump(context, ", "); + else dump(context, " where "); + dumpType(context, constraintDecl->sub); dump(context, " : "); dumpType(context, constraintDecl->sup); @@ -1882,6 +1996,37 @@ namespace Slang dump(context, ";\n"); } + void dumpIRWitnessTableEntry( + IRDumpContext* context, + IRWitnessTableEntry* entry) + { + dump(context, "witness_table_entry("); + dumpOperand(context, entry->requirementKey.usedValue); + dump(context, ","); + dumpOperand(context, entry->satisfyingVal.usedValue); + dump(context, ")\n"); + } + + void dumpIRWitnessTable( + IRDumpContext* context, + IRWitnessTable* witnessTable) + { + dump(context, "\n"); + dumpIndent(context); + dump(context, "ir_witness_table "); + dumpID(context, witnessTable); + dump(context, "\n{\n"); + context->indent++; + + for (auto entry : witnessTable->entries) + { + dumpIRWitnessTableEntry(context, entry); + } + + context->indent--; + dump(context, "}\n"); + } + void dumpIRGlobalValue( IRDumpContext* context, IRGlobalValue* value) @@ -1896,6 +2041,10 @@ namespace Slang dumpIRGlobalVar(context, (IRGlobalVar*)value); break; + case kIROp_witness_table: + dumpIRWitnessTable(context, (IRWitnessTable*)value); + break; + default: dump(context, "???\n"); break; @@ -2000,11 +2149,17 @@ namespace Slang void IRValue::deallocate() { +#if 0 + // I'm going to intentionally leak here, + // just to test that this is the source + // of my heap-corruption crashes. +#else // Run destructor to be sure... this->~IRValue(); // And then free the memory free((void*) this); +#endif } // Insert this instruction into the same basic block @@ -2014,24 +2169,24 @@ namespace Slang // Make sure this instruction has been removed from any previous parent this->removeFromParent(); - auto bb = other->parentBlock; + auto bb = other->getParentBlock(); assert(bb); - auto pp = other->prevInst; + auto pp = other->getPrevInst(); if( pp ) { - pp->nextInst = this; + pp->next = this; } else { bb->firstInst = this; } - this->prevInst = pp; - this->nextInst = other; - this->parentBlock = bb; + this->prev = pp; + this->next = other; + this->parent = bb; - other->prevInst = this; + other->prev = this; } // Remove this instruction from its parent block, @@ -2040,17 +2195,17 @@ namespace Slang { // If we don't currently have a parent, then // we are doing fine. - if(!parentBlock) + if(!getParentBlock()) return; - auto bb = parentBlock; - auto pp = prevInst; - auto nn = nextInst; + auto bb = getParentBlock(); + auto pp = getPrevInst(); + auto nn = getNextInst(); if(pp) { - SLANG_ASSERT(pp->parentBlock == bb); - pp->nextInst = nn; + SLANG_ASSERT(pp->getParentBlock() == bb); + pp->next = nn; } else { @@ -2059,17 +2214,17 @@ namespace Slang if(nn) { - SLANG_ASSERT(nn->parentBlock == bb); - nn->prevInst = pp; + SLANG_ASSERT(nn->getParentBlock() == bb); + nn->prev = pp; } else { bb->lastInst = pp; } - prevInst = nullptr; - nextInst = nullptr; - parentBlock = nullptr; + prev = nullptr; + next = nullptr; + parent = nullptr; } void IRInst::removeArguments() @@ -2552,7 +2707,7 @@ namespace Slang // TODO: This is silly, because we are looking at every instruction, // when we know that a `returnVal` should only ever appear as a // terminator... - for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { if(ii->op != kIROp_ReturnVal) continue; @@ -2720,7 +2875,7 @@ namespace Slang // Finally, we need to patch up the type of the entry point, // because it is no longer accurate. - auto voidFuncType = new FuncType(); + RefPtr<FuncType> voidFuncType = new FuncType(); voidFuncType->setSession(session); voidFuncType->resultType = session->getVoidType(); func->type = voidFuncType; @@ -2797,6 +2952,12 @@ namespace Slang { return originalType; } + + // A callback used to clone (or not) a declaration reference + virtual DeclRef<Decl> maybeCloneDeclRef(DeclRef<Decl> const& declRef) + { + return declRef; + } }; void registerClonedValue( @@ -2844,11 +3005,16 @@ namespace Slang // Override the "maybe clone" logic so that we always clone virtual IRValue* maybeCloneValue(IRValue* originalVal) override; + + // Override teh "maybe clone" logic so that we carefully + // clone any IR proxy values inside substitutions + virtual DeclRef<Decl> maybeCloneDeclRef(DeclRef<Decl> const& declRef) override; }; IRGlobalVar* cloneGlobalVar(IRSpecContext* context, IRGlobalVar* originalVar); IRFunc* cloneFunc(IRSpecContext* context, IRFunc* originalFunc); + IRWitnessTable* cloneWitnessTable(IRSpecContext* context, IRWitnessTable* originalVar); IRValue* IRSpecContext::maybeCloneValue(IRValue* originalValue) { @@ -2862,6 +3028,10 @@ namespace Slang return cloneFunc(this, (IRFunc*)originalValue); break; + case kIROp_witness_table: + return cloneWitnessTable(this, (IRWitnessTable*)originalValue); + break; + case kIROp_boolConst: { IRConstant* c = (IRConstant*)originalValue; @@ -2887,7 +3057,8 @@ namespace Slang case kIROp_decl_ref: { IRDeclRef* od = (IRDeclRef*)originalValue; - return builder->getDeclRefVal(od->declRef); + auto declRef = maybeCloneDeclRef(od->declRef); + return builder->getDeclRefVal(declRef); } break; @@ -2897,6 +3068,66 @@ namespace Slang } } + RefPtr<Val> cloneSubstitutionArg( + IRSpecContext* context, + Val* val) + { + if (auto proxyVal = dynamic_cast<IRProxyVal*>(val)) + { + auto newIRVal = context->maybeCloneValue(proxyVal->inst); + + RefPtr<IRProxyVal> newProxyVal = new IRProxyVal(); + newProxyVal->inst = newIRVal; + return newProxyVal; + } + else if (auto type = dynamic_cast<Type*>(val)) + { + return context->maybeCloneType(type); + } + else + { + return val; + } + } + + RefPtr<Substitutions> cloneSubstitutions( + IRSpecContext* context, + Substitutions* subst) + { + if (!subst) + return nullptr; + + RefPtr<Substitutions> newSubst = new Substitutions(); + newSubst->outer = cloneSubstitutions(context, subst->outer); + newSubst->genericDecl = subst->genericDecl; + + for (auto arg : subst->args) + { + auto newArg = cloneSubstitutionArg(context, arg); + newSubst->args.Add(arg); + } + + return newSubst; + } + + DeclRef<Decl> IRSpecContext::maybeCloneDeclRef(DeclRef<Decl> const& declRef) + { + // Un-specialized decl? Nothing to do. + if (!declRef.substitutions) + return declRef; + + DeclRef<Decl> newDeclRef = declRef; + + // Scan through substitutions and clone as needed. + // + // TODO: this is wasteful since we clone *everything* + newDeclRef.substitutions = cloneSubstitutions(this, declRef.substitutions); + + return newDeclRef; + + } + + IRValue* cloneValue( IRSpecContextBase* context, IRValue* originalValue) @@ -2970,6 +3201,30 @@ namespace Slang return clonedVar; } + IRWitnessTable* cloneWitnessTable(IRSpecContext* context, IRWitnessTable* originalTable) + { + auto clonedTable = context->builder->createWitnessTable(); + registerClonedValue(context, clonedTable, originalTable); + + auto mangledName = originalTable->mangledName; + clonedTable->mangledName = mangledName; + + cloneDecorations(context, clonedTable, originalTable); + + // Clone the entries in the witness table as well + for( auto originalEntry : originalTable->entries ) + { + auto clonedKey = context->maybeCloneValue(originalEntry->requirementKey.usedValue); + auto clonedVal = context->maybeCloneValue(originalEntry->satisfyingVal.usedValue); + auto clonedEntry = context->builder->createWitnessTableEntry( + clonedTable, + clonedKey, + clonedVal); + } + + return clonedTable; + } + void cloneFunctionCommon( IRSpecContextBase* context, IRFunc* clonedFunc, @@ -3023,7 +3278,7 @@ namespace Slang assert(cb); builder->block = cb; - for (auto oi = ob->getFirstInst(); oi; oi = oi->nextInst) + for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst()) { cloneInst(context, builder, oi); } @@ -3444,6 +3699,90 @@ namespace Slang virtual RefPtr<Type> maybeCloneType(Type* originalType) override; }; + // Convert a type-level value into an IR-level equivalent. + IRValue* getIRValue( + IRGenericSpecContext* context, + Val* val) + { + if( auto subtypeWitness = dynamic_cast<SubtypeWitness*>(val) ) + { + // We need to look up the IR value that represents the + // given subtype witness. + String mangledName = getMangledNameForConformanceWitness( + subtypeWitness->sub, + subtypeWitness->sup); + RefPtr<IRSpecSymbol> symbol; + + if( !context->getSymbols().TryGetValue(mangledName, symbol) ) + { + SLANG_UNEXPECTED("couldn't find symbol for conformance!"); + return nullptr; + } + + return symbol->irGlobalValue; + } + else if (auto proxyVal = dynamic_cast<IRProxyVal*>(val)) + { + // The type-level value actually references an IR-level value, + // so we need to make sure to emit as if we were referencing + // the pointed-to value and not the proxy type-level `Val` + // instead. + + return context->maybeCloneValue(proxyVal->inst); + } + else + { + SLANG_UNEXPECTED("unimplemented"); + return nullptr; + } + } + + IRValue* getSubstValue( + IRGenericSpecContext* context, + DeclRef<Decl> declRef) + { + auto subst = context->subst; + auto genericDecl = subst->genericDecl; + + UInt orinaryParamCount = 0; + for( auto mm : genericDecl->Members ) + { + if(mm.As<GenericTypeParamDecl>()) + orinaryParamCount++; + else if(mm.As<GenericValueParamDecl>()) + orinaryParamCount++; + } + + if( auto constraintDeclRef = declRef.As<GenericTypeConstraintDecl>() ) + { + // We have a constraint, but we need to find its index in the + // argument list of the substitutions. + UInt constraintIndex = 0; + bool found = false; + for( auto cd : genericDecl->getMembersOfType<GenericTypeConstraintDecl>() ) + { + if( cd.Ptr() == constraintDeclRef.getDecl() ) + { + found = true; + break; + } + + constraintIndex++; + } + assert(found); + + UInt argIndex = orinaryParamCount + constraintIndex; + assert(argIndex < subst->args.Count()); + + return getIRValue(context, subst->args[argIndex]); + } + else + { + SLANG_UNEXPECTED("unhandled case"); + return nullptr; + } + } + IRValue* IRGenericSpecContext::maybeCloneValue(IRValue* originalVal) { switch( originalVal->op ) @@ -3451,6 +3790,17 @@ namespace Slang case kIROp_decl_ref: { auto declRefVal = (IRDeclRef*) originalVal; + auto declRef = declRefVal->declRef; + + // We may have a direct reference to one of the parameters + // of the generic we are specializing, and in that case + // we nee to translate it over to the equiavalent of + // the `Val` we have been given. + if(declRef.getDecl()->ParentDecl == subst->genericDecl) + { + return getSubstValue(this, declRef); + } + int diff = 0; auto substDeclRef = declRefVal->declRef.SubstituteImpl(subst, &diff); if(!diff) @@ -3539,6 +3889,41 @@ namespace Slang return specFunc; } + // Find the value in the given witness table that + // satisfies the given requirement (or return + // null if not found). + IRValue* findWitnessVal( + IRWitnessTable* witnessTable, + DeclRef<Decl> const& requirementDeclRef) + { + // For now we will do a dumb linear search + for( auto entry : witnessTable->entries ) + { + // We expect the key on the entry to be a decl-ref, + // but lets go ahead and check, just to be sure. + auto requirementKey = entry->requirementKey.usedValue; + if(requirementKey->op != kIROp_decl_ref) + continue; + auto keyDeclRef = ((IRDeclRef*) requirementKey)->declRef; + + // If the keys don't match, continue with the next entry. + if(!keyDeclRef.Equals(requirementDeclRef)) + continue; + + // If the keys matched, then we use the value from + // this entry. + auto satisfyingVal = entry->satisfyingVal.usedValue; + return satisfyingVal; + } + + // No matching entry found. + return nullptr; + } + + // Go through the code in the module and try to identify + // calls to generic functions where the generic arguments + // are known, and specialize the callee based on those + // known values. void specializeGenerics( IRModule* module) { @@ -3601,40 +3986,100 @@ namespace Slang IRInst* nextInst = nullptr; for( auto ii = bb->getFirstInst(); ii; ii = nextInst ) { - nextInst = ii->nextInst; + nextInst = ii->getNextInst(); - // We only care about `specialize` instructions. - if(ii->op != kIROp_specialize) + // We want to handle both `specialize` instructions, + // which trigger specialization, and also `lookup_interface_method` + // instructions, which may allow us to "de-virtualize" + // calls. + + switch( ii->op ) + { + default: + // Most instructions are ones we don't care about here. continue; - IRSpecialize* specInst = (IRSpecialize*) ii; + case kIROp_specialize: + { + // We have a `specialize` instruction, so lets see + // whether we have an opportunity to perform the + // specialization here and now. + IRSpecialize* specInst = (IRSpecialize*) ii; + + // We need to check that the value being specialized is + // a generic function. + auto genericVal = specInst->genericVal.usedValue; + if(genericVal->op != kIROp_Func) + continue; + auto genericFunc = (IRFunc*) genericVal; + if(!genericFunc->genericDecl) + continue; + + // Now we extract the specialized decl-ref that will + // tell us how to specialize things. + auto specDeclRefVal = (IRDeclRef*) specInst->specDeclRefVal.usedValue; + auto specDeclRef = specDeclRefVal->declRef; + + // Okay, we have a candidate for specialization here. + // + // We will first find or construct a specialized version + // of the callee funciton/ + auto specFunc = getSpecializedFunc(sharedContext, genericFunc, specDeclRef); + // + // Then we will replace the use sites for the `specialize` + // instruction with uses of the specialized function. + // + specInst->replaceUsesWith(specFunc); + + specInst->removeAndDeallocate(); + } + break; - // We need to check that the value being specialized is - // a generic function. - auto genericVal = specInst->genericVal.usedValue; - if(genericVal->op != kIROp_Func) - continue; - auto genericFunc = (IRFunc*) genericVal; - if(!genericFunc->genericDecl) + case kIROp_lookup_interface_method: + { + // We have a `lookup_interface_method` instruction, + // so let's see whether it is a lookup in a known + // witness table. + IRLookupWitnessMethod* lookupInst = (IRLookupWitnessMethod*) ii; + + // We only want to deal with the case where the witness-table + // argument points to a concrete global table. + auto witnessTableArg = lookupInst->witnessTable.usedValue; + if(witnessTableArg->op != kIROp_witness_table) + continue; + IRWitnessTable* witnessTable = (IRWitnessTable*)witnessTableArg; + + // We also need to be sure that the requirement we + // are trying to look up is identified via a decl-ref: + auto requirementArg = lookupInst->requirementDeclRef.usedValue; + if(requirementArg->op != kIROp_decl_ref) + continue; + auto requirementDeclRef = ((IRDeclRef*) requirementArg)->declRef; + + // Use the witness table to look up the value that + // satisfies the requirement. + auto satisfyingVal = findWitnessVal(witnessTable, requirementDeclRef); + + // We expect to always find something, but lets just + // be careful here. + if(!satisfyingVal) + continue; + + // If we get through all of the above checks, then we + // have a (more) concrete method that implements the interface, + // and so we should dispatch to that directly, rather than + // use the `lookup_interface_method` instruction. + lookupInst->replaceUsesWith(satisfyingVal); + lookupInst->removeAndDeallocate(); + } + break; + } + + + // We only care about `specialize` instructions. + if(ii->op != kIROp_specialize) continue; - // Now we extract the specialized decl-ref that will - // tell us how to specialize things. - auto specDeclRefVal = (IRDeclRef*) specInst->specDeclRefVal.usedValue; - auto specDeclRef = specDeclRefVal->declRef; - - // Okay, we have a candidate for specialization here. - // - // We will first find or construct a specialized version - // of the callee funciton/ - auto specFunc = getSpecializedFunc(sharedContext, genericFunc, specDeclRef); - // - // Then we will replace the use sites for the `specialize` - // instruction with uses of the specialized function. - // - specInst->replaceUsesWith(specFunc); - - specInst->removeAndDeallocate(); } } } diff --git a/source/slang/ir.h b/source/slang/ir.h index 2477c987f..2e15e39d6 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -21,6 +21,7 @@ class Session; struct IRFunc; struct IRInst; struct IRModule; +struct IRUser; struct IRValue; typedef unsigned int IROpFlags; @@ -82,7 +83,7 @@ struct IRUse IRValue* usedValue; // The value that is doing the using. - IRInst* user; + IRUser* user; // The next use of the same value IRUse* nextUse; @@ -91,7 +92,7 @@ struct IRUse // so that we can simplify updates. IRUse** prevLink; - void init(IRInst* user, IRValue* usedValue); + void init(IRUser* user, IRValue* usedValue); }; enum IRDecorationOp : uint16_t @@ -156,9 +157,83 @@ struct IRValue void deallocate(); }; -// Instructions are values that can be executed, -// and which take other values as operands -struct IRInst : IRValue +// Values that are contained in a doubly-linked +// list inside of some parent. +// +// TODO: consider merging this into `IRValue` so +// that *all* values have a parent. +struct IRChildValue : IRValue +{ + // The parent of this value. + IRValue* parent; + + // The next and previous values in the same + // list on teh same parent. + IRChildValue* next; + IRChildValue* prev; +}; + +// Helper for storing linked lists of child values. +struct IRValueListBase +{ + IRChildValue* first = 0; + IRChildValue* last = 0; + +protected: + void addImpl(IRValue* parent, IRChildValue* val); +}; +template<typename T> +struct IRValueList : IRValueListBase +{ + T* getFirst() { return (T*)first; } + T* getLast() { return (T*)last; } + + void add(IRValue* parent, T* val) + { + addImpl(parent, val); + } + + struct Iterator + { + T* val; + + Iterator() : val(0) {} + Iterator(T* val) : val(val) {} + + void operator++() + { + if (val) + { + val = (T*)val->next; + } + } + + T* operator*() + { + return val; + } + + bool operator!=(Iterator const& i) + { + return val != i.val; + } + }; + + Iterator begin() { return Iterator(getFirst()); } + Iterator end() { return Iterator(nullptr); } +}; + +// Values that can use other values. These always +// have their operands "tail allocated" after +// the fields of this type, so derived types must +// either: +// +// - Add no new fields, or +// - Add only fields that represent the `IRUse` operands +// - Add a fixed number of `IRUse` operand fields and +// then any additional data after them. +// +struct IRUser : IRChildValue { // The total number of arguments of this instruction. // @@ -169,13 +244,6 @@ struct IRInst : IRValue // pointer. uint32_t argCount; - // The basic block that contains this instruction. - IRBlock* parentBlock; - - // The next and previous instructions in the same parent block - IRInst* nextInst; - IRInst* prevInst; - UInt getArgCount() { return argCount; @@ -187,6 +255,17 @@ struct IRInst : IRValue { return getArgs()[index].usedValue; } +}; + +// Instructions are values that are children of a basic block, +// and can actually be executed. +struct IRInst : IRUser +{ + IRBlock* getParentBlock() { return (IRBlock*)parent; } + + IRInst* getPrevInst() { return (IRInst*)prev; } + IRInst* getNextInst() { return (IRInst*)next; } + // Insert this instruction into the same basic block // as `other`, right before it. diff --git a/source/slang/lookup.cpp b/source/slang/lookup.cpp index 95a1b3326..cf51ae720 100644 --- a/source/slang/lookup.cpp +++ b/source/slang/lookup.cpp @@ -323,6 +323,11 @@ void DoLookupImpl( // at the parameters themselves, so provide a fully-resolved // declaration reference for lookup. RefPtr<Substitutions> subst = nullptr; +#if 1 + // Actually, the above rationale seems bogus. If we are looking + // up from "inside" a generic declaration, we don't want to + // get its members pre-specialized, right? +#else if(auto parentGenericDecl = dynamic_cast<GenericDecl*>(containerDecl->ParentDecl)) { subst = new Substitutions(); @@ -342,6 +347,7 @@ void DoLookupImpl( } } } +#endif DeclRef<ContainerDecl> containerRef = DeclRef<Decl>(containerDecl, subst).As<ContainerDecl>(); DoLocalLookupImpl( @@ -370,7 +376,7 @@ LookupResult DoLookup( return result; } -LookupResult LookUp( +LookupResult lookUp( Session* session, SemanticsVisitor* semantics, Name* name, @@ -384,7 +390,7 @@ LookupResult LookUp( // perform lookup within the context of a particular container declaration, // and do *not* look further up the chain -LookupResult LookUpLocal( +LookupResult lookUpLocal( Session* session, SemanticsVisitor* semantics, Name* name, @@ -398,4 +404,72 @@ LookupResult LookUpLocal( return result; } +void lookUpMemberImpl( + Session* session, + SemanticsVisitor* semantics, + Name* name, + Type* type, + LookupResult& ioResult, + BreadcrumbInfo* inBreadcrumbs) +{ + if (auto declRefType = type->As<DeclRefType>()) + { + auto declRef = declRefType->declRef; + if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>()) + { + LookupRequest request; + request.semantics = semantics; + + DoLocalLookupImpl(session, name, aggTypeDeclRef, request, ioResult, inBreadcrumbs); + } + else if (auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>()) + { + auto genericDeclRef = genericTypeParamDeclRef.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(genericTypeParamDeclRef)) + 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, with an appropriate breadcrumb + // to indicate that we indirected through a type constraint. + + BreadcrumbInfo breadcrumb; + breadcrumb.prev = inBreadcrumbs; + breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::Constraint; + breadcrumb.declRef = constraintDeclRef; + + // TODO: Need to consider case where this might recurse infinitely. + lookUpMemberImpl(session, semantics, name, bound, ioResult, &breadcrumb); + } + } + } +} + +LookupResult lookUpMember( + Session* session, + SemanticsVisitor* semantics, + Name* name, + Type* type) +{ + LookupResult result; + lookUpMemberImpl(session, semantics, name, type, result, nullptr); + return result; +} + + } diff --git a/source/slang/lookup.h b/source/slang/lookup.h index be7cb30bc..473ecaf96 100644 --- a/source/slang/lookup.h +++ b/source/slang/lookup.h @@ -17,7 +17,7 @@ void buildMemberDictionary(ContainerDecl* decl); // Look up a name in the given scope, proceeding up through // parent scopes as needed. -LookupResult LookUp( +LookupResult lookUp( Session* session, SemanticsVisitor* semantics, Name* name, @@ -25,12 +25,19 @@ LookupResult LookUp( // perform lookup within the context of a particular container declaration, // and do *not* look further up the chain -LookupResult LookUpLocal( +LookupResult lookUpLocal( Session* session, SemanticsVisitor* semantics, Name* name, DeclRef<ContainerDecl> containerDeclRef); +// Perform member lookup in the context of a type +LookupResult lookUpMember( + Session* session, + SemanticsVisitor* semantics, + Name* name, + Type* type); + // TODO: this belongs somewhere else QualType getTypeForDeclRef( diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 327902e4a..2f0ea810e 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -443,12 +443,62 @@ LoweredValInfo emitPostOp( return LoweredValInfo::ptr(argPtr); } +// Emit a reference to a function, where we have concluded +// that the original AST referenced `funcDeclRef`. The +// optional expression `funcExpr` can provide additional +// detail that might modify how we go about looking up +// the actual value to call. +LoweredValInfo emitFuncRef( + IRGenContext* context, + DeclRef<Decl> funcDeclRef, + Expr* funcExpr) +{ + if( !funcExpr ) + { + return emitDeclRef(context, funcDeclRef); + } + + // Let's look at the expression to see what additional + // information it gives us. + + if(auto funcMemberExpr = dynamic_cast<MemberExpr*>(funcExpr)) + { + auto baseExpr = funcMemberExpr->BaseExpression; + if(auto baseMemberExpr = baseExpr.As<MemberExpr>()) + { + auto baseMemberDeclRef = baseMemberExpr->declRef; + if(auto baseConstraintDeclRef = baseMemberDeclRef.As<GenericTypeConstraintDecl>()) + { + // We are calling a method "through" a generic type + // parameter that was constrained to some type. + // That means `funcDeclRef` is a reference to the method + // on the `interface` type (which doesn't actually have + // a body, so we don't want to emit or call it), and + // we actually want to perform a lookup step to + // find the corresponding member on our chosen type. + + RefPtr<Type> type = funcExpr->type; + + return LoweredValInfo::simple(context->irBuilder->emitLookupInterfaceMethodInst( + type, + baseMemberDeclRef, + funcDeclRef)); + } + } + } + + // We didn't trigger a special case, so just emit a reference + // to the function itself. + return emitDeclRef(context, funcDeclRef); +} + // Given a `DeclRef` for something callable, along with a bunch of // arguments, emit an appropriate call to it. LoweredValInfo emitCallToDeclRef( IRGenContext* context, IRType* type, DeclRef<Decl> funcDeclRef, + Expr* funcExpr, UInt argCount, IRValue* const* args) { @@ -572,7 +622,7 @@ LoweredValInfo emitCallToDeclRef( } // Fallback case is to emit an actual call. - LoweredValInfo funcVal = emitDeclRef(context, funcDeclRef); + LoweredValInfo funcVal = emitFuncRef(context, funcDeclRef, funcExpr); return emitCallToVal(context, type, funcVal, argCount, args); } @@ -580,9 +630,10 @@ LoweredValInfo emitCallToDeclRef( IRGenContext* context, IRType* type, DeclRef<Decl> funcDeclRef, + Expr* funcExpr, List<IRValue*> const& args) { - return emitCallToDeclRef(context, type, funcDeclRef, args.Count(), args.Buffer()); + return emitCallToDeclRef(context, type, funcDeclRef, funcExpr, args.Count(), args.Buffer()); } IRValue* getSimpleVal(IRGenContext* context, LoweredValInfo lowered) @@ -611,6 +662,7 @@ top: context, boundSubscriptInfo->type, getter, + nullptr, boundSubscriptInfo->args); goto top; } @@ -660,7 +712,7 @@ struct LoweredTypeInfo } }; -IRType* getSimpleType(LoweredTypeInfo lowered) +RefPtr<Type> getSimpleType(LoweredTypeInfo lowered) { switch(lowered.flavor) { @@ -700,7 +752,7 @@ static LoweredTypeInfo lowerType( } // Lower a type and expect the result to be simple -IRType* lowerSimpleType( +RefPtr<Type> lowerSimpleType( IRGenContext* context, Type* type) { @@ -708,7 +760,7 @@ IRType* lowerSimpleType( return getSimpleType(lowered); } -IRType* lowerSimpleType( +RefPtr<Type> lowerSimpleType( IRGenContext* context, QualType const& type) { @@ -819,7 +871,15 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower LoweredTypeInfo visitDeclRefType(DeclRefType* type) { - // TODO: is there actually anything to be done at this point? + // If the type in question comes from the module we are + // trying to lower, then we need to make sure to + // emit everything relevant to its declaration. + + // TODO: actually test what module the type is coming from. + + lowerDecl(context, type->declRef); + + return LoweredTypeInfo(type); } @@ -966,6 +1026,16 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> boundMemberInfo->declRef = callableDeclRef; return LoweredValInfo::boundMember(boundMemberInfo); } + else if(auto constraintDeclRef = declRef.As<GenericTypeConstraintDecl>()) + { + // The code is making use of a "witness" that a value of + // some generic type conforms to an interface. + // + // For now we will just emit the base expression as-is. + // TODO: we may need to insert an explicit instruction + // for a cast here (that could become a no-op later). + return loweredBase; + } SLANG_UNIMPLEMENTED_X("codegen for subscript expression"); } @@ -1315,7 +1385,12 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> // require "fixup" work on the other side. // addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups); - auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs); + auto result = emitCallToDeclRef( + context, + type, + funcDeclRef, + funcExpr, + irArgs); applyOutArgumentFixups(argFixups); return result; } @@ -1937,6 +2012,7 @@ top: context, context->getSession()->getVoidType(), setterDeclRef, + nullptr, allArgs); return; } @@ -1972,6 +2048,72 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> SLANG_UNIMPLEMENTED_X("decl catch-all"); } + LoweredValInfo visitGenericTypeParamDecl(GenericTypeParamDecl* decl) + { + return LoweredValInfo(); + } + + LoweredValInfo visitInheritanceDecl(InheritanceDecl* inheritanceDecl) + { + // Construct a type for the parent declaration. + // + // TODO: if this inheritance declaration is under an extension, + // then we should construct the type that is being extended, + // and not a reference to the extension itself. + auto parentDecl = inheritanceDecl->ParentDecl; + RefPtr<Type> type = DeclRefType::Create( + context->getSession(), + makeDeclRef(parentDecl)); + + + // TODO: if the parent type is generic, then I suppose these + // need to be *generic* witness tables? + + // What is the super-type that we have declared we inherit from? + RefPtr<Type> superType = inheritanceDecl->base.type; + + // Construct the mangled name for the witness table, which depends + // on the type that is conforming, and the type that it conforms to. + String mangledName = getMangledNameForConformanceWitness( + type, + superType); + + // Build an IR level witness table, which will represent the + // conformance of the type to its super-type. + auto witnessTable = context->irBuilder->createWitnessTable(); + witnessTable->mangledName = mangledName; + + // Register the value now, rather than later, to avoid + // infinite recursion. + context->shared->declValues[inheritanceDecl] = LoweredValInfo::simple(witnessTable); + + + // Semantic checking will have filled in a dictionary of + // witnesses for requirements in the interface, and we + // will now navigate that dictionary to fill in the witness table. + for (auto entry : inheritanceDecl->requirementWitnesses) + { + auto requiredMemberDeclRef = entry.Key; + auto satisfyingMemberDecl = entry.Value; + + auto irRequirement = context->irBuilder->getDeclRefVal(requiredMemberDeclRef); + auto irSatisfyingVal = getSimpleVal(context, ensureDecl(context, satisfyingMemberDecl)); + + auto witnessTableEntry = context->irBuilder->createWitnessTableEntry( + witnessTable, + irRequirement, + irSatisfyingVal); + } + + witnessTable->moveToEnd(); + + // A direct reference to this inheritance relationship (e.g., + // as a subtype witness) will take the form of a reference to + // the witness table in the IR. + return LoweredValInfo::simple(witnessTable); + } + + LoweredValInfo visitDeclGroup(DeclGroup* declGroup) { // To lowere a group of declarations, we just @@ -2141,91 +2283,25 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> LoweredValInfo visitAggTypeDecl(AggTypeDecl* decl) { -#if 0 - // User-defined aggregate type: need to translate into - // a corresponding IR aggregate type. - - auto builder = getBuilder(); - IRStructDecl* irStruct = builder->createStructType(); - - for (auto fieldDecl : decl->GetFields()) + // Given a declaration of a type, we need to make sure + // to output "witness tables" for any interfaces this + // type has declared conformance to. + for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) { - // TODO: need to track relationship to original fields... - - // TODO: need to be prepared to deal with tuple-ness of fields here - auto fieldType = lowerType(context, fieldDecl->getType()); - - switch (fieldType.flavor) - { - case LoweredTypeInfo::Flavor::Simple: - { - auto irField = builder->createStructField(getSimpleType(fieldType)); - builder->addInst(irStruct, irField); - - builder->addHighLevelDeclDecoration(irField, fieldDecl); - - context->shared->declValues.Add( - DeclRef<StructField>(fieldDecl, nullptr), - LoweredValInfo::simple(irField)); - } - break; - - default: - SLANG_UNIMPLEMENTED_X("struct field type"); - } + ensureDecl(context, inheritanceDecl); } - builder->addHighLevelDeclDecoration(irStruct, decl); - - builder->addInst(irStruct); - - return LoweredValInfo::simple(irStruct); -#else - // TODO: What is there to do with a `struct` type? + // For now, we don't have an IR-level representation + // for the type itself. return LoweredValInfo(); -#endif } - // Sometimes we need to refer to a declaration the way that it would be specialized - // inside the context where it is declared (e.g., with generic parameters filled in - // using their archetypes). - // - RefPtr<Substitutions> createDefaultSubstitutions(Decl* decl) - { - auto dd = decl->ParentDecl; - while( dd ) - { - if( auto genericDecl = dynamic_cast<GenericDecl*>(dd) ) - { - auto session = context->getSession(); - - RefPtr<Substitutions> subst = new Substitutions(); - subst->genericDecl = genericDecl; - subst->outer = createDefaultSubstitutions(genericDecl); - - for( auto mm : genericDecl->Members ) - { - if( auto genericTypeParamDecl = mm.As<GenericTypeParamDecl>() ) - { - subst->args.Add(DeclRefType::Create(session, makeDeclRef(genericTypeParamDecl.Ptr()))); - } - else if( auto genericValueParamDecl = mm.As<GenericValueParamDecl>() ) - { - subst->args.Add(new GenericParamIntVal(makeDeclRef(genericValueParamDecl.Ptr()))); - } - } - return subst; - } - dd = dd->ParentDecl; - } - return nullptr; - } DeclRef<Decl> createDefaultSpecializedDeclRefImpl(Decl* decl) { DeclRef<Decl> declRef; declRef.decl = decl; - declRef.substitutions = createDefaultSubstitutions(decl); + declRef.substitutions = createDefaultSubstitutions(context->getSession(), decl); return declRef; } // @@ -2595,7 +2671,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> for( auto paramInfo : parameterLists.params ) { - IRType* irParamType = lowerSimpleType(context, paramInfo.type); + RefPtr<Type> irParamType = lowerSimpleType(context, paramInfo.type); switch( paramInfo.direction ) { @@ -2854,6 +2930,97 @@ LoweredValInfo ensureDecl( return result; } +IRWitnessTable* findWitnessTable( + IRGenContext* context, + DeclRef<Decl> declRef) +{ + IRValue* irVal = getSimpleVal(context, emitDeclRef(context, declRef)); + if (!irVal) + { + SLANG_UNEXPECTED("expected a witness table"); + return nullptr; + } + + if (irVal->op != kIROp_witness_table) + { + // TODO: We might eventually have cases of `specialize` called + // on a witness table... + SLANG_UNEXPECTED("expected a witness table"); + return nullptr; + } + + return (IRWitnessTable*)irVal; +} + +RefPtr<Val> lowerSubstitutionArg( + IRGenContext* context, + Val* val) +{ + if (auto type = dynamic_cast<Type*>(val)) + { + return lowerSimpleType(context, type); + } + else if (auto declaredSubtypeWitness = dynamic_cast<DeclaredSubtypeWitness*>(val)) + { + // We need to look up the IR-level representation of the witness + // (which is a witness table). + + auto irWitnessTable = findWitnessTable(context, declaredSubtypeWitness->declRef); + + // We have an IR-level value, but we need to embed it into an AST-level + // type, so we will use a proxy `Val` that wraps up an `IRValue` as + // an AST-level value. + // + // TODO: This proxy value currently doesn't enter into use-def chaining, + // and so Bad Things could happen quite easily. We need to fix that + // up in a reasonably clean fashion. + // + RefPtr<IRProxyVal> proxyVal = new IRProxyVal(); + proxyVal->inst = irWitnessTable; + return proxyVal; + } + else + { + // For now, jsut assume that all other values + // lower to themselves. + // + // TODO: we should probably handle the case of + // a `Val` that references an AST-level `constexpr` + // variable, since that would need to be lowered + // to a `Val` that references the IR equivalent. + return val; + } +} + +// Given a set of substitutions, make sure that we have +// lowered the arguments being used into a form that +// is suitable for use in the IR. +RefPtr<Substitutions> lowerSubstitutions( + IRGenContext* context, + Substitutions* subst) +{ + if(!subst) + return nullptr; + + RefPtr<Substitutions> newSubst = new Substitutions(); + if (subst->outer) + { + newSubst->outer = lowerSubstitutions( + context, + subst->outer); + } + + newSubst->genericDecl = subst->genericDecl; + + for (auto arg : subst->args) + { + auto newArg = lowerSubstitutionArg(context, arg); + newSubst->args.Add(newArg); + } + + return newSubst; +} + LoweredValInfo emitDeclRef( IRGenContext* context, DeclRef<Decl> declRef) @@ -2869,6 +3036,14 @@ LoweredValInfo emitDeclRef( auto val = getSimpleVal(context, loweredDecl); + // We have the "raw" substitutions from the AST, but we may + // need to walk through those and replace things in + // cases where the `Val`s used for substitution should + // lower to something other than their original form. + RefPtr<Substitutions> newSubst = lowerSubstitutions(context, declRef.substitutions); + declRef.substitutions = newSubst; + + RefPtr<Type> type; if(auto declType = val->getType()) { diff --git a/source/slang/lower.cpp b/source/slang/lower.cpp index 94738307b..0f16a8ad7 100644 --- a/source/slang/lower.cpp +++ b/source/slang/lower.cpp @@ -688,6 +688,11 @@ struct LoweringVisitor return val; } + RefPtr<Witness> visitWitness(Witness* witness) + { + return witness; + } + // // Types // @@ -797,6 +802,11 @@ struct LoweringVisitor return lowerType(type); } + RefPtr<Val> visitIRProxyVal(IRProxyVal* val) + { + return val; + } + // // Expressions // diff --git a/source/slang/mangle.cpp b/source/slang/mangle.cpp index 71c0605a9..63e54d065 100644 --- a/source/slang/mangle.cpp +++ b/source/slang/mangle.cpp @@ -131,6 +131,27 @@ namespace Slang { emitType(context, type); } + else if( auto witness = dynamic_cast<Witness*>(val) ) + { + // We don't emit witnesses as part of a mangled + // name, because the way that the front-end + // arrived at the witness is not important; + // what matters is that the type constraint + // was satisfied. + // + // TODO: make sure we can't get name collisions + // between specializations of declarations + // with the same numbers of generic parameters, + // but different constraints. We might have + // to mangle in the constraints even when + // the whole thing is specialized... + } + else if (auto proxyVal = dynamic_cast<IRProxyVal*>(val)) + { + // This is a proxy standing in for some IR-level + // value, so we certainly don't want to include + // it in the mangling. + } else { SLANG_UNEXPECTED("unimplemented case in mangling"); @@ -316,4 +337,21 @@ namespace Slang { return getMangledName(makeDeclRef(decl)); } + + String getMangledNameForConformanceWitness( + Type* sub, + Type* sup) + { + // The mangled form for a witness that `sub` + // conforms to `sup` will be named: + // + // {Conforms(sub,sup)} => _SW{sub}{sup} + // + ManglingContext context; + emitRaw(&context, "_SW"); + emitType(&context, sub); + emitType(&context, sup); + return context.sb.ProduceString(); + } + } diff --git a/source/slang/mangle.h b/source/slang/mangle.h index 11196f496..60a1dff9e 100644 --- a/source/slang/mangle.h +++ b/source/slang/mangle.h @@ -11,6 +11,10 @@ namespace Slang String getMangledName(Decl* decl); String getMangledName(DeclRef<Decl> const & declRef); String getMangledName(DeclRefBase const & declRef); + + String getMangledNameForConformanceWitness( + Type* sub, + Type* sup); } #endif
\ No newline at end of file diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index 2b6535bef..3a8c5b362 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -655,7 +655,7 @@ namespace Slang { // Let's look up the name and see what we find. - auto lookupResult = LookUp( + auto lookupResult = lookUp( parser->getSession(), nullptr, // no semantics visitor available yet name, @@ -2080,6 +2080,7 @@ namespace Slang auto inheritanceDecl = new InheritanceDecl(); inheritanceDecl->loc = base.exp->loc; + inheritanceDecl->nameAndLoc.name = getName(parser, "$inheritance"); inheritanceDecl->base = base; AddMember(decl, inheritanceDecl); @@ -2106,6 +2107,16 @@ namespace Slang RefPtr<ConstructorDecl> decl = new ConstructorDecl(); parser->FillPosition(decl.Ptr()); + // TODO: we need to make sure that all initializers have + // the same name, but that this name doesn't conflict + // with any user-defined names. + // Giving them a name (rather than leaving it null) + // ensures that we can use name-based lookup to find + // all of the initializers on a type (and has + // the potential to unify initializer lookup with + // ordinary member lookup). + decl->nameAndLoc.name = getName(parser, "$init"); + parseParameterList(parser, decl); if( AdvanceIf(parser, TokenType::Semicolon) ) @@ -2560,7 +2571,7 @@ namespace Slang static bool isGenericName(Parser* parser, Name* name) { - auto lookupResult = LookUp( + auto lookupResult = lookUp( parser->getSession(), nullptr, // no semantics visitor available yet name, @@ -2582,7 +2593,7 @@ namespace Slang static bool isTypeName(Parser* parser, Name* name) { - auto lookupResult = LookUp( + auto lookupResult = lookUp( parser->getSession(), nullptr, // no semantics visitor available yet name, diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index da7601d41..f1d1d6ba2 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -900,7 +900,7 @@ SLANG_API int spCompile( { auto req = REQ(request); -#if 1 +#if 0 // By default we'd like to catch as many internal errors as possible, // and report them to the user nicely (rather than just crash their // application). Internally Slang currently uses exceptions for this. diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp index 54a4a79b6..6cf3fd7c9 100644 --- a/source/slang/syntax.cpp +++ b/source/slang/syntax.cpp @@ -479,6 +479,25 @@ void Type::accept(IValVisitor* visitor, void* extra) Session* session, DeclRef<Decl> declRef) { + // It is possible that `declRef` refers to a generic type, + // but does not specify arguments for its generic parameters. + // (E.g., this happens when referring to a generic type from + // within its own member functions). To handle this case, + // we will construct a default specialization at the use + // site if needed. + + if( auto genericParent = declRef.GetParent().As<GenericDecl>() ) + { + auto subst = declRef.substitutions; + if( !subst || subst->genericDecl != genericParent.decl ) + { + declRef.substitutions = createDefaultSubstitutions( + session, + declRef.decl, + subst); + } + } + if (auto builtinMod = declRef.getDecl()->FindModifier<BuiltinTypeModifier>()) { auto type = new BasicExpressionType(builtinMod->tag); @@ -1155,6 +1174,9 @@ void Type::accept(IValVisitor* visitor, void* extra) DeclRefBase DeclRefBase::GetParent() const { auto parentDecl = decl->ParentDecl; + if (!parentDecl) + return DeclRefBase(); + if (auto parentGeneric = dynamic_cast<GenericDecl*>(parentDecl)) { if (substitutions && substitutions->genericDecl == parentDecl) @@ -1350,7 +1372,7 @@ void Type::accept(IValVisitor* visitor, void* extra) Session* session, DeclRef<CallableDecl> const& declRef) { - auto funcType = new FuncType(); + RefPtr<FuncType> funcType = new FuncType(); funcType->setSession(session); funcType->resultType = GetResultType(declRef); @@ -1379,4 +1401,72 @@ void Type::accept(IValVisitor* visitor, void* extra) return samplerStateType; } + // TODO: should really have a `type.cpp` and a `witness.cpp` + + bool DeclaredSubtypeWitness::EqualsVal(Val* val) + { + auto otherWitness = dynamic_cast<DeclaredSubtypeWitness*>(val); + if(!otherWitness) + return false; + + return sub->Equals(otherWitness->sub) + && sup->Equals(otherWitness->sup) + && declRef.Equals(otherWitness->declRef); + } + + String DeclaredSubtypeWitness::ToString() + { + StringBuilder sb; + sb << "DeclaredSubtypeWitness("; + sb << this->sub->ToString(); + sb << ", "; + sb << this->sup->ToString(); + sb << ", "; + sb << this->declRef.toString(); + sb << ")"; + return sb.ProduceString(); + } + + int DeclaredSubtypeWitness::GetHashCode() + { + auto hash = sub->GetHashCode(); + hash = combineHash(hash, sup->GetHashCode()); + hash = combineHash(hash, declRef.GetHashCode()); + return hash; + } + + // IRProxyVal + + bool IRProxyVal::EqualsVal(Val* val) + { + auto otherProxy = dynamic_cast<IRProxyVal*>(val); + if(!otherProxy) + return false; + + return this->inst == otherProxy->inst; + } + + String IRProxyVal::ToString() + { + return "IRProxyVal(...)"; + } + + int IRProxyVal::GetHashCode() + { + auto hash = Slang::GetHashCode(inst); + return hash; + } + + // + + String DeclRefBase::toString() const + { + StringBuilder sb; + sb << this->getDecl()->getName()->text; + // TODO: need to print out substitutions too! + return sb.ProduceString(); + } + + + } diff --git a/source/slang/syntax.h b/source/slang/syntax.h index f5e22d9b5..d32c4de15 100644 --- a/source/slang/syntax.h +++ b/source/slang/syntax.h @@ -12,6 +12,7 @@ namespace Slang { + struct IRValue; class Name; class Session; class Substitutions; @@ -450,6 +451,9 @@ namespace Slang DeclRefBase GetParent() const; int GetHashCode() const; + + // Debugging: + String toString() const; }; template<typename T> @@ -787,17 +791,92 @@ namespace Slang // // We build up a list of these "breadcrumbs" while doing // lookup, and store them alongside each item found. + // + // As an example, suppose we have an HLSL `cbuffer` declaration: + // + // cbuffer C { float4 f; } + // + // This is syntax sugar for a global-scope variable of + // type `ConstantBuffer<T>` where `T` is a `struct` containing + // all the members: + // + // struct Anon0 { float4 f; }; + // __transparent ConstantBuffer<Anon0> anon1; + // + // The `__transparent` modifier there captures the fact that + // when somebody writes `f` in their code, they expect it to + // "see through" the `cbuffer` declaration (or the global variable, + // in this case) and find the member inside. + // + // But when the user writes `f` we can't just create a simple + // `VarExpr` that refers directly to that field, because that + // doesn't actually reflect the required steps in a way that + // code generation can use. + // + // Instead we need to construct an expression like `(*anon1).f`, + // where there is are two additional steps in the process: + // + // 1. We needed to dereference the pointer-like type `ConstantBuffer<Anon0>` + // to get at a value of type `Anon0` + // 2. We needed to access a sub-field of the aggregate type `Anon0` + // + // We *could* just create these full-formed expressions during + // lookup, but this might mean creating a large number of + // AST nodes in cases where the user calls an overloaded function. + // At the very least we'd rather not heap-allocate in the common + // case where no "extra" steps need to be performed to get to + // the declarations. + // + // This is where "breadcrumbs" come in. A breadcrumb represents + // an extra "step" that must be performed to turn a declaration + // found by lookup into a valid expression to splice into the + // AST. Most of the time lookup result items don't have any + // breadcrumbs, so that no extra heap allocation takes place. + // When an item does have breadcrumbs, and it is chosen as + // the unique result (perhaps by overload resolution), then + // we can walk the list of breadcrumbs to create a full + // expression. class Breadcrumb : public RefObject { public: enum class Kind { - Member, // A member was references - Deref, // A value with pointer(-like) type was dereferenced + // The lookup process looked "through" an in-scope + // declaration to the fields inside of it, so that + // even if lookup started with a simple name `f`, + // it needs to result in a member expression `obj.f`. + Member, + + // The lookup process took a pointer(-like) value, and then + // proceeded to derefence it and look at the thing(s) + // it points to instead, so that the final expression + // needs to have `(*obj)` + Deref, + + // The lookup process saw a value `obj` of type `T` and + // took into account an in-scope constraint that says + // `T` is a subtype of some other type `U`, so that + // lookup was able to find a member through type `U` + // instead. + Constraint, }; + // The kind of lookup step that was performed Kind kind; + + // As needed, a reference to the declaration that faciliated + // the lookup step. + // + // For a `Member` lookup step, this is the declaration whose + // members were implicitly pulled into scope. + // + // For a `Constraint` lookup step, this is the `ConstraintDecl` + // that serves to witness the subtype relationship. + // DeclRef<Decl> declRef; + + // The next implicit step that the lookup process took to + // arrive at a final value. RefPtr<Breadcrumb> next; Breadcrumb(Kind kind, DeclRef<Decl> declRef, RefPtr<Breadcrumb> next) @@ -1060,6 +1139,16 @@ namespace Slang } } + // TODO: where should this live? + RefPtr<Substitutions> createDefaultSubstitutions( + Session* session, + Decl* decl, + Substitutions* parentSubst); + + RefPtr<Substitutions> createDefaultSubstitutions( + Session* session, + Decl* decl); + } // namespace Slang #endif
\ No newline at end of file diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h index 7ec801a14..2010d07b4 100644 --- a/source/slang/type-defs.h +++ b/source/slang/type-defs.h @@ -491,4 +491,3 @@ protected: virtual Type* CreateCanonicalType() override; ) END_SYNTAX_CLASS() - diff --git a/source/slang/val-defs.h b/source/slang/val-defs.h index 63abf973c..4513873e3 100644 --- a/source/slang/val-defs.h +++ b/source/slang/val-defs.h @@ -40,3 +40,72 @@ SYNTAX_CLASS(GenericParamIntVal, IntVal) virtual RefPtr<Val> SubstituteImpl(Substitutions* subst, int* ioDiff) override; ) END_SYNTAX_CLASS() + +// A witness to the fact that some proposition is true, encoded +// at the level of the type system. +// +// Given a generic like: +// +// void example<L>(L light) +// where L : ILight +// { ... } +// +// a call to `example()` needs two things for us to be sure +// it is valid: +// +// 1. We need a type `X` to use as the argument for the +// parameter `L`. We might supply this explicitly, or +// via inference. +// +// 2. We need a *proof* that whatever `X` we chose conforms +// to the `ILight` interface. +// +// The easiest way to make such a proof is by construction, +// and a `Witness` represents such a constructive proof. +// Conceptually a proposition like `X : ILight` can be +// seen as a type, and witness prooving that proposition +// is a value of that type. +// +// We construct and store witnesses explicitly during +// semantic checking because they can help us with +// generating downstream code. By following the structure +// of a witness (the structure of a proof) we can, e.g., +// navigate from the knowledge that `X : ILight` to +// the concrete declarations that provide the implementation +// of `ILight` for `X`. +// +ABSTRACT_SYNTAX_CLASS(Witness, Val) +END_SYNTAX_CLASS() + +// A witness that one type is a subtype of another +// (where by "subtype" we include both inheritance +// relationships and type-conforms-to-interface relationships) +// +// TODO: we may need to tease those apart. +ABSTRACT_SYNTAX_CLASS(SubtypeWitness, Witness) + FIELD(RefPtr<Type>, sub) + FIELD(RefPtr<Type>, sup) +END_SYNTAX_CLASS() + +// A witness that one type is a subtype of another +// because some in-scope declaration says so +SYNTAX_CLASS(DeclaredSubtypeWitness, SubtypeWitness) + FIELD(DeclRef<Decl>, declRef); +RAW( + virtual bool EqualsVal(Val* val) override; + virtual String ToString() override; + virtual int GetHashCode() override; +) +END_SYNTAX_CLASS() + +// A value that is used as a proxy when we need to +// put an IR-level value into AST types +SYNTAX_CLASS(IRProxyVal, Val) + FIELD(IRValue*, inst) +RAW( + virtual bool EqualsVal(Val* val) override; + virtual String ToString() override; + virtual int GetHashCode() override; +) +END_SYNTAX_CLASS() + diff --git a/tests/compute/generics-constrained.slang b/tests/compute/generics-constrained.slang new file mode 100644 index 000000000..669674376 --- /dev/null +++ b/tests/compute/generics-constrained.slang @@ -0,0 +1,46 @@ +//TEST(smoke,compute):COMPARE_COMPUTE:-xslang -use-ir +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out + +// Use interface constraints on a generic parameter + +interface Helper +{ + float getHelp(); +} + +struct A : Helper +{ + float a; + + float getHelp() + { + // TODO: we should be able to reference a member variable here, + // but the front-end isn't handling references through `this` + // properly yet. +// return a; + + return 1.0f; + } +}; + +__generic<T : Helper> +float testHelp(T helper) +{ + return helper.getHelp(); +} + +RWStructuredBuffer<float> outputBuffer : register(u0); + + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + float inVal = float(tid); + + A a; + a.a = inVal; + float outVal = testHelp<A>(a); + + outputBuffer[tid] = outVal; +}
\ No newline at end of file diff --git a/tests/compute/generics-constrained.slang.expected.txt b/tests/compute/generics-constrained.slang.expected.txt new file mode 100644 index 000000000..cc5e55ab6 --- /dev/null +++ b/tests/compute/generics-constrained.slang.expected.txt @@ -0,0 +1,4 @@ +3F800000 +3F800000 +3F800000 +3F800000 diff --git a/tests/ir/loop.slang.expected b/tests/ir/loop.slang.expected index 390fd80e0..212ce1913 100644 --- a/tests/ir/loop.slang.expected +++ b/tests/ir/loop.slang.expected @@ -1,79 +1,79 @@ result code = 0 standard error = { -ir_global_var %1 : Ptr<@ThreadGroup vector<float,4>[64]>; +ir_global_var @_SV01s : Ptr<@ThreadGroup vector<float,4>[64]>; -ir_global_var %2 : Ptr<StructuredBuffer<vector<float,4>>>; +ir_global_var @_SV05input : Ptr<StructuredBuffer<vector<float,4>>>; -ir_global_var %3 : Ptr<RWStructuredBuffer<vector<float,4>>>; +ir_global_var @_SV06output : Ptr<RWStructuredBuffer<vector<float,4>>>; ir_func @_S04mainp3uuuV : (uint, uint, uint) -> void { -block %4( - param %5 : uint, - param %6 : uint, - param %7 : uint): - let %8 : Ptr<uint> = var() - store(%8, %5) - let %9 : Ptr<uint> = var() - store(%9, %6) - let %10 : Ptr<uint> = var() - store(%10, %7) - let %11 : uint = load(%9) - let %12 : Ptr<vector<float,4>> = getElementPtr(%1, %11) - let %13 : StructuredBuffer<vector<float,4>> = load(%2) - let %14 : uint = load(%8) - let %15 : vector<float,4> = bufferLoad(%13, %14) - store(%12, %15) - let %16 : Ptr<uint> = var() - let %17 : uint = construct(1) - store(%16, %17) - loop(%18, %19, %20) +block %1( + param %2 : uint, + param %3 : uint, + param %4 : uint): + let %5 : Ptr<uint> = var() + store(%5, %2) + let %6 : Ptr<uint> = var() + store(%6, %3) + let %7 : Ptr<uint> = var() + store(%7, %4) + let %8 : uint = load(%6) + let %9 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %8) + let %10 : StructuredBuffer<vector<float,4>> = load(@_SV05input) + let %11 : uint = load(%5) + let %12 : vector<float,4> = bufferLoad(%10, %11) + store(%9, %12) + let %13 : Ptr<uint> = var() + let %14 : uint = construct(1) + store(%13, %14) + loop(%15, %16, %17) -block %18: - let %21 : uint = load(%16) - let %22 : uint = construct(64) - let %23 : bool = cmpLT(%21, %22) - loopTest(%23, %24, %19) +block %15: + let %18 : uint = load(%13) + let %19 : uint = construct(64) + let %20 : bool = cmpLT(%18, %19) + loopTest(%20, %21, %16) -block %24: +block %21: GroupMemoryBarrierWithGroupSync() - let %25 : uint = load(%9) - let %26 : Ptr<vector<float,4>> = getElementPtr(%1, %25) - let %27 : Ptr<vector<float,4>> = var() - let %28 : vector<float,4> = load(%26) - store(%27, %28) - let %29 : uint = load(%9) - let %30 : uint = load(%16) - let %31 : uint = sub(%29, %30) - let %32 : Ptr<vector<float,4>> = getElementPtr(%1, %31) - let %33 : vector<float,4> = load(%32) - let %34 : vector<float,4> = load(%27) - let %35 : vector<float,4> = add(%34, %33) - store(%27, %35) - let %36 : vector<float,4> = load(%27) - store(%26, %36) - unconditionalBranch(%20) + let %22 : uint = load(%6) + let %23 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %22) + let %24 : Ptr<vector<float,4>> = var() + let %25 : vector<float,4> = load(%23) + store(%24, %25) + let %26 : uint = load(%6) + let %27 : uint = load(%13) + let %28 : uint = sub(%26, %27) + let %29 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %28) + let %30 : vector<float,4> = load(%29) + let %31 : vector<float,4> = load(%24) + let %32 : vector<float,4> = add(%31, %30) + store(%24, %32) + let %33 : vector<float,4> = load(%24) + store(%23, %33) + unconditionalBranch(%17) -block %20: - let %37 : Ptr<uint> = var() - let %38 : uint = load(%16) - store(%37, %38) - let %39 : uint = construct(1) - let %40 : uint = load(%37) - let %41 : uint = shl(%40, %39) - store(%37, %41) - let %42 : uint = load(%37) - store(%16, %42) - unconditionalBranch(%18) +block %17: + let %34 : Ptr<uint> = var() + let %35 : uint = load(%13) + store(%34, %35) + let %36 : uint = construct(1) + let %37 : uint = load(%34) + let %38 : uint = shl(%37, %36) + store(%34, %38) + let %39 : uint = load(%34) + store(%13, %39) + unconditionalBranch(%15) -block %19: +block %16: GroupMemoryBarrierWithGroupSync() - let %43 : RWStructuredBuffer<vector<float,4>> = load(%3) - let %44 : uint = load(%8) - let %45 : Ptr<vector<float,4>> = getElementPtr(%1, 0) - let %46 : vector<float,4> = load(%45) - bufferStore(%43, %44, %46) + let %40 : RWStructuredBuffer<vector<float,4>> = load(@_SV06output) + let %41 : uint = load(%5) + let %42 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, 0) + let %43 : vector<float,4> = load(%42) + bufferStore(%40, %41, %43) return_void() } |
