diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-03-08 16:24:02 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-03-08 16:24:02 -0800 |
| commit | 4f94dd46a2d885e570814dd14a5e46f8e0814802 (patch) | |
| tree | 55f22605d5532b8fc30b0d3691d8ba3994d41da8 /source | |
| parent | 281c67b8d92899f462695fe75a26467743a497e8 (diff) | |
Improve support for interfaces as shader parameters (#886)
* Improve support for interfaces as shader parameters
This change adds two main things over the existing support:
1. It is now possible to plug in concrete types that actually contain (uniform/ordinary) fields for the existential type parameters introduced by interface-type shader parameters. The `interface-shader-param2.slang` test shows that this works.
2. There is a limited amount of support for doing correct layout computation and generating output code that matches that layout, so that interface and ordinary-type fields can be interleaved to a limited extent. The `interface-shader-param3.slang` test confirms this behavior.
There are several moving pieces in the change.
* When it comes to terminology, we try to draw a more clear distinction between existial type parameters/arguments and existential/object value parametes/arguments. A simple way to look at it is that an `IFoo[3]` shader parameter introduces a single existential type parameter (so that a concrete type argument like `SomeThing` can be plugged in for the `IFoo`) but introduces three existential object/value parameters (to represent the concrete values for the array elements).
* At the IR level, we support a few new operations. A `BindExistentialsType` can take a type that is not itself an interface/existential type but which depends on interfaces/existentials (e.g., `ConstantBuffer<IFoo>`) and plug in the concrete types to be used for its existential type slots.
* Then a `wrapExistentials` instruction can take a type with all the existentials plugged in (possibly by `BindExistentialsType`) and wrap it into a value of the existential-using type (e.g., turn `ConstantBuffer<SomeThing>` into a `ConstantBuffer<IFoo>`).
* The IR passes for doing generic/existential specialization have been updated to be able to desugar uses of these new operations just enough so that a `ConstantBuffer<IFoo>` can be used.
* When we specialize an IR parameter of an interface type like `IFoo` based on a concrete type `SomeThing`, we turn the parameter into an `ExistentialBox<SomeThing>` to reflect the fact that we are conceptually referring to `SomeThing` indirectly (it shouldn't be factored into the layout of its surrounding type).
* Parameter binding was updated so that it passes along the bound existential type arguments in a `Program` or `EntryPoint` to type layout, so that we can take them into account. The type layout code needs to do a little work to pass the appropriate range of arguments along to sub-fields when computing layout for aggregate types.
* Type layout was updated to have a notion of "pending" items, which represent the concrete types of data that are logically being referenced by existential value slots. The basic idea is that these values aren't included in the layout of a type by default, but then they get "flushed" to come after all the non-existential-related data in a constant buffer, parameter block, etc.
* The logic for computing a parameter group (`ConstantBuffer` or `ParameterBlock`) layout was updated to always "flush" the pending items on the element type of the group, so that the resource usage of specialized existential slots would be taken into account.
* The type legalization pass has been adapted so that we can derive two different passes from it. One does resource-type legalization (which is all that the original pass did). The new pass uses the same basic machinery to legalize `ExistentialBox<T>` types by moving them out of their containing type(s), and then turning them into ordinary variables/parameters of type `T`.
Big things missing from this change include:
- Nothing is making sure that "pending" items at the global or entry-point level will get proper registers/bindings allocated to them. For the uniform case, all that matters in the current compiler is that we declare them in the right order in the output HLSL/GLSL, but for resources to be supported we will need to compute this layout information and start associating it with the existential/interface-type fields.
- Nothing is being done to support `BindExistentials<S, ...>` where `S` is a `struct` type that might have existential-type fields (or nested fields...). Eventually we need to desugar a type like this into a fresh `struct` type that has the same field keys as `S`, but with fields replaced by suitable `BindExistentials` as needed. (The hard part of this would seem to be computing which slots go to which fields). As a practial matter, this missing feature means that interface-type members of `cbuffer` declarations won't work.
The current tests carefully avoid both of these problems. They don't declare any buffer/texture fields in the concrete types, and they don't make use of `cbuffer` declarations or `ConstantBuffer`s over structure types with interface-type fields.
* fixup: add override to methods
* fixup: typos
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 61 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 2 | ||||
| -rw-r--r-- | source/slang/compiler.h | 73 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 111 | ||||
| -rw-r--r-- | source/slang/ir-bind-existentials.cpp | 128 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 29 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 32 | ||||
| -rw-r--r-- | source/slang/ir-legalize-types.cpp | 332 | ||||
| -rw-r--r-- | source/slang/ir-specialize.cpp | 169 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 113 | ||||
| -rw-r--r-- | source/slang/ir.h | 18 | ||||
| -rw-r--r-- | source/slang/legalize-types.cpp | 405 | ||||
| -rw-r--r-- | source/slang/legalize-types.h | 246 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 16 | ||||
| -rw-r--r-- | source/slang/parameter-binding.cpp | 36 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 4 | ||||
| -rw-r--r-- | source/slang/type-layout.cpp | 174 | ||||
| -rw-r--r-- | source/slang/type-layout.h | 60 |
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`). |
