diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-12-19 14:38:04 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-19 14:38:04 -0800 |
| commit | 332056a947ec3d9e3588a60d449d64577a6f18c0 (patch) | |
| tree | e9f0009482fa1c26a9e7ada6d600d05c91c00902 /source/slang/ir.cpp | |
| parent | b6a54744b6980041de0706fdcd9cba57cb706ff1 (diff) | |
Refactor several IR passes (#761)
* Refactor several IR passes
This change takes some IR passes that lived together in `ir.cpp` and moves them into their own files to improve clarity.
In most cases these were passes introduced early in the life of the IR, so that it didn't seem like a big deal to have them all in one file, but now that `ir.cpp` has grown unwieldly this seems like an important cleanup to make.
To give a quick rundown of the passes involved:
* The IR "linking" step has been pulled out to `ir-link.{h,cpp}`. This code for this pass is pretty much identical to what was in `ir.cpp`, and no attempt has been made to clean up or refactor it in the current change.
* The GLSL legalization step has been pulled out to `ir-glsl-legalize.{h,cpp}`. This used to be invoked directly from the linking step, but has been made a new top-level pass invoked from `emit.cpp`. Just like with the linking, the code in the new file is just a copy-paste of what was in `ir.cpp`, and no attempt at cleanup has been made. Also note that it might be a good idea to move this pass later in the overall sequence, but this PR doesn't attempt to do that as it could change results.
* The generic specialization step has been pulled out to `ir-specialize.{h,cpp}`. The file name does not explicitly reference *generic* specialization because I anticipate this pass having to perform other kinds of specialization as well. The code in this case amounts to a heavy cleanup/refactoring pass and thus deserves careful scrutiny. The reason for the cleanup is that the generic specialization step used to be part of the "linking" step long ago, and continued to share infrastructure with it long after that stopped making sense. The newly cleaned up pass has much simpler logic that should be easy enough to follow from the comments.
* In order to reduce code dulication, the IR "cloning" part of the `ir-specialize-resources.{h,cpp}` pass was pulled into its own files (`ir-clone.{h,cpp}`) that both the generic specialization step and the resource-based specialization step now share.
The remaining changes then pertain to deleting a bunch of code out of `ir.cpp` and adding the new files to the build.
The only test that needed updating was `vkray/raygen`, where some subtle ordering change in the refactored generic specialization logic has lead to the relative order of the specialized `TraceRay` and `saturate` functions beind reversed.
* fixup: typo in assert
* fixup: typos in comments
Diffstat (limited to 'source/slang/ir.cpp')
| -rw-r--r-- | source/slang/ir.cpp | 3404 |
1 files changed, 17 insertions, 3387 deletions
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 6899e1494..ca424b4a4 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -1858,18 +1858,32 @@ namespace Slang return inst; } - IRInst* IRBuilder::emitIntrinsicInst( + IRInst* IRBuilder::createIntrinsicInst( IRType* type, IROp op, UInt argCount, IRInst* const* args) { - auto inst = createInstWithTrailingArgs<IRInst>( + return createInstWithTrailingArgs<IRInst>( this, op, type, argCount, args); + } + + + IRInst* IRBuilder::emitIntrinsicInst( + IRType* type, + IROp op, + UInt argCount, + IRInst* const* args) + { + auto inst = createIntrinsicInst( + type, + op, + argCount, + args); addInst(inst); return inst; } @@ -3909,1058 +3923,6 @@ namespace Slang return t; } - // - // Legalization of entry points for GLSL: - // - - IRGlobalParam* addGlobalParam( - IRModule* module, - IRType* valueType) - { - auto session = module->session; - - SharedIRBuilder shared; - shared.module = module; - shared.session = session; - - IRBuilder builder; - builder.sharedBuilder = &shared; - return builder.createGlobalParam(valueType); - } - - void moveValueBefore( - IRInst* valueToMove, - IRInst* placeBefore) - { - valueToMove->removeFromParent(); - valueToMove->insertBefore(placeBefore); - } - - // When scalarizing shader inputs/outputs for GLSL, we need a way - // to refer to a conceptual "value" that might comprise multiple - // IR-level values. We could in principle introduce tuple types - // into the IR so that everything stays at the IR level, but - // it seems easier to just layer it over the top for now. - // - // The `ScalarizedVal` type deals with the "tuple or single value?" - // question, and also the "l-value or r-value?" question. - struct ScalarizedValImpl : RefObject - {}; - struct ScalarizedTupleValImpl; - struct ScalarizedTypeAdapterValImpl; - struct ScalarizedVal - { - enum class Flavor - { - // no value (null pointer) - none, - - // A simple `IRInst*` that represents the actual value - value, - - // An `IRInst*` that represents the address of the actual value - address, - - // A `TupleValImpl` that represents zero or more `ScalarizedVal`s - tuple, - - // A `TypeAdapterValImpl` that wraps a single `ScalarizedVal` and - // represents an implicit type conversion applied to it on read - // or write. - typeAdapter, - }; - - // Create a value representing a simple value - static ScalarizedVal value(IRInst* irValue) - { - ScalarizedVal result; - result.flavor = Flavor::value; - result.irValue = irValue; - return result; - } - - - // Create a value representing an address - static ScalarizedVal address(IRInst* irValue) - { - ScalarizedVal result; - result.flavor = Flavor::address; - result.irValue = irValue; - return result; - } - - static ScalarizedVal tuple(ScalarizedTupleValImpl* impl) - { - ScalarizedVal result; - result.flavor = Flavor::tuple; - result.impl = (ScalarizedValImpl*)impl; - return result; - } - - static ScalarizedVal typeAdapter(ScalarizedTypeAdapterValImpl* impl) - { - ScalarizedVal result; - result.flavor = Flavor::typeAdapter; - result.impl = (ScalarizedValImpl*)impl; - return result; - } - - Flavor flavor = Flavor::none; - IRInst* irValue = nullptr; - RefPtr<ScalarizedValImpl> impl; - }; - - // This is the case for a value that is a "tuple" of other values - struct ScalarizedTupleValImpl : ScalarizedValImpl - { - struct Element - { - IRStructKey* key; - ScalarizedVal val; - }; - - IRType* type; - List<Element> elements; - }; - - // This is the case for a value that is stored with one type, - // but needs to present itself as having a different type - struct ScalarizedTypeAdapterValImpl : ScalarizedValImpl - { - ScalarizedVal val; - IRType* actualType; // the actual type of `val` - IRType* pretendType; // the type this value pretends to have - }; - - struct GlobalVaryingDeclarator - { - enum class Flavor - { - array, - }; - - Flavor flavor; - IRInst* elementCount; - GlobalVaryingDeclarator* next; - }; - - struct GLSLSystemValueInfo - { - // The name of the built-in GLSL variable - char const* name; - - // The name of an outer array that wraps - // the variable, in the case of a GS input - char const* outerArrayName; - - // The required type of the built-in variable - IRType* requiredType; - }; - - void requireGLSLVersionImpl( - ExtensionUsageTracker* tracker, - ProfileVersion version); - - void requireGLSLExtension( - ExtensionUsageTracker* tracker, - String const& name); - - struct GLSLLegalizationContext - { - Session* session; - ExtensionUsageTracker* extensionUsageTracker; - DiagnosticSink* sink; - Stage stage; - - void requireGLSLExtension(String const& name) - { - Slang::requireGLSLExtension(extensionUsageTracker, name); - } - - void requireGLSLVersion(ProfileVersion version) - { - Slang::requireGLSLVersionImpl(extensionUsageTracker, version); - } - - Stage getStage() - { - return stage; - } - - DiagnosticSink* getSink() - { - return sink; - } - - IRBuilder* builder; - IRBuilder* getBuilder() { return builder; } - }; - - GLSLSystemValueInfo* getGLSLSystemValueInfo( - GLSLLegalizationContext* context, - VarLayout* varLayout, - LayoutResourceKind kind, - Stage stage, - GLSLSystemValueInfo* inStorage) - { - char const* name = nullptr; - char const* outerArrayName = nullptr; - - auto semanticNameSpelling = varLayout->systemValueSemantic; - if(semanticNameSpelling.Length() == 0) - return nullptr; - - auto semanticName = semanticNameSpelling.ToLower(); - - IRType* requiredType = nullptr; - - if(semanticName == "sv_position") - { - // This semantic can either work like `gl_FragCoord` - // when it is used as a fragment shader input, or - // like `gl_Position` when used in other stages. - // - // Note: This isn't as simple as testing input-vs-output, - // because a user might have a VS output `SV_Position`, - // and then pass it along to a GS that reads it as input. - // - if( stage == Stage::Fragment - && kind == LayoutResourceKind::VaryingInput ) - { - name = "gl_FragCoord"; - } - else if( stage == Stage::Geometry - && kind == LayoutResourceKind::VaryingInput ) - { - // As a GS input, the correct syntax is `gl_in[...].gl_Position`, - // but that is not compatible with picking the array dimension later, - // of course. - outerArrayName = "gl_in"; - name = "gl_Position"; - } - else - { - name = "gl_Position"; - } - } - else if(semanticName == "sv_target") - { - // Note: we do *not* need to generate some kind of `gl_` - // builtin for fragment-shader outputs: they are just - // ordinary `out` variables, with ordinary `location`s, - // as far as GLSL is concerned. - return nullptr; - } - else if(semanticName == "sv_clipdistance") - { - // TODO: type conversion is required here. - name = "gl_ClipDistance"; - } - else if(semanticName == "sv_culldistance") - { - context->requireGLSLExtension("ARB_cull_distance"); - - // TODO: type conversion is required here. - name = "gl_CullDistance"; - } - else if(semanticName == "sv_coverage") - { - // TODO: deal with `gl_SampleMaskIn` when used as an input. - - // TODO: type conversion is required here. - name = "gl_SampleMask"; - } - else if(semanticName == "sv_depth") - { - name = "gl_FragDepth"; - } - else if(semanticName == "sv_depthgreaterequal") - { - // TODO: layout(depth_greater) out float gl_FragDepth; - name = "gl_FragDepth"; - } - else if(semanticName == "sv_depthlessequal") - { - // TODO: layout(depth_greater) out float gl_FragDepth; - name = "gl_FragDepth"; - } - else if(semanticName == "sv_dispatchthreadid") - { - name = "gl_GlobalInvocationID"; - } - else if(semanticName == "sv_domainlocation") - { - name = "gl_TessCoord"; - } - else if(semanticName == "sv_groupid") - { - name = "gl_WorkGroupID"; - } - else if(semanticName == "sv_groupindex") - { - name = "gl_LocalInvocationIndex"; - } - else if(semanticName == "sv_groupthreadid") - { - name = "gl_LocalInvocationID"; - } - else if(semanticName == "sv_gsinstanceid") - { - name = "gl_InvocationID"; - } - else if(semanticName == "sv_instanceid") - { - name = "gl_InstanceIndex"; - } - else if(semanticName == "sv_isfrontface") - { - name = "gl_FrontFacing"; - } - else if(semanticName == "sv_outputcontrolpointid") - { - name = "gl_InvocationID"; - } - else if(semanticName == "sv_primitiveid") - { - name = "gl_PrimitiveID"; - } - else if (semanticName == "sv_rendertargetarrayindex") - { - switch (context->getStage()) - { - case Stage::Geometry: - context->requireGLSLVersion(ProfileVersion::GLSL_150); - break; - - case Stage::Fragment: - context->requireGLSLVersion(ProfileVersion::GLSL_430); - break; - - default: - context->requireGLSLVersion(ProfileVersion::GLSL_450); - context->requireGLSLExtension("GL_ARB_shader_viewport_layer_array"); - break; - } - - name = "gl_Layer"; - requiredType = context->getBuilder()->getBasicType(BaseType::Int); - } - else if (semanticName == "sv_sampleindex") - { - name = "gl_SampleID"; - } - else if (semanticName == "sv_stencilref") - { - context->requireGLSLExtension("ARB_shader_stencil_export"); - name = "gl_FragStencilRef"; - } - else if (semanticName == "sv_tessfactor") - { - name = "gl_TessLevelOuter"; - } - else if (semanticName == "sv_vertexid") - { - name = "gl_VertexIndex"; - } - else if (semanticName == "sv_viewportarrayindex") - { - name = "gl_ViewportIndex"; - } - else if (semanticName == "nv_x_right") - { - context->requireGLSLVersion(ProfileVersion::GLSL_450); - context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes"); - - // The actual output in GLSL is: - // - // vec4 gl_PositionPerViewNV[]; - // - // and is meant to support an arbitrary number of views, - // while the HLSL case just defines a second position - // output. - // - // For now we will hack this by: - // 1. Mapping an `NV_X_Right` output to `gl_PositionPerViewNV[1]` - // (that is, just one element of the output array) - // 2. Adding logic to copy the traditional `gl_Position` output - // over to `gl_PositionPerViewNV[0]` - // - - name = "gl_PositionPerViewNV[1]"; - -// shared->requiresCopyGLPositionToPositionPerView = true; - } - else if (semanticName == "nv_viewport_mask") - { - context->requireGLSLVersion(ProfileVersion::GLSL_450); - context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes"); - - name = "gl_ViewportMaskPerViewNV"; -// globalVarExpr = createGLSLBuiltinRef("gl_ViewportMaskPerViewNV", -// getUnsizedArrayType(getIntType())); - } - - if( name ) - { - inStorage->name = name; - inStorage->outerArrayName = outerArrayName; - inStorage->requiredType = requiredType; - return inStorage; - } - - context->getSink()->diagnose(varLayout->varDecl.getDecl()->loc, Diagnostics::unknownSystemValueSemantic, semanticNameSpelling); - return nullptr; - } - - ScalarizedVal createSimpleGLSLGlobalVarying( - GLSLLegalizationContext* context, - IRBuilder* builder, - IRType* inType, - VarLayout* inVarLayout, - TypeLayout* inTypeLayout, - LayoutResourceKind kind, - Stage stage, - UInt bindingIndex, - GlobalVaryingDeclarator* declarator) - { - // Check if we have a system value on our hands. - GLSLSystemValueInfo systemValueInfoStorage; - auto systemValueInfo = getGLSLSystemValueInfo( - context, - inVarLayout, - kind, - stage, - &systemValueInfoStorage); - - IRType* type = inType; - - // A system-value semantic might end up needing to override the type - // that the user specified. - if( systemValueInfo && systemValueInfo->requiredType ) - { - type = systemValueInfo->requiredType; - } - - // Construct the actual type and type-layout for the global variable - // - RefPtr<TypeLayout> typeLayout = inTypeLayout; - for( auto dd = declarator; dd; dd = dd->next ) - { - // We only have one declarator case right now... - SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array); - - auto arrayType = builder->getArrayType( - type, - dd->elementCount); - - RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout(); -// arrayTypeLayout->type = arrayType; - arrayTypeLayout->rules = typeLayout->rules; - arrayTypeLayout->originalElementTypeLayout = typeLayout; - arrayTypeLayout->elementTypeLayout = typeLayout; - arrayTypeLayout->uniformStride = 0; - - if( auto resInfo = inTypeLayout->FindResourceInfo(kind) ) - { - // TODO: it is kind of gross to be re-running some - // of the type layout logic here. - - UInt elementCount = (UInt) GetIntVal(dd->elementCount); - arrayTypeLayout->addResourceUsage( - kind, - resInfo->count * elementCount); - } - - type = arrayType; - typeLayout = arrayTypeLayout; - } - - // We need to construct a fresh layout for the variable, even - // if the original had its own layout, because it might be - // an `inout` parameter, and we only want to deal with the case - // described by our `kind` parameter. - RefPtr<VarLayout> varLayout = new VarLayout(); - varLayout->varDecl = inVarLayout->varDecl; - varLayout->typeLayout = typeLayout; - varLayout->flags = inVarLayout->flags; - varLayout->systemValueSemantic = inVarLayout->systemValueSemantic; - varLayout->systemValueSemanticIndex = inVarLayout->systemValueSemanticIndex; - varLayout->semanticName = inVarLayout->semanticName; - varLayout->semanticIndex = inVarLayout->semanticIndex; - varLayout->stage = inVarLayout->stage; - varLayout->AddResourceInfo(kind)->index = bindingIndex; - - // We are going to be creating a global parameter to replace - // the function parameter, but we need to handle the case - // where the parameter represents a varying *output* and not - // just an input. - // - // Our IR global shader parameters are read-only, just - // like our IR function parameters, and need a wrapper - // `Out<...>` type to represent otuputs. - // - bool isOutput = kind == LayoutResourceKind::VaryingOutput; - IRType* paramType = isOutput ? builder->getOutType(type) : type; - - auto globalParam = addGlobalParam(builder->getModule(), paramType); - moveValueBefore(globalParam, builder->getFunc()); - - ScalarizedVal val = isOutput ? ScalarizedVal::address(globalParam) : ScalarizedVal::value(globalParam); - - if( systemValueInfo ) - { - builder->addImportDecoration(globalParam, UnownedTerminatedStringSlice(systemValueInfo->name)); - - if( auto fromType = systemValueInfo->requiredType ) - { - // We may need to adapt from the declared type to/from - // the actual type of the GLSL global. - auto toType = inType; - - if( fromType != toType ) - { - RefPtr<ScalarizedTypeAdapterValImpl> typeAdapter = new ScalarizedTypeAdapterValImpl; - typeAdapter->actualType = systemValueInfo->requiredType; - typeAdapter->pretendType = inType; - typeAdapter->val = val; - - val = ScalarizedVal::typeAdapter(typeAdapter); - } - } - - if(auto outerArrayName = systemValueInfo->outerArrayName) - { - builder->addGLSLOuterArrayDecoration(globalParam, UnownedTerminatedStringSlice(outerArrayName)); - } - } - - builder->addLayoutDecoration(globalParam, varLayout); - - return val; - } - - ScalarizedVal createGLSLGlobalVaryingsImpl( - GLSLLegalizationContext* context, - IRBuilder* builder, - IRType* type, - VarLayout* varLayout, - TypeLayout* typeLayout, - LayoutResourceKind kind, - Stage stage, - UInt bindingIndex, - GlobalVaryingDeclarator* declarator) - { - if( as<IRBasicType>(type) ) - { - return createSimpleGLSLGlobalVarying( - context, - builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator); - } - else if( as<IRVectorType>(type) ) - { - return createSimpleGLSLGlobalVarying( - context, - builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator); - } - else if( as<IRMatrixType>(type) ) - { - // TODO: a matrix-type varying should probably be handled like an array of rows - return createSimpleGLSLGlobalVarying( - context, - builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator); - } - else if( auto arrayType = as<IRArrayType>(type) ) - { - // We will need to SOA-ize any nested types. - - auto elementType = arrayType->getElementType(); - auto elementCount = arrayType->getElementCount(); - auto arrayLayout = dynamic_cast<ArrayTypeLayout*>(typeLayout); - SLANG_ASSERT(arrayLayout); - auto elementTypeLayout = arrayLayout->elementTypeLayout; - - GlobalVaryingDeclarator arrayDeclarator; - arrayDeclarator.flavor = GlobalVaryingDeclarator::Flavor::array; - arrayDeclarator.elementCount = elementCount; - arrayDeclarator.next = declarator; - - return createGLSLGlobalVaryingsImpl( - context, - builder, - elementType, - varLayout, - elementTypeLayout, - kind, - stage, - bindingIndex, - &arrayDeclarator); - } - else if( auto streamType = as<IRHLSLStreamOutputType>(type)) - { - auto elementType = streamType->getElementType(); - auto streamLayout = dynamic_cast<StreamOutputTypeLayout*>(typeLayout); - SLANG_ASSERT(streamLayout); - auto elementTypeLayout = streamLayout->elementTypeLayout; - - return createGLSLGlobalVaryingsImpl( - context, - builder, - elementType, - varLayout, - elementTypeLayout, - kind, - stage, - bindingIndex, - declarator); - } - else if(auto structType = as<IRStructType>(type)) - { - // We need to recurse down into the individual fields, - // and generate a variable for each of them. - - auto structTypeLayout = dynamic_cast<StructTypeLayout*>(typeLayout); - SLANG_ASSERT(structTypeLayout); - RefPtr<ScalarizedTupleValImpl> tupleValImpl = new ScalarizedTupleValImpl(); - - - // Construct the actual type for the tuple (including any outer arrays) - IRType* fullType = type; - for( auto dd = declarator; dd; dd = dd->next ) - { - SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array); - fullType = builder->getArrayType( - fullType, - dd->elementCount); - } - - tupleValImpl->type = fullType; - - // Okay, we want to walk through the fields here, and - // generate one variable for each. - UInt fieldCounter = 0; - for(auto field : structType->getFields()) - { - UInt fieldIndex = fieldCounter++; - - auto fieldLayout = structTypeLayout->fields[fieldIndex]; - - UInt fieldBindingIndex = bindingIndex; - if(auto fieldResInfo = fieldLayout->FindResourceInfo(kind)) - fieldBindingIndex += fieldResInfo->index; - - auto fieldVal = createGLSLGlobalVaryingsImpl( - context, - builder, - field->getFieldType(), - fieldLayout, - fieldLayout->typeLayout, - kind, - stage, - fieldBindingIndex, - declarator); - - ScalarizedTupleValImpl::Element element; - element.val = fieldVal; - element.key = field->getKey(); - - tupleValImpl->elements.Add(element); - } - - return ScalarizedVal::tuple(tupleValImpl); - } - - // Default case is to fall back on the simple behavior - return createSimpleGLSLGlobalVarying( - context, - builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator); - } - - ScalarizedVal createGLSLGlobalVaryings( - GLSLLegalizationContext* context, - IRBuilder* builder, - IRType* type, - VarLayout* layout, - LayoutResourceKind kind, - Stage stage) - { - UInt bindingIndex = 0; - if(auto rr = layout->FindResourceInfo(kind)) - bindingIndex = rr->index; - return createGLSLGlobalVaryingsImpl( - context, - builder, type, layout, layout->typeLayout, kind, stage, bindingIndex, nullptr); - } - - IRType* getFieldType( - IRType* baseType, - IRStructKey* fieldKey) - { - if(auto structType = as<IRStructType>(baseType)) - { - for(auto ff : structType->getFields()) - { - if(ff->getKey() == fieldKey) - return ff->getFieldType(); - } - } - - SLANG_UNEXPECTED("no such field"); - UNREACHABLE_RETURN(nullptr); - } - - ScalarizedVal extractField( - IRBuilder* builder, - ScalarizedVal const& val, - UInt fieldIndex, - IRStructKey* fieldKey) - { - switch( val.flavor ) - { - case ScalarizedVal::Flavor::value: - return ScalarizedVal::value( - builder->emitFieldExtract( - getFieldType(val.irValue->getDataType(), fieldKey), - val.irValue, - fieldKey)); - - case ScalarizedVal::Flavor::address: - { - auto ptrType = as<IRPtrTypeBase>(val.irValue->getDataType()); - auto valType = ptrType->getValueType(); - auto fieldType = getFieldType(valType, fieldKey); - auto fieldPtrType = builder->getPtrType(ptrType->op, fieldType); - return ScalarizedVal::address( - builder->emitFieldAddress( - fieldPtrType, - val.irValue, - fieldKey)); - } - - case ScalarizedVal::Flavor::tuple: - { - auto tupleVal = val.impl.As<ScalarizedTupleValImpl>(); - return tupleVal->elements[fieldIndex].val; - } - - default: - SLANG_UNEXPECTED("unimplemented"); - UNREACHABLE_RETURN(ScalarizedVal()); - } - - } - - ScalarizedVal adaptType( - IRBuilder* builder, - IRInst* val, - IRType* toType, - IRType* /*fromType*/) - { - // TODO: actually consider what needs to go on here... - return ScalarizedVal::value(builder->emitConstructorInst( - toType, - 1, - &val)); - } - - ScalarizedVal adaptType( - IRBuilder* builder, - ScalarizedVal const& val, - IRType* toType, - IRType* fromType) - { - switch( val.flavor ) - { - case ScalarizedVal::Flavor::value: - return adaptType(builder, val.irValue, toType, fromType); - break; - - case ScalarizedVal::Flavor::address: - { - auto loaded = builder->emitLoad(val.irValue); - return adaptType(builder, loaded, toType, fromType); - } - break; - - default: - SLANG_UNEXPECTED("unimplemented"); - UNREACHABLE_RETURN(ScalarizedVal()); - } - } - - void assign( - IRBuilder* builder, - ScalarizedVal const& left, - ScalarizedVal const& right) - { - switch( left.flavor ) - { - case ScalarizedVal::Flavor::address: - switch( right.flavor ) - { - case ScalarizedVal::Flavor::value: - { - builder->emitStore(left.irValue, right.irValue); - } - break; - - case ScalarizedVal::Flavor::address: - { - auto val = builder->emitLoad(right.irValue); - builder->emitStore(left.irValue, val); - } - break; - - case ScalarizedVal::Flavor::tuple: - { - // We are assigning from a tuple to a destination - // that is not a tuple. We will perform assignment - // element-by-element. - auto rightTupleVal = right.impl.As<ScalarizedTupleValImpl>(); - UInt elementCount = rightTupleVal->elements.Count(); - - for( UInt ee = 0; ee < elementCount; ++ee ) - { - auto rightElement = rightTupleVal->elements[ee]; - auto leftElementVal = extractField( - builder, - left, - ee, - rightElement.key); - assign(builder, leftElementVal, rightElement.val); - } - } - break; - - default: - SLANG_UNEXPECTED("unimplemented"); - break; - } - break; - - case ScalarizedVal::Flavor::tuple: - { - // We have a tuple, so we are going to need to try and assign - // to each of its constituent fields. - auto leftTupleVal = left.impl.As<ScalarizedTupleValImpl>(); - UInt elementCount = leftTupleVal->elements.Count(); - - for( UInt ee = 0; ee < elementCount; ++ee ) - { - auto rightElementVal = extractField( - builder, - right, - ee, - leftTupleVal->elements[ee].key); - assign(builder, leftTupleVal->elements[ee].val, rightElementVal); - } - } - break; - - case ScalarizedVal::Flavor::typeAdapter: - { - // We are trying to assign to something that had its type adjusted, - // so we will need to adjust the type of the right-hand side first. - // - // In this case we are converting to the actual type of the GLSL variable, - // from the "pretend" type that it had in the IR before. - auto typeAdapter = left.impl.As<ScalarizedTypeAdapterValImpl>(); - auto adaptedRight = adaptType(builder, right, typeAdapter->actualType, typeAdapter->pretendType); - assign(builder, typeAdapter->val, adaptedRight); - } - break; - - default: - SLANG_UNEXPECTED("unimplemented"); - break; - } - } - - ScalarizedVal getSubscriptVal( - IRBuilder* builder, - IRType* elementType, - ScalarizedVal val, - IRInst* indexVal) - { - switch( val.flavor ) - { - case ScalarizedVal::Flavor::value: - return ScalarizedVal::value( - builder->emitElementExtract( - elementType, - val.irValue, - indexVal)); - - case ScalarizedVal::Flavor::address: - return ScalarizedVal::address( - builder->emitElementAddress( - builder->getPtrType(elementType), - val.irValue, - indexVal)); - - case ScalarizedVal::Flavor::tuple: - { - auto inputTuple = val.impl.As<ScalarizedTupleValImpl>(); - - RefPtr<ScalarizedTupleValImpl> resultTuple = new ScalarizedTupleValImpl(); - resultTuple->type = elementType; - - UInt elementCount = inputTuple->elements.Count(); - UInt elementCounter = 0; - - auto structType = as<IRStructType>(elementType); - for(auto field : structType->getFields()) - { - auto tupleElementType = field->getFieldType(); - - UInt elementIndex = elementCounter++; - - SLANG_RELEASE_ASSERT(elementIndex < elementCount); - auto inputElement = inputTuple->elements[elementIndex]; - - ScalarizedTupleValImpl::Element resultElement; - resultElement.key = inputElement.key; - resultElement.val = getSubscriptVal( - builder, - tupleElementType, - inputElement.val, - indexVal); - - resultTuple->elements.Add(resultElement); - } - SLANG_RELEASE_ASSERT(elementCounter == elementCount); - - return ScalarizedVal::tuple(resultTuple); - } - - default: - SLANG_UNEXPECTED("unimplemented"); - UNREACHABLE_RETURN(ScalarizedVal()); - } - } - - ScalarizedVal getSubscriptVal( - IRBuilder* builder, - IRType* elementType, - ScalarizedVal val, - UInt index) - { - return getSubscriptVal( - builder, - elementType, - val, - builder->getIntValue( - builder->getIntType(), - index)); - } - - IRInst* materializeValue( - IRBuilder* builder, - ScalarizedVal const& val); - - IRInst* materializeTupleValue( - IRBuilder* builder, - ScalarizedVal val) - { - auto tupleVal = val.impl.As<ScalarizedTupleValImpl>(); - SLANG_ASSERT(tupleVal); - - UInt elementCount = tupleVal->elements.Count(); - auto type = tupleVal->type; - - if( auto arrayType = as<IRArrayType>(type)) - { - // The tuple represent an array, which means that the - // individual elements are expected to yield arrays as well. - // - // We will extract a value for each array element, and - // then use these to construct our result. - - List<IRInst*> arrayElementVals; - UInt arrayElementCount = (UInt) GetIntVal(arrayType->getElementCount()); - - for( UInt ii = 0; ii < arrayElementCount; ++ii ) - { - auto arrayElementPseudoVal = getSubscriptVal( - builder, - arrayType->getElementType(), - val, - ii); - - auto arrayElementVal = materializeValue( - builder, - arrayElementPseudoVal); - - arrayElementVals.Add(arrayElementVal); - } - - return builder->emitMakeArray( - arrayType, - arrayElementVals.Count(), - arrayElementVals.Buffer()); - } - else - { - // The tuple represents a value of some aggregate type, - // so we can simply materialize the elements and then - // construct a value of that type. - // - // TODO: this should be using a `makeStruct` instruction. - - List<IRInst*> elementVals; - for( UInt ee = 0; ee < elementCount; ++ee ) - { - auto elementVal = materializeValue(builder, tupleVal->elements[ee].val); - elementVals.Add(elementVal); - } - - return builder->emitConstructorInst( - tupleVal->type, - elementVals.Count(), - elementVals.Buffer()); - } - } - - IRInst* materializeValue( - IRBuilder* builder, - ScalarizedVal const& val) - { - switch( val.flavor ) - { - case ScalarizedVal::Flavor::value: - return val.irValue; - - case ScalarizedVal::Flavor::address: - { - auto loadInst = builder->emitLoad(val.irValue); - return loadInst; - } - break; - - case ScalarizedVal::Flavor::tuple: - { - auto tupleVal = val.impl.As<ScalarizedTupleValImpl>(); - return materializeTupleValue(builder, val); - } - break; - - case ScalarizedVal::Flavor::typeAdapter: - { - // Somebody is trying to use a value where its actual type - // doesn't match the type it pretends to have. To make this - // work we need to adapt the type from its actual type over - // to its pretend type. - auto typeAdapter = val.impl.As<ScalarizedTypeAdapterValImpl>(); - auto adapted = adaptType(builder, typeAdapter->val, typeAdapter->pretendType, typeAdapter->actualType); - return materializeValue(builder, adapted); - } - break; - - default: - SLANG_UNEXPECTED("unimplemented"); - break; - } - } - IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration( IRInst* val, String const& targetName) @@ -4978,1360 +3940,14 @@ namespace Slang return nullptr; } - void legalizeRayTracingEntryPointParameterForGLSL( - GLSLLegalizationContext* context, - IRFunc* func, - IRParam* pp, - VarLayout* paramLayout) - { - auto builder = context->getBuilder(); - auto paramType = pp->getDataType(); - - // The parameter might be either an `in` parameter, - // or an `out` or `in out` parameter, and in those - // latter cases its IR-level type will include a - // wrapping "pointer-like" type (e.g., `Out<Float>` - // instead of just `Float`). - // - // Because global shader parameters are read-only - // in the same way function types are, we can take - // care of that detail here just by allocating a - // global shader parameter with exactly the type - // of the original function parameter. - // - auto globalParam = addGlobalParam(builder->getModule(), paramType); - builder->addLayoutDecoration(globalParam, paramLayout); - moveValueBefore(globalParam, builder->getFunc()); - pp->replaceUsesWith(globalParam); - - // Because linkage between ray-tracing shaders is - // based on the type of incoming/outgoing payload - // and attribute parameters, it would be an error to - // eliminate the global parameter *even if* it is - // not actually used inside the entry point. - // - // We attach a decoration to the entry point that - // makes note of the dependency, so that steps - // like dead code elimination cannot get rid of - // the parameter. - // - // TODO: We could consider using a structure like - // this for *all* of the entry point parameters - // that get moved to the global scope, since SPIR-V - // ends up requiring such information on an `OpEntryPoint`. - // - // As a further alternative, we could decide to - // keep entry point varying input/outtput attached - // to the parameter list through all of the Slang IR - // steps, and only declare it as global variables at - // the last minute when emitting a GLSL `main` or - // SPIR-V for an entry point. - // - builder->addDependsOnDecoration(func, globalParam); - } - - void legalizeEntryPointParameterForGLSL( - GLSLLegalizationContext* context, - IRFunc* func, - IRParam* pp, - VarLayout* paramLayout) - { - auto builder = context->getBuilder(); - auto stage = context->getStage(); - - // We need to create a global variable that will replace the parameter. - // It seems superficially obvious that the variable should have - // the same type as the parameter. - // However, if the parameter was a pointer, in order to - // support `out` or `in out` parameter passing, we need - // to be sure to allocate a variable of the pointed-to - // type instead. - // - // We also need to replace uses of the parameter with - // uses of the variable, and the exact logic there - // will differ a bit between the pointer and non-pointer - // cases. - auto paramType = pp->getDataType(); - - // First we will special-case stage input/outputs that - // don't fit into the standard varying model. - // For right now we are only doing special-case handling - // of geometry shader output streams. - if( auto paramPtrType = as<IROutTypeBase>(paramType) ) - { - auto valueType = paramPtrType->getValueType(); - if( auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) ) - { - // An output stream type like `TriangleStream<Foo>` should - // more or less translate into `out Foo` (plus scalarization). - - auto globalOutputVal = createGLSLGlobalVaryings( - context, - builder, - valueType, - paramLayout, - LayoutResourceKind::VaryingOutput, - stage); - - // TODO: a GS output stream might be passed into other - // functions, so that we should really be modifying - // any function that has one of these in its parameter - // list (and in the limit we should be leagalizing any - // type that nests these...). - // - // For now we will just try to deal with `Append` calls - // directly in this function. - - - - for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) - { - for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) - { - // Is it a call? - if(ii->op != kIROp_Call) - continue; - - // Is it calling the append operation? - auto callee = ii->getOperand(0); - for(;;) - { - // If the instruction is `specialize(X,...)` then - // we want to look at `X`, and if it is `generic { ... return R; }` - // then we want to look at `R`. We handle this - // iteratively here. - // - // TODO: This idiom seems to come up enough that we - // should probably have a dedicated convenience routine - // for this. - // - // Alternatively, we could switch the IR encoding so - // that decorations are added to the generic instead of the - // value it returns. - // - switch(callee->op) - { - case kIROp_Specialize: - { - callee = cast<IRSpecialize>(callee)->getOperand(0); - continue; - } - - case kIROp_Generic: - { - auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee)); - if(genericResult) - { - callee = genericResult; - continue; - } - } - - default: - break; - } - break; - } - if(callee->op != kIROp_Func) - continue; - - // HACK: we will identify the operation based - // on the target-intrinsic definition that was - // given to it. - auto decoration = findTargetIntrinsicDecoration(callee, "glsl"); - if(!decoration) - continue; - - if(decoration->getDefinition() != UnownedStringSlice::fromLiteral("EmitVertex()")) - { - continue; - } - - // Okay, we have a declaration, and we want to modify it! - - builder->setInsertBefore(ii); - - assign(builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2))); - } - } - - return; - } - } - - // When we have an HLSL ray tracing shader entry point, - // we don't want to translate the inputs/outputs for GLSL/SPIR-V - // according to our default rules, for two reasons: - // - // 1. The input and output for these stages are expected to - // be packaged into `struct` types rather than be scalarized, - // so the usual scalarization approach we take here should - // not be applied. - // - // 2. An `in out` parameter isn't just sugar for a combination - // of an `in` and an `out` parameter, and instead represents the - // read/write "payload" that was passed in. It should legalize - // to a single variable, and we can lower reads/writes of it - // directly, rather than introduce an intermediate temporary. - // - switch( stage ) - { - default: - break; - - case Stage::AnyHit: - case Stage::Callable: - case Stage::ClosestHit: - case Stage::Intersection: - case Stage::Miss: - case Stage::RayGeneration: - legalizeRayTracingEntryPointParameterForGLSL(context, func, pp, paramLayout); - return; - } - - // Is the parameter type a special pointer type - // that indicates the parameter is used for `out` - // or `inout` access? - if(auto paramPtrType = as<IROutTypeBase>(paramType) ) - { - // Okay, we have the more interesting case here, - // where the parameter was being passed by reference. - // We are going to create a local variable of the appropriate - // type, which will replace the parameter, along with - // one or more global variables for the actual input/output. - - auto valueType = paramPtrType->getValueType(); - - auto localVariable = builder->emitVar(valueType); - auto localVal = ScalarizedVal::address(localVariable); - - if( auto inOutType = as<IRInOutType>(paramPtrType) ) - { - // In the `in out` case we need to declare two - // sets of global variables: one for the `in` - // side and one for the `out` side. - auto globalInputVal = createGLSLGlobalVaryings( - context, - builder, valueType, paramLayout, LayoutResourceKind::VaryingInput, stage); - - assign(builder, localVal, globalInputVal); - } - - // Any places where the original parameter was used inside - // the function body should instead use the new local variable. - // Since the parameter was a pointer, we use the variable instruction - // itself (which is an `alloca`d pointer) directly: - pp->replaceUsesWith(localVariable); - - // We also need one or more global variables to write the output to - // when the function is done. We create them here. - auto globalOutputVal = createGLSLGlobalVaryings( - context, - builder, valueType, paramLayout, LayoutResourceKind::VaryingOutput, stage); - - // Now we need to iterate over all the blocks in the function looking - // for any `return*` instructions, so that we can write to the output variable - for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) - { - auto terminatorInst = bb->getLastInst(); - if(!terminatorInst) - continue; - - switch( terminatorInst->op ) - { - default: - continue; - - case kIROp_ReturnVal: - case kIROp_ReturnVoid: - break; - } - - // We dont' re-use `builder` here because we don't want to - // disrupt the source location it is using for inserting - // temporary variables at the top of the function. - // - IRBuilder terminatorBuilder; - terminatorBuilder.sharedBuilder = builder->sharedBuilder; - terminatorBuilder.setInsertBefore(terminatorInst); - - // Assign from the local variabel to the global output - // variable before the actual `return` takes place. - assign(&terminatorBuilder, globalOutputVal, localVal); - } - } - else - { - // This is the "easy" case where the parameter wasn't - // being passed by reference. We start by just creating - // one or more global variables to represent the parameter, - // and attach the required layout information to it along - // the way. - - auto globalValue = createGLSLGlobalVaryings( - context, - builder, paramType, paramLayout, LayoutResourceKind::VaryingInput, stage); - - // Next we need to replace uses of the parameter with - // references to the variable(s). We are going to do that - // somewhat naively, by simply materializing the - // variables at the start. - IRInst* materialized = materializeValue(builder, globalValue); - - pp->replaceUsesWith(materialized); - } - } - - void legalizeEntryPointForGLSL( - Session* session, - IRModule* module, - IRFunc* func, - EntryPointLayout* entryPointLayout, - DiagnosticSink* sink, - ExtensionUsageTracker* extensionUsageTracker) - { - GLSLLegalizationContext context; - context.session = session; - context.stage = entryPointLayout->profile.GetStage(); - context.sink = sink; - context.extensionUsageTracker = extensionUsageTracker; - - Stage stage = entryPointLayout->profile.GetStage(); - - // We require that the entry-point function has no uses, - // because otherwise we'd invalidate the signature - // at all existing call sites. - // - // TODO: the right thing to do here is to split any - // function that both gets called as an entry point - // and as an ordinary function. - SLANG_ASSERT(!func->firstUse); - - // We create a dummy IR builder, since some of - // the functions require it. - // - // TODO: make some of these free functions... - // - SharedIRBuilder shared; - shared.module = module; - shared.session = session; - IRBuilder builder; - builder.sharedBuilder = &shared; - builder.setInsertInto(func); - - context.builder = &builder; - - // We will start by looking at the return type of the - // function, because that will enable us to do an - // early-out check to avoid more work. - // - // Specifically, we need to check if the function has - // a `void` return type, because there is no work - // to be done on its return value in that case. - auto resultType = func->getResultType(); - if(as<IRVoidType>(resultType)) - { - // In this case, the function doesn't return a value - // so we don't need to transform its `return` sites. - // - // We can also use this opportunity to quickly - // check if the function has any parameters, and if - // it doesn't use the chance to bail out immediately. - if( func->getParamCount() == 0 ) - { - // This function is already legal for GLSL - // (at least in terms of parameter/result signature), - // so we won't bother doing anything at all. - return; - } - - // If the function does have parameters, then we need - // to let the logic later in this function handle them. - } - else - { - // Function returns a value, so we need - // to introduce a new global variable - // to hold that value, and then replace - // any `returnVal` instructions with - // code to write to that variable. - - auto resultGlobal = createGLSLGlobalVaryings( - &context, - &builder, - resultType, - entryPointLayout->resultLayout, - LayoutResourceKind::VaryingOutput, - stage); - - for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) - { - // TODO: This is silly, because we are looking at every instruction, - // when we know that a `returnVal` should only ever appear as a - // terminator... - for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) - { - if(ii->op != kIROp_ReturnVal) - continue; - - IRReturnVal* returnInst = (IRReturnVal*) ii; - IRInst* returnValue = returnInst->getVal(); - - // Make sure we add these instructions to the right block - builder.setInsertInto(bb); - - // Write to our global variable(s) from the value being returned. - assign(&builder, resultGlobal, ScalarizedVal::value(returnValue)); - - // Emit a `returnVoid` to end the block - auto returnVoid = builder.emitReturn(); - - // Remove the old `returnVal` instruction. - returnInst->removeAndDeallocate(); - - // Make sure to resume our iteration at an - // appropriate instruciton, since we deleted - // the one we had been using. - ii = returnVoid; - } - } - } - - // Next we will walk through any parameters of the entry-point function, - // and turn them into global variables. - if( auto firstBlock = func->getFirstBlock() ) - { - // Any initialization code we insert for parameters needs - // to be at the start of the "ordinary" instructions in the block: - builder.setInsertBefore(firstBlock->getFirstOrdinaryInst()); - - UInt paramCounter = 0; - for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() ) - { - UInt paramIndex = paramCounter++; - - // We assume that the entry-point layout includes information - // on each parameter, and that these arrays are kept aligned. - // Note that this means that any transformations that mess - // with function signatures will need to also update layout info... - // - SLANG_ASSERT(entryPointLayout->fields.Count() > paramIndex); - auto paramLayout = entryPointLayout->fields[paramIndex]; - - legalizeEntryPointParameterForGLSL( - &context, - func, - pp, - paramLayout); - } - - // At this point we should have eliminated all uses of the - // parameters of the entry block. Also, our control-flow - // rules mean that the entry block cannot be the target - // of any branches in the code, so there can't be - // any control-flow ops that try to match the parameter - // list. - // - // We can safely go through and destroy the parameters - // themselves, and then clear out the parameter list. - - for( auto pp = firstBlock->getFirstParam(); pp; ) - { - auto next = pp->getNextParam(); - pp->removeAndDeallocate(); - pp = next; - } - } - - // Finally, we need to patch up the type of the entry point, - // because it is no longer accurate. - - IRFuncType* voidFuncType = builder.getFuncType( - 0, - nullptr, - builder.getVoidType()); - func->setFullType(voidFuncType); - - // TODO: we should technically be constructing - // a new `EntryPointLayout` here to reflect - // the way that things have been moved around. - } - - // Needed for lookup up entry-point layouts. - // - // TODO: maybe arrange so that codegen is driven from the layout layer - // instead of the input/request layer. - EntryPointLayout* findEntryPointLayout( - ProgramLayout* programLayout, - EntryPointRequest* entryPointRequest); - - struct IRSpecSymbol : RefObject - { - IRInst* irGlobalValue; - RefPtr<IRSpecSymbol> nextWithSameName; - }; - - struct IRSpecEnv - { - IRSpecEnv* parent = nullptr; - - // A map from original values to their cloned equivalents. - typedef Dictionary<IRInst*, IRInst*> ClonedValueDictionary; - ClonedValueDictionary clonedValues; - }; - - struct IRSharedSpecContext - { - // The code-generation target in use - CodeGenTarget target; - - // The specialized module we are building - RefPtr<IRModule> module; - - // The original, unspecialized module we are copying - IRModule* originalModule; - - // A map from mangled symbol names to zero or - // more global IR values that have that name, - // in the *original* module. - typedef Dictionary<String, RefPtr<IRSpecSymbol>> SymbolDictionary; - SymbolDictionary symbols; - - SharedIRBuilder sharedBuilderStorage; - IRBuilder builderStorage; - - // The "global" specialization environment. - IRSpecEnv globalEnv; - }; - - struct IRGenericSpecKey - { - // Note: Slang::Dictionary requires key types to have default constructors - IRGenericSpecKey() - {} - - IRGenericSpecKey(IRSpecialize* specializeInst) - { - m_values.Add(specializeInst->getBase()); - auto argCount = specializeInst->getArgCount(); - for(UInt aa = 0; aa < argCount; ++aa) - { - m_values.Add(specializeInst->getArg(aa)); - } - } - - List<IRInst*> m_values; - - bool operator==(IRGenericSpecKey const& other) const - { - auto valueCount = m_values.Count(); - if(valueCount != other.m_values.Count()) return false; - for(UInt ii = 0; ii < valueCount; ++ii) - { - if(m_values[ii] != other.m_values[ii]) return false; - } - return true; - } - - UInt GetHashCode() const - { - auto hash = 0; - auto valueCount = m_values.Count(); - for(UInt ii = 0; ii < valueCount; ++ii) - { - hash = combineHash(hash, Slang::GetHashCode(m_values[ii])); - } - return hash; - } - }; - - struct IRSharedGenericSpecContext : IRSharedSpecContext - { - // Instructions to be processed (for generic specialization context) - List<IRInst*> workList; - HashSet<IRInst*> workListSet; - void addToWorkList(IRInst* inst) - { - if(!workListSet.Contains(inst)) - { - workList.Add(inst); - workListSet.Add(inst); - } - } - IRInst* popWorkList() - { - UInt count = workList.Count(); - if(count != 0) - { - IRInst* inst = workList[count - 1]; - workList.FastRemoveAt(count - 1); - workListSet.Remove(inst); - return inst; - } - return nullptr; - } - - Dictionary<IRGenericSpecKey, IRInst*> specializations; - }; - - struct IRSpecContextBase - { - // A map from the mangled name of a global variable - // to the layout to use for it. - Dictionary<String, VarLayout*> globalVarLayouts; - - IRSharedSpecContext* shared; - - IRSharedSpecContext* getShared() { return shared; } - - IRModule* getModule() { return getShared()->module; } - - IRModule* getOriginalModule() { return getShared()->originalModule; } - - IRSharedSpecContext::SymbolDictionary& getSymbols() { return getShared()->symbols; } - - // The current specialization environment to use. - IRSpecEnv* env = nullptr; - IRSpecEnv* getEnv() - { - // TODO: need to actually establish environments on contexts we create. - // - // Or more realistically we need to change the whole approach - // to specialization and cloning so that we don't try to share - // logic between two very different cases. - - - return env; - } - - // The IR builder to use for creating nodes - IRBuilder* builder; - - // A callback to be used when a value that is not registerd in `clonedValues` - // is needed during cloning. This gives the subtype a chance to intercept - // the operation and clone (or not) as needed. - virtual IRInst* maybeCloneValue(IRInst* originalVal) - { - return originalVal; - } - }; - - void registerClonedValue( - IRSpecContextBase* context, - IRInst* clonedValue, - IRInst* originalValue) - { - if(!originalValue) - return; - - // TODO: now that things are scoped using environments, we - // shouldn't be running into the cases where a value with - // the same key already exists. This should be changed to - // an `Add()` call. - // - context->getEnv()->clonedValues[originalValue] = clonedValue; - } - - // Information on values to use when registering a cloned value - struct IROriginalValuesForClone - { - IRInst* originalVal = nullptr; - IRSpecSymbol* sym = nullptr; - - IROriginalValuesForClone() {} - - IROriginalValuesForClone(IRInst* originalValue) - : originalVal(originalValue) - {} - - IROriginalValuesForClone(IRSpecSymbol* symbol) - : sym(symbol) - {} - }; - - void registerClonedValue( - IRSpecContextBase* context, - IRInst* clonedValue, - IROriginalValuesForClone const& originalValues) - { - registerClonedValue(context, clonedValue, originalValues.originalVal); - for( auto s = originalValues.sym; s; s = s->nextWithSameName ) - { - registerClonedValue(context, clonedValue, s->irGlobalValue); - } - } - - IRInst* cloneInst( - IRSpecContextBase* context, - IRBuilder* builder, - IRInst* originalInst, - IROriginalValuesForClone const& originalValues); - - IRInst* cloneInst( - IRSpecContextBase* context, - IRBuilder* builder, - IRInst* originalInst) - { - return cloneInst(context, builder, originalInst, originalInst); - } - - /// Clone any decorations from `originalValue` onto `clonedValue` - void cloneDecorations( - IRSpecContextBase* context, - IRInst* clonedValue, - IRInst* originalValue) - { - // TODO: In many cases we might be able to use this as a general-purpose - // place to do cloning of *all* the children of an instruction, and - // not just its decorations. We should look to refactor this code - // later. - - IRBuilder builderStorage = *context->builder; - IRBuilder* builder = &builderStorage; - builder->setInsertInto(clonedValue); - - - SLANG_UNUSED(context); - for(auto originalDecoration : originalValue->getDecorations()) - { - cloneInst(context, builder, originalDecoration); - } - - // We will also clone the location here, just because this is a convenient bottleneck - clonedValue->sourceLoc = originalValue->sourceLoc; - } - - /// Clone any decorations and children from `originalValue` onto `clonedValue` - void cloneDecorationsAndChildren( - IRSpecContextBase* context, - IRInst* clonedValue, - IRInst* originalValue) - { - IRBuilder builderStorage = *context->builder; - IRBuilder* builder = &builderStorage; - builder->setInsertInto(clonedValue); - - SLANG_UNUSED(context); - for(auto originalItem : originalValue->getDecorationsAndChildren()) - { - cloneInst(context, builder, originalItem); - } - - // We will also clone the location here, just because this is a convenient bottleneck - clonedValue->sourceLoc = originalValue->sourceLoc; - } - - // We use an `IRSpecContext` for the case where we are cloning - // code from one or more input modules to create a "linked" output - // module. Along the way, we will resolve profile-specific functions - // to the best definition for a given target. - // - struct IRSpecContext : IRSpecContextBase - { - // Override the "maybe clone" logic so that we always clone - virtual IRInst* maybeCloneValue(IRInst* originalVal) override; - }; - - - IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal); - - IRInst* cloneValue( - IRSpecContextBase* context, - IRInst* originalValue); - - IRType* cloneType( - IRSpecContextBase* context, - IRType* originalType); - - IRInst* IRSpecContext::maybeCloneValue(IRInst* originalValue) - { - switch (originalValue->op) - { - case kIROp_StructType: - case kIROp_Func: - case kIROp_Generic: - case kIROp_GlobalVar: - case kIROp_GlobalConstant: - case kIROp_GlobalParam: - case kIROp_StructKey: - case kIROp_GlobalGenericParam: - case kIROp_WitnessTable: - return cloneGlobalValue(this, originalValue); - - case kIROp_BoolLit: - { - IRConstant* c = (IRConstant*)originalValue; - return builder->getBoolValue(c->value.intVal != 0); - } - break; - - - case kIROp_IntLit: - { - IRConstant* c = (IRConstant*)originalValue; - return builder->getIntValue(cloneType(this, c->getDataType()), c->value.intVal); - } - break; - - case kIROp_FloatLit: - { - IRConstant* c = (IRConstant*)originalValue; - return builder->getFloatValue(cloneType(this, c->getDataType()), c->value.floatVal); - } - break; - - case kIROp_StringLit: - { - IRConstant* c = (IRConstant*)originalValue; - return builder->getStringValue(c->getStringSlice()); - } - break; - - case kIROp_PtrLit: - { - IRConstant* c = (IRConstant*)originalValue; - return builder->getPtrValue(c->value.ptrVal); - } - break; - - default: - { - // In the deafult case, assume that we have some sort of "hoistable" - // instruction that requires us to create a clone of it. - - UInt argCount = originalValue->getOperandCount(); - IRInst* clonedValue = createInstWithTrailingArgs<IRInst>( - builder, - originalValue->op, - cloneType(this, originalValue->getFullType()), - 0, nullptr, - argCount, nullptr); - registerClonedValue(this, clonedValue, originalValue); - for (UInt aa = 0; aa < argCount; ++aa) - { - IRInst* originalArg = originalValue->getOperand(aa); - IRInst* clonedArg = cloneValue(this, originalArg); - clonedValue->getOperands()[aa].init(clonedValue, clonedArg); - } - cloneDecorationsAndChildren(this, clonedValue, originalValue); - - addHoistableInst(builder, clonedValue); - - return clonedValue; - } - break; - } - } - - IRInst* cloneValue( - IRSpecContextBase* context, - IRInst* originalValue); - - // Find a pre-existing cloned value, or return null if none is available. - IRInst* findClonedValue( - IRSpecContextBase* context, - IRInst* originalValue) - { - IRInst* clonedValue = nullptr; - for (auto env = context->getEnv(); env; env = env->parent) - { - if (env->clonedValues.TryGetValue(originalValue, clonedValue)) - { - return clonedValue; - } - } - - return nullptr; - } - - IRInst* cloneValue( - IRSpecContextBase* context, - IRInst* originalValue) - { - if (!originalValue) - return nullptr; - - if (IRInst* clonedValue = findClonedValue(context, originalValue)) - return clonedValue; - - return context->maybeCloneValue(originalValue); - } - - IRType* cloneType( - IRSpecContextBase* context, - IRType* originalType) - { - return (IRType*)cloneValue(context, originalType); - } - - void cloneGlobalValueWithCodeCommon( - IRSpecContextBase* context, - IRGlobalValueWithCode* clonedValue, - IRGlobalValueWithCode* originalValue); - - IRRate* cloneRate( - IRSpecContextBase* context, - IRRate* rate) - { - return (IRRate*) cloneType(context, rate); - } - - void maybeSetClonedRate( - IRSpecContextBase* context, - IRBuilder* builder, - IRInst* clonedValue, - IRInst* originalValue) - { - if(auto rate = originalValue->getRate() ) - { - clonedValue->setFullType(builder->getRateQualifiedType( - cloneRate(context, rate), - clonedValue->getFullType())); - } - } - - IRGlobalVar* cloneGlobalVarImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRGlobalVar* originalVar, - IROriginalValuesForClone const& originalValues) - { - auto clonedVar = builder->createGlobalVar( - cloneType(context, originalVar->getDataType()->getValueType())); - - maybeSetClonedRate(context, builder, clonedVar, originalVar); - - registerClonedValue(context, clonedVar, originalValues); - - // Clone any code in the body of the variable, since this - // represents the initializer. - cloneGlobalValueWithCodeCommon( - context, - clonedVar, - originalVar); - - return clonedVar; - } - - IRGlobalConstant* cloneGlobalConstantImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRGlobalConstant* originalVal, - IROriginalValuesForClone const& originalValues) - { - auto clonedVal = builder->createGlobalConstant( - cloneType(context, originalVal->getFullType())); - registerClonedValue(context, clonedVal, originalValues); - - // Clone any code in the body of the constant, since this - // represents the initializer. - cloneGlobalValueWithCodeCommon( - context, - clonedVal, - originalVal); - - return clonedVal; - } - - void cloneSimpleGlobalValueImpl( - IRSpecContextBase* context, - IRInst* originalInst, - IROriginalValuesForClone const& originalValues, - IRInst* clonedInst, - bool registerValue = true) - { - if (registerValue) - registerClonedValue(context, clonedInst, originalValues); - - // Set up an IR builder for inserting into the inst - IRBuilder builderStorage = *context->builder; - IRBuilder* builder = &builderStorage; - builder->setInsertInto(clonedInst); - - // Clone any children of the instruction - for (auto child : originalInst->getDecorationsAndChildren()) - { - cloneInst(context, builder, child); - } - } - - IRGlobalParam* cloneGlobalParamImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRGlobalParam* originalVal, - IROriginalValuesForClone const& originalValues) - { - auto clonedVal = builder->createGlobalParam( - cloneType(context, originalVal->getFullType())); - cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal); - - if(auto linkage = originalVal->findDecoration<IRLinkageDecoration>()) - { - auto mangledName = String(linkage->getMangledName()); - VarLayout* layout = nullptr; - if (context->globalVarLayouts.TryGetValue(mangledName, layout)) - { - builder->addLayoutDecoration(clonedVal, layout); - } - } - - return clonedVal; - } - - IRGeneric* cloneGenericImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRGeneric* originalVal, - IROriginalValuesForClone const& originalValues) - { - auto clonedVal = builder->emitGeneric(); - registerClonedValue(context, clonedVal, originalValues); - - // Clone any code in the body of the generic, since this - // computes its result value. - cloneGlobalValueWithCodeCommon( - context, - clonedVal, - originalVal); - - return clonedVal; - } - - IRStructKey* cloneStructKeyImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRStructKey* originalVal, - IROriginalValuesForClone const& originalValues) - { - auto clonedVal = builder->createStructKey(); - cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal); - return clonedVal; - } - - IRGlobalGenericParam* cloneGlobalGenericParamImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRGlobalGenericParam* originalVal, - IROriginalValuesForClone const& originalValues) - { - auto clonedVal = builder->emitGlobalGenericParam(); - cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal); - return clonedVal; - } - - - IRWitnessTable* cloneWitnessTableImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRWitnessTable* originalTable, - IROriginalValuesForClone const& originalValues, - IRWitnessTable* dstTable = nullptr, - bool registerValue = true) - { - auto clonedTable = dstTable ? dstTable : builder->createWitnessTable(); - cloneSimpleGlobalValueImpl(context, originalTable, originalValues, clonedTable, registerValue); - return clonedTable; - } - - IRWitnessTable* cloneWitnessTableWithoutRegistering( - IRSpecContextBase* context, - IRBuilder* builder, - IRWitnessTable* originalTable, - IRWitnessTable* dstTable = nullptr) - { - return cloneWitnessTableImpl(context, builder, originalTable, IROriginalValuesForClone(), dstTable, false); - } - - IRStructType* cloneStructTypeImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRStructType* originalStruct, - IROriginalValuesForClone const& originalValues) - { - auto clonedStruct = builder->createStructType(); - cloneSimpleGlobalValueImpl(context, originalStruct, originalValues, clonedStruct); - return clonedStruct; - } - - - IRInterfaceType* cloneInterfaceTypeImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRInterfaceType* originalInterface, - IROriginalValuesForClone const& originalValues) - { - auto clonedInterface = builder->createInterfaceType(); - cloneSimpleGlobalValueImpl(context, originalInterface, originalValues, clonedInterface); - return clonedInterface; - } - - void cloneGlobalValueWithCodeCommon( - IRSpecContextBase* context, - IRGlobalValueWithCode* clonedValue, - IRGlobalValueWithCode* originalValue) - { - // Next we are going to clone the actual code. - IRBuilder builderStorage = *context->builder; - IRBuilder* builder = &builderStorage; - builder->setInsertInto(clonedValue); - - cloneDecorations(context, clonedValue, originalValue); - - // We will walk through the blocks of the function, and clone each of them. - // - // We need to create the cloned blocks first, and then walk through them, - // because blocks might be forward referenced (this is not possible - // for other cases of instructions). - for (auto originalBlock = originalValue->getFirstBlock(); - originalBlock; - originalBlock = originalBlock->getNextBlock()) - { - IRBlock* clonedBlock = builder->createBlock(); - clonedValue->addBlock(clonedBlock); - registerClonedValue(context, clonedBlock, originalBlock); - #if 0 - // We can go ahead and clone parameters here, while we are at it. - builder->curBlock = clonedBlock; - for (auto originalParam = originalBlock->getFirstParam(); - originalParam; - originalParam = originalParam->getNextParam()) - { - IRParam* clonedParam = builder->emitParam( - context->maybeCloneType( - originalParam->getFullType())); - cloneDecorations(context, clonedParam, originalParam); - registerClonedValue(context, clonedParam, originalParam); - } -#endif - } - - // Okay, now we are in a good position to start cloning - // the instructions inside the blocks. - { - IRBlock* ob = originalValue->getFirstBlock(); - IRBlock* cb = clonedValue->getFirstBlock(); - while (ob) - { - SLANG_ASSERT(cb); - - builder->setInsertInto(cb); - for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst()) - { - cloneInst(context, builder, oi); - } - - ob = ob->getNextBlock(); - cb = cb->getNextBlock(); - } - } - - } - - void checkIRDuplicate(IRInst* inst, IRInst* moduleInst, UnownedStringSlice const& mangledName) - { -#ifdef _DEBUG - for (auto child : moduleInst->getDecorationsAndChildren()) - { - if (child == inst) - continue; - - if(auto childLinkage = child->findDecoration<IRLinkageDecoration>()) - { - if(mangledName == childLinkage->getMangledName()) - { - SLANG_UNEXPECTED("duplicate global instruction"); - } - } - } -#else - SLANG_UNREFERENCED_PARAMETER(inst); - SLANG_UNREFERENCED_PARAMETER(moduleInst); - SLANG_UNREFERENCED_PARAMETER(mangledName); -#endif - } - - void cloneFunctionCommon( - IRSpecContextBase* context, - IRFunc* clonedFunc, - IRFunc* originalFunc, - bool checkDuplicate = true) - { - // First clone all the simple properties. - clonedFunc->setFullType(cloneType(context, originalFunc->getFullType())); - - cloneGlobalValueWithCodeCommon( - context, - clonedFunc, - originalFunc); - - // Shuffle the function to the end of the list, because - // it needs to follow its dependencies. - // - // TODO: This isn't really a good requirement to place on the IR... - clonedFunc->moveToEnd(); - - if( checkDuplicate ) - { - if( auto linkage = clonedFunc->findDecoration<IRLinkageDecoration>() ) - { - checkIRDuplicate(clonedFunc, context->getModule()->getModuleInst(), linkage->getMangledName()); - } - } - } - - IRFunc* specializeIRForEntryPoint( - IRSpecContext* context, - EntryPointRequest* entryPointRequest, - EntryPointLayout* entryPointLayout) - { - // Look up the IR symbol by name - auto mangledName = getMangledName(entryPointRequest->decl); - RefPtr<IRSpecSymbol> sym; - if (!context->getSymbols().TryGetValue(mangledName, sym)) - { - SLANG_UNEXPECTED("no matching IR symbol"); - return nullptr; - } - - // TODO: deal with the case where we might - // have multiple versions... - - auto globalValue = sym->irGlobalValue; - if (globalValue->op != kIROp_Func) - { - SLANG_UNEXPECTED("expected an IR function"); - return nullptr; - } - auto originalFunc = (IRFunc*)globalValue; - - // Create a clone for the IR function - auto clonedFunc = context->builder->createFunc(); - - // Note: we do *not* register this cloned declaration - // as the cloned value for the original symbol. - // This is kind of a kludge, but it ensures that - // in the unlikely case that the function is both - // used as an entry point and a callable function - // (yes, this would imply recursion...) we actually - // have two copies, which lets us arbitrarily - // transform the entry point to meet target requirements. - // - // TODO: The above statement is kind of bunk, though, - // because both versions of the function would have - // the same mangled name... :( - - // We need to clone all the properties of the original - // function, including any blocks, their parameters, - // and their instructions. - cloneFunctionCommon(context, clonedFunc, originalFunc); - - // We need to attach the layout information for - // the entry point to this declaration, so that - // we can use it to inform downstream code emit. - context->builder->addLayoutDecoration( - clonedFunc, - entryPointLayout); - - // We will also go on and attach layout information - // to the function parameters, so that we have it - // available directly on the parameters, rather - // than having to look it up on the original entry-point layout. - if( auto firstBlock = clonedFunc->getFirstBlock() ) - { - UInt paramLayoutCount = entryPointLayout->fields.Count(); - UInt paramCounter = 0; - for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() ) - { - UInt paramIndex = paramCounter++; - if( paramIndex < paramLayoutCount ) - { - auto paramLayout = entryPointLayout->fields[paramIndex]; - context->builder->addLayoutDecoration( - pp, - paramLayout); - } - else - { - SLANG_UNEXPECTED("too many parameters"); - } - } - } - - return clonedFunc; - } - IRFunc* cloneSimpleFuncWithoutRegistering(IRSpecContextBase* context, IRFunc* originalFunc) { auto clonedFunc = context->builder->createFunc(); cloneFunctionCommon(context, clonedFunc, originalFunc, false); return clonedFunc; } - - // Get a string form of the target so that we can - // use it to match against target-specialization modifiers - // - // TODO: We shouldn't be using strings for this. - String getTargetName(IRSpecContext* context) - { - switch( context->shared->target ) - { - case CodeGenTarget::HLSL: - return "hlsl"; - - case CodeGenTarget::GLSL: - return "glsl"; - - default: - SLANG_UNEXPECTED("unhandled case"); - UNREACHABLE_RETURN("unknown"); - } - } - - // How specialized is a given declaration for the chosen target? - enum class TargetSpecializationLevel - { - specializedForOtherTarget = 0, - notSpecialized, - specializedForTarget, - }; - - TargetSpecializationLevel getTargetSpecialiationLevel( - IRInst* inVal, - String const& targetName) - { - // HACK: Currently the front-end is placing modifiers related - // to target specialization on nodes like functions, even when - // those functions are being returned by a generic. This - // means that we need to try and inspect the value being - // returned by the generic if we are looking at a generic. - IRInst* val = inVal; - while( auto genericVal = as<IRGeneric>(val) ) - { - auto firstBlock = genericVal->getFirstBlock(); - if(!firstBlock) break; - - auto returnInst = as<IRReturnVal>(firstBlock->getLastInst()); - if(!returnInst) break; - - val = returnInst->getVal(); - } - - TargetSpecializationLevel result = TargetSpecializationLevel::notSpecialized; - for(auto dd : val->getDecorations()) - { - if(dd->op != kIROp_TargetDecoration) - continue; - - auto decoration = (IRTargetDecoration*) dd; - if(String(decoration->getTargetName()) == targetName) - return TargetSpecializationLevel::specializedForTarget; - - result = TargetSpecializationLevel::specializedForOtherTarget; - } - - return result; - } +#endif IRInst* findGenericReturnVal(IRGeneric* generic) { @@ -6406,992 +4022,6 @@ namespace Slang } } - // Is `newVal` marked as being a better match for our - // chosen code-generation target? - // - // TODO: there is a missing step here where we need - // to check if things are even available in the first place... - bool isBetterForTarget( - IRSpecContext* context, - IRInst* newVal, - IRInst* oldVal) - { - String targetName = getTargetName(context); - - // For right now every declaration might have zero or more - // modifiers, representing the targets for which it is specialized. - // Each modifier has a single string "tag" to represent a target. - // We thus decide that a declaration is "more specialized" by: - // - // - Does it have a modifier with a tag with the string for the current target? - // If yes, it is the most specialized it can be. - // - // - Does it have a no tags? Then it is "unspecialized" and that is okay. - // - // - Does it have a modifier with a tag for a *different* target? - // If yes, then it shouldn't even be usable on this target. - // - // Longer term a better approach is to think of this in terms - // of a "disjunction of conjunctions" that is: - // - // (A and B and C) or (A and D) or (E) or (F and G) ... - // - // A code generation target would then consist of a - // conjunction of invidual tags: - // - // (HLSL and SM_4_0 and Vertex and ...) - // - // A declaration is *applicable* on a target if one of - // its conjunctions of tags is a subset of the target's. - // - // One declaration is *better* than another on a target - // if it is applicable and its tags are a superset - // of the other's. - - auto newLevel = getTargetSpecialiationLevel(newVal, targetName); - auto oldLevel = getTargetSpecialiationLevel(oldVal, targetName); - if(newLevel != oldLevel) - return UInt(newLevel) > UInt(oldLevel); - - // All other factors being equal, a definition is - // better than a declaration. - auto newIsDef = isDefinition(newVal); - auto oldIsDef = isDefinition(oldVal); - if (newIsDef != oldIsDef) - return newIsDef; - - return false; - } - - IRFunc* cloneFuncImpl( - IRSpecContextBase* context, - IRBuilder* builder, - IRFunc* originalFunc, - IROriginalValuesForClone const& originalValues) - { - auto clonedFunc = builder->createFunc(); - registerClonedValue(context, clonedFunc, originalValues); - cloneFunctionCommon(context, clonedFunc, originalFunc); - return clonedFunc; - } - - - IRInst* cloneInst( - IRSpecContextBase* context, - IRBuilder* builder, - IRInst* originalInst, - IROriginalValuesForClone const& originalValues) - { - switch (originalInst->op) - { - // We need to special-case any instruction that is not - // allocated like an ordinary `IRInst` with trailing args. - case kIROp_Func: - return cloneFuncImpl(context, builder, cast<IRFunc>(originalInst), originalValues); - - case kIROp_GlobalVar: - return cloneGlobalVarImpl(context, builder, cast<IRGlobalVar>(originalInst), originalValues); - - case kIROp_GlobalConstant: - return cloneGlobalConstantImpl(context, builder, cast<IRGlobalConstant>(originalInst), originalValues); - - case kIROp_GlobalParam: - return cloneGlobalParamImpl(context, builder, cast<IRGlobalParam>(originalInst), originalValues); - - case kIROp_WitnessTable: - return cloneWitnessTableImpl(context, builder, cast<IRWitnessTable>(originalInst), originalValues); - - case kIROp_StructType: - return cloneStructTypeImpl(context, builder, cast<IRStructType>(originalInst), originalValues); - - case kIROp_InterfaceType: - return cloneInterfaceTypeImpl(context, builder, cast<IRInterfaceType>(originalInst), originalValues); - - case kIROp_Generic: - return cloneGenericImpl(context, builder, cast<IRGeneric>(originalInst), originalValues); - - case kIROp_StructKey: - return cloneStructKeyImpl(context, builder, cast<IRStructKey>(originalInst), originalValues); - - case kIROp_GlobalGenericParam: - return cloneGlobalGenericParamImpl(context, builder, cast<IRGlobalGenericParam>(originalInst), originalValues); - - default: - break; - } - - // The common case is that we just need to construct a cloned - // instruction with the right number of operands, intialize - // it, and then add it to the sequence. - UInt argCount = originalInst->getOperandCount(); - IRInst* clonedInst = createInstWithTrailingArgs<IRInst>( - builder, originalInst->op, - cloneType(context, originalInst->getFullType()), - 0, nullptr, - argCount, nullptr); - registerClonedValue(context, clonedInst, originalValues); - auto oldBuilder = context->builder; - context->builder = builder; - for (UInt aa = 0; aa < argCount; ++aa) - { - IRInst* originalArg = originalInst->getOperand(aa); - IRInst* clonedArg = cloneValue(context, originalArg); - clonedInst->getOperands()[aa].init(clonedInst, clonedArg); - } - builder->addInst(clonedInst); - context->builder = oldBuilder; - cloneDecorations(context, clonedInst, originalInst); - - return clonedInst; - } - - IRInst* cloneGlobalValueImpl( - IRSpecContext* context, - IRInst* originalInst, - IROriginalValuesForClone const& originalValues) - { - auto clonedValue = cloneInst(context, &context->shared->builderStorage, originalInst, originalValues); - clonedValue->moveToEnd(); - return clonedValue; - } - - - /// Clone a global value, which has the given `originalLinkage`. - /// - /// The `originalVal` is a known global IR value with that linkage, if one is available. - /// (It is okay for this parameter to be null). - /// - IRInst* cloneGlobalValueWithLinkage( - IRSpecContext* context, - IRInst* originalVal, - IRLinkageDecoration* originalLinkage) - { - // If the global value being cloned is already in target module, don't clone - // Why checking this? - // When specializing a generic function G (which is already in target module), - // where G calls a normal function F (which is already in target module), - // then when we are making a copy of G via cloneFuncCommom(), it will recursively clone F, - // however we don't want to make a duplicate of F in the target module. - if (originalVal->getParent() == context->getModule()->getModuleInst()) - return originalVal; - - // Check if we've already cloned this value, for the case where - // an original value has already been established. - if (originalVal) - { - if (IRInst* clonedVal = findClonedValue(context, originalVal)) - { - return clonedVal; - } - } - - if(!originalLinkage) - { - // If there is no mangled name, then we assume this is a local symbol, - // and it can't possibly have multiple declarations. - return cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone()); - } - - // - // We will scan through all of the available declarations - // with the same mangled name as `originalVal` and try - // to pick the "best" one for our target. - - auto mangledName = String(originalLinkage->getMangledName()); - RefPtr<IRSpecSymbol> sym; - if( !context->getSymbols().TryGetValue(mangledName, sym) ) - { - if(!originalVal) - return nullptr; - - // This shouldn't happen! - SLANG_UNEXPECTED("no matching values registered"); - UNREACHABLE_RETURN(cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone())); - } - - // We will try to track the "best" declaration we can find. - // - // Generally, one declaration wil lbe better than another if it is - // more specialized for the chosen target. Otherwise, we simply favor - // definitions over declarations. - // - IRInst* bestVal = sym->irGlobalValue; - for( auto ss = sym->nextWithSameName; ss; ss = ss->nextWithSameName ) - { - IRInst* newVal = ss->irGlobalValue; - if(isBetterForTarget(context, newVal, bestVal)) - bestVal = newVal; - } - - // Check if we've already cloned this value, for the case where - // we didn't have an original value (just a name), but we've - // now found a representative value. - if (!originalVal) - { - if (IRInst* clonedVal = findClonedValue(context, bestVal)) - { - return clonedVal; - } - } - - return cloneGlobalValueImpl(context, bestVal, IROriginalValuesForClone(sym)); - } - - // Clone a global value, where `originalVal` is one declaration/definition, but we might - // have to consider others, in order to find the "best" version of the symbol. - IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal) - { - // We are being asked to clone a particular global value, but in - // the IR that comes out of the front-end there could still - // be multiple, target-specific, declarations of any given - // global value, all of which share the same mangled name. - return cloneGlobalValueWithLinkage( - context, - originalVal, - originalVal->findDecoration<IRLinkageDecoration>()); - } - - StructTypeLayout* getGlobalStructLayout( - ProgramLayout* programLayout); - - void insertGlobalValueSymbol( - IRSharedSpecContext* sharedContext, - IRInst* gv) - { - auto linkage = gv->findDecoration<IRLinkageDecoration>(); - - // Don't try to register a symbol for global values - // that don't have linkage. - // - if (!linkage) - return; - - auto mangledName = String(linkage->getMangledName()); - - RefPtr<IRSpecSymbol> sym = new IRSpecSymbol(); - sym->irGlobalValue = gv; - - RefPtr<IRSpecSymbol> prev; - if (sharedContext->symbols.TryGetValue(mangledName, prev)) - { - sym->nextWithSameName = prev->nextWithSameName; - prev->nextWithSameName = sym; - } - else - { - sharedContext->symbols.Add(mangledName, sym); - } - } - - void insertGlobalValueSymbols( - IRSharedSpecContext* sharedContext, - IRModule* originalModule) - { - if (!originalModule) - return; - - for(auto ii : originalModule->getGlobalInsts()) - { - insertGlobalValueSymbol(sharedContext, ii); - } - } - - void initializeSharedSpecContext( - IRSharedSpecContext* sharedContext, - Session* session, - IRModule* module, - IRModule* originalModule, - CodeGenTarget target) - { - - SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage; - sharedBuilder->module = nullptr; - sharedBuilder->session = session; - - IRBuilder* builder = &sharedContext->builderStorage; - builder->sharedBuilder = sharedBuilder; - - if( !module ) - { - module = builder->createModule(); - } - - sharedBuilder->module = module; - sharedContext->module = module; - sharedContext->originalModule = originalModule; - sharedContext->target = target; - // We will populate a map with all of the IR values - // that use the same mangled name, to make lookup easier - // in other steps. - insertGlobalValueSymbols(sharedContext, originalModule); - } - - // implementation provided in parameter-binding.cpp - RefPtr<ProgramLayout> specializeProgramLayout( - TargetRequest * targetReq, - ProgramLayout* programLayout, - SubstitutionSet typeSubst); - - struct IRSpecializationState - { - ProgramLayout* programLayout; - CodeGenTarget target; - TargetRequest* targetReq; - - IRModule* irModule = nullptr; - RefPtr<ProgramLayout> newProgramLayout; - - IRSharedSpecContext sharedContextStorage; - IRSpecContext contextStorage; - - IRSpecEnv globalEnv; - - IRSharedSpecContext* getSharedContext() { return &sharedContextStorage; } - IRSpecContext* getContext() { return &contextStorage; } - - IRSpecializationState() - { - contextStorage.env = &globalEnv; - } - - ~IRSpecializationState() - { - newProgramLayout = nullptr; - contextStorage = IRSpecContext(); - sharedContextStorage = IRSharedSpecContext(); - } - }; - - IRSpecializationState* createIRSpecializationState( - EntryPointRequest* entryPointRequest, - ProgramLayout* programLayout, - CodeGenTarget target, - TargetRequest* targetReq) - { - IRSpecializationState* state = new IRSpecializationState(); - - state->programLayout = programLayout; - state->target = target; - state->targetReq = targetReq; - - - auto compileRequest = entryPointRequest->compileRequest; - auto translationUnit = entryPointRequest->getTranslationUnit(); - auto originalIRModule = translationUnit->irModule; - - auto sharedContext = state->getSharedContext(); - initializeSharedSpecContext( - sharedContext, - compileRequest->mSession, - nullptr, - originalIRModule, - target); - - state->irModule = sharedContext->module; - - // We also need to attach the IR definitions for symbols from - // any loaded modules: - for (auto loadedModule : compileRequest->loadedModulesList) - { - insertGlobalValueSymbols(sharedContext, loadedModule->irModule); - } - - auto context = state->getContext(); - context->shared = sharedContext; - context->builder = &sharedContext->builderStorage; - - // Now specialize the program layout using the substitution - // - // TODO: The specialization of the layout is conceptually an AST-level operations, - // and shouldn't be done here in the IR at all. - // - RefPtr<ProgramLayout> newProgramLayout = specializeProgramLayout( - targetReq, - programLayout, - SubstitutionSet(entryPointRequest->globalGenericSubst)); - - // TODO: we need to register the (IR-level) arguments of the global generic parameters as the - // substitutions for the generic parameters in the original IR. - - // applyGlobalGenericParamSubsitution(...); - - - state->newProgramLayout = newProgramLayout; - - // Next, we want to optimize lookup for layout infromation - // associated with global declarations, so that we can - // look things up based on the IR values (using mangled names) - auto globalStructLayout = getGlobalStructLayout(newProgramLayout); - for (auto globalVarLayout : globalStructLayout->fields) - { - auto mangledName = getMangledName(globalVarLayout->varDecl); - context->globalVarLayouts.AddIfNotExists(mangledName, globalVarLayout); - } - - // for now, clone all unreferenced witness tables - for (auto sym :context->getSymbols()) - { - if (sym.Value->irGlobalValue->op == kIROp_WitnessTable) - cloneGlobalValue(context, (IRWitnessTable*)sym.Value->irGlobalValue); - } - return state; - } - - void destroyIRSpecializationState(IRSpecializationState* state) - { - delete state; - } - - IRModule* getIRModule(IRSpecializationState* state) - { - return state->irModule; - } - - void specializeIRForEntryPoint( - IRSpecializationState* state, - EntryPointRequest* entryPointRequest, - ExtensionUsageTracker* extensionUsageTracker) - { - auto target = state->target; - - auto compileRequest = entryPointRequest->compileRequest; - auto session = compileRequest->mSession; - auto translationUnit = entryPointRequest->getTranslationUnit(); - auto originalIRModule = translationUnit->irModule; - if (!originalIRModule) - { - // We should already have emitted IR for the original - // translation unit, and it we don't have it, then - // we are now in trouble. - return; - } - - auto context = state->getContext(); - auto newProgramLayout = state->newProgramLayout; - - auto entryPointLayout = findEntryPointLayout(newProgramLayout, entryPointRequest); - - - // Next, we make sure to clone the global value for - // the entry point function itself, and rely on - // this step to recursively copy over anything else - // it might reference. - auto irEntryPoint = specializeIRForEntryPoint(context, entryPointRequest, entryPointLayout); - - // HACK: right now the bindings for global generic parameters are coming in - // as part of the original IR module, and we need to make sure these get - // copied over, even if they aren't referenced. - // - for(auto inst : originalIRModule->getGlobalInsts()) - { - auto bindInst = as<IRBindGlobalGenericParam>(inst); - if(!bindInst) - continue; - - cloneValue(context, bindInst); - } - - - // TODO: *technically* we should consider the case where - // we have global variables with initializers, since - // these should get run whether or not the entry point - // references them. - - // For GLSL only, we will need to perform "legalization" of - // the entry point and any entry-point parameters. - switch (target) - { - case CodeGenTarget::GLSL: - { - legalizeEntryPointForGLSL( - session, - context->getModule(), - irEntryPoint, - entryPointLayout, - &compileRequest->mSink, - extensionUsageTracker); - } - break; - - default: - break; - } - } - - struct IRGenericSpecContext : IRSpecContextBase - { - IRSpecContextBase* parent = nullptr; - - IRSharedSpecContext* getShared() { return shared; } - - // Override the "maybe clone" logic so that we always clone - virtual IRInst* maybeCloneValue(IRInst* originalVal) override; - }; - - IRInst* IRGenericSpecContext::maybeCloneValue(IRInst* originalVal) - { - if (parent) - { - return parent->maybeCloneValue(originalVal); - } - else - { - return originalVal; - } - } - - // See the work list for the generic spec context with - // every relevant instruction from `inst` through its - // descendents. - void addToSpecializationWorkListRec( - IRSharedGenericSpecContext* sharedContext, - IRInst* inst) - { - if(auto genericInst = as<IRGeneric>(inst)) - { - // We do *not* consider generics, or instructions nested under them. - return; - } - else - { - for(auto child : inst->getChildren()) - { - addToSpecializationWorkListRec(sharedContext, child); - } - - // Default case: consider this instruction for specialization. - sharedContext->addToWorkList(inst); - } - } - - IRInst* specializeGeneric( - IRSharedGenericSpecContext* sharedContext, - IRSpecContextBase* parentContext, - IRGeneric* genericVal, - IRSpecialize* specializeInst) - { - // First, we want to see if an existing specialization - // has already been made. To do that we will construct a key - // for lookup in the generic specialization context. - // - IRGenericSpecKey specializationKey(specializeInst); - { - IRInst* specializedValue = nullptr; - if(sharedContext->specializations.TryGetValue(specializationKey, specializedValue)) - return specializedValue; - } - - // If we get to this point, then we need to construct a - // new IR value to represent the result of specialization. - - // We need to establish a new mapping from inst->inst to - // handle the specialization, because we don't want the - // clones we register in this pass to cause confusion - // in later steps that might clone the same code. - - IRSpecEnv env; - env.parent = &sharedContext->globalEnv; - if (parentContext) - { - env.parent = parentContext->getEnv(); - } - - // The result of specialization should be inserted - // into the global scope, at the same location as - // the original generic. - IRBuilder builderStorage; - IRBuilder* builder = &builderStorage; - builder->sharedBuilder = &sharedContext->sharedBuilderStorage; - builder->setInsertBefore(genericVal); - - IRGenericSpecContext context; - context.shared = sharedContext; - context.parent = parentContext; - context.builder = builder; - context.env = &env; - - // Register the arguments of the `specialize` instruction to be used - // as the "cloned" value for each of the parameters of the generic. - // - UInt argCounter = 0; - for (auto param = genericVal->getFirstParam(); param; param = param->getNextParam()) - { - UInt argIndex = argCounter++; - SLANG_ASSERT(argIndex < specializeInst->getArgCount()); - - IRInst* arg = specializeInst->getArg(argIndex); - - registerClonedValue(&context, arg, param); - } - - // Okay, now we want to run through the body of the generic - // and clone stuff into the parent scope (which had - // better be the global scope). - for (auto bb : genericVal->getBlocks()) - { - // We expect a generic to only ever contain a single block. - SLANG_ASSERT(bb == genericVal->getFirstBlock()); - - // Iterate over the non-parameter ("ordinary") instructions. - for (auto ii : bb->getOrdinaryInsts()) - { - // The last block of the generic is expected to end with - // a `return` instruction for the specialized value that - // comes out of the abstraction. - // - // We thus use that cloned value as the result of the - // specialization step. - if (auto returnValInst = as<IRReturnVal>(ii)) - { - auto clonedResult = cloneValue(&context, returnValInst->getVal()); - - sharedContext->specializations.Add(specializationKey, clonedResult); - - return clonedResult; - } - - // Otherwise, clone the instruction into the global scope - IRInst* clonedInst = cloneInst(&context, context.builder, ii); - - // Now that we've cloned the instruction to a location outside - // of a generic, we should consider whether it can now be specialized. - addToSpecializationWorkListRec(sharedContext, clonedInst); - } - } - - // If we reach this point, something went wrong, because we - // never encountered a `return` inside the body of the generic. - SLANG_UNEXPECTED("no return from generic"); - UNREACHABLE_RETURN(nullptr); - } - - // Find the value in the given witness table that - // satisfies the given requirement (or return - // null if not found). - IRInst* findWitnessVal( - IRWitnessTable* witnessTable, - IRInst* requirementKey) - { - // For now we will do a dumb linear search - for( auto entry : witnessTable->getEntries() ) - { - // If the keys matched, then we use the value from this entry. - if (requirementKey == entry->requirementKey.get()) - { - auto satisfyingVal = entry->satisfyingVal.get(); - return satisfyingVal; - } - } - - // No matching entry found. - return nullptr; - } - - static bool canSpecializeGeneric( - IRGeneric* generic) - { - IRGeneric* g = generic; - for(;;) - { - auto val = findGenericReturnVal(g); - if(!val) - return false; - - if (auto nestedGeneric = as<IRGeneric>(val)) - { - // The outer generic returns an *inner* generic - // (so that multiple calls to `specialize` are - // needed to resolve it). We should look at - // what the nested generic returns to figure - // out whether specialization is allowed. - g = nestedGeneric; - continue; - } - - // We've found the leaf value that will be produced after - // all of the specialization is done. Now we want to know - // if that is a value suitable for actually specializing - // - if (isDefinition(val)) - return true; - return false; - } - } - - // Add any instruction that uses `inst` to the work list, - // so that it can be evaluated (or re-evaluated) for specialization. - void addUsesToWorkList( - IRSharedGenericSpecContext* sharedContext, - IRInst* inst) - { - for(auto u = inst->firstUse; u; u = u->nextUse) - { - sharedContext->addToWorkList(u->getUser()); - } - } - - void specializeGenericsForInst( - IRSharedGenericSpecContext* sharedContext, - IRInst* inst) - { - switch(inst->op) - { - default: - // The default behavior is to do nothing. - // An instruction is specialize-able once its operands - // are specialized, and after that it is also safe - // to consider the instruction specialized. - break; - - case kIROp_Specialize: - { - // We have a `specialize` instruction, so lets see - // whether we have an opportunity to perform the - // specialization here and now. - IRSpecialize* specInst = cast<IRSpecialize>(inst); - - // Look at the base of the `specialize`, and see if - // it directly names a generic, so that we can apply - // specialization here and now. - auto baseVal = specInst->getBase(); - if(auto genericVal = as<IRGeneric>(baseVal)) - { - if (canSpecializeGeneric(genericVal)) - { - // Okay, we have a candidate for specialization here. - // - // We will apply the specialization logic to the body of the generic, - // which will yield, e.g., a specialized `IRFunc`. - // - auto specializedVal = specializeGeneric(sharedContext, nullptr, genericVal, specInst); - // - // Then we will replace the use sites for the `specialize` - // instruction with uses of the specialized value. - // - addUsesToWorkList(sharedContext, specInst); - specInst->replaceUsesWith(specializedVal); - specInst->removeAndDeallocate(); - } - } - } - break; - - case kIROp_lookup_interface_method: - { - // We have a `lookup_interface_method` instruction, - // so let's see whether it is a lookup in a known - // witness table. - IRLookupWitnessMethod* lookupInst = cast<IRLookupWitnessMethod>(inst); - - // We only want to deal with the case where the witness-table - // argument points to a concrete global table (and not, e.g., a - // `specialize` instruction that will yield a table) - auto witnessTable = as<IRWitnessTable>(lookupInst->witnessTable.get()); - if(!witnessTable) - break; - - // Use the witness table to look up the value that - // satisfies the requirement. - auto requirementKey = lookupInst->getRequirementKey(); - auto satisfyingVal = findWitnessVal(witnessTable, requirementKey); - // We expect to always find something, but lets just - // be careful here. - if(!satisfyingVal) - break; - - // If we get through all of the above checks, then we - // have a (more) concrete method that implements the interface, - // and so we should dispatch to that directly, rather than - // use the `lookup_interface_method` instruction. - addUsesToWorkList(sharedContext, lookupInst); - lookupInst->replaceUsesWith(satisfyingVal); - lookupInst->removeAndDeallocate(); - } - break; - } - } - - static bool isInstSpecialized( - IRSharedGenericSpecContext* sharedContext, - IRInst* inst) - { - // If an instruction is still on our work list, then - // it isn't specialized, and conversely we say that - // if it *isn't* on the work list, it must be specialized. - // - // Note: if we end up with bugs in this logic, we could - // maintain an explicit set of specialized insts instead. - // - return !sharedContext->workListSet.Contains(inst); - } - - static bool canSpecializeInst( - IRSharedGenericSpecContext* sharedContext, - IRInst* inst) - { - // We can specialize an instruction once all its - // operands are specialized. - - UInt operandCount = inst->getOperandCount(); - for(UInt ii = 0; ii < operandCount; ++ii) - { - IRInst* operand = inst->getOperand(ii); - if(!isInstSpecialized(sharedContext, operand)) - return false; - } - return true; - } - - // Go through the code in the module and try to identify - // calls to generic functions where the generic arguments - // are known, and specialize the callee based on those - // known values. - void specializeGenerics( - IRModule* module, - CodeGenTarget target) - { - IRSharedGenericSpecContext sharedContextStorage; - auto sharedContext = &sharedContextStorage; - - initializeSharedSpecContext( - sharedContext, - module->session, - module, - module, - target); - - auto moduleInst = module->getModuleInst(); - - // First things first, let's deal with any bindings for global generic parameters. - for(auto inst : moduleInst->getChildren()) - { - auto bindInst = as<IRBindGlobalGenericParam>(inst); - if(!bindInst) - continue; - - // HACK: Our current front-end emit logic can end up emitting multiple - // `bindGlobalGeneric` instructions for the same parameter. This is - // a buggy behavior, but a real fix would require refactoring the way - // global generic arguments are specified today. - // - // For now we will do a sanity check to detect parameters that - // have already been specialized. - if( !as<IRGlobalGenericParam>(bindInst->getOperand(0)) ) - { - // parameter operand is no longer a parameter, so it - // seems things must have been specialized already. - continue; - } - - auto param = bindInst->getParam(); - auto val = bindInst->getVal(); - - param->replaceUsesWith(val); - } - { - // Now we will do a second pass to clean up the - // generic parameters and their bindings. - IRInst* next = nullptr; - for(auto inst = moduleInst->getFirstChild(); inst; inst = next) - { - next = inst->getNextInst(); - - switch(inst->op) - { - default: - break; - - case kIROp_GlobalGenericParam: - case kIROp_BindGlobalGenericParam: - // A "bind" instruction should have no uses in the - // first place, and all the global generic parameters - // should have had their uses replaced. - SLANG_ASSERT(!inst->firstUse); - inst->removeAndDeallocate(); - break; - } - } - } - - // Our goal here is to find `specialize` instructions that - // can be replaced with references to, e.g., a suitably - // specialized function, and to resolve any `lookup_interface_method` - // instructions to the concrete value fetched from a witness - // table. - // - // We need to be careful of a few things: - // - // * It would not in general make sense to consider specialize-able - // instructions under an `IRGeneric`, since that could mean "specialziing" - // code to parameter values that are still unknown. - // - // * We *also* need to be careful not to specialize something when one - // or more of its inputs is also a `specialize` or `lookup_interface_method` - // instruction, because then we'd be propagating through non-concrete - // values. - // - // The approach we use here is to build a work list of instructions - // that *can* become fully specialized, but aren't yet. Any - // instruction on the work list will be considered to be "unspecialized" - // and any instruction not on the work list is considered specialized. - // - // We will start by recursively walking all the instructions to add - // the appropriate ones to our work list: - // - addToSpecializationWorkListRec(sharedContext, moduleInst); - - // Now we are going to repeatedly walk our work list, and filter - // it to create a new work list. - List<IRInst*> workListCopy; - for(;;) - { - // Swap out the work list on the context so we can - // process it here without worrying about concurrent - // modifications. - workListCopy.Clear(); - workListCopy.SwapWith(sharedContext->workList); - - if(workListCopy.Count() == 0) - break; - - for(auto inst : workListCopy) - { - // We need to check whether it is possible to specialize - // the instruction yet (it might not be because its - // operands haven't been specialized) - if(!canSpecializeInst(sharedContext, inst)) - { - // Put it back on the fresh work list, so that - // we can re-consider it in another iteration. - sharedContext->workList.Add(inst); - } - else - { - // Okay, perform any specialization step on this - // instruction that makes sense (which might be - // doing nothing). - specializeGenericsForInst(sharedContext, inst); - - // Remove the instruction from consideration. - sharedContext->workListSet.Remove(inst); - } - } - } - - // Once the work list has gone dry, we should have the invariant - // that there are no `specialize` instructions inside of non-generic - // functions that in turn reference a generic function, *except* - // in the case where that generic is for a builtin function, in - // which case we wouldn't want to specialize it anyway. - } - - void applyGlobalGenericParamSubstitution( - IRSpecContext* /*context*/) - { - // TODO: we need to figure out how to apply this - } - - void markConstExpr( IRBuilder* builder, IRInst* irValue) |
