diff options
| -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() } |
