summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/check.cpp61
-rw-r--r--source/slang/compiler.cpp2
-rw-r--r--source/slang/compiler.h73
-rw-r--r--source/slang/emit.cpp111
-rw-r--r--source/slang/ir-bind-existentials.cpp128
-rw-r--r--source/slang/ir-inst-defs.h29
-rw-r--r--source/slang/ir-insts.h32
-rw-r--r--source/slang/ir-legalize-types.cpp332
-rw-r--r--source/slang/ir-specialize.cpp169
-rw-r--r--source/slang/ir.cpp113
-rw-r--r--source/slang/ir.h18
-rw-r--r--source/slang/legalize-types.cpp405
-rw-r--r--source/slang/legalize-types.h246
-rw-r--r--source/slang/lower-to-ir.cpp16
-rw-r--r--source/slang/parameter-binding.cpp36
-rw-r--r--source/slang/slang.cpp4
-rw-r--r--source/slang/type-layout.cpp174
-rw-r--r--source/slang/type-layout.h60
18 files changed, 1644 insertions, 365 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 91bbd694a..9f3282900 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -9363,12 +9363,15 @@ namespace Slang
}
/// Recursively walk `paramDeclRef` and add any required existential slots to `ioSlots`.
- static void _collectExistentialSlotsRec(
- ExistentialSlots& ioSlots,
- DeclRef<VarDeclBase> paramDeclRef)
+ static void _collectExistentialTypeParamsRec(
+ ExistentialTypeSlots& ioSlots,
+ DeclRef<VarDeclBase> paramDeclRef);
+
+ /// Recursively walk `type` and discover any required existential type parameters.
+ static void _collectExistentialTypeParamsRec(
+ ExistentialTypeSlots& ioSlots,
+ Type* type)
{
- auto type = GetType(paramDeclRef);
-
// Whether or not something is an array does not affect
// the number of existential slots it introduces.
//
@@ -9377,6 +9380,12 @@ namespace Slang
type = arrayType->baseType;
}
+ if( auto parameterGroupType = as<ParameterGroupType>(type) )
+ {
+ _collectExistentialTypeParamsRec(ioSlots, parameterGroupType->getElementType());
+ return;
+ }
+
if( auto declRefType = as<DeclRefType>(type) )
{
auto typeDeclRef = declRefType->declRef;
@@ -9384,7 +9393,7 @@ namespace Slang
{
// Each leaf parameter of interface type adds one slot.
//
- ioSlots.types.Add(type);
+ ioSlots.paramTypes.Add(type);
}
else if( auto structDeclRef = typeDeclRef.as<StructDecl>() )
{
@@ -9396,7 +9405,7 @@ namespace Slang
if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>())
continue;
- _collectExistentialSlotsRec(ioSlots, fieldDeclRef);
+ _collectExistentialTypeParamsRec(ioSlots, fieldDeclRef);
}
}
}
@@ -9406,15 +9415,23 @@ namespace Slang
// element types.
}
+ static void _collectExistentialTypeParamsRec(
+ ExistentialTypeSlots& ioSlots,
+ DeclRef<VarDeclBase> paramDeclRef)
+ {
+ _collectExistentialTypeParamsRec(ioSlots, GetType(paramDeclRef));
+ }
+
+
/// Add information about a shader parameter to `ioParams` and `ioSlots`
static void _collectExistentialSlotsForShaderParam(
ShaderParamInfo& ioParamInfo,
- ExistentialSlots& ioSlots,
+ ExistentialTypeSlots& ioSlots,
DeclRef<VarDeclBase> paramDeclRef)
{
- UInt startSlot = ioSlots.types.Count();
- _collectExistentialSlotsRec(ioSlots, paramDeclRef);
- UInt endSlot = ioSlots.types.Count();
+ UInt startSlot = ioSlots.paramTypes.Count();
+ _collectExistentialTypeParamsRec(ioSlots, paramDeclRef);
+ UInt endSlot = ioSlots.paramTypes.Count();
UInt slotCount = endSlot - startSlot;
ioParamInfo.firstExistentialTypeSlot = startSlot;
@@ -10343,13 +10360,13 @@ static bool doesParameterMatch(
return program;
}
- static void _specializeExistentialSlots(
+ static void _specializeExistentialTypeParams(
Linkage* linkage,
- ExistentialSlots& ioSlots,
+ ExistentialTypeSlots& ioSlots,
List<RefPtr<Expr>> const& args,
DiagnosticSink* sink)
{
- UInt slotCount = ioSlots.types.Count();
+ UInt slotCount = ioSlots.paramTypes.Count();
UInt argCount = args.Count();
if( slotCount != argCount )
@@ -10362,7 +10379,7 @@ static bool doesParameterMatch(
for( UInt ii = 0; ii < slotCount; ++ii )
{
- auto slotType = ioSlots.types[ii];
+ auto slotType = ioSlots.paramTypes[ii];
auto argExpr = args[ii];
auto argType = checkProperType(linkage, TypeExp(argExpr), sink);
@@ -10385,18 +10402,18 @@ static bool doesParameterMatch(
return;
}
- ExistentialSlots::Arg arg;
+ ExistentialTypeSlots::Arg arg;
arg.type = argType;
arg.witness = witness;
ioSlots.args.Add(arg);
}
}
- void EntryPoint::_specializeExistentialSlots(
+ void EntryPoint::_specializeExistentialTypeParams(
List<RefPtr<Expr>> const& args,
DiagnosticSink* sink)
{
- Slang::_specializeExistentialSlots(getLinkage(), m_existentialSlots, args, sink);
+ Slang::_specializeExistentialTypeParams(getLinkage(), m_existentialSlots, args, sink);
}
/// Create a specialization an existing entry point based on generic arguments.
@@ -10489,7 +10506,7 @@ static bool doesParameterMatch(
unspecializedEntryPoint->getProfile());
// Next we need to validate the existential arguments.
- specializedEntryPoint->_specializeExistentialSlots(existentialArgs, sink);
+ specializedEntryPoint->_specializeExistentialTypeParams(existentialArgs, sink);
return specializedEntryPoint;
}
@@ -10548,11 +10565,11 @@ static bool doesParameterMatch(
}
}
- void Program::_specializeExistentialSlots(
+ void Program::_specializeExistentialTypeParams(
List<RefPtr<Expr>> const& args,
DiagnosticSink* sink)
{
- Slang::_specializeExistentialSlots(getLinkage(), m_globalExistentialSlots, args, sink);
+ Slang::_specializeExistentialTypeParams(getLinkage(), m_globalExistentialSlots, args, sink);
}
@@ -10733,7 +10750,7 @@ static bool doesParameterMatch(
// unspecialized on first, which is maybe not always desirable.
//
specializedProgram->_collectShaderParams(sink);
- specializedProgram->_specializeExistentialSlots(globalExistentialArgs, sink);
+ specializedProgram->_specializeExistentialTypeParams(globalExistentialArgs, sink);
return specializedProgram;
}
diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp
index 40bc83052..bc4152244 100644
--- a/source/slang/compiler.cpp
+++ b/source/slang/compiler.cpp
@@ -746,7 +746,7 @@ namespace Slang
flags |= D3DCOMPILE_ENABLE_STRICTNESS;
flags |= D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES;
- const String sourcePath = "slang-geneated";// calcTranslationUnitSourcePath(entryPoint->getTranslationUnit());
+ const String sourcePath = calcSourcePathForEntryPoint(endToEndReq, entryPointIndex);
ComPtr<ID3DBlob> codeBlob;
ComPtr<ID3DBlob> diagnosticsBlob;
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index f456e2be2..71cd0adbf 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -117,23 +117,28 @@ namespace Slang
ComPtr<ISlangBlob> blob;
};
- /// Collects information about placeholder "slots" for interface/existential types.
- struct ExistentialSlots
+ /// Collects information about existential type parameters and their arguments.
+ struct ExistentialTypeSlots
{
- /// The existential/interface type associated with each slot.
- List<RefPtr<Type>> types;
+ /// For each type parameter, holds the interface/existential type that constrains it.
+ List<RefPtr<Type>> paramTypes;
- /// 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
+ /// An argument for an existential type parameter.
+ ///
+ /// Comprises a concrete type and a witness for its conformance to the desired
+ /// interface/existential type for the corresponding parameter.
+ ///
struct Arg
{
RefPtr<Type> type;
RefPtr<Val> witness;
};
- /// Concrete type arguments to plug into each slot
+ /// Any arguments provided for the existential type parameters.
+ ///
+ /// It is possible for `args` to be empty even if `paramTypes` is non-empty;
+ /// that situation represents an unspecialized program or entry point.
+ ///
List<Arg> args;
};
@@ -299,13 +304,28 @@ 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]; }
+ /// Get the number of existential type parameters for the entry point.
+ UInt getExistentialTypeParamCount() { return m_existentialSlots.paramTypes.Count(); }
+
+ /// Get the existential type parameter at `index`.
+ Type* getExistentialTypeParam(UInt index) { return m_existentialSlots.paramTypes[index]; }
+
+ /// Get the number of arguments supplied for existential type parameters.
+ ///
+ /// Note that the number of arguments may not match the number of parameters.
+ /// In particular, an unspecialized entry point may have many parameters, but zero arguments.
+ UInt getExistentialTypeArgCount() { return m_existentialSlots.args.Count(); }
+ /// Get the existential type argument (type and witness table) at `index`.
+ ExistentialTypeSlots::Arg getExistentialTypeArg(UInt index) { return m_existentialSlots.args[index]; }
+
+ /// Get an array of all existential type arguments.
+ ExistentialTypeSlots::Arg const* getExistentialTypeArgs() { return m_existentialSlots.args.Buffer(); }
+
+ /// Get an array of all entry-point shader parameters.
List<ShaderParamInfo> const& getShaderParams() { return m_shaderParams; }
- void _specializeExistentialSlots(
+ void _specializeExistentialTypeParams(
List<RefPtr<Expr>> const& args,
DiagnosticSink* sink);
@@ -326,7 +346,7 @@ namespace Slang
DeclRef<FuncDecl> m_funcDeclRef;
/// The existential/interface slots associated with the entry point parameter scope.
- ExistentialSlots m_existentialSlots;
+ ExistentialTypeSlots m_existentialSlots;
/// Information about entry-point parameters
List<ShaderParamInfo> m_shaderParams;
@@ -939,14 +959,29 @@ 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]; }
+ /// Get the number of existential type parameters for the program.
+ UInt getExistentialTypeParamCount() { return m_globalExistentialSlots.paramTypes.Count(); }
+
+ /// Get the existential type parameter at `index`.
+ Type* getExistentialTypeParam(UInt index) { return m_globalExistentialSlots.paramTypes[index]; }
+
+ /// Get the number of arguments supplied for existential type parameters.
+ ///
+ /// Note that the number of arguments may not match the number of parameters.
+ /// In particular, an unspecialized program may have many parameters, but zero arguments.
+ UInt getExistentialTypeArgCount() { return m_globalExistentialSlots.args.Count(); }
+
+ /// Get the existential type argument (type and witness table) at `index`.
+ ExistentialTypeSlots::Arg getExistentialTypeArg(UInt index) { return m_globalExistentialSlots.args[index]; }
+
+ /// Get an array of all existential type arguments.
+ ExistentialTypeSlots::Arg const* getExistentialTypeArgs() { return m_globalExistentialSlots.args.Buffer(); }
+ /// Get an array of all global shader parameters.
List<GlobalShaderParamInfo> const& getShaderParams() { return m_shaderParams; }
void _collectShaderParams(DiagnosticSink* sink);
- void _specializeExistentialSlots(
+ void _specializeExistentialTypeParams(
List<RefPtr<Expr>> const& args,
DiagnosticSink* sink);
@@ -972,7 +1007,7 @@ namespace Slang
RefPtr<Substitutions> m_globalGenericSubst;
// The existential/interface slots associated with the global scope.
- ExistentialSlots m_globalExistentialSlots;
+ ExistentialTypeSlots m_globalExistentialSlots;
/// Information about global shader parameters
List<GlobalShaderParamInfo> m_shaderParams;
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 815f27bfe..dcc99fc0d 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -1672,7 +1672,8 @@ struct EmitVisitor
case LayoutResourceKind::RegisterSpace:
case LayoutResourceKind::GenericResource:
- case LayoutResourceKind::ExistentialSlot:
+ case LayoutResourceKind::ExistentialTypeParam:
+ case LayoutResourceKind::ExistentialObjectParam:
// ignore
break;
default:
@@ -6641,9 +6642,28 @@ StructTypeLayout* getGlobalStructLayout(
return getScopeStructLayout(programLayout);
}
-void legalizeTypes(
- TypeLegalizationContext* context,
- IRModule* module);
+static void dumpIR(
+ BackEndCompileRequest* compileRequest,
+ IRModule* irModule,
+ char const* label)
+{
+ DiagnosticSinkWriter writerImpl(compileRequest->getSink());
+ WriterHelper writer(&writerImpl);
+
+ if(label)
+ {
+ writer.put("### ");
+ writer.put(label);
+ writer.put(":\n");
+ }
+
+ dumpIR(irModule, writer.getWriter());
+
+ if( label )
+ {
+ writer.put("###\n");
+ }
+}
static void dumpIRIfEnabled(
BackEndCompileRequest* compileRequest,
@@ -6652,22 +6672,7 @@ static void dumpIRIfEnabled(
{
if(compileRequest->shouldDumpIR)
{
- DiagnosticSinkWriter writerImpl(compileRequest->getSink());
- WriterHelper writer(&writerImpl);
-
- if(label)
- {
- writer.put("### ");
- writer.put(label);
- writer.put(":\n");
- }
-
- dumpIR(irModule, writer.getWriter());
-
- if( label )
- {
- writer.put("###\n");
- }
+ dumpIR(compileRequest, irModule, label);
}
}
@@ -6825,30 +6830,54 @@ String emitEntryPoint(
#endif
validateIRModuleIfEnabled(compileRequest, irModule);
+ // The Slang language allows interfaces to be used like
+ // ordinary types (including placing them in constant
+ // buffers and entry-point parameter lists), but then
+ // getting them to lay out in a reasonable way requires
+ // us to treat fields/variables with interface type
+ // *as if* they were pointers to heap-allocated "objects."
+ //
+ // Specialization will have replaced fields/variables
+ // with interface types like `IFoo` with fields/variables
+ // with pointer-like types like `ExistentialBox<SomeType>`.
+ //
+ // We need to legalize these pointer-like types away,
+ // which involves two main changes:
+ //
+ // 1. Any `ExistentialBox<...>` fields need to be moved
+ // out of their enclosing `struct` type, so that the layout
+ // of the enclosing type is computed as if the field had
+ // zero size.
+ //
+ // 2. Once an `ExistentialBox<X>` has been floated out
+ // of its parent and landed somwhere permanent (e.g., either
+ // a dedicated variable, or a field of constant buffer),
+ // we need to replace it with just an `X`, after which we
+ // will have (more) legal shader code.
+ //
+ legalizeExistentialTypeLayout(
+ irModule,
+ sink);
+#if 0
+ dumpIRIfEnabled(compileRequest, irModule, "EXISTENTIALS LEGALIZED");
+#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
- // that are legal on the chosen target.
+ // Many of our target languages and/or downstream compilers
+ // don't support `struct` types that have resource-type fields.
+ // In order to work around this limitation, we will rewrite the
+ // IR so that any structure types with resource-type fields get
+ // split into a "tuple" that comprises the ordinary fields (still
+ // bundles up as a `struct`) and one element for each resource-type
+ // field (recursively).
//
- {
- // TODO: The presence of `TypeLegalizationContext`
- // in the public API of the `legalizeTypes` function
- // is a throwback to when there was AST-level
- // type legalization and all the complications it
- // created. The pass should be refactored to not
- // expose these details.
- //
- TypeLegalizationContext typeLegalizationContext;
- initialize(&typeLegalizationContext,
- session,
- irModule);
- legalizeTypes(
- &typeLegalizationContext,
- irModule);
- }
+ // What used to be individual variables/parameters/arguments/etc.
+ // then become multiple variables/parameters/arguments/etc.
+ //
+ legalizeResourceTypes(
+ irModule,
+ sink);
// Debugging output of legalization
#if 0
diff --git a/source/slang/ir-bind-existentials.cpp b/source/slang/ir-bind-existentials.cpp
index 7188e2503..f87a7d233 100644
--- a/source/slang/ir-bind-existentials.cpp
+++ b/source/slang/ir-bind-existentials.cpp
@@ -198,7 +198,7 @@ struct BindExistentialSlots
// We only care about parameters that are associated
// with one or more existential slots.
//
- auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::ExistentialSlot);
+ auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::ExistentialTypeParam);
if(!resInfo)
return;
@@ -208,7 +208,7 @@ struct BindExistentialSlots
//
UInt firstSlot = resInfo->index;
UInt slotCount = 0;
- if(auto typeResInfo = varLayout->getTypeLayout()->FindResourceInfo(LayoutResourceKind::ExistentialSlot))
+ if(auto typeResInfo = varLayout->getTypeLayout()->FindResourceInfo(LayoutResourceKind::ExistentialTypeParam))
slotCount = UInt(typeResInfo->count.getFiniteValue());
// At this point we know that the parameter consumes
@@ -266,12 +266,9 @@ struct BindExistentialSlots
// 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`.
+ // the `slotArgs`.
auto fullType = inst->getFullType();
- auto type = inst->getDataType();
SharedIRBuilder sharedBuilder;
sharedBuilder.session = module->getSession();
@@ -280,81 +277,64 @@ struct BindExistentialSlots
IRBuilder builder;
builder.sharedBuilder = &sharedBuilder;
- // The easy case is when the `type` of `inst`
- // is directly an interface type.
+ // Every argument that is filling an existential
+ // type param/slot comprises both a type and
+ // a witness table, so the total number of operands
+ // is twice the number of slots we are filling.
//
- 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();
+ UInt slotOperandCount = slotCount*2;
+ List<IRInst*> slotOperands;
+ for(UInt ii = 0; ii < slotOperandCount; ++ii)
+ slotOperands.Add(slotArgs[ii].get());
+
+ // We are going to create a proxy type that represents
+ // the results of plugging all the information
+ // from the existential slots into the original type.
+ //
+ auto newType = builder.getBindExistentialsType(
+ fullType,
+ slotOperandCount,
+ slotOperands.Buffer());
- // We are going to replace the (interface) type of
- // the parameter with the new (concrete) type.
- //
- builder.setDataType(inst, newType);
+ // We will replace the type of the original parameter
+ // with the new proxy 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);
+ // Next we want to replace all uses of `inst` (which
+ // expect a value of its old type) with a fresh
+ // `wrapExistential(...)` 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
+ // Now we can loop over our list of uses and replace each.
+ //
+ for(auto use : usesToReplace)
{
- // 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>`
+ // First we emit a `makeExisential` right before the
+ // use site.
//
- // * Nested combinations of the above; e.g., a `ConstantBuffer`
- // of a struct with a field that is an array of `IFoo`.
+ builder.setInsertBefore(use->getUser());
+ auto newVal = builder.emitWrapExistential(
+ fullType,
+ inst,
+ slotOperandCount,
+ slotOperands.Buffer());
+
+ // Second we make the use site point at the new
+ // value instead.
//
- SLANG_UNIMPLEMENTED_X("shader parameters with nested existentials");
+ use->set(newVal);
}
}
};
diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h
index 6163feb1c..809b588ef 100644
--- a/source/slang/ir-inst-defs.h
+++ b/source/slang/ir-inst-defs.h
@@ -43,6 +43,14 @@ INST(Nop, nop, 0, 0)
INST(TaggedUnionType, TaggedUnion, 0, 0)
+ // A `BindExistentials<B, T0,w0, T1,w1, ...>` represents
+ // taking type `B` and binding each of its existential type
+ // parameters, recursively, with the specified arguments,
+ // where each `Ti, wi` pair represents the concrete type
+ // and witness table to plug in for parameter `i`.
+ //
+ INST(BindExistentialsType, BindExistentials, 1, 0)
+
/* Rate */
INST(ConstExprRate, ConstExpr, 0, 0)
INST(GroupSharedRate, GroupShared, 0, 0)
@@ -63,6 +71,14 @@ INST(Nop, nop, 0, 0)
/* PtrTypeBase */
INST(PtrType, Ptr, 1, 0)
INST(RefType, Ref, 1, 0)
+
+ // An `ExistentialBox<T>` represents a logical pointer to a value of type `T`.
+ // On targets that support pointers this might lower to a pointer, but on
+ // current targets it will lower to zero bytes, with a value of type `T`
+ // being stored "out of line" somewhere.
+ //
+ INST(ExistentialBoxType, ExistentialBox, 1, 0)
+
/* OutTypeBase */
INST(OutType, Out, 1, 0)
INST(InOutType, InOut, 1, 0)
@@ -408,8 +424,19 @@ INST_RANGE(Decoration, HighLevelDeclDecoration, ExportDecoration)
//
-
+// A `makeExistential(v : C, w) : I` instruction takes a value `v` of type `C`
+// and produces a value of interface type `I` by using the witness `w` which
+// shows that `C` conforms to `I`.
+//
INST(MakeExistential, makeExistential, 2, 0)
+
+// A `wrapExistential(v, T0,w0, T1,w0) : T` instruction is similar to `makeExistential`.
+// but applies to a value `v` that is of type `BindExistentials(T, T0,w0, ...)`. The
+// result of the `wrapExistentials` operation is a value of type `T`, allowing us to
+// "smuggle" a value of specialized type into computations that expect an unspecialized type.
+//
+INST(WrapExistential, wrapExistential, 2, 0)
+
INST(ExtractExistentialValue, extractExistentialValue, 1, 0)
INST(ExtractExistentialType, extractExistentialType, 1, 0)
INST(ExtractExistentialWitnessTable, extractExistentialWitnessTable, 1, 0)
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 0c3622172..86ad114c6 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -630,6 +630,18 @@ struct IRMakeExistential : IRInst
IR_LEAF_ISA(MakeExistential)
};
+ /// Generalizes `IRMakeExistential` by allowing a type with existential sub-fields to be boxed
+struct IRWrapExistential : IRInst
+{
+ IRInst* getWrappedValue() { return getOperand(0); }
+
+ UInt getSlotOperandCount() { return getOperandCount() - 1; }
+ IRInst* getSlotOperand(UInt index) { return getOperand(index + 1); }
+ IRUse* getSlotOperands() { return getOperands() + 1; }
+
+ IR_LEAF_ISA(WrapExistential)
+};
+
// Description of an instruction to be used for global value numbering
struct IRInstKey
@@ -779,6 +791,16 @@ struct IRBuilder
return getTaggedUnionType(caseTypes.Count(), caseTypes.Buffer());
}
+ IRType* getBindExistentialsType(
+ IRInst* baseType,
+ UInt slotArgCount,
+ IRInst* const* slotArgs);
+
+ IRType* getBindExistentialsType(
+ IRInst* baseType,
+ UInt slotArgCount,
+ IRUse const* slotArgs);
+
// Set the data type of an instruction, while preserving
// its rate, if any.
void setDataType(IRInst* inst, IRType* dataType);
@@ -877,6 +899,12 @@ struct IRBuilder
IRInst* value,
IRInst* witnessTable);
+ IRInst* emitWrapExistential(
+ IRType* type,
+ IRInst* value,
+ UInt slotArgCount,
+ IRInst* const* slotArgs);
+
IRUndefined* emitUndefined(IRType* type);
@@ -957,6 +985,10 @@ struct IRBuilder
IRType* type);
IRInst* emitLoad(
+ IRType* type,
+ IRInst* ptr);
+
+ IRInst* emitLoad(
IRInst* ptr);
IRInst* emitStore(
diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp
index f73ef06a3..0c1465fb4 100644
--- a/source/slang/ir-legalize-types.cpp
+++ b/source/slang/ir-legalize-types.cpp
@@ -74,30 +74,21 @@ LegalVal LegalVal::getImplicitDeref()
return as<ImplicitDerefVal>(obj)->val;
}
+//
-struct IRTypeLegalizationContext
+IRTypeLegalizationContext::IRTypeLegalizationContext(
+ IRModule* inModule)
{
- Session* session;
- IRModule* module;
- IRBuilder* builder;
-
- /// Context to use for underlying (non-IR) type legalization.
- TypeLegalizationContext* typeLegalizationContext;
-
- // When inserting new globals, put them before this one.
- IRInst* insertBeforeGlobal = nullptr;
-
- // When inserting new parameters, put them before this one.
- IRParam* insertBeforeParam = nullptr;
+ session = inModule->getSession();
+ module = inModule;
- Dictionary<IRInst*, LegalVal> mapValToLegalVal;
-
- IRVar* insertBeforeLocalVar = nullptr;
+ auto sharedBuilder = &sharedBuilderStorage;
+ sharedBuilder->session = session;
+ sharedBuilder->module = module;
- // store instructions that have been replaced here, so we can free them
- // when legalization has done
- List<IRInst*> replacedInstructions;
-};
+ builder = &builderStorage;
+ builder->sharedBuilder = sharedBuilder;
+}
static void registerLegalizedValue(
IRTypeLegalizationContext* context,
@@ -122,13 +113,6 @@ static LegalVal declareVars(
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo);
-static LegalType legalizeType(
- IRTypeLegalizationContext* context,
- IRType* type)
-{
- return legalizeType(context->typeLegalizationContext, type);
-}
-
// Take a value that is being used as an operand,
// and turn it into the equivalent legalized value.
static LegalVal legalizeOperand(
@@ -306,7 +290,16 @@ static LegalVal legalizeStore(
case LegalVal::Flavor::implicitDeref:
// TODO: what is the right behavior here?
- if (legalVal.flavor == LegalVal::Flavor::implicitDeref)
+ //
+ // The crux of the problem is that we may legalize a pointer-to-pointer
+ // type in cases where one of the two needs to become an implicit-deref,
+ // so that we have `PtrA<PtrB<Thing>>` become, say, `PtrA<Thing>` with
+ // an `implicitDeref` wrapper. When we encounter a store to that
+ // wrapped value, we seemingly need to know whether the original code
+ // meant to store to `*ptrPtr` or `**ptrPtr`, and need to legalize
+ // the result accordingly...
+ //
+ if( legalVal.flavor == LegalVal::Flavor::implicitDeref )
return legalizeStore(context, legalPtrVal.getImplicitDeref(), legalVal.getImplicitDeref());
else
return legalizeStore(context, legalPtrVal.getImplicitDeref(), legalVal);
@@ -462,6 +455,111 @@ static LegalVal legalizeFieldExtract(
(IRStructKey*) fieldKey);
}
+ /// Take a value of some buffer/pointer type and wrap it according to provided info.
+static LegalVal wrapBufferValue(
+ IRTypeLegalizationContext* context,
+ LegalVal legalPtrOperand,
+ LegalElementWrapping const& elementInfo)
+{
+ // The `elementInfo` tells us how a non-simple element
+ // type was wrapped up into a new structure types used
+ // as the element type of the buffer.
+ //
+ // This function will recurse through the structure of
+ // `elementInfo` to pull out all the required data from
+ // the buffer represented by `legalPtrOperand`.
+
+ switch( elementInfo.flavor )
+ {
+ default:
+ SLANG_UNEXPECTED("unhandled");
+ UNREACHABLE_RETURN(LegalVal());
+ break;
+
+ case LegalElementWrapping::Flavor::none:
+ return LegalVal();
+
+ case LegalElementWrapping::Flavor::simple:
+ {
+ // In the leaf case, we just had to store some
+ // data of a simple type in the buffer. We can
+ // produce a valid result by computing the
+ // address of the field used to represent the
+ // element, and then returning *that* as if
+ // it were the buffer type itself.
+ //
+ // (Basically instead of `someBuffer` we will
+ // end up with `&(someBuffer->field)`.
+ //
+ auto builder = context->getBuilder();
+
+ auto simpleElementInfo = elementInfo.getSimple();
+ auto valPtr = builder->emitFieldAddress(
+ simpleElementInfo->type,
+ legalPtrOperand.getSimple(),
+ simpleElementInfo->key);
+
+ return LegalVal::simple(valPtr);
+ }
+
+ case LegalElementWrapping::Flavor::implicitDeref:
+ {
+ // If the element type was logically `ImplicitDeref<T>`,
+ // then we declared actual fields based on `T`, and
+ // we need to extract references to those fields and
+ // wrap them up in an `implicitDeref` value.
+ //
+ auto derefField = elementInfo.getImplicitDeref();
+ auto baseVal = wrapBufferValue(context, legalPtrOperand, derefField->field);
+ return LegalVal::implicitDeref(baseVal);
+ }
+
+ case LegalElementWrapping::Flavor::pair:
+ {
+ // If the element type was logically a `Pair<O,S>`
+ // then we encoded fields for both `O` and `S` into
+ // the actual element type, and now we need to
+ // extract references to both and pair them up.
+ //
+ auto pairField = elementInfo.getPair();
+ auto pairInfo = pairField->pairInfo;
+
+ auto ordinaryVal = wrapBufferValue(context, legalPtrOperand, pairField->ordinary);
+ auto specialVal = wrapBufferValue(context, legalPtrOperand, pairField->special);
+ return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
+ }
+
+ case LegalElementWrapping::Flavor::tuple:
+ {
+ // If the element type was logically a `Tuple<E0, E1, ...>`
+ // then we encoded fields for each of the `Ei` and
+ // need to extract references to all of them and
+ // encode them as a tuple.
+ //
+ auto tupleField = elementInfo.getTuple();
+
+ RefPtr<TuplePseudoVal> obj = new TuplePseudoVal();
+ for( auto ee : tupleField->elements )
+ {
+ auto elementVal = wrapBufferValue(
+ context,
+ legalPtrOperand,
+ ee.field);
+
+ TuplePseudoVal::Element element;
+ element.key = ee.key;
+ element.val = wrapBufferValue(
+ context,
+ legalPtrOperand,
+ ee.field);
+ obj->elements.Add(element);
+ }
+
+ return LegalVal::tuple(obj);
+ }
+ }
+}
+
static LegalVal legalizeFieldAddress(
IRTypeLegalizationContext* context,
LegalType type,
@@ -478,11 +576,23 @@ static LegalVal legalizeFieldAddress(
return LegalVal();
case LegalVal::Flavor::simple:
- return LegalVal::simple(
- builder->emitFieldAddress(
- type.getSimple(),
- legalPtrOperand.getSimple(),
- fieldKey));
+ switch( type.flavor )
+ {
+ case LegalType::Flavor::implicitDeref:
+ // TODO: Should this case be needed?
+ return legalizeFieldAddress(
+ context,
+ type.getImplicitDeref()->valueType,
+ legalPtrOperand,
+ fieldKey);
+
+ default:
+ return LegalVal::simple(
+ builder->emitFieldAddress(
+ type.getSimple(),
+ legalPtrOperand.getSimple(),
+ fieldKey));
+ }
case LegalVal::Flavor::pair:
{
@@ -801,7 +911,7 @@ static LegalVal legalizeGetElementPtr(
// and somebody is trying to get at an element pointer.
// Now we just have an array (wrapped with an implicit
// dereference) and need to just fetch the chosen element
- // instead (and then wrapp the element value with an
+ // instead (and then wrap the element value with an
// implicit dereference).
//
auto implicitDerefVal = legalPtrOperand.getImplicitDeref();
@@ -884,10 +994,9 @@ static LegalVal legalizeMakeStruct(
UInt argIndex = argCounter++;
LegalVal arg = legalArgs[argIndex];
- if((ee.flags & Slang::PairInfo::kFlag_hasOrdinaryAndSpecial) == Slang::PairInfo::kFlag_hasOrdinaryAndSpecial)
+ if( arg.flavor == LegalVal::Flavor::pair )
{
- // The field is itself a pair type, so we expect
- // the argument value to be one too...
+ // The argument is itself a pair
auto argPair = arg.getPair();
ordinaryArgs.Add(argPair->ordinaryVal);
specialArgs.Add(argPair->specialVal);
@@ -1470,7 +1579,7 @@ static LegalVal declareVars(
context,
op,
type.getImplicitDeref()->valueType,
- getDerefTypeLayout(typeLayout),
+ typeLayout,
varChain,
nameHint,
globalNameInfo);
@@ -1551,8 +1660,30 @@ static LegalVal declareVars(
}
break;
+ case LegalType::Flavor::wrappedBuffer:
+ {
+ auto wrappedBuffer = type.getWrappedBuffer();
+
+ auto innerVal = declareSimpleVar(
+ context,
+ op,
+ wrappedBuffer->simpleType,
+ typeLayout,
+ varChain,
+ nameHint,
+ globalNameInfo);
+
+ auto wrappedVal = wrapBufferValue(
+ context,
+ innerVal,
+ wrappedBuffer->elementInfo);
+
+ return wrappedVal;
+ }
+
default:
SLANG_UNEXPECTED("unhandled");
+ UNREACHABLE_RETURN(LegalVal());
break;
}
}
@@ -1714,52 +1845,107 @@ static void legalizeTypes(
}
}
-
-void legalizeTypes(
- TypeLegalizationContext* typeLegalizationContext,
- IRModule* module)
+// We use the same basic type legalization machinery for both simplifying
+// away resource-type fields nested in `struct`s and for shuffling around
+// exisential-box fields to get the layout right.
+//
+// The differences between the two passes come down to some very small
+// distinctions about what types each pass considers "special" (e.g.,
+// resources in one case and existential boxes in the other), along
+// with what they want to do when a uniform/constant buffer needs to
+// be made where the element type is non-simple (that is, includes
+// some fields of "special" type).
+//
+// The resource case is then the simpler one:
+//
+struct IRResourceTypeLegalizationContext : IRTypeLegalizationContext
{
- auto session = module->session;
+ IRResourceTypeLegalizationContext(IRModule* module)
+ : IRTypeLegalizationContext(module)
+ {}
- SharedIRBuilder sharedBuilderStorage;
- auto sharedBuilder = &sharedBuilderStorage;
+ bool isSpecialType(IRType* type) override
+ {
+ // For resource type legalization, the "special" types
+ // we are working with are resource types.
+ //
+ return isResourceType(type);
+ }
- sharedBuilder->session = session;
- sharedBuilder->module = module;
+ LegalType createLegalUniformBufferType(
+ IROp op,
+ LegalType legalElementType) override
+ {
+ // The appropriate strategy for legalizing uniform buffers
+ // with resources inside already exists, so we can delegate to it.
+ //
+ return createLegalUniformBufferTypeForResources(
+ this,
+ op,
+ legalElementType);
+ }
+};
- IRBuilder builderStorage;
- auto builder = &builderStorage;
+// The case for legalizing existential box types is then similar.
+//
+struct IRExistentialTypeLegalizationContext : IRTypeLegalizationContext
+{
+ IRExistentialTypeLegalizationContext(IRModule* module)
+ : IRTypeLegalizationContext(module)
+ {}
- builder->sharedBuilder = sharedBuilder;
+ bool isSpecialType(IRType* inType) override
+ {
+ // The "special" types for our purposes are existential
+ // boxes, or arrays thereof.
+ //
+ auto type = unwrapArray(inType);
+ return as<IRExistentialBoxType>(type) != nullptr;
+ }
+ LegalType createLegalUniformBufferType(
+ IROp op,
+ LegalType legalElementType) override
+ {
+ // We'll delegate the logic for creating uniform buffers
+ // over a mix of ordinary and existential-box types to
+ // a subroutine so it can live near the resource case.
+ //
+ // TODO: We should eventually try to refactor this code
+ // so that related functionality is grouped together.
+ //
+ return createLegalUniformBufferTypeForExistentials(
+ this,
+ op,
+ legalElementType);
+ }
+};
- IRTypeLegalizationContext contextStorage;
- auto context = &contextStorage;
+// The main entry points that are used when transforming IR code
+// to get it ready for lower-level codegen are then simple
+// wrappers around `legalizeTypes()` that pick an appropriately
+// specialized context type to use to get the job done.
- context->session = session;
- context->module = module;
- context->builder = builder;
+void legalizeResourceTypes(
+ IRModule* module,
+ DiagnosticSink* sink)
+{
+ SLANG_UNUSED(sink);
- context->typeLegalizationContext = typeLegalizationContext;
+ IRResourceTypeLegalizationContext context(module);
+ legalizeTypes(&context);
+}
- legalizeTypes(context);
+void legalizeExistentialTypeLayout(
+ IRModule* module,
+ DiagnosticSink* sink)
+{
+ SLANG_UNUSED(module);
+ SLANG_UNUSED(sink);
- // Clean up after any type instructions we removed (e.g.,
- // global `struct` types).
- //
- // TODO: this logic should probably get paired up with
- // the case for `IRTypeLegalizationContext::replacedInstructions`,
- // but we haven't yet folded all the legalization logic into
- // the IR legalization pass (since it used to apply to the AST too).
- //
- // TODO: This code has issues that can lead to IR validation
- // failure, because we might remove a `struct X` that has been
- // legalized away, but leave around a `ParameterBlock<X>` instruction
- // that is no longer valid.
- for (auto& oldInst : typeLegalizationContext->instsToRemove)
- {
- oldInst->removeAndDeallocate();
- }
+ IRExistentialTypeLegalizationContext context(module);
+ legalizeTypes(&context);
}
+
}
diff --git a/source/slang/ir-specialize.cpp b/source/slang/ir-specialize.cpp
index 0da06580f..7a129a03b 100644
--- a/source/slang/ir-specialize.cpp
+++ b/source/slang/ir-specialize.cpp
@@ -426,6 +426,14 @@ struct SpecializationContext
case kIROp_ExtractExistentialWitnessTable:
maybeSpecializeExtractExistentialWitnessTable(inst);
break;
+
+ case kIROp_Load:
+ maybeSpecializeLoad(as<IRLoad>(inst));
+ break;
+
+ case kIROp_BindExistentialsType:
+ maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst));
+ break;
}
}
@@ -1145,6 +1153,167 @@ struct SpecializationContext
}
}
+ /// Given a type being used as pointer, try to determine the type it points to.
+ IRType* tryGetPointedToType(
+ IRBuilder* builder,
+ IRType* type)
+ {
+ // The "true" pointers and the pointer-like stdlib types are the easy cases.
+ if( auto ptrType = as<IRPtrTypeBase>(type) )
+ {
+ return ptrType->getValueType();
+ }
+ else if( auto ptrLikeType = as<IRPointerLikeType>(type) )
+ {
+ return ptrLikeType->getElementType();
+ }
+ //
+ // A more interesting case arises when we have a `BindExistentials<P<T>, ...>`
+ // where `P<T>` is a pointer(-like) type.
+ //
+ else if( auto bindExistentials = as<IRBindExistentialsType>(type) )
+ {
+ // We know that `BindExistentials` won't introduce its own
+ // existential type parameters, nor will any of the pointer(-like)
+ // type constructors `P`.
+ //
+ // Thus we know that the type that is pointed to should be
+ // the same as `BindExistentials<T, ...>`.
+ //
+ auto baseType = bindExistentials->getBaseType();
+ if( auto baseElementType = tryGetPointedToType(builder, baseType) )
+ {
+ UInt existentialArgCount = bindExistentials->getExistentialArgCount();
+ List<IRInst*> existentialArgs;
+ for( UInt ii = 0; ii < existentialArgCount; ++ii )
+ {
+ existentialArgs.Add(bindExistentials->getExistentialArg(ii));
+ }
+ return builder->getBindExistentialsType(
+ baseElementType,
+ existentialArgCount,
+ existentialArgs.Buffer());
+ }
+ }
+
+ // TODO: We may need to handle other cases here.
+
+ return nullptr;
+ }
+
+ void maybeSpecializeLoad(IRLoad* inst)
+ {
+ auto ptrArg = inst->ptr.get();
+
+ if( auto wrapInst = as<IRWrapExistential>(ptrArg) )
+ {
+ // We have an instruction of the form `load(wrapExistential(val, ...))`
+ //
+ auto val = wrapInst->getWrappedValue();
+
+ // We know what type we are expected to
+ // produce (which should be the pointed-to
+ // type for whatever the type of the
+ // `wrapExistential` is).
+ //
+ auto resultType = inst->getFullType();
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilderStorage;
+ builder.setInsertBefore(inst);
+
+ // We'd *like* to replace this instruction with
+ // `wrapExistential(load(val))` instead, since that
+ // will enable subsequent specializations.
+ //
+ // To do that, we need to be able to determine
+ // the type that `load(val)` should return.
+ //
+ auto elementType = tryGetPointedToType(&builder, val->getDataType());
+ if(!elementType)
+ return;
+
+
+ List<IRInst*> slotOperands;
+ UInt slotOperandCount = wrapInst->getSlotOperandCount();
+ for( UInt ii = 0; ii < slotOperandCount; ++ii )
+ {
+ slotOperands.Add(wrapInst->getSlotOperand(ii));
+ }
+
+ auto newLoadInst = builder.emitLoad(elementType, val);
+ auto newWrapExistentialInst = builder.emitWrapExistential(
+ resultType,
+ newLoadInst,
+ slotOperandCount,
+ slotOperands.Buffer());
+
+ addUsersToWorkList(inst);
+
+ inst->replaceUsesWith(newWrapExistentialInst);
+ inst->removeAndDeallocate();
+ }
+ }
+
+ void maybeSpecializeBindExistentialsType(IRBindExistentialsType* type)
+ {
+ auto baseType = type->getBaseType();
+ UInt slotOperandCount = type->getExistentialArgCount();
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilderStorage;
+ builder.setInsertBefore(type);
+
+ if( auto baseInterfaceType = as<IRInterfaceType>(baseType) )
+ {
+ // A `BindExistentials<ISomeInterface, ConcreteType, ...>` can
+ // just be simplified to `ExistentialBox<ConcreteType>`.
+ //
+ // Note: We do *not* simplify straight to `ConcreteType`, because
+ // that would mess up the layout for aggregate types that
+ // contain interfaces. The logical indirection introduced
+ // by `ExistentialBox<...>` will be handled by a later type
+ // legalization pass that moved the type "pointed to" by
+ // the box out of line from other fields.
+
+ // We always expect two slot operands, one for the concrete type
+ // and one for the witness table.
+ //
+ SLANG_ASSERT(slotOperandCount == 2);
+ if(slotOperandCount <= 1) return;
+
+ auto concreteType = (IRType*) type->getExistentialArg(0);
+ auto newVal = builder.getPtrType(kIROp_ExistentialBoxType, concreteType);
+
+ addUsersToWorkList(type);
+ type->replaceUsesWith(newVal);
+ type->removeAndDeallocate();
+ return;
+ }
+ else if( auto basePtrLikeType = as<IRPointerLikeType>(baseType) )
+ {
+ // A `BindExistentials<P<T>, ...>` can be simplified to
+ // `P<BindExistentials<T, ...>>` when `P` is a pointer-like
+ // type constructor.
+ //
+ auto baseElementType = basePtrLikeType->getElementType();
+ IRInst* wrappedElementType = builder.getBindExistentialsType(
+ baseElementType,
+ slotOperandCount,
+ type->getExistentialArgs());
+
+ auto newPtrLikeType = builder.getType(
+ basePtrLikeType->op,
+ 1,
+ &wrappedElementType);
+
+ addUsersToWorkList(type);
+ type->replaceUsesWith(newPtrLikeType);
+ type->removeAndDeallocate();
+ return;
+ }
+ }
+
// The handling of specialization for global generic type
// parameters involves searching for all `bind_global_generic_param`
// instructions in the input module.
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 4a0a371d8..677429e32 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1770,6 +1770,55 @@ namespace Slang
(IRInst* const*) caseTypes);
}
+ IRType* IRBuilder::getBindExistentialsType(
+ IRInst* baseType,
+ UInt slotArgCount,
+ IRInst* const* slotArgs)
+ {
+ // If we are trying to bind an interface type, then
+ // we will go ahead and simplify the instruction
+ // away impmediately.
+ //
+ if(as<IRInterfaceType>(baseType))
+ {
+ if(slotArgCount >= 1)
+ {
+ // We are being asked to emit `BindExistentials(someInterface, someConcreteType, ...)`
+ // so we just want to return `ExistentialBox<someConcreteType>`.
+ //
+ auto concreteType = (IRType*) slotArgs[0];
+ auto ptrType = getPtrType(kIROp_ExistentialBoxType, concreteType);
+ return ptrType;
+ }
+ }
+
+ return (IRType*) findOrEmitHoistableInst(
+ this,
+ getTypeKind(),
+ kIROp_BindExistentialsType,
+ baseType,
+ slotArgCount,
+ (IRInst* const*) slotArgs);
+ }
+
+ IRType* IRBuilder::getBindExistentialsType(
+ IRInst* baseType,
+ UInt slotArgCount,
+ IRUse const* slotArgUses)
+ {
+ List<IRInst*> slotArgs;
+ for( UInt ii = 0; ii < slotArgCount; ++ii )
+ {
+ slotArgs.Add(slotArgUses[ii].get());
+ }
+ return getBindExistentialsType(
+ baseType,
+ slotArgCount,
+ slotArgs.Buffer());
+ }
+
+
+
void IRBuilder::setDataType(IRInst* inst, IRType* dataType)
{
if (auto oldRateQualifiedType = as<IRRateQualifiedType>(inst->getFullType()))
@@ -1983,6 +2032,46 @@ namespace Slang
return emitIntrinsicInst(type, kIROp_MakeExistential, SLANG_COUNT_OF(args), args);
}
+ IRInst* IRBuilder::emitWrapExistential(
+ IRType* type,
+ IRInst* value,
+ UInt slotArgCount,
+ IRInst* const* slotArgs)
+ {
+ // If we are wrapping a single concrete value into
+ // an interface type, then this is really a `makeExistential`
+ //
+ // TODO: We may want to check for a `specialize` of a generic interface as well.
+ //
+ if(as<IRInterfaceType>(type))
+ {
+ if(slotArgCount >= 2)
+ {
+ // We are being asked to emit `wrapExistential(value, concreteType, witnessTable, ...) : someInterface`
+ //
+ // We also know that a concrete value being wrapped will always be an existential box,
+ // so we expect that `value : ExistentialBox<T>` for some `T`.
+ //
+ // We want to emit `makeExistential(load(value), witnessTable)`.
+ //
+ auto deref = emitLoad(value);
+ return emitMakeExistential(type, deref, slotArgs[1]);
+ }
+ }
+
+ IRInst* fixedArgs[] = {value};
+ auto inst = createInstImpl<IRInst>(
+ this,
+ kIROp_WrapExistential,
+ type,
+ SLANG_COUNT_OF(fixedArgs),
+ fixedArgs,
+ slotArgCount,
+ slotArgs);
+ addInst(inst);
+ return inst;
+ }
+
IRModule* IRBuilder::createModule()
{
auto module = new IRModule();
@@ -2283,6 +2372,20 @@ namespace Slang
}
IRInst* IRBuilder::emitLoad(
+ IRType* type,
+ IRInst* ptr)
+ {
+ auto inst = createInst<IRLoad>(
+ this,
+ kIROp_Load,
+ type,
+ ptr);
+
+ addInst(inst);
+ return inst;
+ }
+
+ IRInst* IRBuilder::emitLoad(
IRInst* ptr)
{
// Note: a `load` operation does not consider the rate
@@ -2324,14 +2427,7 @@ namespace Slang
valueType = rateType->getValueType();
}
- auto inst = createInst<IRLoad>(
- this,
- kIROp_Load,
- valueType,
- ptr);
-
- addInst(inst);
- return inst;
+ return emitLoad(valueType, ptr);
}
IRInst* IRBuilder::emitStore(
@@ -4046,6 +4142,7 @@ namespace Slang
case kIROp_ExtractExistentialType:
case kIROp_ExtractExistentialValue:
case kIROp_ExtractExistentialWitnessTable:
+ case kIROp_WrapExistential:
return false;
}
}
diff --git a/source/slang/ir.h b/source/slang/ir.h
index 515b34399..09013fe48 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -926,15 +926,13 @@ struct IRPtrTypeBase : IRType
IR_PARENT_ISA(PtrTypeBase)
};
-struct IRPtrType : IRPtrTypeBase
-{
- IR_LEAF_ISA(PtrType)
-};
+SIMPLE_IR_TYPE(PtrType, PtrTypeBase)
+SIMPLE_IR_TYPE(RefType, PtrTypeBase)
SIMPLE_IR_PARENT_TYPE(OutTypeBase, PtrTypeBase)
SIMPLE_IR_TYPE(OutType, OutTypeBase)
SIMPLE_IR_TYPE(InOutType, OutTypeBase)
-SIMPLE_IR_TYPE(RefType, OutTypeBase)
+SIMPLE_IR_TYPE(ExistentialBoxType, PtrTypeBase)
struct IRFuncType : IRType
{
@@ -1007,6 +1005,16 @@ struct IRTaggedUnionType : IRType
IR_LEAF_ISA(TaggedUnionType)
};
+struct IRBindExistentialsType : IRType
+{
+ IR_LEAF_ISA(BindExistentialsType)
+
+ IRType* getBaseType() { return (IRType*) getOperand(0); }
+ UInt getExistentialArgCount() { return getOperandCount() - 1; }
+ IRUse* getExistentialArgs() { return getOperands() + 1; }
+ IRInst* getExistentialArg(UInt index) { return getExistentialArgs()[index].get(); }
+};
+
/// @brief A global value that potentially holds executable code.
///
struct IRGlobalValueWithCode : IRInst
diff --git a/source/slang/legalize-types.cpp b/source/slang/legalize-types.cpp
index 3accad448..6e459ecf1 100644
--- a/source/slang/legalize-types.cpp
+++ b/source/slang/legalize-types.cpp
@@ -69,9 +69,103 @@ LegalType LegalType::pair(
return LegalType::pair(obj);
}
+LegalType LegalType::makeWrappedBuffer(
+ IRType* simpleType,
+ LegalElementWrapping const& elementInfo)
+{
+ RefPtr<WrappedBufferPseudoType> obj = new WrappedBufferPseudoType();
+ obj->simpleType = simpleType;
+ obj->elementInfo = elementInfo;
+
+ LegalType result;
+ result.flavor = Flavor::wrappedBuffer;
+ result.obj = obj;
+ return result;
+}
+
+//
+
+LegalElementWrapping LegalElementWrapping::makeVoid()
+{
+ LegalElementWrapping result;
+ result.flavor = Flavor::none;
+ return result;
+}
+
+LegalElementWrapping LegalElementWrapping::makeSimple(IRStructKey* key, IRType* type)
+{
+ RefPtr<SimpleLegalElementWrappingObj> obj = new SimpleLegalElementWrappingObj();
+ obj->key = key;
+ obj->type = type;
+
+ LegalElementWrapping result;
+ result.flavor = Flavor::simple;
+ result.obj = obj;
+ return result;
+}
+
+RefPtr<SimpleLegalElementWrappingObj> LegalElementWrapping::getSimple() const
+{
+ SLANG_ASSERT(flavor == Flavor::simple);
+ return obj.as<SimpleLegalElementWrappingObj>();
+}
+
+LegalElementWrapping LegalElementWrapping::makeImplicitDeref(LegalElementWrapping const& field)
+{
+ RefPtr<ImplicitDerefLegalElementWrappingObj> obj = new ImplicitDerefLegalElementWrappingObj();
+ obj->field = field;
+
+ LegalElementWrapping result;
+ result.flavor = Flavor::implicitDeref;
+ result.obj = obj;
+ return result;
+}
+
+RefPtr<ImplicitDerefLegalElementWrappingObj> LegalElementWrapping::getImplicitDeref() const
+{
+ SLANG_ASSERT(flavor == Flavor::implicitDeref);
+ return obj.as<ImplicitDerefLegalElementWrappingObj>();
+}
+
+LegalElementWrapping LegalElementWrapping::makePair(
+ LegalElementWrapping const& ordinary,
+ LegalElementWrapping const& special,
+ PairInfo* pairInfo)
+{
+ RefPtr<PairLegalElementWrappingObj> obj = new PairLegalElementWrappingObj();
+ obj->ordinary = ordinary;
+ obj->special = special;
+ obj->pairInfo = pairInfo;
+
+ LegalElementWrapping result;
+ result.flavor = Flavor::pair;
+ result.obj = obj;
+ return result;
+}
+
+RefPtr<PairLegalElementWrappingObj> LegalElementWrapping::getPair() const
+{
+ SLANG_ASSERT(flavor == Flavor::pair);
+ return obj.as<PairLegalElementWrappingObj>();
+}
+
+LegalElementWrapping LegalElementWrapping::makeTuple(TupleLegalElementWrappingObj* obj)
+{
+ LegalElementWrapping result;
+ result.flavor = Flavor::tuple;
+ result.obj = obj;
+ return result;
+}
+
+RefPtr<TupleLegalElementWrappingObj> LegalElementWrapping::getTuple() const
+{
+ SLANG_ASSERT(flavor == Flavor::tuple);
+ return obj.as<TupleLegalElementWrappingObj>();
+}
+
//
-static bool isResourceType(IRType* type)
+bool isResourceType(IRType* type)
{
while (auto arrayType = as<IRArrayTypeBase>(type))
{
@@ -152,7 +246,7 @@ struct TupleTypeBuilder
IRStructKey* fieldKey,
LegalType legalFieldType,
LegalType legalLeafType,
- bool isResource)
+ bool isSpecial)
{
LegalType ordinaryType;
LegalType specialType;
@@ -164,7 +258,7 @@ struct TupleTypeBuilder
// We need to add an actual field, but we need
// to check if it is a resource type to know
// whether it should go in the "ordinary" list or not.
- if (!isResource)
+ if (!isSpecial)
{
ordinaryType = legalLeafType;
}
@@ -198,7 +292,7 @@ struct TupleTypeBuilder
fieldKey,
legalFieldType,
legalLeafType.getImplicitDeref()->valueType,
- isResource);
+ isSpecial);
return;
}
break;
@@ -214,7 +308,7 @@ struct TupleTypeBuilder
//
// This is because the "ordinary" side of the legalization
// of `ConstantBuffer<Foo>` will still be a resource type.
- if(isResource)
+ if(isSpecial)
{
specialType = legalFieldType;
}
@@ -239,8 +333,6 @@ struct TupleTypeBuilder
break;
}
-// String mangledFieldName = getMangledName(fieldDeclRef.getDecl());
-
PairInfo::Element pairElement;
pairElement.flags = 0;
pairElement.key = fieldKey;
@@ -295,14 +387,14 @@ struct TupleTypeBuilder
{
auto fieldType = field->getFieldType();
- bool isResourceField = isResourceType(fieldType);
+ bool isSpecialField = context->isSpecialType(fieldType);
auto legalFieldType = legalizeType(context, fieldType);
addField(
field->getKey(),
legalFieldType,
legalFieldType,
- isResourceField);
+ isSpecialField);
}
LegalType getResult()
@@ -317,14 +409,6 @@ struct TupleTypeBuilder
// then we can use the type as-is.
// we can conceivably just use the type as-is
//
- // TODO: this might be a good place to turn
- // a reference to a generic `struct` type into
- // a concrete non-generic type so that downstream
- // codegen doesn't have to deal with generics...
- //
- // TODO: In fact, why not just fully replace
- // all aggregate types here with some structural
- // types defined in the IR?
if (!anyComplex)
{
return LegalType::simple(type);
@@ -368,7 +452,7 @@ struct TupleTypeBuilder
// collide.
//
// (Also, the original type wasn't legal - that was the whole point...)
- context->instsToRemove.Add(originalStructType);
+ context->replacedInstructions.Add(originalStructType);
for(auto ee : ordinaryElements)
{
@@ -440,8 +524,19 @@ static LegalType createLegalUniformBufferType(
IROp op,
LegalType legalElementType)
{
+ // We will handle some of the easy/non-interesting
+ // cases here in the main routine, but for all
+ // the non-trivial cases we will dispatch to logic
+ // on the `context` (which may differ depending
+ // on what we are using legalization to accomplish).
+ //
switch (legalElementType.flavor)
{
+ default:
+ return context->createLegalUniformBufferType(
+ op,
+ legalElementType);
+
case LegalType::Flavor::none:
return LegalType();
@@ -449,6 +544,11 @@ static LegalType createLegalUniformBufferType(
{
// Easy case: we just have a simple element type,
// so we want to create a uniform buffer that wraps it.
+ //
+ // TODO: This isn't *quite* right, since it won't handle something
+ // like a `ParameterBlock<Texture2D>`, but that seems like
+ // an unlikely case in practice.
+ //
return LegalType::simple(createBuiltinGenericType(
context,
op,
@@ -478,36 +578,90 @@ static LegalType createLegalUniformBufferType(
legalElementType.getImplicitDeref()->valueType));
}
break;
+ }
+}
+// Create a uniform buffer type with a given legalized element type,
+// under the assumption that we are doing resource-based type legalization.
+//
+LegalType createLegalUniformBufferTypeForResources(
+ TypeLegalizationContext* context,
+ IROp op,
+ LegalType legalElementType)
+{
+ switch (legalElementType.flavor)
+ {
case LegalType::Flavor::pair:
{
- // We assume that the "ordinary" part of things
- // will get wrapped in a constant-buffer type,
- // and the "special" part needs to be wrapped
- // with an `implicitDeref`.
auto pairType = legalElementType.getPair();
+ // The pair has both an "ordinary" and a "special"
+ // side, where the ordinary side is just plain data
+ // that we can put in a constant buffer type without
+ // any problems. The special side will (recursively)
+ // contain any resource-type fields that were nested
+ // in the constant buffer, and we'll need to
+ // treat those as resources that stand alongside
+ // the original constant buffer.
+ //
+ // We can start with the ordinary side, which we
+ // just want to wrap up in an ordinary uniform
+ // buffer with the appropriate `op`, so that case
+ // is easy:
+ //
auto ordinaryType = createLegalUniformBufferType(
context,
op,
pairType->ordinaryType);
+
+ // For the special side, we really just want to turn
+ // a special field of type `R` into a value of type
+ // `R`, and the main detail we have to be aware of
+ // is that any use sites for the original buffer/block
+ // will include a dereferencing step to get from
+ // the block to this field, so we need to add
+ // something to the type structure to account for
+ // that step.
+ //
+ // We handle that issue by wrapping the special
+ // part of the type in an `implicitDeref` wrapper,
+ // which indicates that we logically have `SomePtr<R>`
+ // but we actually just have `R`, and any attempt to
+ // load from or otherwise indirect through that pointer
+ // will turn into a plain old reference to the `R` value.
+ //
auto specialType = LegalType::implicitDeref(pairType->specialType);
+ // Once we've wrapped up both the ordinary and special
+ // sides suitably, we tie them back together in a pair
+ // and make that be the legalized type of the result.
+ //
return LegalType::pair(ordinaryType, specialType, pairType->pairInfo);
}
case LegalType::Flavor::tuple:
{
- // if we have a tuple type, then it must be representing
- // the fields that can't be stored in a buffer anyway,
- // so we just need to wrap each of them in an `implicitDeref`
+ // A tuple type always represents purely "special" data,
+ // which in this case means resources.
+ //
+ // As in the `pair` case, the main thing we have to
+ // take into account is that each of the entries in the
+ // tuple itself (e.g., a value of type `R`) and the code
+ // that uses the legalized buffer type will expect a
+ // `ConstantBuffer<R>` or at least `SomePtrType<R>`.
+ //
+ // We will construct a new tuple type that wraps each
+ // of the element types in an `implicitDeref` to
+ // account for the different in levels of indirection.
+ //
+ // TODO: This seems odd, because we *should* be able to
+ // just wrap the whole thing in an `implicitDeref` and
+ // have done. We should investigate why this roundabout
+ // way of doing things was ever necessary.
auto elementPseudoTupleType = legalElementType.getTuple();
-
RefPtr<TuplePseudoType> bufferPseudoTupleType = new TuplePseudoType();
- // Wrap all the pseudo-tuple elements with `implicitDeref`,
- // since they used to be inside a tuple, but aren't any more.
for (auto ee : elementPseudoTupleType->elements)
{
TuplePseudoType::Element newElement;
@@ -523,12 +677,174 @@ static LegalType createLegalUniformBufferType(
break;
default:
- SLANG_UNEXPECTED("unknown legal type flavor");
+ SLANG_UNEXPECTED("unhandled legal type flavor");
UNREACHABLE_RETURN(LegalType());
break;
}
}
+// Legalizing a uniform buffer/block type for existentials is
+// more interesting, because we don't actually want to push
+// the "special" fields out of the buffer entirely (as we
+// do for resources), and instead we just want to place
+// them in the buffer *after* all the ordinary data.
+//
+// In order to accomplish this we need a way to emit a
+// constant buffer with a new element type, and then
+// "wrap" that constant buffer so that it looks like
+// something that matches the legalization of the original
+// element type.
+//
+// As a concrete example, suppose we have:
+//
+// struct Params { ExistentialBox<Foo> f; int x; ExistentialBox<Bar> b; };
+// ConstantBuffer<Params> gParams;
+//
+// The legalized form of `Params` will be something like:
+//
+// Pair(
+// /* ordinary: */ struct Params_Ordinary { int x; },
+// /* special: */ Tuple(
+// f -> ImplicitDeref(Foo),
+// b -> ImplicitDeref(Bar)))
+//
+// We need to be able to splat that all out into a single
+// structure declaration like:
+//
+// struct Params_Reordered
+// {
+// Params_Ordinary ordinary;
+// Foo f;
+// Bar b;
+// }
+//
+// That allows us to declare:
+//
+// ConstantBuffer<Params_Reordered> gParams;
+//
+// That gets the in-memory layout of things correct for the
+// way we are defining existential value slots to work.
+// The challenge is that elsewehere in the code there are
+// operations like `gParams.x` need to now refer to
+// `gParams.ordinary.x`. Furthermore, even for something like
+// `f` that seems fine in the example above, we have lost
+// a level of indirection, so that where we had `load(gParams.f)`
+// we now want just `gParams.f`.
+//
+// The solution is to take `gParams` as soon as it is declared
+// and wrap it up as a new value:
+//
+// pair(
+// /* ordinary: */ gParams.ordinary,
+// /* special: */ tuple(
+// f -> implicitDeref(gParams.f),
+// b -> implicitDeref(gParams.b)))
+//
+//
+// Let's begin by just defining a function that can take
+// a `LegalType` and turn it into zero or more field
+// declarations, and return enough tracking information
+// for us to be able to reconstruct a value like the above.
+//
+LegalElementWrapping declareStructFields(
+ TypeLegalizationContext* context,
+ IRStructType* structType,
+ LegalType fieldType)
+{
+ // TODO: We should eventually thread through some kind
+ // of "name hint" that can be used to give the generated
+ // fields more useful names.
+
+ switch(fieldType.flavor)
+ {
+ case LegalType::Flavor::none:
+ return LegalElementWrapping::makeVoid();
+
+ case LegalType::Flavor::simple:
+ {
+ auto simpleFieldType = fieldType.getSimple();
+ auto builder = context->getBuilder();
+ auto fieldKey = builder->createStructKey();
+ builder->createStructField(structType, fieldKey, simpleFieldType);
+ return LegalElementWrapping::makeSimple(fieldKey, simpleFieldType);
+ }
+
+ case LegalType::Flavor::implicitDeref:
+ {
+ auto subField = declareStructFields(context, structType, fieldType.getImplicitDeref()->valueType);
+ return LegalElementWrapping::makeImplicitDeref(subField);
+ }
+
+ case LegalType::Flavor::pair:
+ {
+ auto pairType = fieldType.getPair();
+ auto ordinaryField = declareStructFields(context, structType, pairType->ordinaryType);
+ auto specialField = declareStructFields(context, structType, pairType->specialType);
+ return LegalElementWrapping::makePair(
+ ordinaryField,
+ specialField,
+ pairType->pairInfo);
+ }
+
+ case LegalType::Flavor::tuple:
+ {
+ auto tupleType = fieldType.getTuple();
+
+ RefPtr<TupleLegalElementWrappingObj> obj = new TupleLegalElementWrappingObj();
+ for( auto ee : tupleType->elements )
+ {
+ TupleLegalElementWrappingObj::Element element;
+ element.key = ee.key;
+ element.field = declareStructFields(context, structType, ee.type);
+ obj->elements.Add(element);
+ }
+ return LegalElementWrapping::makeTuple(obj);
+ }
+
+ default:
+ SLANG_UNEXPECTED("unhandled legal type flavor");
+ UNREACHABLE_RETURN(LegalElementWrapping::makeVoid());
+ break;
+ }
+}
+
+LegalType createLegalUniformBufferTypeForExistentials(
+ TypeLegalizationContext* context,
+ IROp op,
+ LegalType legalElementType)
+{
+ auto builder = context->getBuilder();
+
+ // In order to wrap up all the data in `legalElementType`,
+ // will create a fresh `struct` type and then declare
+ // fields in it that are sufficient to hold that data
+ // in `legalElementType`.
+ //
+ auto structType = builder->createStructType();
+ auto elementWrapping = declareStructFields(
+ context, structType, legalElementType);
+
+ // Because the `structType` is an ordinary IR type
+ // (not a `LegalType`) we can go ahead and create an
+ // IR uniform buffer type that wraps it.
+ //
+ auto bufferType = createBuiltinGenericType(
+ context,
+ op,
+ structType);
+
+ // The `elementWrapping` computed when we declared all
+ // the `struct` fields tells us how to get from the
+ // actual fields declared in the structure type to a
+ // `LegalVal` with the right shape for what users of
+ // the buffer will expect. We record both the underlying
+ // IR buffer type and that wrapping information into
+ // the resulting `LegalType` so that we can use it
+ // when declaring variables of this type.
+ //
+ return LegalType::makeWrappedBuffer(bufferType, elementWrapping);
+}
+
static LegalType createLegalUniformBufferType(
TypeLegalizationContext* context,
IRUniformParameterGroupType* uniformBufferType,
@@ -789,7 +1105,7 @@ LegalType legalizeTypeImpl(
// we still need to create a new uniform buffer type
// from `legalElementType` instead of `type`
// because the `legalElementType` may still differ from `type`
- // if `type` contains empty structs.
+ // if, e.g., `type` contains empty structs.
return createLegalUniformBufferType(
context,
uniformBufferType,
@@ -814,6 +1130,20 @@ LegalType legalizeTypeImpl(
{
return LegalType::simple(type);
}
+ else if( auto existentialPtrType = as<IRExistentialBoxType>(type))
+ {
+ // We want to transform an `ExistentialBox<T>` into just
+ // a `T`, with an `iplicitDeref` to make sure that any
+ // pointer-related operations on the box Just Work.
+ //
+ // Note: the logic here doesn't have to deal with moving
+ // existential-type fields to the end of their outer
+ // type(s) because that is mostly dealt with in the
+ // case for struct types below.
+ //
+ auto legalValueType = legalizeType(context, existentialPtrType->getValueType());
+ return LegalType::implicitDeref(legalValueType);
+ }
else if (auto ptrType = as<IRPtrTypeBase>(type))
{
auto legalValueType = legalizeType(context, ptrType->getValueType());
@@ -909,21 +1239,6 @@ LegalType legalizeTypeImpl(
return LegalType::simple(type);
}
-void initialize(
- TypeLegalizationContext* context,
- Session* session,
- IRModule* module)
-{
- context->session = session;
- context->irModule = module;
-
- context->sharedBuilder.session = session;
- context->sharedBuilder.module = module;
-
- context->builder.sharedBuilder = &context->sharedBuilder;
- context->builder.setInsertInto(module->moduleInst);
-}
-
LegalType legalizeType(
TypeLegalizationContext* context,
IRType* type)
diff --git a/source/slang/legalize-types.h b/source/slang/legalize-types.h
index f28078010..831d3538d 100644
--- a/source/slang/legalize-types.h
+++ b/source/slang/legalize-types.h
@@ -41,30 +41,34 @@ struct ImplicitDerefType;
struct TuplePseudoType;
struct PairPseudoType;
struct PairInfo;
+struct LegalElementWrapping;
+struct WrappedBufferPseudoType;
-struct LegalType
+ /// A flavor for types or values that arise during legalization.
+enum class LegalFlavor
{
- enum class Flavor
- {
- // Nothing: a NULL type
- none,
+ /// Nothing: an empty type or value. Equivalent to `void`.
+ none,
- // A simple type that can be represented directly as a `Type`
- simple,
+ /// A simple type/value that can be represented as an `IRType*` or `IRInst*`
+ simple,
- // Logically, we have a pointer-like type, but we are
- // going to represnet it as the pointed-to type
- implicitDeref,
+ /// Logically, a pointer-like type/value, but represented as the type/value being pointed type.
+ implicitDeref,
- // A compound type was broken apart into its constituent fields,
- // so a tuple "pseduo-type" is being used to collect
- // those fields together.
- tuple,
+ /// A compound type/value made up of the constituent fields of some original value.
+ tuple,
- // A type has to get split into "ordinary" and "special" parts,
- // each of which will be represented with its own `LegalType`.
- pair,
- };
+ /// A type/value that was split into "ordinary" and "special" parts.
+ pair,
+
+ /// A type/value that represents, e.g., `ConstantBuffer<T>` where `T` needed legalization.
+ wrappedBuffer,
+};
+
+struct LegalType
+{
+ typedef LegalFlavor Flavor;
Flavor flavor = Flavor::none;
RefPtr<RefObject> obj;
@@ -103,7 +107,7 @@ struct LegalType
}
static LegalType pair(
- RefPtr<PairPseudoType> pairType);
+ RefPtr<PairPseudoType> pairType);
static LegalType pair(
LegalType const& ordinaryType,
@@ -115,11 +119,93 @@ struct LegalType
SLANG_ASSERT(flavor == Flavor::pair);
return obj.as<PairPseudoType>();
}
+
+ static LegalType makeWrappedBuffer(
+ IRType* simpleType,
+ LegalElementWrapping const& elementInfo);
+
+ RefPtr<WrappedBufferPseudoType> getWrappedBuffer() const
+ {
+ SLANG_ASSERT(flavor == Flavor::wrappedBuffer);
+ return obj.as<WrappedBufferPseudoType>();
+ }
+};
+
+struct LegalElementWrappingObj : RefObject
+{
+};
+
+struct SimpleLegalElementWrappingObj;
+struct ImplicitDerefLegalElementWrappingObj;
+struct PairLegalElementWrappingObj;
+struct TupleLegalElementWrappingObj;
+
+ /// Information on how the element type of a buffer needs to be wrapped.
+struct LegalElementWrapping
+{
+ typedef LegalFlavor Flavor;
+
+ Flavor flavor;
+ RefPtr<LegalElementWrappingObj> obj;
+
+ static LegalElementWrapping makeVoid();
+ static LegalElementWrapping makeSimple(IRStructKey* key, IRType* type);
+ static LegalElementWrapping makeImplicitDeref(LegalElementWrapping const& field);
+ static LegalElementWrapping makePair(
+ LegalElementWrapping const& ordinary,
+ LegalElementWrapping const& special,
+ PairInfo* pairInfo);
+ static LegalElementWrapping makeTuple(TupleLegalElementWrappingObj* obj);
+
+ RefPtr<SimpleLegalElementWrappingObj> getSimple() const;
+ RefPtr<ImplicitDerefLegalElementWrappingObj> getImplicitDeref() const;
+ RefPtr<PairLegalElementWrappingObj> getPair() const;
+ RefPtr<TupleLegalElementWrappingObj> getTuple() const;
+};
+
+struct SimpleLegalElementWrappingObj : LegalElementWrappingObj
+{
+ IRStructKey* key;
+ IRType* type;
+};
+
+struct ImplicitDerefLegalElementWrappingObj : LegalElementWrappingObj
+{
+ LegalElementWrapping field;
+};
+
+struct PairLegalElementWrappingObj : LegalElementWrappingObj
+{
+ LegalElementWrapping ordinary;
+ LegalElementWrapping special;
+ RefPtr<PairInfo> pairInfo;
+};
+
+struct TupleLegalElementWrappingObj : LegalElementWrappingObj
+{
+ struct Element
+ {
+ IRStructKey* key;
+ LegalElementWrapping field;
+ };
+
+ List<Element> elements;
};
// Represents the pseudo-type of a type that is pointer-like
// (and thus requires dereferencing, even if implicit), but
// was legalized to just use the type of the pointed-type value.
+//
+// The two cases where this comes up are:
+//
+// 1. When we have a type like `ConstantBuffer<Texture2D>` that
+// implies a level of indirection, but need to legalize it to just
+// `Texture2D`, which eliminates that indirection.
+//
+// 2. When we have a type like `ExistentialBox<Foo>` that will
+// become just a `Foo` field, but which needs to be allocated
+// out-of-line from the rest of its enclosing type.
+//
struct ImplicitDerefType : LegalTypeImpl
{
LegalType valueType;
@@ -162,7 +248,6 @@ struct PairInfo : RefObject
{
kFlag_hasOrdinary = 0x1,
kFlag_hasSpecial = 0x2,
- kFlag_hasOrdinaryAndSpecial = kFlag_hasOrdinary | kFlag_hasSpecial,
};
@@ -209,8 +294,8 @@ struct PairInfo : RefObject
struct PairPseudoType : LegalTypeImpl
{
// Any field(s) with ordinary types will
- // get captured here (usually as a `fieldRemap`
- // type)
+ // get captured here, usually as a single
+ // `simple` or `implicitDeref` type.
LegalType ordinaryType;
// Any fields with "special" (not ordinary)
@@ -224,6 +309,18 @@ struct PairPseudoType : LegalTypeImpl
RefPtr<PairInfo> pairInfo;
};
+
+struct WrappedBufferPseudoType : LegalTypeImpl
+{
+ // The actual IR type that was used for the buffer.
+ IRType* simpleType;
+
+ // Adjustments that need to be made when fetching
+ // an element from this buffer type.
+ //
+ LegalElementWrapping elementInfo;
+};
+
//
RefPtr<TypeLayout> getDerefTypeLayout(
@@ -266,17 +363,11 @@ struct LegalValImpl : RefObject
};
struct TuplePseudoVal;
struct PairPseudoVal;
+struct WrappedBufferPseudoVal;
struct LegalVal
{
- enum class Flavor
- {
- none,
- simple,
- implicitDeref,
- tuple,
- pair,
- };
+ typedef LegalFlavor Flavor;
Flavor flavor = Flavor::none;
RefPtr<RefObject> obj;
@@ -348,29 +439,61 @@ struct ImplicitDerefVal : LegalValImpl
//
-struct TypeLegalizationContext
+ /// Context that drives type legalization
+ ///
+ /// This type is an abstract base class, and there are
+ /// customization points that a concrete pass needs to
+ /// override (e.g., to specify what needs to be legalized).
+struct IRTypeLegalizationContext
{
- /// The overall compilation session..
- Session* session;
+ Session* session;
+ IRModule* module;
+ IRBuilder* builder;
- IRModule* irModule = nullptr;
+ SharedIRBuilder sharedBuilderStorage;
+ IRBuilder builderStorage;
- SharedIRBuilder sharedBuilder;
- IRBuilder builder;
+ IRTypeLegalizationContext(
+ IRModule* inModule);
- IRBuilder* getBuilder() { return &builder; }
+ // When inserting new globals, put them before this one.
+ IRInst* insertBeforeGlobal = nullptr;
+
+ // When inserting new parameters, put them before this one.
+ IRParam* insertBeforeParam = nullptr;
+
+ Dictionary<IRInst*, LegalVal> mapValToLegalVal;
+
+ IRVar* insertBeforeLocalVar = nullptr;
+
+ // store instructions that have been replaced here, so we can free them
+ // when legalization has done
+ List<IRInst*> replacedInstructions;
Dictionary<IRType*, LegalType> mapTypeToLegalType;
- // Intstructions to be removed when legalization is done
- HashSet<IRInst*> instsToRemove;
+ IRBuilder* getBuilder() { return builder; }
+
+ /// Customization point to decide what types are "special."
+ ///
+ /// When legalizing a `struct` type, any fields that have "special"
+ /// types will get moved out of the `struc` itself.
+ virtual bool isSpecialType(IRType* type) = 0;
+
+ /// Customization point to construct uniform-buffer/block types.
+ ///
+ /// This function will only be called if `legalElementType` is
+ /// somehow non-trivial.
+ ///
+ virtual LegalType createLegalUniformBufferType(
+ IROp op,
+ LegalType legalElementType) = 0;
};
-void initialize(
- TypeLegalizationContext* context,
- Session* session,
- IRModule* module);
-
+// This typedef exists to support pre-existing code from when
+// `IRTypeLegalizationContext` and `TypeLegalizationContext` were
+// two different types that had to coordinate.
+typedef struct IRTypeLegalizationContext TypeLegalizationContext;
LegalType legalizeType(
TypeLegalizationContext* context,
@@ -380,6 +503,41 @@ LegalType legalizeType(
ModuleDecl* findModuleForDecl(
Decl* decl);
+ /// Create a uniform buffer type suitable for resource legalization.
+ ///
+ /// This will allocate a real buffer for the ordinary data (if any),
+ /// and leave the special data (if any) as a tuple.
+ ///
+LegalType createLegalUniformBufferTypeForResources(
+ TypeLegalizationContext* context,
+ IROp op,
+ LegalType legalElementType);
+
+ /// Create a uniform buffer type suitable for existential legalization.
+ ///
+ /// This will allocate a real uniform buffer for *all* the data, by
+ /// declaring an intermediate `struct` type to hold the ordinary and
+ /// special (existential-box) fields, if required.
+ ///
+LegalType createLegalUniformBufferTypeForExistentials(
+ TypeLegalizationContext* context,
+ IROp op,
+ LegalType legalElementType);
+
+
+
+
+void legalizeExistentialTypeLayout(
+ IRModule* module,
+ DiagnosticSink* sink);
+
+void legalizeResourceTypes(
+ IRModule* module,
+ DiagnosticSink* sink);
+
+bool isResourceType(IRType* type);
+
+
}
#endif
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index af0664288..1e50c8aff 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -6212,13 +6212,13 @@ static void lowerProgramEntryPointToIR(
// 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 )
+ auto existentialTypeArgCount = entryPoint->getExistentialTypeArgCount();
+ if( existentialTypeArgCount )
{
List<IRInst*> existentialSlotArgs;
- for( UInt ii = 0; ii < existentialSlotCount; ++ii )
+ for( UInt ii = 0; ii < existentialTypeArgCount; ++ii )
{
- auto arg = entryPoint->getExistentialSlotArg(ii);
+ auto arg = entryPoint->getExistentialTypeArg(ii);
auto irArgType = lowerType(context, arg.type);
auto irWitnessTable = lowerSimpleVal(context, arg.witness);
@@ -6442,13 +6442,13 @@ 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 )
+ auto existentialTypeArgCount = program->getExistentialTypeArgCount();
+ if( existentialTypeArgCount )
{
List<IRInst*> existentialSlotArgs;
- for( UInt ii = 0; ii < existentialSlotCount; ++ii )
+ for( UInt ii = 0; ii < existentialTypeArgCount; ++ii )
{
- auto arg = program->getExistentialSlotArg(ii);
+ auto arg = program->getExistentialTypeArg(ii);
auto irArgType = lowerType(context, arg.type);
auto irWitnessTable = lowerSimpleVal(context, arg.witness);
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp
index a472a9280..a74e4639f 100644
--- a/source/slang/parameter-binding.cpp
+++ b/source/slang/parameter-binding.cpp
@@ -1862,6 +1862,18 @@ struct ScopeLayoutBuilder
{
m_structLayout->mapVarToLayout.Add(firstVarLayout->varDecl.getDecl(), firstVarLayout);
}
+
+ // Any "pending" items on a field type become "pending" items
+ // on the overall `struct` type layout.
+ //
+ // TODO: This logic ends up duplicated between here and the main
+ // `struct` layout logic in `type-layout.cpp`. If this gets any
+ // more complicated we should see if there is a way to share it.
+ //
+ for( auto pendingItem : firstVarLayout->typeLayout->pendingItems )
+ {
+ m_structLayout->pendingItems.Add(pendingItem);
+ }
}
void addParameter(
@@ -2001,6 +2013,18 @@ static void collectEntryPointParameters(
{
auto paramDeclRef = shaderParamInfo.paramDeclRef;
+ // When computing layout for an entry-point parameter,
+ // we want to make sure that the layout context has access
+ // to the existential type arguments (if any) that were
+ // provided for the entry-point existential type parameters (if any).
+ //
+ context->layoutContext= context->layoutContext
+ .withExistentialTypeArgs(
+ entryPoint->getExistentialTypeArgCount(),
+ entryPoint->getExistentialTypeArgs())
+ .withExistentialTypeSlotsOffsetBy(
+ shaderParamInfo.firstExistentialTypeSlot);
+
// Any error messages we emit during the process should
// refer to the location of this parameter.
//
@@ -2144,6 +2168,18 @@ static void collectParameters(
for(auto& globalParamInfo : program->getShaderParams() )
{
+ // When computing layout for a global shader parameter,
+ // we want to make sure that the layout context has access
+ // to the existential type arguments (if any) that were
+ // provided for the global existential type parameters (if any).
+ //
+ context->layoutContext= context->layoutContext
+ .withExistentialTypeArgs(
+ program->getExistentialTypeArgCount(),
+ program->getExistentialTypeArgs())
+ .withExistentialTypeSlotsOffsetBy(
+ globalParamInfo.firstExistentialTypeSlot);
+
collectGlobalScopeParameter(context, globalParamInfo, globalGenericSubst);
}
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index 1eea13230..3582a6571 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -2098,7 +2098,7 @@ SLANG_API SlangResult spSetGlobalGenericArgs(
return SLANG_OK;
}
-SLANG_API SlangResult spSetTypeNameForGlobalExistentialSlot(
+SLANG_API SlangResult spSetTypeNameForGlobalExistentialTypeParam(
SlangCompileRequest* request,
int slotIndex,
char const* typeName)
@@ -2115,7 +2115,7 @@ SLANG_API SlangResult spSetTypeNameForGlobalExistentialSlot(
return SLANG_OK;
}
-SLANG_API SlangResult spSetTypeNameForEntryPointExistentialSlot(
+SLANG_API SlangResult spSetTypeNameForEntryPointExistentialTypeParam(
SlangCompileRequest* request,
int entryPointIndex,
int slotIndex,
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp
index ea5287999..77588036d 100644
--- a/source/slang/type-layout.cpp
+++ b/source/slang/type-layout.cpp
@@ -973,10 +973,6 @@ static SimpleLayoutInfo getParameterGroupLayoutInfo(
}
}
-RefPtr<TypeLayout> createTypeLayout(
- TypeLayoutContext const& context,
- Type* type);
-
static bool isOpenGLTarget(TargetRequest*)
{
// We aren't officially supporting OpenGL right now
@@ -1184,12 +1180,76 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout(
return newTypeLayout;
}
+ /// Take a type layout that might include pending items and fold them into the layout.
+static RefPtr<TypeLayout> flushPendingItems(
+ TypeLayoutContext const& context,
+ RefPtr<TypeLayout> layout)
+{
+ SLANG_UNUSED(context);
+
+ // If there are no pending items on the layout,
+ // then there is nothing to be done.
+ //
+ if(layout->pendingItems.Count() == 0)
+ return layout;
+
+ // We need to compute a new type layout that reflects
+ // the resource usage of the provided `layout`, plus
+ // any resource usage for the pending items.
+ //
+ // TODO: To be correct we should construct a new `TypeLayout`
+ // of the same class, but that would take more work, so
+ // we'll re-use the one we already have... kind of gross...
+ //
+ for( auto pendingItem : layout->pendingItems )
+ {
+ auto itemTypeLayout = pendingItem.getTypeLayout();
+
+ // Any resources used by a pending item should be
+ // billed against the flushed layout we are computing.
+ //
+ // TODO: We need to make this handlde ordinary/uniform
+ // data carefully, so that it respects alignment and
+ // other layout rules for the target.
+ //
+ // TODO: We should only be adding in resource usage
+ // that can be "hidden" by the type of parameter block
+ // being built (e.g., only a `ParameterBlock` that allocates
+ // full `set`s/`space`s can hide the `register`s/`binding`s
+ // used by resource fields).
+ //
+ // TODO: we need to write something back to the item,
+ // which should have a `VarLayout` or something like
+ // that attached to it!
+ //
+ for( auto resInfo : itemTypeLayout->resourceInfos )
+ {
+ layout->addResourceUsage(resInfo);
+ }
+ }
+ layout->pendingItems.Clear();
+
+ return layout;
+}
+
static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType,
SimpleLayoutInfo parameterGroupInfo,
RefPtr<TypeLayout> rawElementTypeLayout)
{
+ // If there are any "pending" items that need to be laid out in
+ // the element type of the parameter group, then we want to flush
+ // them here.
+ //
+ // TODO: We might need to make this only flush *parts* of the pending
+ // items, based on what the parameter group can absorb, and leave
+ // other parts still pending in the type layout we return...
+ //
+ rawElementTypeLayout = flushPendingItems(
+ context.with(context.getRulesFamily()->getConstantBufferRules()),
+ rawElementTypeLayout);
+
auto parameterGroupRules = context.rules;
RefPtr<ParameterGroupTypeLayout> typeLayout = new ParameterGroupTypeLayout();
@@ -1333,7 +1393,7 @@ static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
{
// A parameter block type that gets its own register space will only
// include resource usage from the element type when it itself consumes
- // while register spaces.
+ // whole register spaces.
if (auto elementResInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace))
{
typeLayout->addResourceUsage(*elementResInfo);
@@ -1378,14 +1438,33 @@ static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
return typeLayout;
}
- /// Doe we need to wrap the given element type in a constant buffer layout?
+static bool usesResourceKind(RefPtr<TypeLayout> typeLayout, LayoutResourceKind kind)
+{
+ auto resInfo = typeLayout->FindResourceInfo(kind);
+ return resInfo && resInfo->count != 0;
+}
+
+static bool usesOrdinaryData(RefPtr<TypeLayout> typeLayout)
+{
+ return usesResourceKind(typeLayout, LayoutResourceKind::Uniform);
+}
+
+ /// Do we need to wrap the given element type in a constant buffer layout?
static bool needsConstantBuffer(RefPtr<TypeLayout> elementTypeLayout)
{
- auto uniformResInfo = elementTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
- if(uniformResInfo && uniformResInfo->count != 0)
+ // We need a constant buffer if the element type has ordinary/uniform data.
+ //
+ if(usesOrdinaryData(elementTypeLayout))
return true;
- // Note: additional cases are expected here, to deal with existential types.
+ // We also need a constant buffer if there are any "pending"
+ // items that need ordinary/uniform data allocated to them.
+ //
+ for( auto pendingItem : elementTypeLayout->pendingItems )
+ {
+ if(usesOrdinaryData(pendingItem.getTypeLayout()))
+ return true;
+ }
return false;
}
@@ -2302,8 +2381,32 @@ static TypeLayoutResult _createTypeLayout(
for (auto field : GetFields(structDeclRef))
{
+ // Static fields shouldn't take part in layout.
+ if(field.getDecl()->HasModifier<HLSLStaticModifier>())
+ continue;
+
+ // The fields of a `struct` type may include existential (interface)
+ // types (including as nested sub-fields), and any types present
+ // in those fields will need to be specialized based on the
+ // input arguments being passed to `_createTypeLayout`.
+ //
+ // We won't know how many type slots each field consumes until
+ // we process it, but we can figure out the starting index for
+ // the slots its will consume by looking at the layout we've
+ // computed so far.
+ //
+ Int baseExistentialSlotIndex = 0;
+ if(auto resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::ExistentialTypeParam))
+ baseExistentialSlotIndex = Int(resInfo->count.getFiniteValue());
+ //
+ // When computing the layout for the field, we will give it access
+ // to all the incoming specialized type slots that haven't already
+ // been consumed/claimed by preceding fields.
+ //
+ auto fieldLayoutContext = context.withExistentialTypeSlotsOffsetBy(baseExistentialSlotIndex);
+
TypeLayoutResult fieldResult = _createTypeLayout(
- context,
+ fieldLayoutContext,
GetType(field).Ptr(),
field.getDecl());
RefPtr<TypeLayout> fieldTypeLayout = fieldResult.layout;
@@ -2384,6 +2487,19 @@ static TypeLayoutResult _createTypeLayout(
structTypeResourceInfo->count += fieldTypeResourceInfo.count;
}
}
+
+ // Fields of a structure type may have existential/interface type,
+ // or nested existential/interface-type fields. When doing layout
+ // for a specialized program, these will show up as "pending" types
+ // that need to be laid out at the end of the surrounding block/container.
+ //
+ // Any pending types on fields of a structure become pending types
+ // on the structure itself.
+ //
+ for( auto pendingItem : fieldTypeLayout->pendingItems )
+ {
+ typeLayout->pendingItems.Add(pendingItem);
+ }
}
rules->EndStructLayout(&info);
@@ -2446,10 +2562,35 @@ static TypeLayoutResult _createTypeLayout(
// represents the indirections needed to reference the
// data to be referenced by this field.
//
- return createSimpleTypeLayout(
- SimpleLayoutInfo(LayoutResourceKind::ExistentialSlot, 1),
- type,
- rules);
+
+ RefPtr<TypeLayout> typeLayout = new TypeLayout();
+ typeLayout->type = type;
+ typeLayout->rules = rules;
+
+ typeLayout->addResourceUsage(LayoutResourceKind::ExistentialTypeParam, 1);
+ typeLayout->addResourceUsage(LayoutResourceKind::ExistentialObjectParam, 1);
+
+ // If there are any concrete types available, the first one will be
+ // the value that should be plugged into the slot we just introduced.
+ //
+ if( context.existentialTypeArgCount )
+ {
+ RefPtr<Type> concreteType = context.existentialTypeArgs[0].type;
+
+ RefPtr<TypeLayout> concreteTypeLayout = createTypeLayout(context, concreteType);
+
+ // Layout for this specialized interface type then results
+ // in a type layout that tracks both the resource usage of the
+ // interface type itself (just the type + value slots introduced
+ // above), plus a "pending" type that represents the value
+ // conceptually pointed to by the interface-type field/variable at runtime.
+ //
+ TypeLayout::PendingItem pendingItem;
+ pendingItem.typeLayout = concreteTypeLayout;
+ typeLayout->pendingItems.Add(pendingItem);
+ }
+
+ return TypeLayoutResult(typeLayout, SimpleLayoutInfo());
}
}
else if (auto errorType = as<ErrorType>(type))
@@ -2487,6 +2628,11 @@ static TypeLayoutResult _createTypeLayout(
//
for( auto caseType : taggedUnionType->caseTypes )
{
+ // Note: A tagged union type is not expected to have any existential/interface type
+ // slots; the case types that are provided must be fully specialized before the union is
+ // formed. Thus we don't need to mess around with existential type slots here the
+ // way we do for the `struct` case.
+
auto caseTypeResult = _createTypeLayout(context, caseType);
RefPtr<TypeLayout> caseTypeLayout = caseTypeResult.layout;
UniformLayoutInfo caseTypeInfo = caseTypeResult.info.getUniformLayout();
diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h
index bb32d769e..cdeadbb16 100644
--- a/source/slang/type-layout.h
+++ b/source/slang/type-layout.h
@@ -328,6 +328,24 @@ public:
// the space storing it in the above array
UInt uniformAlignment = 1;
+ /// An item that is conceptually owned by this type, but is pending layout.
+ ///
+ /// When a type contains interface/existential fields (recursively), the
+ /// actual data referenced by these fields needs to get allocated somewhere,
+ /// but it cannot go inline at the point where the interface/existential
+ /// type appears, or else
+ ///
+ struct PendingItem
+ {
+ RefPtr<TypeLayout> typeLayout;
+
+ RefPtr<TypeLayout> getTypeLayout() const { return typeLayout; }
+ RefPtr<Type> getType() const { return typeLayout->type; }
+ };
+
+ /// The pending items owned by this type, but which are pending layout.
+ List<PendingItem> pendingItems;
+
ResourceInfo* FindResourceInfo(LayoutResourceKind kind)
{
for(auto& rr : resourceInfos)
@@ -854,6 +872,12 @@ struct TypeLayoutContext
// or row-major.
MatrixLayoutMode matrixLayoutMode;
+ // The concrete types (if any) to plug into the currently in-scope
+ // existential type slots.
+ //
+ Int existentialTypeArgCount = 0;
+ ExistentialTypeSlots::Arg const* existentialTypeArgs = nullptr;
+
LayoutRulesImpl* getRules() { return rules; }
LayoutRulesFamilyImpl* getRulesFamily() const { return rules->getLayoutRulesFamily(); }
@@ -870,6 +894,34 @@ struct TypeLayoutContext
result.matrixLayoutMode = inMatrixLayoutMode;
return result;
}
+
+ TypeLayoutContext withExistentialTypeArgs(
+ Int argCount,
+ ExistentialTypeSlots::Arg const* args) const
+ {
+ TypeLayoutContext result = *this;
+ result.existentialTypeArgCount = argCount;
+ result.existentialTypeArgs = args;
+ return result;
+ }
+
+ TypeLayoutContext withExistentialTypeSlotsOffsetBy(
+ Int offset) const
+ {
+ TypeLayoutContext result = *this;
+ if( existentialTypeArgCount > offset )
+ {
+ result.existentialTypeArgCount = existentialTypeArgCount - offset;
+ result.existentialTypeArgs = existentialTypeArgs + offset;
+ }
+ else
+ {
+ result.existentialTypeArgCount = 0;
+ result.existentialTypeArgs = nullptr;
+ }
+ return result;
+
+ }
};
@@ -908,14 +960,6 @@ RefPtr<TypeLayout> createTypeLayout(
TypeLayoutContext const& context,
Type* type);
-// Create a full type layout for a type, while applying the given "simple"
-// layout information as an offset to any `VarLayout`s created along
-// the way.
-RefPtr<TypeLayout> createTypeLayout(
- TypeLayoutContext const& context,
- Type* type,
- SimpleLayoutInfo offset);
-
//
/// Create a layout for a parameter-group type (a `ConstantBuffer` or `ParameterBlock`).