Skip to content

Commit 7e2bc8e

Browse files
authored
Allow passing sized array to unsized array parameter. (shader-slang#4744)
1 parent c0bff66 commit 7e2bc8e

15 files changed

+278
-17
lines changed

docs/user-guide/02-conventional-features.md

+41-1
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,54 @@ In some cases the element count is then inferred from the initial value of a var
111111
int a[] = { 1, 2, 3 };
112112
```
113113

114-
In other cases, the result is a _runtime-sized_ array, where the actual element count will be determined later:
114+
In other cases, the result is a _unsized_ array, where the actual element count will be determined later:
115115

116116
```hlsl
117117
// the type of `b` is `int[]`
118118
void f( int b[] )
119119
{ ... }
120120
```
121121

122+
It is allowed to pass a sized array as argument to an unsized array parameter when calling a function.
123+
124+
Array types has a `getCount()` memeber function that returns the length of the array.
125+
126+
```hlsl
127+
int f( int b[] )
128+
{
129+
return b.getCount(); // Note: all arguments to `b` must be resolvable to sized arrays.
130+
}
131+
132+
void test()
133+
{
134+
int arr[3] = { 1, 2, 3 };
135+
int x = f(arr); // OK, passing sized array to unsized array parameter, x will be 3.
136+
}
137+
```
138+
139+
Please note that if a function calls `getCount()` method on an unsized array parameter, then all
140+
calls to that function must provide a sized array argument, otherwise the compiler will not be able
141+
to resolve the size and will report an error. The following code shows an example of valid and
142+
invalid cases.
143+
144+
```hlsl
145+
int f( int b[] )
146+
{
147+
return b.getCount();
148+
}
149+
int g( int b[] )
150+
{
151+
return f(b); // transitive calls are allowed.
152+
}
153+
uniform int unsizedParam[];
154+
void test()
155+
{
156+
g(unsizedParam); // Not OK, `unsizedParam` doesn't have a known size at compile time.
157+
int arr[3];
158+
g(arr); // OK.
159+
}
160+
```
161+
122162
There are more limits on how runtime-sized arrays can be used than on arrays of statically-known element count.
123163

124164
> #### Note ####

source/slang/core.meta.slang

+2-3
Original file line numberDiff line numberDiff line change
@@ -1040,16 +1040,15 @@ __generic<T, let N:int>
10401040
__magic_type(ArrayExpressionType)
10411041
struct Array : IArray<T>
10421042
{
1043-
[ForceInline]
1044-
int getCount() { return N; }
1043+
__intrinsic_op($(kIROp_GetArrayLength))
1044+
int getCount();
10451045

10461046
__subscript(int index) -> T
10471047
{
10481048
__intrinsic_op($(kIROp_GetElement))
10491049
get;
10501050
}
10511051
}
1052-
10531052
/// An `N` component vector with elements of type `T`.
10541053
__generic<T = float, let N : int = 4>
10551054
__magic_type(VectorExpressionType)

source/slang/slang-ast-support-types.h

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ namespace Slang
9696

9797
kConversionCost_GenericParamUpcast = 1,
9898
kConversionCost_UnconstraintGenericParam = 20,
99+
kConversionCost_SizedArrayToUnsizedArray = 30,
99100

100101
// Convert between matrices of different layout
101102
kConversionCost_MatrixLayout = 5,

source/slang/slang-check-conversion.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,31 @@ namespace Slang
750750
return true;
751751
}
752752

753+
// Allow implicit conversion from sized array to unsized array when
754+
// calling a function.
755+
// Note: we implement the logic here instead of an implicit_conversion
756+
// intrinsic in the stdlib because we only want to allow this conversion
757+
// when calling a function.
758+
//
759+
if (site == CoercionSite::Argument)
760+
{
761+
if (auto fromArrayType = as<ArrayExpressionType>(fromType))
762+
{
763+
if (auto toArrayType = as<ArrayExpressionType>(toType))
764+
{
765+
if (fromArrayType->getElementType()->equals(toArrayType->getElementType())
766+
&& toArrayType->isUnsized())
767+
{
768+
if (outToExpr)
769+
*outToExpr = fromExpr;
770+
if (outCost)
771+
*outCost = kConversionCost_SizedArrayToUnsizedArray;
772+
return true;
773+
}
774+
}
775+
}
776+
}
777+
753778
// Another important case is when either the "to" or "from" type
754779
// represents an error. In such a case we must have already
755780
// reporeted the error, so it is better to allow the conversion

source/slang/slang-diagnostic-defs.h

+1
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ DIAGNOSTIC(55201, Error, unsupportedRecursion, "recursion detected in call to '$
857857
DIAGNOSTIC(55202, Error, systemValueAttributeNotSupported, "system value semantic '$0' is not supported for the current target.")
858858
DIAGNOSTIC(55203, Error, systemValueTypeIncompatible, "system value semantic '$0' should have type '$1' or be convertible to type '$1'.")
859859
DIAGNOSTIC(56001, Error, unableToAutoMapCUDATypeToHostType, "Could not automatically map '$0' to a host type. Automatic binding generation failed for '$1'")
860+
DIAGNOSTIC(56002, Error, attemptToQuerySizeOfUnsizedArray, "cannot obtain the size of an unsized array.")
860861

861862
DIAGNOSTIC(57001, Warning, spirvOptFailed, "spirv-opt failed. $0")
862863
DIAGNOSTIC(57002, Error, unknownPatchConstantParameter, "unknown patch constant parameter '$0'.")

source/slang/slang-emit.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -947,13 +947,13 @@ Result linkAndOptimizeIR(
947947
specializeResourceUsage(codeGenContext, irModule);
948948
specializeFuncsForBufferLoadArgs(codeGenContext, irModule);
949949

950-
// For GLSL targets, we also want to specialize calls to functions that
951-
// takes array parameters if possible, to avoid performance issues on
952-
// those platforms.
953-
if (isKhronosTarget(targetRequest))
954-
{
955-
specializeArrayParameters(codeGenContext, irModule);
956-
}
950+
// We also want to specialize calls to functions that
951+
// takes unsized array parameters if possible.
952+
// Moreover, for Khronos targets, we also want to specialize calls to functions
953+
// that takes arrays/structs containing arrays as parameters with the actual
954+
// global array object to avoid loading big arrays into SSA registers, which seems
955+
// to cause performance issues.
956+
specializeArrayParameters(codeGenContext, irModule);
957957

958958
#if 0
959959
dumpIRIfEnabled(codeGenContext, irModule, "AFTER RESOURCE SPECIALIZATION");

source/slang/slang-ir-autodiff-fwd.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,9 @@ InstPair ForwardDiffTranscriber::transcribeInstImpl(IRBuilder* builder, IRInst*
19431943
case kIROp_DebugLine:
19441944
case kIROp_DebugVar:
19451945
case kIROp_DebugValue:
1946+
case kIROp_GetArrayLength:
1947+
case kIROp_SizeOf:
1948+
case kIROp_AlignOf:
19461949
return transcribeNonDiffInst(builder, origInst);
19471950

19481951
// A call to createDynamicObject<T>(arbitraryData) cannot provide a diff value,

source/slang/slang-ir-check-unsupported-inst.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ namespace Slang
4242
}
4343
}
4444

45+
void checkUnsupportedInst(TargetRequest* target, IRFunc* func, DiagnosticSink* sink)
46+
{
47+
SLANG_UNUSED(target);
48+
for (auto block : func->getBlocks())
49+
{
50+
for (auto inst : block->getChildren())
51+
{
52+
switch (inst->getOp())
53+
{
54+
case kIROp_GetArrayLength:
55+
sink->diagnose(inst, Diagnostics::attemptToQuerySizeOfUnsizedArray);
56+
break;
57+
}
58+
}
59+
}
60+
}
61+
4562
void checkUnsupportedInst(TargetRequest* target, IRModule* module, DiagnosticSink* sink)
4663
{
4764
HashSet<IRFunc*> checkedFuncsForRecursionDetection;
@@ -62,6 +79,16 @@ namespace Slang
6279
case kIROp_Func:
6380
if (!isCPUTarget(target))
6481
checkRecursion(checkedFuncsForRecursionDetection, as<IRFunc>(globalInst), sink);
82+
checkUnsupportedInst(target, as<IRFunc>(globalInst), sink);
83+
break;
84+
case kIROp_Generic:
85+
{
86+
auto generic = as<IRGeneric>(globalInst);
87+
auto innerFunc = as<IRFunc>(findGenericReturnVal(generic));
88+
if (innerFunc)
89+
checkUnsupportedInst(target, innerFunc, sink);
90+
break;
91+
}
6592
default:
6693
break;
6794
}

source/slang/slang-ir-inst-defs.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1108,7 +1108,7 @@ INST(TreatAsDynamicUniform, TreatAsDynamicUniform, 1, 0)
11081108

11091109
INST(SizeOf, sizeOf, 1, 0)
11101110
INST(AlignOf, alignOf, 1, 0)
1111-
1111+
INST(GetArrayLength, GetArrayLength, 1, 0)
11121112
INST(IsType, IsType, 3, 0)
11131113
INST(TypeEquals, TypeEquals, 2, 0)
11141114
INST(IsInt, IsInt, 1, 0)

source/slang/slang-ir-peephole.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ struct PeepholeContext : InstPassBase
283283
changed = true;
284284
}
285285
break;
286+
case kIROp_GetArrayLength:
287+
if (auto arrayType = as<IRArrayType>(inst->getOperand(0)->getDataType()))
288+
{
289+
inst->replaceUsesWith(arrayType->getElementCount());
290+
maybeRemoveOldInst(inst);
291+
changed = true;
292+
}
293+
break;
286294
case kIROp_GetResultError:
287295
if (inst->getOperand(0)->getOp() == kIROp_MakeResultError)
288296
{

source/slang/slang-ir-specialize-arrays.cpp

+45-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ struct ArrayParameterSpecializationCondition : FunctionCallSpecializeCondition
1313
// This pass is intended to specialize functions
1414
// with struct parameters that has array fields
1515
// to avoid performance problems for GLSL targets.
16-
1716
// Returns true if `type` is an `IRStructType` with array-typed fields.
17+
// It will also specialize functions with unsized array parameters into
18+
// sized arrays, if the function is called with an argument that has a
19+
// sized array type.
20+
//
1821
bool isStructTypeWithArray(IRType* type)
1922
{
2023
if (auto structType = as<IRStructType>(type))
@@ -38,15 +41,55 @@ struct ArrayParameterSpecializationCondition : FunctionCallSpecializeCondition
3841
bool doesParamWantSpecialization(IRParam* param, IRInst* arg)
3942
{
4043
SLANG_UNUSED(arg);
41-
return isStructTypeWithArray(param->getDataType());
44+
if (isKhronosTarget(codeGenContext->getTargetReq()))
45+
return isStructTypeWithArray(param->getDataType());
46+
return false;
4247
}
48+
49+
bool doesParamTypeWantSpecialization(IRParam* param, IRInst* arg)
50+
{
51+
auto paramType = param->getDataType();
52+
auto argType = arg->getDataType();
53+
if (auto outTypeBase = as<IROutTypeBase>(paramType))
54+
{
55+
paramType = outTypeBase->getValueType();
56+
SLANG_ASSERT(as<IRPtrTypeBase>(argType));
57+
argType = as<IRPtrTypeBase>(argType)->getValueType();
58+
}
59+
else if (auto refType = as<IRRefType>(paramType))
60+
{
61+
paramType = refType->getValueType();
62+
SLANG_ASSERT(as<IRPtrTypeBase>(argType));
63+
argType = as<IRPtrTypeBase>(argType)->getValueType();
64+
}
65+
else if (auto constRefType = as<IRConstRefType>(paramType))
66+
{
67+
paramType = constRefType->getValueType();
68+
SLANG_ASSERT(as<IRPtrTypeBase>(argType));
69+
argType = as<IRPtrTypeBase>(argType)->getValueType();
70+
}
71+
auto arrayType = as<IRUnsizedArrayType>(paramType);
72+
if (!arrayType)
73+
return false;
74+
auto argArrayType = as<IRArrayType>(argType);
75+
if (!argArrayType)
76+
return false;
77+
if (as<IRIntLit>(argArrayType->getElementCount()))
78+
{
79+
return true;
80+
}
81+
return false;
82+
}
83+
84+
CodeGenContext* codeGenContext = nullptr;
4385
};
4486

4587
void specializeArrayParameters(
4688
CodeGenContext* codeGenContext,
4789
IRModule* module)
4890
{
4991
ArrayParameterSpecializationCondition condition;
92+
condition.codeGenContext = codeGenContext;
5093
specializeFunctionCalls(codeGenContext, module, &condition);
5194
}
5295

0 commit comments

Comments
 (0)