From 4f94dd46a2d885e570814dd14a5e46f8e0814802 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Fri, 8 Mar 2019 16:24:02 -0800 Subject: 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`) 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` into a `ConstantBuffer`). * 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` 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` 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` 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` 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 --- source/slang/emit.cpp | 111 +++++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 41 deletions(-) (limited to 'source/slang/emit.cpp') 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`. + // + // 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` 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 -- cgit v1.2.3