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