summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/core/core.natvis30
-rw-r--r--source/slang/ir-entry-point-uniforms.cpp2
-rw-r--r--source/slang/ir-insts.h13
-rw-r--r--source/slang/ir-specialize.cpp473
-rw-r--r--source/slang/ir.cpp39
-rw-r--r--source/slang/ir.h3
-rw-r--r--tests/compute/interface-shader-param-in-struct.slang127
-rw-r--r--tests/compute/interface-shader-param-in-struct.slang.expected.txt4
8 files changed, 633 insertions, 58 deletions
diff --git a/source/core/core.natvis b/source/core/core.natvis
index 91fdbb49b..9d3f52839 100644
--- a/source/core/core.natvis
+++ b/source/core/core.natvis
@@ -3,41 +3,41 @@
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Slang::String">
- <DisplayString>{((char*) (buffer.pointer+1)),s}</DisplayString>
- <StringView>((char*) (buffer.pointer+1)),s</StringView>
+ <DisplayString>{((char*) (m_buffer.pointer+1)),s}</DisplayString>
+ <StringView>((char*) (m_buffer.pointer+1)),s</StringView>
</Type>
<Type Name="Slang::ArrayView&lt;*&gt;">
- <DisplayString>{{ size={_count} }}</DisplayString>
+ <DisplayString>{{ size={m_count} }}</DisplayString>
<Expand>
- <Item Name="[size]">_count</Item>
+ <Item Name="[size]">m_count</Item>
<ArrayItems>
- <Size>_count</Size>
- <ValuePointer>_buffer</ValuePointer>
+ <Size>m_count</Size>
+ <ValuePointer>m_buffer</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="Slang::List&lt;*&gt;">
- <DisplayString>{{ size={_count} }}</DisplayString>
+ <DisplayString>{{ size={m_count} }}</DisplayString>
<Expand>
- <Item Name="[size]">_count</Item>
- <Item Name="[capacity]">bufferSize</Item>
+ <Item Name="[size]">m_count</Item>
+ <Item Name="[capacity]">m_capacity</Item>
<ArrayItems>
- <Size>_count</Size>
- <ValuePointer>buffer</ValuePointer>
+ <Size>m_count</Size>
+ <ValuePointer>m_buffer</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="Slang::Array&lt;*,*&gt;">
- <DisplayString>{{ size={_count} }}</DisplayString>
+ <DisplayString>{{ size={m_count} }}</DisplayString>
<Expand>
- <Item Name="[size]">_count</Item>
+ <Item Name="[size]">m_count</Item>
<ArrayItems>
- <Size>_count</Size>
- <ValuePointer>_buffer</ValuePointer>
+ <Size>m_count</Size>
+ <ValuePointer>m_buffer</ValuePointer>
</ArrayItems>
</Expand>
</Type>
diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp
index 6ee05611d..5c7cdb5b4 100644
--- a/source/slang/ir-entry-point-uniforms.cpp
+++ b/source/slang/ir-entry-point-uniforms.cpp
@@ -347,6 +347,8 @@ struct MoveEntryPointUniformParametersToGlobalScope
//
param->removeAndDeallocate();
}
+
+ fixUpFuncType(func);
}
// We need to be able to determine if a parameter is logically
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 5970e2b36..0dc4dbbac 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -919,6 +919,19 @@ struct IRBuilder
UInt slotArgCount,
IRInst* const* slotArgs);
+ IRInst* emitWrapExistential(
+ IRType* type,
+ IRInst* value,
+ UInt slotArgCount,
+ IRUse const* slotArgs)
+ {
+ List<IRInst*> slotArgVals;
+ for(UInt ii = 0; ii < slotArgCount; ++ii)
+ slotArgVals.add(slotArgs[ii].get());
+
+ return emitWrapExistential(type, value, slotArgCount, slotArgVals.getBuffer());
+ }
+
IRUndefined* emitUndefined(IRType* type);
diff --git a/source/slang/ir-specialize.cpp b/source/slang/ir-specialize.cpp
index 57d14c11a..b57d2b58f 100644
--- a/source/slang/ir-specialize.cpp
+++ b/source/slang/ir-specialize.cpp
@@ -98,6 +98,9 @@ struct SpecializationContext
// whether generic, existential, etc.
//
List<IRInst*> workList;
+ HashSet<IRInst*> workListSet;
+
+ HashSet<IRInst*> cleanInsts;
void addToWorkList(
IRInst* inst)
@@ -112,7 +115,14 @@ struct SpecializationContext
return;
}
+ if(workListSet.Contains(inst))
+ return;
+
workList.add(inst);
+ workListSet.Add(inst);
+ cleanInsts.Remove(inst);
+
+ addUsersToWorkList(inst);
}
// When a transformation makes a change to an instruction,
@@ -367,6 +377,7 @@ struct SpecializationContext
case kIROp_Specialize:
case kIROp_lookup_interface_method:
case kIROp_ExtractExistentialType:
+ case kIROp_BindExistentialsType:
break;
}
}
@@ -431,6 +442,13 @@ struct SpecializationContext
maybeSpecializeLoad(as<IRLoad>(inst));
break;
+ case kIROp_FieldExtract:
+ maybeSpecializeFieldExtract(as<IRFieldExtract>(inst));
+ break;
+ case kIROp_FieldAddress:
+ maybeSpecializeFieldAddress(as<IRFieldAddress>(inst));
+ break;
+
case kIROp_BindExistentialsType:
maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst));
break;
@@ -566,12 +584,18 @@ struct SpecializationContext
//
addToWorkList(module->getModuleInst());
+ while(workList.getCount() != 0)
+ {
+
// We will then iterate until our work list goes dry.
//
while(workList.getCount() != 0)
{
IRInst* inst = workList.getLast();
+
workList.removeLast();
+ workListSet.Remove(inst);
+ cleanInsts.Add(inst);
// For each instruction we process, we want to perform
// a few steps.
@@ -610,6 +634,10 @@ struct SpecializationContext
}
}
+ addDirtyInstsToWorkListRec(module->getModuleInst());
+
+ }
+
// 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 type/function, *except*
@@ -617,6 +645,19 @@ struct SpecializationContext
// which case we wouldn't want to specialize it anyway.
}
+ void addDirtyInstsToWorkListRec(IRInst* inst)
+ {
+ if( !cleanInsts.Contains(inst) )
+ {
+ addToWorkList(inst);
+ }
+
+ for(auto child = inst->getLastChild(); child; child = child->getPrevInst())
+ {
+ addDirtyInstsToWorkListRec(child);
+ }
+ }
+
// Given a `call` instruction in the IR, we need to detect the case
// where the callee has some interface-type parameter(s) and at the
// call site it is statically clear what concrete type(s) the arguments
@@ -719,6 +760,19 @@ struct SpecializationContext
auto witnessTable = makeExistential->getWitnessTable();
key.vals.add(witnessTable);
}
+ else if( auto wrapExistential = as<IRWrapExistential>(arg) )
+ {
+ auto val = wrapExistential->getWrappedValue();
+ auto valType = val->getFullType();
+ key.vals.add(valType);
+
+ UInt slotOperandCount = wrapExistential->getSlotOperandCount();
+ for( UInt ii = 0; ii < slotOperandCount; ++ii )
+ {
+ auto slotOperand = wrapExistential->getSlotOperand(ii);
+ key.vals.add(slotOperand);
+ }
+ }
else
{
SLANG_UNEXPECTED("missing case for existential argument");
@@ -770,6 +824,11 @@ struct SpecializationContext
auto val = makeExistential->getWrappedValue();
newArgs.add(val);
}
+ else if( auto wrapExistential = as<IRWrapExistential>(arg) )
+ {
+ auto val = wrapExistential->getWrappedValue();
+ newArgs.add(val);
+ }
else
{
SLANG_UNEXPECTED("missing case for existential argument");
@@ -838,6 +897,13 @@ struct SpecializationContext
if(as<IRMakeExistential>(inst))
return true;
+ // A `wrapExistential(v, T0,w0, T1, w1, ...)` instruction
+ // is just a generalization of `makeExistential`, so it
+ // can apply in the same cases.
+ //
+ if(as<IRWrapExistential>(inst))
+ return true;
+
// If we start to specialize functions that take arrays
// of existentials as input, we will need a strategy to
// determine arguments suitable for use in specializing
@@ -904,7 +970,66 @@ struct SpecializationContext
//
IRInst* replacementVal = nullptr;
- if( !isExistentialType(oldParam->getDataType()) )
+ // The trickier case is when we have an existential-type
+ // parameter, because we need to extract out the concrete
+ // type that is coming from the call site.
+ //
+ if( auto oldMakeExistential = as<IRMakeExistential>(arg) )
+ {
+ // In this case, the `arg` is `makeExistential(val, witnessTable)`
+ // and we know that the specialized call site will just be
+ // passing in `val`.
+ //
+ auto val = oldMakeExistential->getWrappedValue();
+ auto witnessTable = oldMakeExistential->getWitnessTable();
+
+ // Our specialized function needs to take a parameter with the
+ // same type as `val`, to match the call site(s) that will be
+ // created.
+ //
+ auto valType = val->getFullType();
+ auto newParam = builder->createParam(valType);
+ newParams.add(newParam);
+
+ // Within the body of the function we cannot just use `val`
+ // directly, because the existing code expects an existential
+ // value, including its witness table.
+ //
+ // Therefore we will create a `makeExistential(newParam, witnessTable)`
+ // in the body of the new function and use *that* as the replacement
+ // value for the original parameter (since it will have the
+ // correct existential type, and stores the right witness table).
+ //
+ auto newMakeExistential = builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable);
+ newBodyInsts.add(newMakeExistential);
+ replacementVal = newMakeExistential;
+ }
+ else if( auto oldWrapExistential = as<IRWrapExistential>(arg) )
+ {
+ auto val = oldWrapExistential->getWrappedValue();
+ auto valType = val->getFullType();
+
+ auto newParam = builder->createParam(valType);
+ newParams.add(newParam);
+
+ // Within the body of the function we cannot just use `val`
+ // directly, because the existing code expects an existential
+ // value, including its witness table.
+ //
+ // Therefore we will create a `makeExistential(newParam, witnessTable)`
+ // in the body of the new function and use *that* as the replacement
+ // value for the original parameter (since it will have the
+ // correct existential type, and stores the right witness table).
+ //
+ auto newWrapExistential = builder->emitWrapExistential(
+ oldParam->getFullType(),
+ newParam,
+ oldWrapExistential->getSlotOperandCount(),
+ oldWrapExistential->getSlotOperands());
+ newBodyInsts.add(newWrapExistential);
+ replacementVal = newWrapExistential;
+ }
+ else
{
// For parameters that don't have an existential type,
// there is nothing interesting to do. The new function
@@ -915,47 +1040,6 @@ struct SpecializationContext
newParams.add(newParam);
replacementVal = newParam;
}
- else
- {
- // The trickier case is when we have an existential-type
- // parameter, because we need to extract out the concrete
- // type that is coming from the call site.
- //
- if( auto oldMakeExistential = as<IRMakeExistential>(arg) )
- {
- // In this case, the `arg` is `makeExistential(val, witnessTable)`
- // and we know that the specialized call site will just be
- // passing in `val`.
- //
- auto val = oldMakeExistential->getWrappedValue();
- auto witnessTable = oldMakeExistential->getWitnessTable();
-
- // Our specialized function needs to take a parameter with the
- // same type as `val`, to match the call site(s) that will be
- // created.
- //
- auto valType = val->getFullType();
- auto newParam = builder->createParam(valType);
- newParams.add(newParam);
-
- // Within the body of the function we cannot just use `val`
- // directly, because the existing code expects an existential
- // value, including its witness table.
- //
- // Therefore we will create a `makeExistential(newParam, witnessTable)`
- // in the body of the new function and use *that* as the replacement
- // value for the original parameter (since it will have the
- // correct existential type, and stores the right witness table).
- //
- auto newMakeExistential = builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable);
- newBodyInsts.add(newMakeExistential);
- replacementVal = newMakeExistential;
- }
- else
- {
- SLANG_UNEXPECTED("missing case for existential argument");
- }
- }
// Whatever replacement value was constructed, we need to
// register it as the replacement for the original parameter.
@@ -1207,6 +1291,246 @@ struct SpecializationContext
}
}
+ UInt calcExistentialBoxSlotCount(IRType* type)
+ {
+ top:
+ if( as<IRExistentialBoxType>(type) )
+ {
+ return 2;
+ }
+ else if( auto ptrType = as<IRPtrTypeBase>(type) )
+ {
+ type = ptrType->getValueType();
+ goto top;
+ }
+ else if( auto ptrLikeType = as<IRPointerLikeType>(type) )
+ {
+ type = ptrLikeType->getElementType();
+ goto top;
+ }
+ else if( auto structType = as<IRStructType>(type) )
+ {
+ UInt count = 0;
+ for( auto field : structType->getFields() )
+ {
+ count += calcExistentialBoxSlotCount(field->getFieldType());
+ }
+ return count;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ void maybeSpecializeFieldExtract(IRFieldExtract* inst)
+ {
+ auto baseArg = inst->getBase();
+ auto fieldKey = inst->getField();
+
+ if( auto wrapInst = as<IRWrapExistential>(baseArg) )
+ {
+ // We have `getField(wrapExistential(val, ...), fieldKey)`
+ //
+ auto val = wrapInst->getWrappedValue();
+
+ // We know what type we are expected to produce.
+ //
+ auto resultType = inst->getFullType();
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilderStorage;
+ builder.setInsertBefore(inst);
+
+ // We'd *like* to replace this instruction with
+ // `wrapExistential(getField(val, fieldKey), ...)` instead, since that
+ // will enable subsequent specializations.
+ //
+ // To do that, we need to figure out:
+ //
+ // 1. What type that inner `getField` would return (what
+ // is the type of the `fieldKey` field in `val`?)
+ //
+ // 2. Which of the existential slot operands in `...` there
+ // actually apply to the given field.
+ //
+
+ // To determine these things, we need the type of
+ // `val` to be a structure type so that we can look
+ // up the field corresponding to `fieldKey`.
+ //
+ auto valType = val->getDataType();
+ auto valStructType = as<IRStructType>(valType);
+ if(!valStructType)
+ return;
+
+ UInt slotOperandOffset = 0;
+
+ IRStructField* foundField = nullptr;
+ for( auto valField : valStructType->getFields() )
+ {
+ if( valField->getKey() == fieldKey )
+ {
+ foundField = valField;
+ break;
+ }
+
+ slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType());
+ }
+
+ if(!foundField)
+ return;
+
+ auto foundFieldType = foundField->getFieldType();
+
+ List<IRInst*> slotOperands;
+ UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType);
+
+ for( UInt ii = 0; ii < slotOperandCount; ++ii )
+ {
+ slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii));
+ }
+
+ auto newGetField = builder.emitFieldExtract(
+ foundFieldType,
+ val,
+ fieldKey);
+
+ auto newWrapExistentialInst = builder.emitWrapExistential(
+ resultType,
+ newGetField,
+ slotOperandCount,
+ slotOperands.getBuffer());
+
+ addUsersToWorkList(inst);
+ inst->replaceUsesWith(newWrapExistentialInst);
+ inst->removeAndDeallocate();
+ }
+ }
+
+
+ void maybeSpecializeFieldAddress(IRFieldAddress* inst)
+ {
+ auto baseArg = inst->getBase();
+ auto fieldKey = inst->getField();
+
+ if( auto wrapInst = as<IRWrapExistential>(baseArg) )
+ {
+ // We have `getFieldAddr(wrapExistential(val, ...), fieldKey)`
+ //
+ auto val = wrapInst->getWrappedValue();
+
+ // We know what type we are expected to produce.
+ //
+ auto resultType = inst->getFullType();
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilderStorage;
+ builder.setInsertBefore(inst);
+
+ // We'd *like* to replace this instruction with
+ // `wrapExistential(getFieldAddr(val, fieldKey), ...)` instead, since that
+ // will enable subsequent specializations.
+ //
+ // To do that, we need to figure out:
+ //
+ // 1. What type that inner `getFieldAddr` would return (what
+ // is the type of the `fieldKey` field in `val`?)
+ //
+ // 2. Which of the existential slot operands in `...` there
+ // actually apply to the given field.
+ //
+
+ // To determine these things, we need the type of
+ // `val` to be a (pointer to a) structure type so that we can look
+ // up the field corresponding to `fieldKey`.
+ //
+ auto valType = tryGetPointedToType(&builder, val->getDataType());
+ if(!valType)
+ return;
+
+ auto valStructType = as<IRStructType>(valType);
+ if(!valStructType)
+ return;
+
+ UInt slotOperandOffset = 0;
+
+ IRStructField* foundField = nullptr;
+ for( auto valField : valStructType->getFields() )
+ {
+ if( valField->getKey() == fieldKey )
+ {
+ foundField = valField;
+ break;
+ }
+
+ slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType());
+ }
+
+ if(!foundField)
+ return;
+
+ auto foundFieldType = foundField->getFieldType();
+
+ List<IRInst*> slotOperands;
+ UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType);
+
+ for( UInt ii = 0; ii < slotOperandCount; ++ii )
+ {
+ slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii));
+ }
+
+ auto newGetFieldAddr = builder.emitFieldAddress(
+ builder.getPtrType(foundFieldType),
+ val,
+ fieldKey);
+
+ auto newWrapExistentialInst = builder.emitWrapExistential(
+ resultType,
+ newGetFieldAddr,
+ slotOperandCount,
+ slotOperands.getBuffer());
+
+ addUsersToWorkList(inst);
+ inst->replaceUsesWith(newWrapExistentialInst);
+ inst->removeAndDeallocate();
+ }
+ }
+
+ UInt calcExistentialTypeParamSlotCount(IRType* type)
+ {
+ top:
+ if( as<IRInterfaceType>(type) )
+ {
+ return 2;
+ }
+ else if( auto ptrType = as<IRPtrTypeBase>(type) )
+ {
+ type = ptrType->getValueType();
+ goto top;
+ }
+ else if( auto ptrLikeType = as<IRPointerLikeType>(type) )
+ {
+ type = ptrLikeType->getElementType();
+ goto top;
+ }
+ else if( auto structType = as<IRStructType>(type) )
+ {
+ UInt count = 0;
+ for( auto field : structType->getFields() )
+ {
+ count += calcExistentialTypeParamSlotCount(field->getFieldType());
+ }
+ return count;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ Dictionary<IRSimpleSpecializationKey, IRStructType*> existentialSpecializedStructs;
+
void maybeSpecializeBindExistentialsType(IRBindExistentialsType* type)
{
auto baseType = type->getBaseType();
@@ -1253,17 +1577,82 @@ struct SpecializationContext
baseElementType,
slotOperandCount,
type->getExistentialArgs());
+ addToWorkList(wrappedElementType);
auto newPtrLikeType = builder.getType(
basePtrLikeType->op,
1,
&wrappedElementType);
+ addToWorkList(newPtrLikeType);
addUsersToWorkList(type);
type->replaceUsesWith(newPtrLikeType);
type->removeAndDeallocate();
return;
}
+ else if( auto baseStructType = as<IRStructType>(baseType) )
+ {
+ // In order to bind a `struct` type we will generate
+ // a new specialized `struct` type on demand and then
+ // cache and re-use it.
+ //
+ // We don't want to start specializing here unless
+ // all the operand types (and witness tables) we
+ // will be specializing to are themselves fully
+ // specialized, so that we can be sure that we
+ // have a unique type.
+ //
+ if( !areAllOperandsFullySpecialized(type) )
+ return;
+
+ // Now we we check to see if we've already created
+ // a specialized struct type or not.
+ //
+ IRSimpleSpecializationKey key;
+ key.vals.add(baseStructType);
+ for( UInt ii = 0; ii < slotOperandCount; ++ii )
+ {
+ key.vals.add(type->getExistentialArg(ii));
+ }
+
+ IRStructType* newStructType = nullptr;
+ if( !existentialSpecializedStructs.TryGetValue(key, newStructType) )
+ {
+ builder.setInsertBefore(baseStructType);
+ newStructType = builder.createStructType();
+
+ auto fieldSlotArgs = type->getExistentialArgs();
+
+ for( auto oldField : baseStructType->getFields() )
+ {
+ // TODO: we need to figure out which of the specialization arguments
+ // apply to this field...
+
+ auto oldFieldType = oldField->getFieldType();
+ auto fieldSlotArgCount = calcExistentialTypeParamSlotCount(oldFieldType);
+
+ auto newFieldType = builder.getBindExistentialsType(
+ oldFieldType,
+ fieldSlotArgCount,
+ fieldSlotArgs);
+
+ addToWorkList(newFieldType);
+
+ fieldSlotArgs += fieldSlotArgCount;
+
+ builder.createStructField(newStructType, oldField->getKey(), newFieldType);
+ }
+
+ existentialSpecializedStructs.Add(key, newStructType);
+ addToWorkList(newStructType);
+ }
+
+ addUsersToWorkList(type);
+ type->replaceUsesWith(newStructType);
+ type->removeAndDeallocate();
+ return;
+
+ }
}
// The handling of specialization for global generic type
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 150a5d544..5015fda9d 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -636,6 +636,33 @@ namespace Slang
block->insertAtEnd(this);
}
+ void fixUpFuncType(IRFunc* func)
+ {
+ SLANG_ASSERT(func);
+
+ auto irModule = func->getModule();
+ SLANG_ASSERT(irModule);
+
+ SharedIRBuilder sharedBuilder;
+ sharedBuilder.module = irModule;
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilder;
+
+ builder.setInsertBefore(func);
+
+ List<IRType*> paramTypes;
+ for(auto param : func->getParams())
+ {
+ paramTypes.add(param->getFullType());
+ }
+
+ auto resultType = func->getResultType();
+
+ auto funcType = builder.getFuncType(paramTypes, resultType);
+ builder.setDataType(func, funcType);
+ }
+
//
bool isTerminatorInst(IROp op)
@@ -1828,6 +1855,9 @@ namespace Slang
UInt slotArgCount,
IRInst* const* slotArgs)
{
+ if(slotArgCount == 0)
+ return (IRType*) baseType;
+
// If we are trying to bind an interface type, then
// we will go ahead and simplify the instruction
// away impmediately.
@@ -1859,6 +1889,9 @@ namespace Slang
UInt slotArgCount,
IRUse const* slotArgUses)
{
+ if(slotArgCount == 0)
+ return (IRType*) baseType;
+
List<IRInst*> slotArgs;
for( UInt ii = 0; ii < slotArgCount; ++ii )
{
@@ -2091,6 +2124,9 @@ namespace Slang
UInt slotArgCount,
IRInst* const* slotArgs)
{
+ if(slotArgCount == 0)
+ return value;
+
// If we are wrapping a single concrete value into
// an interface type, then this is really a `makeExistential`
//
@@ -3978,13 +4014,14 @@ namespace Slang
void IRInst::insertAfter(IRInst* other)
{
SLANG_ASSERT(other);
-
+ removeFromParent();
_insertAt(other, other->getNextInst(), other->getParent());
}
void IRInst::insertAtEnd(IRInst* newParent)
{
SLANG_ASSERT(newParent);
+ removeFromParent();
_insertAt(newParent->getLastDecorationOrChild(), nullptr, newParent);
}
diff --git a/source/slang/ir.h b/source/slang/ir.h
index d4497ec91..61cae5847 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -1075,6 +1075,9 @@ struct IRFunc : IRGlobalValueWithParams
IR_LEAF_ISA(Func)
};
+ /// Adjust the type of an IR function based on its parameter list.
+void fixUpFuncType(IRFunc* func);
+
// A generic is akin to a function, but is conceptually executed
// before runtime, to specialize the code nested within.
//
diff --git a/tests/compute/interface-shader-param-in-struct.slang b/tests/compute/interface-shader-param-in-struct.slang
new file mode 100644
index 000000000..2ffc70c36
--- /dev/null
+++ b/tests/compute/interface-shader-param-in-struct.slang
@@ -0,0 +1,127 @@
+// interface-shader-param-in-struct.slang
+
+// This test puts interface-type shader parameters
+// inside of structure types to make sure that works
+
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12
+//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
+
+// A lot of the setup is the same as for `interface-shader-param`,
+// so look there if you want the comments.
+
+interface IRandomNumberGenerator
+{
+ [mutating]
+ int randomInt();
+}
+
+interface IRandomNumberGenerationStrategy
+{
+ associatedtype Generator : IRandomNumberGenerator;
+ Generator makeGenerator(int seed);
+}
+
+interface IModifier
+{
+ int modify(int val);
+}
+
+int test(
+ int seed,
+ IRandomNumberGenerationStrategy inStrategy,
+ IModifier modifier)
+{
+ let strategy = inStrategy;
+ var generator = strategy.makeGenerator(seed);
+ let unused = generator.randomInt();
+ let val = generator.randomInt();
+ let modifiedVal = modifier.modify(val);
+ return modifiedVal;
+}
+
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<int> gOutputBuffer;
+
+cbuffer C
+{
+ IRandomNumberGenerationStrategy gStrategy;
+}
+
+struct Stuff
+{
+ IModifier modifier;
+ int extra;
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(
+//TEST_INPUT:cbuffer(data=[256]):dxbinding(0),glbinding(2)
+ uniform Stuff stuff,
+
+ uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ let tid = dispatchThreadID.x;
+
+ let inputVal : int = tid;
+ let outputVal = test(inputVal, gStrategy, stuff.modifier)
+ + stuff.extra*stuff.extra;
+
+ gOutputBuffer[tid] = outputVal;
+}
+
+// Okay, now we get to the part that is unique starting
+// in this test: we add data to the concrete types
+// that we will use as parameters.
+
+struct MyStrategy : IRandomNumberGenerationStrategy
+{
+ RWStructuredBuffer<int> globalSeeds;
+
+ struct Generator : IRandomNumberGenerator
+ {
+ int state;
+
+ [mutating]
+ int randomInt()
+ {
+ return state++;
+ }
+ }
+
+ Generator makeGenerator(int seed)
+ {
+ Generator generator = { globalSeeds[seed] };
+ return generator;
+ }
+}
+
+struct MyModifier : IModifier
+{
+ RWStructuredBuffer<int> localModifiers;
+
+ int modify(int val)
+ {
+ return val ^ localModifiers[val & 3];
+ }
+}
+
+//TEST_INPUT: globalExistentialType MyStrategy
+//TEST_INPUT: entryPointExistentialType MyModifier
+
+// The concrete types we plug in for `gStrategy` and `modifier`
+// have buffer resources in them, so we need to assign them
+// data. The registers/bindings for these parameters will
+// always come after all other shader parameters, and their
+// relative order will match the relative order of their
+// declarations in the global order that Slang uses for
+// assigning bindings (all globals before all entry point parameters).
+//
+// Here's the data for `gStrategy`:
+//
+//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1)
+//
+// Here's the data for `stuff.modifier`:
+//
+//TEST_INPUT:ubuffer(data=[16 32 64 128], stride=4):dxbinding(2),glbinding(3)
diff --git a/tests/compute/interface-shader-param-in-struct.slang.expected.txt b/tests/compute/interface-shader-param-in-struct.slang.expected.txt
new file mode 100644
index 000000000..e962124e1
--- /dev/null
+++ b/tests/compute/interface-shader-param-in-struct.slang.expected.txt
@@ -0,0 +1,4 @@
+10042
+10083
+10025
+10029