// legalize-types.cpp #include "legalize-types.h" #include "ir-insts.h" #include "mangle.h" namespace Slang { LegalType LegalType::implicitDeref( LegalType const& valueType) { RefPtr obj = new ImplicitDerefType(); obj->valueType = valueType; LegalType result; result.flavor = Flavor::implicitDeref; result.obj = obj; return result; } LegalType LegalType::tuple( RefPtr tupleType) { SLANG_ASSERT(tupleType->elements.Count()); LegalType result; result.flavor = Flavor::tuple; result.obj = tupleType; return result; } LegalType LegalType::pair( RefPtr pairType) { LegalType result; result.flavor = Flavor::pair; result.obj = pairType; return result; } LegalType LegalType::pair( LegalType const& ordinaryType, LegalType const& specialType, RefPtr pairInfo) { // Handle some special cases for when // one or the other of the types isn't // actually used. if (ordinaryType.flavor == LegalType::Flavor::none) { // There was nothing ordinary. return specialType; } if (specialType.flavor == LegalType::Flavor::none) { return ordinaryType; } // There were both ordinary and special fields, // and so we need to handle them here. RefPtr obj = new PairPseudoType(); obj->ordinaryType = ordinaryType; obj->specialType = specialType; obj->pairInfo = pairInfo; return LegalType::pair(obj); } // static bool isResourceType(IRType* type) { while (auto arrayType = as(type)) { type = arrayType->getElementType(); } if (auto resourceTypeBase = as(type)) { return true; } else if (auto builtinGenericType = as(type)) { return true; } else if (auto pointerLikeType = as(type)) { return true; } else if (auto samplerType = as(type)) { return true; } else if(auto untypedBufferType = as(type)) { return true; } // TODO: need more comprehensive coverage here return false; } ModuleDecl* findModuleForDecl( Decl* decl) { for (auto dd = decl; dd; dd = dd->ParentDecl) { if (auto moduleDecl = as(dd)) return moduleDecl; } return nullptr; } // Helper type for legalization of aggregate types // that might need to be turned into tuple pseudo-types. struct TupleTypeBuilder { TypeLegalizationContext* context; IRType* type; IRStructType* originalStructType; struct OrdinaryElement { IRStructKey* fieldKey = nullptr; IRType* type = nullptr; }; List ordinaryElements; List specialElements; List pairElements; // Did we have any fields that forced us to change // the actual type away from the declared type? bool anyComplex = false; // Did we have any fields that actually required // storage in the "special" part of things? bool anySpecial = false; // Did we have any fields that actually used ordinary storage? bool anyOrdinary = false; // Add a field to the (pseudo-)type we are building void addField( IRStructKey* fieldKey, LegalType legalFieldType, LegalType legalLeafType, bool isResource) { LegalType ordinaryType; LegalType specialType; RefPtr elementPairInfo; switch (legalLeafType.flavor) { case LegalType::Flavor::simple: { // 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) { ordinaryType = legalLeafType; } else { specialType = legalFieldType; } } break; case LegalType::Flavor::none: anyComplex = true; break; case LegalType::Flavor::implicitDeref: { // TODO: we may want to say that any use // of `implicitDeref` puts the entire thing // into the "special" category, rather than // try to look under the hood... anyComplex = true; // We want to recursively add data // based on the unwrapped type. // // Note: this assumes we can't have a tuple // or a pair "under" an `implicitDeref`, so // we'll need to ensure that elsewhere. addField( fieldKey, legalFieldType, legalLeafType.getImplicitDeref()->valueType, isResource); return; } break; case LegalType::Flavor::pair: { // The field's type had both special and non-special parts auto pairType = legalLeafType.getPair(); // If things originally started as a resource type, then // we want to externalize all the fields that arose, even // if there is (nominally) ordinary data. // // This is because the "ordinary" side of the legalization // of `ConstantBuffer` will still be a resource type. if(isResource) { specialType = legalFieldType; } else { ordinaryType = pairType->ordinaryType; specialType = pairType->specialType; elementPairInfo = pairType->pairInfo; } } break; case LegalType::Flavor::tuple: { // A tuple always represents "special" data specialType = legalFieldType; } break; default: SLANG_UNEXPECTED("unknown legal type flavor"); break; } // String mangledFieldName = getMangledName(fieldDeclRef.getDecl()); PairInfo::Element pairElement; pairElement.flags = 0; pairElement.key = fieldKey; pairElement.fieldPairInfo = elementPairInfo; // We will always add a field to the "ordinary" // side of things, even if it has no ordinary // data, just to keep the list of fields aligned // with the original type. OrdinaryElement ordinaryElement; ordinaryElement.fieldKey = fieldKey; if (ordinaryType.flavor != LegalType::Flavor::none) { anyOrdinary = true; pairElement.flags |= PairInfo::kFlag_hasOrdinary; LegalType ot = ordinaryType; // TODO: any cases we should "unwrap" here? // E.g., `implicitDeref`? if(ot.flavor == LegalType::Flavor::simple) { ordinaryElement.type = ot.getSimple(); } else { SLANG_UNEXPECTED("unexpected ordinary field type"); } } ordinaryElements.Add(ordinaryElement); if (specialType.flavor != LegalType::Flavor::none) { anySpecial = true; anyComplex = true; pairElement.flags |= PairInfo::kFlag_hasSpecial; TuplePseudoType::Element specialElement; specialElement.key = fieldKey; specialElement.type = specialType; specialElements.Add(specialElement); } pairElement.type = LegalType::pair(ordinaryType, specialType, elementPairInfo); pairElements.Add(pairElement); } // Add a field to the (pseudo-)type we are building void addField( IRStructField* field) { auto fieldType = field->getFieldType(); bool isResourceField = isResourceType(fieldType); auto legalFieldType = legalizeType(context, fieldType); addField( field->getKey(), legalFieldType, legalFieldType, isResourceField); } LegalType getResult() { // If this is an empty struct, return a none type // This helps get rid of emtpy structs that often trips up the // downstream compiler if (!anyOrdinary && !anySpecial && !anyComplex) return LegalType(); // If we didn't see anything "special" // 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); } // If there were any "ordinary" fields along the way, // then we need to collect them into a new `struct` type // that represents these fields. // LegalType ordinaryType; if (anyOrdinary) { // We are going to create an new IR `struct` type that contains // the "ordinary" fields from the original type. Note that these // fields may have different types from what they did before, // because the fields themselves might have been legalized. // // The new type will have the same mangled name as the old one, so // downstream code is going to need to be careful not to emit declarations // for both of them. This should be okay, though, because the original // type was illegal (that was the whole point) and so it shouldn't be // referenced in the output anyway. // IRBuilder* builder = context->getBuilder(); IRStructType* ordinaryStructType = builder->createStructType(); ordinaryStructType->sourceLoc = originalStructType->sourceLoc; if(auto nameHintDecoration = originalStructType->findDecoration()) { builder->addNameHintDecoration(ordinaryStructType, nameHintDecoration->getNameOperand()); } // The new struct type will appear right after the original in the IR, // so that we can be sure any instruction that could reference the // original can also reference the new one. ordinaryStructType->insertAfter(originalStructType); // Mark the original type for removal once all the other legalization // activity is completed. This is necessary because both the original // and replacement type have the same mangled name, so they would // collide. // // (Also, the original type wasn't legal - that was the whole point...) context->instsToRemove.Add(originalStructType); for(auto ee : ordinaryElements) { // We will ensure that all the original fields are represented, // although they may have different types (due to legalization). // For fields that have *no* ordinary data, we will give them // a dummy `void` type and rely on downstream passes to not // actually emit declarations for those fields. // // (This helps keeps things simple because both the original // and modified type will have the same number of fields, so // we can continue to look up field layouts by index in the // emit logic) // // TODO: we should scrap that, and layout lookup should just // be based on mangled field names in all cases. // IRType* fieldType = ee.type; if(!fieldType) fieldType = context->getBuilder()->getVoidType(); // TODO: shallow clone of modifiers, etc. builder->createStructField( ordinaryStructType, ee.fieldKey, fieldType); } ordinaryType = LegalType::simple((IRType*) ordinaryStructType); } LegalType specialType; if (anySpecial) { RefPtr specialTuple = new TuplePseudoType(); specialTuple->elements = specialElements; specialType = LegalType::tuple(specialTuple); } RefPtr pairInfo; if (anyOrdinary && anySpecial) { pairInfo = new PairInfo(); pairInfo->elements = pairElements; } return LegalType::pair(ordinaryType, specialType, pairInfo); } }; static IRType* createBuiltinGenericType( TypeLegalizationContext* context, IROp op, IRType* elementType) { IRInst* operands[] = { elementType }; return context->getBuilder()->getType( op, 1, operands); } // Create a uniform buffer type with a given legalized // element type. static LegalType createLegalUniformBufferType( TypeLegalizationContext* context, IROp op, LegalType legalElementType) { switch (legalElementType.flavor) { case LegalType::Flavor::none: return LegalType(); case LegalType::Flavor::simple: { // Easy case: we just have a simple element type, // so we want to create a uniform buffer that wraps it. return LegalType::simple(createBuiltinGenericType( context, op, legalElementType.getSimple())); } break; case LegalType::Flavor::implicitDeref: { // This is actually an annoying case, because // we are being asked to convert, e.g.,: // // cbuffer Foo { ParameterBlock bar; } // // into the equivalent of: // // cbuffer Foo { Bar bar; } // // Which would really require a new `LegalType` that // would reprerent a resource type with a modified // element type. // // I'm going to attempt to hack this for now. return LegalType::implicitDeref(createLegalUniformBufferType( context, op, legalElementType.getImplicitDeref()->valueType)); } break; 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(); auto ordinaryType = createLegalUniformBufferType( context, op, pairType->ordinaryType); auto specialType = LegalType::implicitDeref(pairType->specialType); 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` auto elementPseudoTupleType = legalElementType.getTuple(); RefPtr 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; newElement.key = ee.key; newElement.type = LegalType::implicitDeref(ee.type); bufferPseudoTupleType->elements.Add(newElement); } return LegalType::tuple(bufferPseudoTupleType); } break; default: SLANG_UNEXPECTED("unknown legal type flavor"); UNREACHABLE_RETURN(LegalType()); break; } } static LegalType createLegalUniformBufferType( TypeLegalizationContext* context, IRUniformParameterGroupType* uniformBufferType, LegalType legalElementType) { return createLegalUniformBufferType( context, uniformBufferType->op, legalElementType); } // Create a pointer type with a given legalized value type. static LegalType createLegalPtrType( TypeLegalizationContext* context, IROp op, LegalType legalValueType) { switch (legalValueType.flavor) { case LegalType::Flavor::none: return LegalType(); case LegalType::Flavor::simple: { // Easy case: we just have a simple element type, // so we want to create a uniform buffer that wraps it. return LegalType::simple(createBuiltinGenericType( context, op, legalValueType.getSimple())); } break; case LegalType::Flavor::implicitDeref: { // We are being asked to create a pointer type to something // that is implicitly dereferenced, meaning we had: // // Ptr(PtrLike(T)) // // and now are being asked to make: // // Ptr(implicitDeref(LegalT)) // // So it seems like we can just create: // // implicitDeref(Ptr(LegalT)) // // and nobody should really be able to tell the difference, right? // // TODO: invetigate whether there are situations where this // will matter. return LegalType::implicitDeref(createLegalPtrType( context, op, legalValueType.getImplicitDeref()->valueType)); } break; case LegalType::Flavor::pair: { // We just need to pointer-ify both sides of the pair. auto pairType = legalValueType.getPair(); auto ordinaryType = createLegalPtrType( context, op, pairType->ordinaryType); auto specialType = createLegalPtrType( context, op, pairType->specialType); return LegalType::pair(ordinaryType, specialType, pairType->pairInfo); } case LegalType::Flavor::tuple: { // Wrap each of the tuple elements up as a pointer. auto valuePseudoTupleType = legalValueType.getTuple(); RefPtr ptrPseudoTupleType = 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 : valuePseudoTupleType->elements) { TuplePseudoType::Element newElement; newElement.key = ee.key; newElement.type = createLegalPtrType( context, op, ee.type); ptrPseudoTupleType->elements.Add(newElement); } return LegalType::tuple(ptrPseudoTupleType); } break; default: SLANG_UNEXPECTED("unknown legal type flavor"); UNREACHABLE_RETURN(LegalType()); break; } } struct LegalTypeWrapper { virtual LegalType wrap(TypeLegalizationContext* context, IRType* type) = 0; }; struct ArrayLegalTypeWrapper : LegalTypeWrapper { IRArrayTypeBase* arrayType; LegalType wrap(TypeLegalizationContext* context, IRType* type) { return LegalType::simple(context->getBuilder()->getArrayTypeBase( arrayType->op, type, arrayType->getElementCount())); } }; struct BuiltinGenericLegalTypeWrapper : LegalTypeWrapper { IROp op; LegalType wrap(TypeLegalizationContext* context, IRType* type) { return LegalType::simple(createBuiltinGenericType( context, op, type)); } }; struct ImplicitDerefLegalTypeWrapper : LegalTypeWrapper { LegalType wrap(TypeLegalizationContext*, IRType* type) { return LegalType::implicitDeref(LegalType::simple(type)); } }; static LegalType wrapLegalType( TypeLegalizationContext* context, LegalType legalType, LegalTypeWrapper* ordinaryWrapper, LegalTypeWrapper* specialWrapper) { switch (legalType.flavor) { case LegalType::Flavor::none: return LegalType(); case LegalType::Flavor::simple: { return ordinaryWrapper->wrap(context, legalType.getSimple()); } break; case LegalType::Flavor::implicitDeref: { return LegalType::implicitDeref(wrapLegalType( context, legalType, ordinaryWrapper, specialWrapper)); } break; case LegalType::Flavor::pair: { // We just need to pointer-ify both sides of the pair. auto pairType = legalType.getPair(); auto ordinaryType = wrapLegalType( context, pairType->ordinaryType, ordinaryWrapper, ordinaryWrapper); auto specialType = wrapLegalType( context, pairType->specialType, specialWrapper, specialWrapper); return LegalType::pair(ordinaryType, specialType, pairType->pairInfo); } case LegalType::Flavor::tuple: { // Wrap each of the tuple elements up as a pointer. auto tupleType = legalType.getTuple(); RefPtr resultTupleType = 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 : tupleType->elements) { TuplePseudoType::Element element; element.key = ee.key; element.type = wrapLegalType( context, ee.type, ordinaryWrapper, specialWrapper); resultTupleType->elements.Add(element); } return LegalType::tuple(resultTupleType); } break; default: SLANG_UNEXPECTED("unknown legal type flavor"); UNREACHABLE_RETURN(LegalType()); break; } } // Legalize a type, including any nested types // that it transitively contains. LegalType legalizeTypeImpl( TypeLegalizationContext* context, IRType* type) { if (auto uniformBufferType = as(type)) { // We have one of: // // ConstantBuffer // TextureBuffer // ParameterBlock // // or some other pointer-like type that represents uniform // parameters. We need to pull any resource-type fields out // of it, but leave non-resource fields where they are. // // As a special case, if the type contains *no* uniform data, // we'll want to completely eliminate the uniform/ordinary // part. // Legalize the element type to see what we are working with. auto legalElementType = legalizeType(context, uniformBufferType->getElementType()); // Note that even when legalElementType.flavor == Simple // 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. return createLegalUniformBufferType( context, uniformBufferType, legalElementType); } else if (isResourceType(type)) { // We assume that any resource types not handled above // are legal as-is. return LegalType::simple(type); } else if (as(type)) { return LegalType::simple(type); } else if (as(type)) { return LegalType::simple(type); } else if (as(type)) { return LegalType::simple(type); } else if (auto ptrType = as(type)) { auto legalValueType = legalizeType(context, ptrType->getValueType()); return createLegalPtrType(context, ptrType->op, legalValueType); } else if(auto structType = as(type)) { // Look at the (non-static) fields, and // see if anything needs to be cleaned up. // The things that need to be "cleaned up" for // our purposes are: // // - Fields of resource type, or any other future // type we run into that isn't allowed in // aggregates for at least some targets // // - Fields with types that themselves had to // get legalized. // // If we don't run into any of these, we // can just use the type as-is. Hooray! // // Otherwise, we are effectively going to split // the type apart and create a `TuplePseudoType`. // Every field of the original type will be // represented as an element of this pseudo-type. // Each element will record its `LegalType`, // and the original field that it was created from. // An element will also track whether it contains // any "ordinary" data, and if so, it will remember // an element index in a real (AST-level, non-pseudo) // `TupleType` that is used to bundle together // such fields. // // Storing all the simple fields together like this // obviously adds complexity to the legalization // pass, but it has important benefits: // // - It avoids creating functions with a very large // number of parameters (when passing a structure // with many fields), which might confuse downstream // compilers. // // - It avoids applying AOS->SOA conversion to fields // that don't actually need it, which is basically // required if we want type layout to work. // // - It ensures that we can actually construct a // constant-buffer type that wraps a legalized // aggregate type; the ordinary fields will get // placed inside a new constant-buffer type, // while the special ones will get left outside. // // TODO: there is a risk here that we might recursively // invole `legalizeType` on the type that we are // currently trying to legalize. We need to detect that // situation somehow, by inserting a sentinel value // into `mapTypeToLegalType` during the per-field // legalization process, and then if we ever see that // sentinel in a call to `legalizeType`, we need // to construct some kind of proxy type to help resolve // the problem. TupleTypeBuilder builder; builder.context = context; builder.type = type; builder.originalStructType = structType; for (auto ff : structType->getFields()) { builder.addField(ff); } return builder.getResult(); } else if(auto arrayType = as(type)) { auto legalElementType = legalizeType( context, arrayType->getElementType()); ArrayLegalTypeWrapper wrapper; wrapper.arrayType = arrayType; return wrapLegalType( context, legalElementType, &wrapper, &wrapper); } 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) { LegalType legalType; if(context->mapTypeToLegalType.TryGetValue(type, legalType)) return legalType; legalType = legalizeTypeImpl(context, type); context->mapTypeToLegalType[type] = legalType; return legalType; } // RefPtr getDerefTypeLayout( TypeLayout* typeLayout) { if (!typeLayout) return nullptr; if (auto parameterGroupTypeLayout = as(typeLayout)) { return parameterGroupTypeLayout->offsetElementTypeLayout; } return typeLayout; } RefPtr getFieldLayout( TypeLayout* typeLayout, String const& mangledFieldName) { if (!typeLayout) return nullptr; for(;;) { if(auto arrayTypeLayout = as(typeLayout)) { typeLayout = arrayTypeLayout->elementTypeLayout; } else if(auto parameterGroupTypeLayout = as(typeLayout)) { typeLayout = parameterGroupTypeLayout->offsetElementTypeLayout; } else { break; } } if (auto structTypeLayout = as(typeLayout)) { for(auto ff : structTypeLayout->fields) { if(mangledFieldName == getMangledName(ff->varDecl.getDecl()) ) { return ff; } } } return nullptr; } RefPtr createVarLayout( LegalVarChain* varChain, TypeLayout* typeLayout) { if (!typeLayout) return nullptr; // We need to construct a layout for the new variable // that reflects both the type we have given it, as // well as all the offset information that has accumulated // along the chain of parent variables. // TODO: this logic needs to propagate through semantics... RefPtr varLayout = new VarLayout(); varLayout->typeLayout = typeLayout; // For most resource kinds, the register index/space to use should // be the sum along the entire chain of variables. // // For example, if we had input: // // struct S { Texture2D a; Texture2D b; }; // S s : register(t10); // // And we were generating a stand-alone variable for `s.b`, then // we'd need to add the offset for `b` (1 texture register), to // the offset for `s` (10 texture registers) to get the final // binding to apply. // for (auto rr : typeLayout->resourceInfos) { auto resInfo = varLayout->findOrAddResourceInfo(rr.kind); for (auto vv = varChain; vv; vv = vv->next) { if (auto parentResInfo = vv->varLayout->FindResourceInfo(rr.kind)) { resInfo->index += parentResInfo->index; resInfo->space += parentResInfo->space; } } } // As a special case, if the leaf variable doesn't hold an entry for // `RegisterSpace`, but at least one declaration in the chain *does*, // then we want to make sure that we add such an entry. if (!varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) { // Sum up contributions from all parents. UInt space = 0; for (auto vv = varChain; vv; vv = vv->next) { if (auto parentResInfo = vv->varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) { space += parentResInfo->index; } } // If there were non-zero contributions, then add an entry to represent them. if (space) { varLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace)->index = space; } } return varLayout; } // // TODO(tfoley): The code captured here is the logic that used to be // applied to decide whether or not to desugar aggregate types that // contain resources. Right now the implementation will *always* legalize // away such types (since the IR always does this), while the AST-to-AST // pass would only do it if required (according to the tests below). // // For right now this is an academic distinction, since the only project // using Slang right now enables this tansformation unconditionally, but // we probably need to re-parent this code back into the `TypeLegalizationContext` // somewhere. #if 0 bool shouldDesugarTupleTypes = false; if (getTarget() == CodeGenTarget::GLSL) { // Always desugar this stuff for GLSL, since it doesn't // support nesting of resources in structs. // // TODO: Need a way to make this more fine-grained to // handle cases where a nested member might be allowed // due to, e.g., bindless textures. shouldDesugarTupleTypes = true; } else if( shared->compileRequest->compileFlags & SLANG_COMPILE_FLAG_SPLIT_MIXED_TYPES ) { // If the user is directly asking us to do this transformation, // then obviously we need to do it. // // TODO: The way this is defined here means it will even apply to user // HLSL code (not just code written in Slang). We may want to // reconsider that choice, and only split things that originated in Slang. // shouldDesugarTupleTypes = true; } #endif }