summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/check.cpp395
-rw-r--r--source/slang/compiler.cpp4
-rw-r--r--source/slang/compiler.h54
-rw-r--r--source/slang/diagnostic-defs.h10
-rw-r--r--source/slang/emit.cpp51
-rw-r--r--source/slang/ir-bind-existentials.cpp372
-rw-r--r--source/slang/ir-bind-existentials.h15
-rw-r--r--source/slang/ir-inst-defs.h2
-rw-r--r--source/slang/ir-insts.h9
-rw-r--r--source/slang/ir-link.cpp25
-rw-r--r--source/slang/ir.cpp35
-rw-r--r--source/slang/ir.h4
-rw-r--r--source/slang/lower-to-ir.cpp72
-rw-r--r--source/slang/lower-to-ir.h1
-rw-r--r--source/slang/slang.cpp39
-rw-r--r--source/slang/slang.vcxproj2
-rw-r--r--source/slang/slang.vcxproj.filters6
-rw-r--r--source/slang/type-layout.cpp22
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))
{