diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-02-19 11:46:05 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-19 11:46:05 -0800 |
| commit | 32135c5bdfb4d387f8227742a2d2fd555898aca8 (patch) | |
| tree | fe78cbff0927a41ec759ba859cc16dd1280f661b /source | |
| parent | a3fd4e2bc40cfc77db953b14744c30e7a18e7c1d (diff) | |
First steps toward supporting interface-type parameters on shaders (#852)
* First steps toward supporting interface-type parameters on shaders
What's New
----------
From the perspective of a user, the main thing this change adds is the ability to declare top-level shader parameters (either at global scope, or in an entry-point parameter list) with interface types. For example, the following becomes possible:
```hlsl
// Define an interface to modify values
interface IModifier { float4 modify(float4 val); }
// Define some concrete implementations
struct Doubler : IModifier
{
float4 modify(float4 val) { return val + val; }
}
struct Squarer : IModifier { ... }
// Define a global shader parameter of interface type
IModifier gGlobalModifier;
// Define an entry point with an interface-type `uniform` parameter
void myShader(
unifrom IModifier entryPointModifier,
float4 inColor : COLOR,
out float4 outColor : SV_Target)
{
// Use the interface-type parameters to compute things
float4 color = inColor;
color = gGlobalModifier.modify(color);
color = entryPointModifier.modify(color);
outColor = color;
}
```
The user can specialize that shader by specifying the concrete types to use for global and entry-point parameters of interface types (e.g., plugging in `Doubler` for `gGlobalModifier` and `Squarer` for `entryPointModifier`).
The "plugging in" process is done in terms of a concept of both global and local "existential slots" which are a new `LayoutResourceKind` that represents the holes where concrete types need to be plugged in for existential/interface types.
In simple cases like the above, each interface-type parameter will yield a single existential slot in either the global or entry-point parameter layout. Users can query the start slot and number of slots for each shader parameter, just like they would for any other resource that a parameter can consume. Before generating specialized code, the user plugs in the name of the concrete type they would like to use for each slot using `spSetTypeNameForGlobalExistentialSlot` and/or `spSetTypeNameForEntryPointExistentialSlot`.
There are some major limitations to the implementation in this first change:
* Parameters must be of interface type (e.g., `IFoo`) and not an array (`IFoo[3]`), or buffer (`ConstantBuffer<IFoo>`) over an interface type. Similarly, `struct` types with interface-type fields still don't work.
* The work on interface-type function parameters still doesn't include support for `out` or `inout` parameters, nor for functions that return interface types (that isn't technically related to this change, but affects its usefullness).
* No work is being done to correctly lay out shader parameters once the concrete types for existential slots are known, so that this change really only works when the concrete type that gets plugged in is empty.
These limitations are severe enough that this feature isn't really usable as implemented in this change, and this merely represents a stepping stone toward a more complete implementation.
Implementation
--------------
The API side of thing largely mirrors what was already done to support passing strings for the type names to use for global/entry-point generic arguments, so there should be no major surprises there.
The logic in `check.cpp` computes the list of existential slots when creating unspecialized `Program`s and `EntryPoint`s (this is logically the "front end" of the compiler), and then checks the supplied argument types against what is expected in each slot when creating specialized `Program`s and `EntryPoint`s. This again mirrors how generic arguments are handled.
Type layout was extended to compute the number of existential slots that a type consumes, and will thus automatically assign ranges of slots to top-level and entry-point shader parameters in the same way it already allocates `register`s and `binding`s. The big missing feature is the ability to specialize a layout to account for the concrete types plugged into the existential-type slots.
IR generation for specialized programs and entry points was slightly extended so that it attaches information about the concrete types plugged into the existential slots, and the witness tables that show how they conform to the interface for that slot. The linking step needed some small tweaks to make sure that information gets copied over to the target-specific program when we start code generation.
The meat of the IR-level work is in `ir-bind-existentials.cpp`, which takes the information that was placed in the IR module by the generation/linking steps and uses it to rewrite shader parameters. For example, if there is a shader parameter `p` of type `IModifier`, and the corresponding existential slot has the type `Doubler` in it, we will rewrite the parameter to have type `Doubler`, and rewrite any uses of `p` to instead use `makeExistential(p, /*witness that Doubler conforms to IModifier*/)`.
Once the replacement is done on the parameters, the existing work for specializing existential-based code when the input type(s) are known kicks in and does the rest.
Testing
-------
A single compute test is added to validate that this feature works. It is narrowly tailored to not require any of the features not supported by the initial implementation (e.g., all of the concrete types used have no members).
The test case *does* include use of an associated type through one of these existential-type parameters, which has exposed a subtle bug in how "opening" of existential values is implemented in the front-end. Rather than fix the underlying problem, I cleaned up the code in the front-end to special case when the existential value being opened is a variable bound with `let`, to directly use a reference to that variable rather than introduce a temporary. Similarly, in the IR generation step, I added an optimization to make variables declared with `let` skip introducing an IR-level variable and just use the SSA value of their initializer directly instead.
* fixup: missing files
* fixup: incorrect type for unreachable return
* fixup: actually comment ir-bind-existentials.cpp
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)) { |
