diff --git a/docs/DXIL.rst b/docs/DXIL.rst index 7d4db04840..aa45bfc62d 100644 --- a/docs/DXIL.rst +++ b/docs/DXIL.rst @@ -3231,7 +3231,10 @@ INSTR.LINALGMATRIXSCOPEREQLAYOUT2 %0 matrix with scope '%1' INSTR.LINALGMATRIXUNSIGNEDFLOATTYPENOTALLOWED Float-like type '%0' must be signed INSTR.LINALGMATRIXUSEMISMATCH %0 matrix use '%1' does not match expected use %2. INSTR.LINALGMATRIXUSEMISMATCH2 %0 matrix use '%1' does not match expected use %2 or %3. +INSTR.LINALGMATRIXVECELEMCOUNTMISMATCH Return vector size '%0' must match size '%1' derived from input vector size and type. INSTR.LINALGMATRIXVECELEMENTTYPEMISMATCH %0 vector element type '%1' must match %2 vector element type '%3' +INSTR.LINALGMATRIXVECTORTYPEMUSTMATCH %0 vector element type '%1' must match %2 matrix element type '%3'. +INSTR.LINALGMATRIXVECTORTYPEMUSTMATCHPACKED %0 vector element type '%1' must be i32 for %2 matrix with non-native element type '%3'. INSTR.LINALGMETADATAMISSING %0 matrix must have well-formed metadata. INSTR.MAYREORDERTHREADUNDEFCOHERENCEHINTPARAM Use of undef coherence hint or num coherence hint bits in MaybeReorderThread. INSTR.MINPRECISIONNOTPRECISE Instructions marked precise may not refer to minprecision values. diff --git a/lib/DxilValidation/DxilValidation.cpp b/lib/DxilValidation/DxilValidation.cpp index 511efb52ea..2dd9f65c45 100644 --- a/lib/DxilValidation/DxilValidation.cpp +++ b/lib/DxilValidation/DxilValidation.cpp @@ -989,7 +989,9 @@ ValidateConstantIntGetValue(CallInst *CI, Value *V, ValidationContext &ValCtx, return cast(V)->getLimitedValue(); } -static void ValidateLinAlgComponentType(CallInst *CI, DXIL::ComponentType CT, +// Determines if a ComponentType is allowed in LinAlg builtins. +// Returns true when the CT is allowed. +static bool ValidateLinAlgComponentType(CallInst *CI, DXIL::ComponentType CT, ValidationContext &ValCtx, StringRef SourceName) { switch (CT) { @@ -1007,12 +1009,12 @@ static void ValidateLinAlgComponentType(CallInst *CI, DXIL::ComponentType CT, case DXIL::ComponentType::F32: case DXIL::ComponentType::F64: case DXIL::ComponentType::BFloat16: - break; + return true; default: ValCtx.EmitInstrFormatError(CI, ValidationRule::InstrLinAlgIllegalComponentType, {ComponentTypeToString(CT), SourceName}); - break; + return false; } } @@ -1547,6 +1549,85 @@ static void ValidateLinAlgMatrixAccumulateToMemory(CallInst *CI, static void ValidateLinAlgConvert(CallInst *CI, ValidationContext &ValCtx) { ValidateLinAlgOpParameters(CI, ValCtx); + DxilInst_LinAlgConvert Op(CI); + + VectorType *RetVecTy = cast(CI->getType()); + VectorType *InVecTy = cast(Op.get_inputVector()->getType()); + bool IsComponentTypeValid = true; + + // Input interp must be a immarg of allowed ComponentType + std::optional InputInterpV = + ValidateConstantIntGetValue(CI, Op.get_inputInterpretation(), ValCtx, + "InputInterpretation", "LinAlgConvert"); + if (!InputInterpV) + return; + DXIL::ComponentType InputInterp = + static_cast(*InputInterpV); + IsComponentTypeValid &= ValidateLinAlgComponentType(CI, InputInterp, ValCtx, + "InputInterpretation"); + + // Output interp must be a immarg of allowed ComponentType + std::optional OutputInterpV = + ValidateConstantIntGetValue(CI, Op.get_outputInterpretation(), ValCtx, + "OutputInterpretation", "LinAlgConvert"); + if (!OutputInterpV) + return; + DXIL::ComponentType OutputInterp = + static_cast(*OutputInterpV); + IsComponentTypeValid &= ValidateLinAlgComponentType(CI, OutputInterp, ValCtx, + "OutputInterpretation"); + + // The remaining validations only give reasonable errors when assuming valid + // ComponentTypes. Stop early to minimize noise/avoid being unhelpful + if (!IsComponentTypeValid) + return; + + bool IsNativeInputInterp = IsComponentTypeNative(InputInterp); + bool IsNativeOutputInterp = IsComponentTypeNative(OutputInterp); + + // If input interp is native then input vector element type must match that + // type + if (IsNativeInputInterp && + !IsComponentTypeSameNativeType(InputInterp, InVecTy->getElementType())) + ValCtx.EmitInstrFormatError( + CI, ValidationRule::InstrLinAlgMatrixVectorTypeMustMatch, + {"Input", TypeToString(InVecTy->getElementType()), + "InputInterpretation", ComponentTypeToString(InputInterp)}); + + // If input interp is non-native then input vector element type must be i32 + if (!IsNativeInputInterp && !InVecTy->getElementType()->isIntegerTy(32)) + ValCtx.EmitInstrFormatError( + CI, ValidationRule::InstrLinAlgMatrixVectorTypeMustMatchPacked, + {"Input", TypeToString(InVecTy->getElementType()), + "InputInterpretation", ComponentTypeToString(InputInterp)}); + + // If output interp is native then output vector element type must match that + // type + if (IsNativeOutputInterp && + !IsComponentTypeSameNativeType(OutputInterp, RetVecTy->getElementType())) + ValCtx.EmitInstrFormatError( + CI, ValidationRule::InstrLinAlgMatrixVectorTypeMustMatch, + {"Output", TypeToString(RetVecTy->getElementType()), + "OutputInterpretation", ComponentTypeToString(OutputInterp)}); + + // If output interp is non-native then output vector element type must be i32 + if (!IsNativeOutputInterp && !RetVecTy->getElementType()->isIntegerTy(32)) + ValCtx.EmitInstrFormatError( + CI, ValidationRule::InstrLinAlgMatrixVectorTypeMustMatchPacked, + {"Output", TypeToString(RetVecTy->getElementType()), + "OutputInterpretation", ComponentTypeToString(OutputInterp)}); + + // output vector length must be properly converted from input length + unsigned InputElemCount = + InVecTy->getNumElements() * ComponentTypeElementsPerScalar(InputInterp); + unsigned OutputElemPerScalar = ComponentTypeElementsPerScalar(OutputInterp); + unsigned ExpectedOutVecSize = + (InputElemCount + OutputElemPerScalar - 1) / OutputElemPerScalar; + if (RetVecTy->getNumElements() != ExpectedOutVecSize) + ValCtx.EmitInstrFormatError( + CI, ValidationRule::InstrLinAlgMatrixVecElemCountMismatch, + {std::to_string(RetVecTy->getNumElements()), + std::to_string(ExpectedOutVecSize)}); } static void diff --git a/lib/DxilValidation/DxilValidationUtils.cpp b/lib/DxilValidation/DxilValidationUtils.cpp index 0bc191eb1b..d6fdf44937 100644 --- a/lib/DxilValidation/DxilValidationUtils.cpp +++ b/lib/DxilValidation/DxilValidationUtils.cpp @@ -747,4 +747,21 @@ bool IsComponentTypeSameNativeType(DXIL::ComponentType CT, llvm::Type *Ty) { } } +bool IsComponentTypeNative(DXIL::ComponentType CT) { + switch (CT) { + case DXIL::ComponentType::I16: + case DXIL::ComponentType::U16: + case DXIL::ComponentType::I32: + case DXIL::ComponentType::U32: + case DXIL::ComponentType::I64: + case DXIL::ComponentType::U64: + case DXIL::ComponentType::F16: + case DXIL::ComponentType::F32: + case DXIL::ComponentType::F64: + return true; + default: + return false; + } +} + } // namespace hlsl diff --git a/lib/DxilValidation/DxilValidationUtils.h b/lib/DxilValidation/DxilValidationUtils.h index 1dc322e181..e854ad97c5 100644 --- a/lib/DxilValidation/DxilValidationUtils.h +++ b/lib/DxilValidation/DxilValidationUtils.h @@ -167,4 +167,6 @@ llvm::StringRef MatrixLayoutToString(DXIL::MatrixLayout ML); std::string TypeToString(llvm::Type *Ty); bool IsComponentTypeSameNativeType(DXIL::ComponentType CT, llvm::Type *Ty); + +bool IsComponentTypeNative(DXIL::ComponentType CT); } // namespace hlsl diff --git a/tools/clang/test/CodeGenDXIL/hlsl/linalg/builtins/convert/nominal.hlsl b/tools/clang/test/CodeGenDXIL/hlsl/linalg/builtins/convert/nominal.hlsl index d0270cb12f..de96c2e36b 100644 --- a/tools/clang/test/CodeGenDXIL/hlsl/linalg/builtins/convert/nominal.hlsl +++ b/tools/clang/test/CodeGenDXIL/hlsl/linalg/builtins/convert/nominal.hlsl @@ -7,22 +7,22 @@ void main() { // CHECK-LABEL: define void @main() // CHECK: %{{.*}} = call <4 x i32> @dx.op.linAlgConvert.v4i32.v4f32 - // CHECK-SAME: (i32 -2147483618, <4 x float> , i32 1, i32 2) + // CHECK-SAME: (i32 -2147483618, <4 x float> , i32 9, i32 4) // CHECK-SAME: ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) // CHECK2: call void @"dx.hl.op..void (i32, <4 x i32>*, <4 x float>, i32, i32)" - // CHECK2-SAME: (i32 422, <4 x i32>* %result1, <4 x float> %{{.*}}, i32 1, i32 2) + // CHECK2-SAME: (i32 422, <4 x i32>* %result1, <4 x float> %{{.*}}, i32 9, i32 4) float4 vec1 = {9.0, 8.0, 7.0, 6.0}; int4 result1; - __builtin_LinAlg_Convert(result1, vec1, 1, 2); + __builtin_LinAlg_Convert(result1, vec1, 9, 4); // CHECK: %{{.*}} = call <4 x i64> @dx.op.linAlgConvert.v4i64.v4f64 - // CHECK-SAME: (i32 -2147483618, <4 x double> , i32 1, i32 2) + // CHECK-SAME: (i32 -2147483618, <4 x double> , i32 10, i32 7) // CHECK-SAME: ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) // CHECK2: call void @"dx.hl.op..void (i32, <4 x i64>*, <4 x double>, i32, i32)" - // CHECK2-SAME: (i32 422, <4 x i64>* %result2, <4 x double> %{{.*}}, i32 1, i32 2) + // CHECK2-SAME: (i32 422, <4 x i64>* %result2, <4 x double> %{{.*}}, i32 10, i32 7) double4 vec2 = {9.0, 8.0, 7.0, 6.0}; vector result2; - __builtin_LinAlg_Convert(result2, vec2, 1, 2); + __builtin_LinAlg_Convert(result2, vec2, 10, 7); } diff --git a/tools/clang/test/LitDXILValidation/LinAlgMatrix/linalgmatrix-convert.ll b/tools/clang/test/LitDXILValidation/LinAlgMatrix/linalgmatrix-convert.ll new file mode 100644 index 0000000000..4b27272467 --- /dev/null +++ b/tools/clang/test/LitDXILValidation/LinAlgMatrix/linalgmatrix-convert.ll @@ -0,0 +1,103 @@ +; REQUIRES: dxil-1-10 +; RUN: not %dxv %s 2>&1 | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +%dx.types.Handle = type { i8* } +%dx.types.ResBind = type { i32, i32, i32, i8 } +%dx.types.ResourceProperties = type { i32, i32 } +%dx.types.ResRet.i32 = type { i32, i32, i32, i32, i32 } +%struct.ByteAddressBuffer = type { i32 } + +define void @main() { + %1 = call %dx.types.Handle @dx.op.createHandleFromBinding(i32 217, %dx.types.ResBind zeroinitializer, i32 0, i1 false) ; CreateHandleFromBinding(bind,index,nonUniformIndex) + %2 = call %dx.types.Handle @dx.op.annotateHandle(i32 216, %dx.types.Handle %1, %dx.types.ResourceProperties { i32 11, i32 0 }) ; AnnotateHandle(res,props) resource: ByteAddressBuffer + %3 = call %dx.types.ResRet.i32 @dx.op.rawBufferLoad.i32(i32 139, %dx.types.Handle %2, i32 0, i32 undef, i8 1, i32 4) ; RawBufferLoad(srv,index,elementOffset,mask,alignment) + %4 = extractvalue %dx.types.ResRet.i32 %3, 0 + + ; okay + %5 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32 -2147483618, <8 x i32> , i32 4, i32 4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK: Function: main: error: InputInterpretation of LinAlgConvert must be an immediate constant. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8i32 + %6 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32 -2147483618, <8 x i32> , i32 %4, i32 4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: OutputInterpretation of LinAlgConvert must be an immediate constant. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8i32 + %7 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32 -2147483618, <8 x i32> , i32 4, i32 %4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Component type 'Invalid' from InputInterpretation not allowed in LinAlg Matrix operations. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8i32 + %8 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32 -2147483618, <8 x i32> , i32 0, i32 4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Component type 'Invalid' from OutputInterpretation not allowed in LinAlg Matrix operations. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8i32 + %9 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32 -2147483618, <8 x i32> , i32 4, i32 0) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Input vector element type 'float' must match InputInterpretation matrix element type 'I32'. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8f32 + %10 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8f32(i32 -2147483618, <8 x float> , i32 4, i32 4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Output vector element type 'i32' must match OutputInterpretation matrix element type 'F32'. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v8i32.v8f32 + %11 = call <8 x i32> @dx.op.linAlgConvert.v8i32.v8f32(i32 -2147483618, <8 x float> , i32 9, i32 9) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Input vector element type 'float' must be i32 for InputInterpretation matrix with non-native element type 'F8_E4M3FN'. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v32i32.v8f32 + %12 = call <32 x i32> @dx.op.linAlgConvert.v32i32.v8f32(i32 -2147483618, <8 x float> , i32 21, i32 4) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Return vector size '32' must match size '2' derived from input vector size and type. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v32i32.v8f32 + %13 = call <32 x i32> @dx.op.linAlgConvert.v32i32.v8f32(i32 -2147483618, <8 x float> , i32 9, i32 21) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Function: main: error: Output vector element type 'float' must be i32 for OutputInterpretation matrix with non-native element type 'F8_E4M3FN'. + ; CHECK-NEXT: note: at {{.*}} @dx.op.linAlgConvert.v2f32.v8f32 + %14 = call <2 x float> @dx.op.linAlgConvert.v2f32.v8f32(i32 -2147483618, <8 x float> , i32 9, i32 21) ; LinAlgConvert(inputVector,inputInterpretation,outputInterpretation) + + ; CHECK-NEXT: Validation failed. + ret void +} + +; Function Attrs: nounwind readonly +declare %dx.types.ResRet.i32 @dx.op.rawBufferLoad.i32(i32, %dx.types.Handle, i32, i32, i8, i32) #0 + +; Function Attrs: nounwind +declare <8 x i32> @dx.op.linAlgConvert.v8i32.v8i32(i32, <8 x i32>, i32, i32) #1 + +; Function Attrs: nounwind +declare <8 x i32> @dx.op.linAlgConvert.v8i32.v8f32(i32, <8 x float>, i32, i32) #1 + +; Function Attrs: nounwind +declare <32 x i32> @dx.op.linAlgConvert.v32i32.v8f32(i32, <8 x float>, i32, i32) #1 + +; Function Attrs: nounwind +declare <2 x float> @dx.op.linAlgConvert.v2f32.v8f32(i32, <8 x float>, i32, i32) #1 + +; Function Attrs: nounwind readnone +declare %dx.types.Handle @dx.op.annotateHandle(i32, %dx.types.Handle, %dx.types.ResourceProperties) #2 + +; Function Attrs: nounwind readnone +declare %dx.types.Handle @dx.op.createHandleFromBinding(i32, %dx.types.ResBind, i32, i1) #2 + +attributes #0 = { nounwind readonly } +attributes #1 = { nounwind } +attributes #2 = { nounwind readnone } + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.resources = !{!3} +!dx.entryPoints = !{!6} + +!0 = !{!"dxc(private) 1.9.0.5484 (linalg-vali-convert, 2832abc07)"} +!1 = !{i32 1, i32 10} +!2 = !{!"cs", i32 6, i32 10} +!3 = !{!4, null, null, null} +!4 = !{!5} +!5 = !{i32 0, %struct.ByteAddressBuffer* undef, !"", i32 0, i32 0, i32 1, i32 11, i32 0, null} +!6 = !{void ()* @main, !"main", null, !3, !7} +!7 = !{i32 0, i64 8388624, i32 4, !8} +!8 = !{i32 1, i32 1, i32 1} + diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index 32ca46495a..fc50aec039 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -8743,6 +8743,18 @@ def build_valrules(self): "Instr.LinAlgMatrixGSMemMustBeLargeEnough", "Groupshared memory holds '%0' scalars but must hold at least '%1' scalars.", ) + self.add_valrule( + "Instr.LinAlgMatrixVectorTypeMustMatch", + "%0 vector element type '%1' must match %2 matrix element type '%3'.", + ) + self.add_valrule( + "Instr.LinAlgMatrixVectorTypeMustMatchPacked", + "%0 vector element type '%1' must be i32 for %2 matrix with non-native element type '%3'." + ) + self.add_valrule( + "Instr.LinAlgMatrixVecElemCountMismatch", + "Return vector size '%0' must match size '%1' derived from input vector size and type.", + ) # Some legacy rules: # - space is only supported for shader targets 5.1 and higher