summaryrefslogtreecommitdiff
path: root/source/slang/ir.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/ir.cpp')
-rw-r--r--source/slang/ir.cpp3404
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)