diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 395 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 4 | ||||
| -rw-r--r-- | source/slang/compiler.h | 54 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 10 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 51 | ||||
| -rw-r--r-- | source/slang/ir-bind-existentials.cpp | 372 | ||||
| -rw-r--r-- | source/slang/ir-bind-existentials.h | 15 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 9 | ||||
| -rw-r--r-- | source/slang/ir-link.cpp | 25 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 35 | ||||
| -rw-r--r-- | source/slang/ir.h | 4 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 72 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.h | 1 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 39 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 6 | ||||
| -rw-r--r-- | source/slang/type-layout.cpp | 22 |
18 files changed, 1024 insertions, 94 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 483db60bb..a9f84c5c3 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -557,6 +557,117 @@ namespace Slang return isDeclUsableAsStaticMember(decl); } + /// Move `expr` into a temporary variable and execute `func` on that variable. + /// + /// Returns an expression that wraps both the creation and initialization of + /// the temporary, and the computation created by `func`. + /// + template<typename F> + RefPtr<Expr> moveTemp(RefPtr<Expr> const& expr, F const& func) + { + RefPtr<VarDecl> varDecl = new VarDecl(); + varDecl->ParentDecl = nullptr; // TODO: need to fill this in somehow! + varDecl->checkState = DeclCheckState::Checked; + varDecl->nameAndLoc.loc = expr->loc; + varDecl->initExpr = expr; + varDecl->type.type = expr->type.type; + + auto varDeclRef = makeDeclRef(varDecl.Ptr()); + + RefPtr<LetExpr> letExpr = new LetExpr(); + letExpr->decl = varDecl; + + auto body = func(varDeclRef); + + letExpr->body = body; + letExpr->type = body->type; + + return letExpr; + } + + /// Execute `func` on a variable with the value of `expr`. + /// + /// If `expr` is just a reference to an immutable (e.g., `let`) variable + /// then this might use the existing variable. Otherwise it will create + /// a new variable to hold `expr`, using `moveTemp()`. + /// + template<typename F> + RefPtr<Expr> maybeMoveTemp(RefPtr<Expr> const& expr, F const& func) + { + if(auto varExpr = as<VarExpr>(expr)) + { + auto declRef = varExpr->declRef; + if(auto varDeclRef = declRef.as<LetDecl>()) + return func(varDeclRef); + } + + return moveTemp(expr, func); + } + + /// Return an expression that represents "opening" the existential `expr`. + /// + /// The type of `expr` must be an interface type, matching `interfaceDeclRef`. + /// + /// If we scope down the PL theory to just the case that Slang cares about, + /// a value of an existential type like `IMover` is a tuple of: + /// + /// * a concrete type `X` + /// * a witness `w` of the fact that `X` implements `IMover` + /// * a value `v` of type `X` + /// + /// "Opening" an existential value is the process of decomposing a single + /// value `e : IMover` into the pieces `X`, `w`, and `v`. + /// + /// Rather than return all those pieces individually, this operation + /// returns an expression that logically corresponds to `v`: an expression + /// of type `X`, where the type carries the knowledge that `X` implements `IMover`. + /// + RefPtr<Expr> openExistential( + RefPtr<Expr> expr, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + // If `expr` refers to an immutable binding, + // then we can use it directly. If it refers + // to an arbitrary expression or a mutable + // binding, we will move its value into an + // immutable temporary so that we can use + // it directly. + // + auto interfaceDecl = interfaceDeclRef.getDecl(); + return maybeMoveTemp(expr, [&](DeclRef<VarDeclBase> varDeclRef) + { + RefPtr<ExtractExistentialType> openedType = new ExtractExistentialType(); + openedType->declRef = varDeclRef; + + RefPtr<ExtractExistentialSubtypeWitness> openedWitness = new ExtractExistentialSubtypeWitness(); + openedWitness->sub = openedType; + openedWitness->sup = expr->type.type; + openedWitness->declRef = varDeclRef; + + RefPtr<ThisTypeSubstitution> openedThisType = new ThisTypeSubstitution(); + openedThisType->outer = interfaceDeclRef.substitutions.substitutions; + openedThisType->interfaceDecl = interfaceDecl; + openedThisType->witness = openedWitness; + + DeclRef<InterfaceDecl> substDeclRef = DeclRef<InterfaceDecl>(interfaceDecl, openedThisType); + auto substDeclRefType = DeclRefType::Create(getSession(), substDeclRef); + + RefPtr<ExtractExistentialValueExpr> openedValue = new ExtractExistentialValueExpr(); + openedValue->declRef = varDeclRef; + openedValue->type = QualType(substDeclRefType); + + return openedValue; + }); + } + + /// If `expr` has existential type, then open it. + /// + /// Returns an expression that opens `expr` if it had existential type. + /// Otherwise returns `expr` itself. + /// + /// See `openExistential` for a discussion of what "opening" an + /// existential-type value means. + /// RefPtr<Expr> maybeOpenExistential(RefPtr<Expr> expr) { auto exprType = expr->type.type; @@ -584,45 +695,7 @@ namespace Slang { // Okay, here is the case that matters. // - - auto interfaceDecl = interfaceDeclRef.getDecl(); - - RefPtr<VarDecl> varDecl = new VarDecl(); - varDecl->ParentDecl = nullptr; // TODO: need to fill this in somehow! - varDecl->checkState = DeclCheckState::Checked; - varDecl->nameAndLoc.loc = expr->loc; - varDecl->initExpr = expr; - varDecl->type.type = expr->type.type; - - auto varDeclRef = makeDeclRef(varDecl.Ptr()); - - RefPtr<LetExpr> letExpr = new LetExpr(); - letExpr->decl = varDecl; - - RefPtr<ExtractExistentialType> openedType = new ExtractExistentialType(); - openedType->declRef = varDeclRef; - - RefPtr<ExtractExistentialSubtypeWitness> openedWitness = new ExtractExistentialSubtypeWitness(); - openedWitness->sub = openedType; - openedWitness->sup = expr->type.type; - openedWitness->declRef = varDeclRef; - - RefPtr<ThisTypeSubstitution> openedThisType = new ThisTypeSubstitution(); - openedThisType->outer = interfaceDeclRef.substitutions.substitutions; - openedThisType->interfaceDecl = interfaceDecl; - openedThisType->witness = openedWitness; - - DeclRef<InterfaceDecl> substDeclRef = DeclRef<InterfaceDecl>(interfaceDecl, openedThisType); - auto substDeclRefType = DeclRefType::Create(getSession(), substDeclRef); - - RefPtr<ExtractExistentialValueExpr> openedValue = new ExtractExistentialValueExpr(); - openedValue->declRef = varDeclRef; - openedValue->type = QualType(substDeclRefType); - - letExpr->body = openedValue; - letExpr->type = openedValue->type; - - return letExpr; + return openExistential(expr, interfaceDeclRef); } } } @@ -9232,13 +9305,78 @@ namespace Slang } } + /// Recursively walk `paramDeclRef` and add any required existential slots to `ioSlots`. + static void _collectExistentialParamsRec( + ExistentialSlots& ioSlots, + DeclRef<VarDeclBase> paramDeclRef) + { + auto type = GetType(paramDeclRef); + + // Whether or not something is an array does not affect + // the number of existential slots it introduces. + // + while( auto arrayType = as<ArrayExpressionType>(type) ) + { + type = arrayType->baseType; + } + + if( auto declRefType = as<DeclRefType>(type) ) + { + auto typeDeclRef = declRefType->declRef; + if( auto interfaceDeclRef = typeDeclRef.as<InterfaceDecl>() ) + { + // Each leaf parameter of interface type adds one slot. + // + ioSlots.types.Add(type); + } + else if( auto structDeclRef = typeDeclRef.as<StructDecl>() ) + { + // A structure type should recursively introduce + // existential slots for its fields. + // + for( auto fieldDeclRef : GetFields(structDeclRef) ) + { + if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()) + continue; + + _collectExistentialParamsRec(ioSlots, fieldDeclRef); + } + } + } + + // TODO: We eventually need to handle cases like constant + // buffers and parameter blocks that may have existential + // element types. + } + + /// Enumerate the existential-type parameters of an `EntryPoint`. + /// + /// Any parameters found will be added to the list of existential slots on `this`. + /// + void EntryPoint::_collectExistentialParams() + { + // Note: we defensively test whether there is a function decl-ref + // because this routine gets called from the constructor, and + // a "dummy" entry point will have a null pointer for the function. + // + if( auto funcDeclRef = getFuncDeclRef() ) + { + for( auto paramDeclRef : GetParameters(funcDeclRef) ) + { + _collectExistentialParamsRec(m_existentialSlots, paramDeclRef); + } + } + } + // Validate that an entry point function conforms to any additional // constraints based on the stage (and profile?) it specifies. void validateEntryPoint( - FuncDecl* entryPointFuncDecl, - Stage stage, + EntryPoint* entryPoint, DiagnosticSink* sink) { + auto entryPointFuncDecl = entryPoint->getFuncDecl(); + auto stage = entryPoint->getStage(); + // TODO: We currently do minimal checking here, but this is the // right place to perform the following validation checks: // @@ -9494,21 +9632,43 @@ namespace Slang } - // Now that we've *found* the entry point, it is time to validate - // that it actually meets the constraints for the chosen stage/profile. - // - validateEntryPoint( - entryPointFuncDecl, - entryPointProfile.GetStage(), - sink); - RefPtr<EntryPoint> entryPoint = EntryPoint::create( makeDeclRef(entryPointFuncDecl), entryPointProfile); + // Now that we've *found* the entry point, it is time to validate + // that it actually meets the constraints for the chosen stage/profile. + // + validateEntryPoint(entryPoint, sink); + return entryPoint; } + /// Enumerate the existential-type parameters of a `Program`. + /// + /// Any parameters found will be added to the list of existential slots on `this`. + /// + void Program::_collectExistentialParams() + { + // We need to inspect all of the global shader parameters + // referenced by the compile request, and for each we + // need to determine what existential types parameters it implies. + // + for( auto module : getModuleDependencies() ) + { + auto moduleDecl = module->getModuleDecl(); + for( auto globalVar : moduleDecl->getMembersOfType<VarDecl>() ) + { + if(!isGlobalShaderParameter(globalVar)) + continue; + + _collectExistentialParamsRec( + m_globalExistentialSlots, + makeDeclRef(globalVar.Ptr())); + } + } + } + /// Create a `Program` to represent the compiled code. /// /// The created program will comprise all of the translation @@ -9623,32 +9783,99 @@ namespace Slang Profile profile; profile.setStage(entryPointAttr->stage); - validateEntryPoint(funcDecl, entryPointAttr->stage, sink); - RefPtr<EntryPoint> entryPoint = EntryPoint::create( makeDeclRef(funcDecl), profile); + + validateEntryPoint(entryPoint, sink); + program->addEntryPoint(entryPoint); translationUnit->entryPoints.Add(entryPoint); } } } + program->_collectExistentialParams(); + return program; } - /// Create a specialization an existing entry point based on generic arguments. - DeclRef<FuncDecl> specializeEntryPoint( + static void _specializeExistentialSlots( Linkage* linkage, - FuncDecl* entryPointFuncDecl, + ExistentialSlots& ioSlots, + List<RefPtr<Expr>> const& args, + DiagnosticSink* sink) + { + UInt slotCount = ioSlots.types.Count(); + UInt argCount = args.Count(); + + if( slotCount != argCount ) + { + sink->diagnose(SourceLoc(), Diagnostics::mismatchExistentialSlotArgCount, slotCount, argCount); + return; + } + + SemanticsVisitor visitor(linkage, sink); + + for( UInt ii = 0; ii < slotCount; ++ii ) + { + auto slotType = ioSlots.types[ii]; + auto argExpr = args[ii]; + + auto argType = checkProperType(linkage, TypeExp(argExpr), sink); + if(!argType) + { + // TODO: Each slot should track a source location and/or a `VarDeclBase` + // that names the parameter that the slot corresponds to. + + sink->diagnose(SourceLoc(), Diagnostics::existentialSlotArgNotAType, ii); + return; + } + + + auto witness = visitor.tryGetSubtypeWitness(argType, slotType); + if (!witness) + { + // If no witness was found, then we will be unable to satisfy + // the conformances required. + sink->diagnose(SourceLoc(), Diagnostics::existentialSlotArgDoesNotConform, ii, slotType); + return; + } + + ExistentialSlots::Arg arg; + arg.type = argType; + arg.witness = witness; + ioSlots.args.Add(arg); + } + } + + void EntryPoint::_specializeExistentialSlots( + List<RefPtr<Expr>> const& args, + DiagnosticSink* sink) + { + Slang::_specializeExistentialSlots(getLinkage(), m_existentialSlots, args, sink); + } + + /// Create a specialization an existing entry point based on generic arguments. + RefPtr<EntryPoint> createSpecializedEntryPoint( + EntryPoint* unspecializedEntryPoint, List<RefPtr<Expr>> const& genericArgs, + List<RefPtr<Expr>> const& existentialArgs, DiagnosticSink* sink) { + auto linkage = unspecializedEntryPoint->getLinkage(); + + // TODO: Need to be careful in case entry point already has a decl-ref, + // pertaining to outer specializations (e.g., when entry point was + // nested in a generic type. + // + auto entryPointFuncDecl = unspecializedEntryPoint->getFuncDecl(); + SemanticsVisitor semantics( linkage, sink); - DeclRef<FuncDecl> entryPointFuncDeclRef = makeDeclRef(entryPointFuncDecl); + DeclRef<FuncDecl> entryPointFuncDeclRef = makeDeclRef(entryPointFuncDecl.Ptr()); if( auto genericDecl = as<GenericDecl>(entryPointFuncDecl->ParentDecl) ) { // We will construct a suitable `GenericAppExpr` to represent @@ -9702,7 +9929,7 @@ namespace Slang { // Any semantic error that occured should have been // reported already. - return DeclRef<FuncDecl>(); + return nullptr; } else { @@ -9710,11 +9937,18 @@ namespace Slang // function should always be a `DeclRefExpr` // SLANG_UNEXPECTED("reference to generic decl wasn't a `DeclRefExpr`"); - UNREACHABLE_RETURN(DeclRef<FuncDecl>()); + UNREACHABLE_RETURN(nullptr); } } - return entryPointFuncDeclRef; + RefPtr<EntryPoint> specializedEntryPoint = EntryPoint::create( + entryPointFuncDeclRef, + unspecializedEntryPoint->getProfile()); + + // Next we need to validate the existential arguments. + specializedEntryPoint->_specializeExistentialSlots(existentialArgs, sink); + + return specializedEntryPoint; } /// Parse an array of strings as generic arguments. @@ -9771,11 +10005,20 @@ namespace Slang } } + void Program::_specializeExistentialSlots( + List<RefPtr<Expr>> const& args, + DiagnosticSink* sink) + { + Slang::_specializeExistentialSlots(getLinkage(), m_globalExistentialSlots, args, sink); + } + + /// Specialize a program to global generic arguments RefPtr<Program> createSpecializedProgram( Linkage* linkage, Program* unspecializedProgram, List<RefPtr<Expr>> const& globalGenericArgs, + List<RefPtr<Expr>> const& globalExistentialArgs, DiagnosticSink* sink) { // The given `unspecializedProgram` should be one that @@ -9827,6 +10070,8 @@ namespace Slang // We have an appropriate number of arguments for the global generic parameters, // and now we need to check that the arguments conform to the declared constraints. // + SemanticsVisitor visitor(linkage, sink); + // Along the way, we will build up an appropriate set of substitutions to represent // the generic arguments and their conformances. // @@ -9905,7 +10150,6 @@ namespace Slang auto interfaceType = GetSup(DeclRef<GenericTypeConstraintDecl>(constraint, nullptr)); // Use our semantic-checking logic to search for a witness to the required conformance - SemanticsVisitor visitor(linkage, sink); auto witness = visitor.tryGetSubtypeWitness(globalGenericArg, interfaceType); if (!witness) { @@ -9937,6 +10181,10 @@ namespace Slang specializedProgram->setGlobalGenericSubsitution(globalGenericSubsts); + // Now deal with the existential arguments + specializedProgram->_collectExistentialParams(); + specializedProgram->_specializeExistentialSlots(globalExistentialArgs, sink); + return specializedProgram; } @@ -9949,12 +10197,11 @@ namespace Slang /// Returns a specialized entry point if everything worked as expected. /// Returns null and diagnoses errors if anything goes wrong. /// - RefPtr<EntryPoint> specializeEntryPoint( + RefPtr<EntryPoint> createSpecializedEntryPoint( EndToEndCompileRequest* endToEndReq, EntryPoint* unspecializedEntryPoint, EndToEndCompileRequest::EntryPointInfo const& entryPointInfo) { - auto linkage = endToEndReq->getLinkage(); auto sink = endToEndReq->getSink(); auto entryPointFuncDecl = unspecializedEntryPoint->getFuncDecl(); @@ -9967,19 +10214,21 @@ namespace Slang entryPointInfo.genericArgStrings, genericArgs); + List<RefPtr<Expr>> existentialArgs; + parseGenericArgStrings( + endToEndReq, + entryPointInfo.existentialArgStrings, + existentialArgs); + // Next we specialize the entry point function given the parsed // generic argument expressions. // - auto entryPointFuncDeclRef = specializeEntryPoint( - linkage, - entryPointFuncDecl, + auto entryPoint = createSpecializedEntryPoint( + unspecializedEntryPoint, genericArgs, + existentialArgs, sink); - RefPtr<EntryPoint> entryPoint = EntryPoint::create( - entryPointFuncDeclRef, - unspecializedEntryPoint->getProfile()); - return entryPoint; } @@ -10005,6 +10254,13 @@ namespace Slang endToEndReq->globalGenericArgStrings, globalGenericArgs); + // Also handle global existential type arguments. + List<RefPtr<Expr>> globalExistentialArgs; + parseGenericArgStrings( + endToEndReq, + endToEndReq->globalExistentialSlotArgStrings, + globalExistentialArgs); + // Now we create the initial specialized program by // applying the global generic arguments (if any) to the // unspecialized program. @@ -10013,6 +10269,7 @@ namespace Slang endToEndReq->getLinkage(), unspecializedProgram, globalGenericArgs, + globalExistentialArgs, endToEndReq->getSink()); // If anything went wrong with the global generic @@ -10045,7 +10302,7 @@ namespace Slang auto unspecializedEntryPoint = unspecializedProgram->getEntryPoint(ii); auto& entryPointInfo = endToEndReq->entryPoints[ii]; - auto specializedEntryPoint = specializeEntryPoint(endToEndReq, unspecializedEntryPoint, entryPointInfo); + auto specializedEntryPoint = createSpecializedEntryPoint(endToEndReq, unspecializedEntryPoint, entryPointInfo); specializedProgram->addEntryPoint(specializedEntryPoint); } diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 3bc34692d..a380231e9 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -226,6 +226,10 @@ namespace Slang } } } + + // Collect any existential-type parameters used by the entry point + // + _collectExistentialParams(); } Module* EntryPoint::getModule() diff --git a/source/slang/compiler.h b/source/slang/compiler.h index c975c1c2b..246b34ea7 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -17,7 +17,6 @@ namespace Slang { struct PathInfo; struct IncludeHandler; - class CompileRequest; class ProgramLayout; class PtrType; class TargetProgram; @@ -118,6 +117,26 @@ namespace Slang ComPtr<ISlangBlob> blob; }; + /// Collects information about placeholder "slots" for interface/existential types. + struct ExistentialSlots + { + /// The existential/interface type associated with each slot. + List<RefPtr<Type>> types; + + /// Source code for concrete type to plug in for each slot. +// List<String> argStrings; + + /// A concrete type argument plus a witness table for its conformance to the desired interface + struct Arg + { + RefPtr<Type> type; + RefPtr<Val> witness; + }; + + /// Concrete type arguments to plug into each slot + List<Arg> args; + }; + /// A request for the front-end to find and validate an entry-point function struct FrontEndEntryPointRequest : RefObject { @@ -264,12 +283,22 @@ namespace Slang Name* name, Profile profile); + UInt getExistentialSlotCount() { return m_existentialSlots.types.Count(); } + Type* getExistentialSlotType(UInt index) { return m_existentialSlots.types[index]; } + ExistentialSlots::Arg getExistentialSlotArg(UInt index) { return m_existentialSlots.args[index]; } + + void _specializeExistentialSlots( + List<RefPtr<Expr>> const& args, + DiagnosticSink* sink); + private: EntryPoint( Name* name, Profile profile, DeclRef<FuncDecl> funcDeclRef); + void _collectExistentialParams(); + // The name of the entry point function (e.g., `main`) // Name* m_name = nullptr; @@ -278,6 +307,9 @@ namespace Slang // DeclRef<FuncDecl> m_funcDeclRef; + /// The existential/interface slots associated with the entry point parameter scope. + ExistentialSlots m_existentialSlots; + // The profile that the entry point will be compiled for // (this is a combination of the target stage, and also // a feature level that sets capabilities) @@ -528,8 +560,6 @@ namespace Slang // Definitions to provide during preprocessing Dictionary<String, String> preprocessorDefinitions; - - // Source manager to help track files loaded SourceManager m_defaultSourceManager; SourceManager* m_sourceManager = nullptr; @@ -886,7 +916,17 @@ namespace Slang /// RefPtr<IRModule> getOrCreateIRModule(DiagnosticSink* sink); + UInt getExistentialSlotCount() { return m_globalExistentialSlots.types.Count(); } + Type* getExistentialSlotType(UInt index) { return m_globalExistentialSlots.types[index]; } + ExistentialSlots::Arg getExistentialSlotArg(UInt index) { return m_globalExistentialSlots.args[index]; } + + void _collectExistentialParams(); + void _specializeExistentialSlots( + List<RefPtr<Expr>> const& args, + DiagnosticSink* sink); + private: + // The linakge this program is associated with. // // Note that a `Program` keeps its associated linkage alive, @@ -906,6 +946,9 @@ namespace Slang // Specializations for global generic parameters (if any) RefPtr<Substitutions> m_globalGenericSubst; + // The existential/interface slots associated with the global scope. + ExistentialSlots m_globalExistentialSlots; + // Generated IR for this program. RefPtr<IRModule> m_irModule; @@ -1044,6 +1087,8 @@ namespace Slang /// Source code for the generic arguments to use for the global generic parameters of the program. List<String> globalGenericArgStrings; + /// Types to use to fill global existential "slots" + List<String> globalExistentialSlotArgStrings; bool shouldSkipCodegen = false; @@ -1061,6 +1106,9 @@ namespace Slang public: /// Source code for the generic arguments to use for the generic parameters of the entry point. List<String> genericArgStrings; + + /// Source code for the type arguments to plug into the existential type "slots" of the entry point + List<String> existentialArgStrings; }; List<EntryPointInfo> entryPoints; diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 2d0dd7fdd..a00c69dba 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -356,9 +356,12 @@ DIAGNOSTIC(38024, Error, invalidDispatchThreadIDType, "parameter with SV_Dispatc DIAGNOSTIC(-1, Note, noteWhenCompilingEntryPoint, "when compiling entry point '$0'") -DIAGNOSTIC(38020, Error, mismatchGlobalGenericArguments, "expected $0 global generic arguments ($1 provided)") -DIAGNOSTIC(38021, Error, globalTypeArgumentDoesNotConformToInterface, "type argument `$1` for global generic parameter `$0` does not conform to interface `$2`.") +DIAGNOSTIC(38025, Error, mismatchGlobalGenericArguments, "expected $0 global generic arguments ($1 provided)") +DIAGNOSTIC(38026, Error, globalTypeArgumentDoesNotConformToInterface, "type argument `$1` for global generic parameter `$0` does not conform to interface `$2`.") +DIAGNOSTIC(38027, Error, mismatchExistentialSlotArgCount, "expected $0 existential slot arguments ($1 provided)") +DIAGNOSTIC(38028, Error, existentialSlotArgNotAType, "existential slot argument $0 was not a type") +DIAGNOSTIC(38029, Error, existentialSlotArgDoesNotConform, "existential slot argument $0 does not conform to the required interface '$1'") DIAGNOSTIC(38200, Error, recursiveModuleImport, "module `$0` recursively imports itself") DIAGNOSTIC(39999, Fatal, errorInImportedModule, "error in imported module, compilation ceased.") @@ -420,6 +423,9 @@ DIAGNOSTIC(41010, Warning, missingReturn, "control flow may reach end of non-'vo // // 5xxxx - Target code generation. // + +DIAGNOSTIC(50010, Internal, missingExistentialBindingsForParameter, "missing argument for existential parameter slot"); + DIAGNOSTIC(50020, Error, invalidTessCoordType, "TessCoord must have vec2 or vec3 type.") DIAGNOSTIC(50020, Error, invalidFragCoordType, "FragCoord must be a vec4.") DIAGNOSTIC(50020, Error, invalidInvocationIdType, "InvocationId must have int type.") diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 2603df11e..6260ac218 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -2,6 +2,7 @@ #include "emit.h" #include "../core/slang-writer.h" +#include "ir-bind-existentials.h" #include "ir-dce.h" #include "ir-entry-point-uniforms.h" #include "ir-glsl-legalize.h" @@ -1671,6 +1672,7 @@ struct EmitVisitor case LayoutResourceKind::RegisterSpace: case LayoutResourceKind::GenericResource: + case LayoutResourceKind::ExistentialSlot: // ignore break; default: @@ -6730,7 +6732,7 @@ String emitEntryPoint( auto irEntryPoint = linkedIR.entryPoint; #if 0 - dumpIRIfEnabled(compileRequest, irModule, "CLONED"); + dumpIRIfEnabled(compileRequest, irModule, "LINKED"); #endif validateIRModuleIfEnabled(compileRequest, irModule); @@ -6740,6 +6742,23 @@ String emitEntryPoint( // un-specialized IR. dumpIRIfEnabled(compileRequest, irModule); + // When there are top-level existential-type parameters + // to the shader, we need to take the side-band information + // on how the existential "slots" were bound to concrete + // types, and use it to introduce additional explicit + // shader parameters for those slots, to be wired up to + // use sites. + // + bindExistentialSlots(irModule, sink); +#if 0 + dumpIRIfEnabled(compileRequest, irModule, "EXISTENTIALS BOUND"); +#endif + validateIRModuleIfEnabled(compileRequest, irModule); + + + + + // Now that we've linked the IR code, any layout/binding // information has been attached to shader parameters // and entry points. Now we are safe to make transformations @@ -6751,6 +6770,10 @@ String emitEntryPoint( // the global scope instead. // moveEntryPointUniformParamsToGlobalScope(irModule); +#if 0 + dumpIRIfEnabled(compileRequest, irModule, "ENTRY POINT UNIFORMS MOVED"); +#endif + validateIRModuleIfEnabled(compileRequest, irModule); // Desguar any union types, since these will be illegal on // various targets. @@ -6788,6 +6811,23 @@ String emitEntryPoint( #endif validateIRModuleIfEnabled(compileRequest, irModule); + + // Specialization can introduce dead code that could trip + // up downstream passes like type legalization, so we + // will run a DCE pass to clean up after the specialization. + // + // TODO: Are there other cleanup optimizations we should + // apply at this point? + // + eliminateDeadCode(compileRequest, irModule); +#if 0 + dumpIRIfEnabled(compileRequest, irModule, "AFTER DCE"); +#endif + validateIRModuleIfEnabled(compileRequest, irModule); + + + + // After we've fully specialized all generics, and // "devirtualized" all the calls through interfaces, // we need to ensure that the code only uses types @@ -6869,16 +6909,17 @@ String emitEntryPoint( irEntryPoint, compileRequest->getSink(), &sharedContext.extensionUsageTracker); + +#if 0 + dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED"); +#endif + validateIRModuleIfEnabled(compileRequest, irModule); } break; default: break; } -#if 0 - dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED"); -#endif - validateIRModuleIfEnabled(compileRequest, irModule); // The resource-based specialization pass above // may create specialized versions of functions, but diff --git a/source/slang/ir-bind-existentials.cpp b/source/slang/ir-bind-existentials.cpp new file mode 100644 index 000000000..7188e2503 --- /dev/null +++ b/source/slang/ir-bind-existentials.cpp @@ -0,0 +1,372 @@ +// ir-bind-existentials.cpp +#include "ir-bind-existentials.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang +{ + +// The code that comes out of the linking step will have instructions added +// that indicate how parameters with existential (interface) types are supposed +// to be specialized to concrete types. +// +// If there are any global existential-type parameters there should be a +// `bindGlobalExistentialSlots(...)` instruction at module scope. +// +// For each entry point with entry-point existential parameters, there should +// be a `[bindExistentialSlots(...)]` decoration attached to the entry +// point itself. +// +// In each case, the operands of the instruction should be a sequence of +// pairs. The number of pairs should match the number of existential "slots" +// at global or entry-point scope. Each pair should comprise a type `T` +// to plug into the slot, and a witness table `w` for the conformance of +// `T` to the interface type in that slot. +// +// In the simplest case, if we have a global shader parameter of interface +// type: +// +// IFoo p; +// +// Then this will lower to the IR as: +// +// global_param p : IFoo; +// +// And if the user tries to specialie `p` to type `Bar`, and a witness +// table `bar_is_ifoo`, we've have: +// +// bindGlobalExistentialSlots(Bar, bar_is_ifoo); +// +// The goal of this pass is to replace the parameter of interface type +// with one of concrete type: +// +// global_param p_new : Bar; +// +// and replace any reference to the old `p` parameter with +// a `makeExistential(p_new, bar_is_ifoo)`. That preserves the +// fact that a reference to `p` is conceptually of type `IFoo`, +// but allows downstream optimization passes to start specializing +// code based on the concrete knowledge that the value "backing" +// the parameter is actaully of type `Bar`. + +// As is typically for IR passes, we will encapsulate all the +// logic in a `struct` type. +// +struct BindExistentialSlots +{ + IRModule* module = nullptr; + DiagnosticSink* sink = nullptr; + + void processModule() + { + // We will start by dealing with the global existential slots. + processGlobalExistentialSlots(); + + // Then we will process the per-entry-point existential slots. + processEntryPointExistentialSlots(); + } + + void processGlobalExistentialSlots() + { + // If there are any global existential slots, we will expect + // to find a `bindGlobalExistentialSlots` instruction at module scope. + // + // We will start out by finding that instruction, if it exists. + // + IRInst* bindGlobalExistentialSlotsInst = nullptr; + for( auto inst : module->getGlobalInsts() ) + { + if( inst->op == kIROp_BindGlobalExistentialSlots ) + { + bindGlobalExistentialSlotsInst = inst; + break; + } + } + + // Now we will start looking for global shader parameters that make + // use of existential slots (we can determine this from their + // layout). + // + for( auto inst : module->getGlobalInsts() ) + { + // We only care about global shader parameters. + // + auto globalParam = as<IRGlobalParam>(inst); + if(!globalParam) + continue; + + // We will delegate to a subroutine for the meat + // of the work, since much of it can be shared + // with the case for entry-point existential + // parameters. + // + processParameter(globalParam, bindGlobalExistentialSlotsInst); + } + + // Once we are done looping over global shader parameters, + // all of the relevant information from the + // `bindGlobalExistentialSlots` instruction will have + // been moved to the parameters themselves, so we + // can eliminate the binding instruction. + // + if( bindGlobalExistentialSlotsInst ) + { + bindGlobalExistentialSlotsInst->removeAndDeallocate(); + } + } + + void processEntryPointExistentialSlots() + { + // The overall flow for the entry-point case is similar + // to the global case. + // + // We start by iterating over all the functions at + // global scope and look for entry points. + // + for( auto inst : module->getGlobalInsts() ) + { + auto func = as<IRFunc>(inst); + if(!func) + continue; + + if(!func->findDecorationImpl(kIROp_EntryPointDecoration)) + continue; + + // We then process each entry point we find. + // + processEntryPointExistentialSlots(func); + } + } + + void processEntryPointExistentialSlots(IRFunc* func) + { + // When looking at a single `func`, we need + // to find the `[bindExistentialSlots(...)]` decoration, + // if it has one. + // + auto bindEntryPointExistentialSlotsInst = func->findDecorationImpl(kIROp_BindExistentialSlotsDecoration); + + // We then need to process each of the entry-point + // parameters just like we did for global parameters. + // + for( auto param : func->getParams() ) + { + processParameter(param, bindEntryPointExistentialSlotsInst); + } + + // TODO: We would need to consider what to do if + // we had an existential return type for `func`. + // + // In general, it probably doesn't make sense to + // have existential types in varying input/output + // at all, so the front-end should probably be + // validating that. + + // Once we've processed all the parameters, the information + // in the `[bindExistentialSlots(...)]` decoration is + // no longer needed, and we can remove it. + // + if( bindEntryPointExistentialSlotsInst ) + { + bindEntryPointExistentialSlotsInst->removeAndDeallocate(); + } + } + + // When processing a single parameter we need to have access + // to the corresponding instruction that will bind its slots. + // + // We don't care whether we have a `global_param` and a + // `bindGlobalExistentialSlots` instruction, or an entry-point + // function `param` and a `[bindExistentialSlots(...)]` + // decoration; both use the same subroutine. + // + void processParameter( + IRInst* param, + IRInst* bindSlotsInst) + { + // We expect all shader parameters to have layout information, + // but to be defensive we will skip any that don't. + // + auto layoutDecoration = param->findDecoration<IRLayoutDecoration>(); + if(!layoutDecoration) + return; + auto varLayout = as<VarLayout>(layoutDecoration->getLayout()); + if(!varLayout) + return; + + // We only care about parameters that are associated + // with one or more existential slots. + // + auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::ExistentialSlot); + if(!resInfo) + return; + + // We will use the layout information on the variable to + // find out the stating slot, and the information on + // the type to find out the number of slots. + // + UInt firstSlot = resInfo->index; + UInt slotCount = 0; + if(auto typeResInfo = varLayout->getTypeLayout()->FindResourceInfo(LayoutResourceKind::ExistentialSlot)) + slotCount = UInt(typeResInfo->count.getFiniteValue()); + + // At this point we know that the parameter consumes + // some number of slots, so it would be an error + // if we don't have an instruction to bind the slots. + // + if( !bindSlotsInst ) + { + // Note: This error is considered an internal error because + // we should be detecting and diagnosing this problem before + // we make it to back-end code generation. + // + sink->diagnose(param->sourceLoc, Diagnostics::missingExistentialBindingsForParameter); + return; + } + + // Each existential slot corresponds to *two* arguments + // on the binding instruction: one for the type, and + // another for the witness table. + // + // We will check to make sure we have enough operands to cover + // this parameter. + // + UInt bindOperandCount = bindSlotsInst->getOperandCount(); + if( 2*(firstSlot + slotCount) > bindOperandCount ) + { + sink->diagnose(param->sourceLoc, Diagnostics::missingExistentialBindingsForParameter); + return; + } + // + // If there are enough operands, then we will offset to + // get to the starting point for the current parameter, + // keeping in mind that each slot accounts for two + // operands. + // + auto operandsForInst = bindSlotsInst->getOperands() + firstSlot; + + // Once we've found the operands that are relevent to + // the slots used by `param`, we will defer to a routine + // that replaces the type of `param` based on the + // information in the slots. + // + replaceTypeUsingExistentialSlots( + param, + slotCount, + operandsForInst); + } + + void replaceTypeUsingExistentialSlots( + IRInst* inst, + UInt slotCount, + IRUse const* slotArgs) + { + SLANG_UNUSED(slotCount); + + // We are going to alter the type of the + // given `inst` based on information in + // the `slotArgs`, but the exact kind + // of modification will depend on the + // original type of `inst`. + + auto fullType = inst->getFullType(); + auto type = inst->getDataType(); + + SharedIRBuilder sharedBuilder; + sharedBuilder.session = module->getSession(); + sharedBuilder.module = module; + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilder; + + // The easy case is when the `type` of `inst` + // is directly an interface type. + // + if( auto interfaceType = as<IRInterfaceType>(type) ) + { + // An intereface-type parameter will use a + // single slot, which consits of a pair of + // operands. + // + // The first operand is the concrete type + // we want to plug in. + // + auto newType = (IRType*) slotArgs[0].get(); + + // The second operand is a witness that + // the concrete type conforms to the interface + // used for the original parameter. + // + auto newWitnessTable = slotArgs[1].get(); + + // We are going to replace the (interface) type of + // the parameter with the new (concrete) type. + // + builder.setDataType(inst, newType); + + // Next we want to replace all uses of `inst` (which + // expect a value of its old type) with a fresh + // `makeExistential(...)` instruction that refers to + // `inst` with its new type. + // + // Note: we make a copy of the list of uses for `inst` + // before going through and replacing them, because + // during the replacement we make *more* uses of `inst`, + // as an operand to the `makeExistential` instructions. + // We only want to replace the old uses, and not the + // new ones we'll be making. + // + List<IRUse*> usesToReplace; + for(auto use = inst->firstUse; use; use = use->nextUse ) + usesToReplace.Add(use); + + // Now we can loop over our list of uses and replace each. + // + for(auto use : usesToReplace) + { + // First we emit a `makeExisential` right before the + // use site. + // + builder.setInsertBefore(use->getUser()); + auto newVal = builder.emitMakeExistential( + fullType, + inst, + newWitnessTable); + + // Second we make the use site point at the new + // value instead. + // + use->set(newVal); + } + } + else + { + // TODO: We eventually need to handle cases where there + // are: + // + // * Arrays over existential types; e.g.: `IFoo[3]` + // + // * Structs with existential-type fields. + // + // * Constant buffers or other "containers" over existentials; e.g., `ConstantBuffer<IFoo>` + // + // * Nested combinations of the above; e.g., a `ConstantBuffer` + // of a struct with a field that is an array of `IFoo`. + // + SLANG_UNIMPLEMENTED_X("shader parameters with nested existentials"); + } + } +}; + +void bindExistentialSlots( + IRModule* module, + DiagnosticSink* sink) +{ + BindExistentialSlots context; + context.module = module; + context.sink = sink; + context.processModule(); +} + +} diff --git a/source/slang/ir-bind-existentials.h b/source/slang/ir-bind-existentials.h new file mode 100644 index 000000000..c7fca3bb3 --- /dev/null +++ b/source/slang/ir-bind-existentials.h @@ -0,0 +1,15 @@ +// ir-bind-existentials.h +#pragma once + +namespace Slang +{ + +class DiagnosticSink; +struct IRModule; + + /// Bind concrete types to paameters that use existential slots. +void bindExistentialSlots( + IRModule* module, + DiagnosticSink* sink); + +} diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h index eada52e4d..6163feb1c 100644 --- a/source/slang/ir-inst-defs.h +++ b/source/slang/ir-inst-defs.h @@ -190,6 +190,7 @@ INST(Specialize, specialize, 2, 0) INST(lookup_interface_method, lookup_interface_method, 2, 0) INST(lookup_witness_table, lookup_witness_table, 2, 0) INST(BindGlobalGenericParam, bind_global_generic_param, 2, 0) +INST(BindGlobalExistentialSlots, bindGlobalExistentialSlots, 0, 0) INST(Construct, construct, 0, 0) @@ -395,6 +396,7 @@ INST(HighLevelDeclDecoration, highLevelDecl, 1, 0) /// A `[keepAlive]` decoration marks an instruction that should not be eliminated. INST(KeepAliveDecoration, keepAlive, 0, 0) + INST(BindExistentialSlotsDecoration, bindExistentialSlots, 0, 0) /* LinkageDecoration */ INST(ImportDecoration, import, 1, 0) diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index b7c1b2744..0c3622172 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -1081,6 +1081,15 @@ struct IRBuilder IRInst* param, IRInst* val); + IRInst* emitBindGlobalExistentialSlots( + UInt argCount, + IRInst* const* args); + + IRDecoration* addBindExistentialSlotsDecoration( + IRInst* value, + UInt argCount, + IRInst* const* args); + IRInst* emitExtractTaggedUnionTag( IRInst* val); diff --git a/source/slang/ir-link.cpp b/source/slang/ir-link.cpp index 2eef10614..fd9f1222e 100644 --- a/source/slang/ir-link.cpp +++ b/source/slang/ir-link.cpp @@ -731,6 +731,23 @@ IRFunc* specializeIRForEntryPoint( clonedVal = specializeGeneric(clonedSpec); } + // TODO: If there is an existential-related decoration + // on the entry point, we need to transfer it over + // to the specialized function. + if( auto bindExistentialSlots = originalVal->findDecorationImpl(kIROp_BindExistentialSlotsDecoration) ) + { + if( !clonedVal->findDecorationImpl(kIROp_BindExistentialSlotsDecoration) ) + { + IRBuilder builderStorage = *context->builder; + IRBuilder* builder = &builderStorage; + builder->setInsertInto(clonedVal); + + auto clonedBind = cloneInst(context, builder, bindExistentialSlots); + clonedBind->moveToStart(); + } + } + + auto clonedFunc = as<IRFunc>(clonedVal); if(!clonedFunc) { @@ -1315,6 +1332,14 @@ LinkedIR linkIR( cloneValue(context, bindInst); } + for(auto inst : originalProgramIRModule->getGlobalInsts()) + { + if(inst->op != kIROp_BindGlobalExistentialSlots) + continue; + + cloneValue(context, inst); + } + // HACK: we need to ensure that any tagged union types // in the IR module have layout information copied over to them. // diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index ce587aec5..4a0a371d8 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -2723,6 +2723,41 @@ namespace Slang return inst; } + IRInst* IRBuilder::emitBindGlobalExistentialSlots( + UInt argCount, + IRInst* const* args) + { + auto inst = createInstWithTrailingArgs<IRInst>( + this, + kIROp_BindGlobalExistentialSlots, + getVoidType(), + 0, + nullptr, + argCount, + args); + addInst(inst); + return inst; + } + + IRDecoration* IRBuilder::addBindExistentialSlotsDecoration( + IRInst* value, + UInt argCount, + IRInst* const* args) + { + auto decoration = createInstWithTrailingArgs<IRDecoration>( + this, + kIROp_BindExistentialSlotsDecoration, + getVoidType(), + 0, + nullptr, + argCount, + args); + + decoration->insertAtStart(value); + + return decoration; + } + IRInst* IRBuilder::emitExtractTaggedUnionTag( IRInst* val) { diff --git a/source/slang/ir.h b/source/slang/ir.h index bbb68cdda..515b34399 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -114,8 +114,8 @@ IROpInfo getIROpInfo(IROp op); // A use of another value/inst within an IR operation struct IRUse { - IRInst* get() { return usedValue; } - IRInst* getUser() { return user; } + IRInst* get() const { return usedValue; } + IRInst* getUser() const { return user; } void init(IRInst* user, IRInst* usedValue); void set(IRInst* usedValue); diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index b53bb8ebb..e5d62d257 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -4793,21 +4793,23 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // A user-defined variable declaration will usually turn into // an `alloca` operation for the variable's storage, // plus some code to initialize it and then store to the variable. - // - // TODO: we may want to special-case things when the variable's - // type, qualifiers, or context mark it as something that can't - // be mutable (or even do some limited dataflow pass to check - // which variables ever get assigned) so that we can directly - // emit an SSA value in this common case. - // IRType* varType = lowerType(context, decl->getType()); - // TODO: If the variable is marked `static` then we need to - // deal with it specially: we should move its allocation out - // to the global scope, and then we have to deal with its - // initializer expression a bit carefully (it should only - // be initialized on-demand at its first use). + // As a special case, an immutable local variable with an + // initializer can just lower to the SSA value of its initializer. + // + if(as<LetDecl>(decl)) + { + if(auto initExpr = decl->initExpr) + { + auto initVal = lowerRValueExpr(context, initExpr); + initVal = materialize(context, initVal); + setGlobalValue(context, decl, initVal); + return initVal; + } + } + LoweredValInfo varVal = createVar(context, varType, decl); @@ -6187,6 +6189,30 @@ static void lowerProgramEntryPointToIR( { builder->addExportDecoration(loweredEntryPointFunc, getMangledName(entryPointFuncDeclRef).getUnownedSlice()); } + + // We may have shader parameters of interface/existential type, + // which need us to supply concrete type information for specialization. + // + auto existentialSlotCount = entryPoint->getExistentialSlotCount(); + if( existentialSlotCount ) + { + List<IRInst*> existentialSlotArgs; + for( UInt ii = 0; ii < existentialSlotCount; ++ii ) + { + auto arg = entryPoint->getExistentialSlotArg(ii); + + auto irArgType = lowerType(context, arg.type); + auto irWitnessTable = lowerSimpleVal(context, arg.witness); + + existentialSlotArgs.Add(irArgType); + existentialSlotArgs.Add(irWitnessTable); + } + + builder->addBindExistentialSlotsDecoration(loweredEntryPointFunc, existentialSlotArgs.Count(), existentialSlotArgs.Buffer()); + } + + + } /// Ensure that `decl` and all relevant declarations under it get emitted. @@ -6394,6 +6420,28 @@ RefPtr<IRModule> generateIRForProgram( } } + // We may have shader parameters of interface/existential type, + // which need us to supply concrete type information for specialization. + // + auto existentialSlotCount = program->getExistentialSlotCount(); + if( existentialSlotCount ) + { + List<IRInst*> existentialSlotArgs; + for( UInt ii = 0; ii < existentialSlotCount; ++ii ) + { + auto arg = program->getExistentialSlotArg(ii); + + auto irArgType = lowerType(context, arg.type); + auto irWitnessTable = lowerSimpleVal(context, arg.witness); + + existentialSlotArgs.Add(irArgType); + existentialSlotArgs.Add(irWitnessTable); + } + + builder->emitBindGlobalExistentialSlots(existentialSlotArgs.Count(), existentialSlotArgs.Buffer()); + } + + // TODO: Should we apply any of the validation or // mandatory optimization passes here? diff --git a/source/slang/lower-to-ir.h b/source/slang/lower-to-ir.h index f607e852c..79e1acc61 100644 --- a/source/slang/lower-to-ir.h +++ b/source/slang/lower-to-ir.h @@ -13,7 +13,6 @@ namespace Slang { - class CompileRequest; class EntryPoint; class ProgramLayout; class TranslationUnitRequest; diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 7a5b58d07..45834c31d 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -2081,6 +2081,45 @@ SLANG_API SlangResult spSetGlobalGenericArgs( return SLANG_OK; } +SLANG_API SlangResult spSetTypeNameForGlobalExistentialSlot( + SlangCompileRequest* request, + int slotIndex, + char const* typeName) +{ + if(!request) return SLANG_FAIL; + if(slotIndex < 0) return SLANG_FAIL; + if(!typeName) return SLANG_FAIL; + + auto req = convert(request); + auto& typeArgStrings = req->globalExistentialSlotArgStrings; + if(Slang::UInt(slotIndex) >= typeArgStrings.Count()) + typeArgStrings.SetSize(slotIndex+1); + typeArgStrings[slotIndex] = Slang::String(typeName); + return SLANG_OK; +} + +SLANG_API SlangResult spSetTypeNameForEntryPointExistentialSlot( + SlangCompileRequest* request, + int entryPointIndex, + int slotIndex, + char const* typeName) +{ + if(!request) return SLANG_FAIL; + if(entryPointIndex < 0) return SLANG_FAIL; + if(slotIndex < 0) return SLANG_FAIL; + if(!typeName) return SLANG_FAIL; + + auto req = convert(request); + if(Slang::UInt(entryPointIndex) >= req->entryPoints.Count()) + return SLANG_FAIL; + + auto& entryPointInfo = req->entryPoints[entryPointIndex]; + auto& typeArgStrings = entryPointInfo.existentialArgStrings; + if(Slang::UInt(slotIndex) >= typeArgStrings.Count()) + typeArgStrings.SetSize(slotIndex+1); + typeArgStrings[slotIndex] = Slang::String(typeName); + return SLANG_OK; +} // Compile in a context that already has its translation units specified SLANG_API SlangResult spCompile( diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index b0ac37440..577097c93 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -181,6 +181,7 @@ <ClInclude Include="expr-defs.h" /> <ClInclude Include="glsl.meta.slang.h" /> <ClInclude Include="hlsl.meta.slang.h" /> + <ClInclude Include="ir-bind-existentials.h" /> <ClInclude Include="ir-clone.h" /> <ClInclude Include="ir-constexpr.h" /> <ClInclude Include="ir-dce.h" /> @@ -237,6 +238,7 @@ <ClCompile Include="diagnostics.cpp" /> <ClCompile Include="dxc-support.cpp" /> <ClCompile Include="emit.cpp" /> + <ClCompile Include="ir-bind-existentials.cpp" /> <ClCompile Include="ir-clone.cpp" /> <ClCompile Include="ir-constexpr.cpp" /> <ClCompile Include="ir-dce.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index 0a44f9f57..f9514ce99 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -42,6 +42,9 @@ <ClInclude Include="hlsl.meta.slang.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="ir-bind-existentials.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="ir-clone.h"> <Filter>Header Files</Filter> </ClInclude> @@ -206,6 +209,9 @@ <ClCompile Include="emit.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ir-bind-existentials.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="ir-clone.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp index 95a92ee2c..6040df155 100644 --- a/source/slang/type-layout.cpp +++ b/source/slang/type-layout.cpp @@ -2357,6 +2357,28 @@ SimpleLayoutInfo GetLayoutImpl( rules, outTypeLayout); } + else if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() ) + { + // When laying out a type that includes interface-type fields, + // we cannot know how much space the concrete type that + // gets stored into the field consumes. + // + // If we were doing layout for a typical CPU target, then + // we could just say that each interface-type field consumes + // some fixed number of pointers (e.g., a data pointer plus a witness + // table pointer). + // + // We will borrow the intuition from that and invent a new + // resource kind for "existential slots" which conceptually + // represents the indirections needed to reference the + // data to be referenced by this field. + // + return GetSimpleLayoutImpl( + SimpleLayoutInfo(LayoutResourceKind::ExistentialSlot, 1), + type, + rules, + outTypeLayout); + } } else if (auto errorType = as<ErrorType>(type)) { |
