diff options
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`). |
