Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Validation] Add validation and tests for invalid system values in incompatible node launch type #6108

Merged
1 change: 1 addition & 0 deletions docs/DXIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,7 @@ INSTR.FAILTORESLOVETGSMPOINTER TGSM pointers must originate from an u
INSTR.HANDLENOTFROMCREATEHANDLE Resource handle should returned by createHandle.
INSTR.IMMBIASFORSAMPLEB bias amount for sample_b must be in the range [%0,%1], but %2 was specified as an immediate.
INSTR.INBOUNDSACCESS Access to out-of-bounds memory is disallowed.
INSTR.INVALIDSVINFUNCTION An SV value was used on a parameter to a node shader function that isn't compatible.
INSTR.MINPRECISIONNOTPRECISE Instructions marked precise may not refer to minprecision values.
INSTR.MINPRECISONBITCAST Bitcast on minprecison types is not allowed.
INSTR.MIPLEVELFORGETDIMENSION Use mip level on buffer when GetDimensions.
Expand Down
192 changes: 136 additions & 56 deletions lib/HLSL/DxilValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2333,7 +2333,7 @@ static void ValidateDxilOperationCallInProfile(CallInst *CI,
DXIL::ResourceClass::UAV)
ValCtx.EmitInstrError(CI, ValidationRule::InstrAtomicIntrinNonUAV);
} break;
case DXIL::OpCode::CreateHandle:
case DXIL::OpCode::CreateHandle: {
if (ValCtx.isLibProfile) {
ValCtx.EmitInstrFormatError(CI, ValidationRule::SmOpcodeInInvalidFunction,
{"CreateHandle", "non-library targets"});
Expand All @@ -2345,7 +2345,83 @@ static void ValidateDxilOperationCallInProfile(CallInst *CI,
CI, ValidationRule::SmOpcodeInInvalidFunction,
{"CreateHandle", "Shader model 6.5 and below"});
}
} break;

// this intrinsic is generated when the SV_DispatchThreadID system value is used
case DXIL::OpCode::ThreadId: {
// get the node launch type
DxilModule &DM = ValCtx.DxilMod;
if (!DM.HasDxilEntryProps(F)) {
break;
}

DxilEntryProps &entryProps = DM.GetDxilEntryProps(F);
DXIL::NodeLaunchType NLT = entryProps.props.Node.LaunchType;
if (NLT == DXIL::NodeLaunchType::Thread || NLT == DXIL::NodeLaunchType::Coalescing) {
ValCtx.EmitInstrFormatError(CI, ValidationRule::InstrInvalidSVInFunction,
{"ThreadId", NLT ==
DXIL::NodeLaunchType::Thread
? "Thread"
: "Coalescing"});
}
} break;
// this intrinsic is generated when the SV_GroupId
// system value is used

case DXIL::OpCode::GroupId: {
// get the node launch type
DxilModule &DM = ValCtx.DxilMod;
if (!DM.HasDxilEntryProps(F)) {
break;
}

DxilEntryProps &entryProps = DM.GetDxilEntryProps(F);
DXIL::NodeLaunchType NLT = entryProps.props.Node.LaunchType;
if (NLT == DXIL::NodeLaunchType::Thread || NLT == DXIL::NodeLaunchType::Coalescing) {
ValCtx.EmitInstrFormatError(CI, ValidationRule::InstrInvalidSVInFunction,
{"GroupId", NLT ==
DXIL::NodeLaunchType::Thread
? "Thread"
: "Coalescing"});
}
}
break;

// this intrinsic is generated when the SV_GroupThreadID
// system value is used
case DXIL::OpCode::ThreadIdInGroup: {
// get the node launch type
DxilModule &DM = ValCtx.DxilMod;
if (!DM.HasDxilEntryProps(F)) {
break;
}

DxilEntryProps &entryProps = DM.GetDxilEntryProps(F);
DXIL::NodeLaunchType NLT = entryProps.props.Node.LaunchType;
if (NLT == DXIL::NodeLaunchType::Thread) {
ValCtx.EmitInstrFormatError(
CI, ValidationRule::InstrInvalidSVInFunction,
{"ThreadIdInGroup", "Thread"});
}
} break;

// this intrinsic is generated when the SV_GroupIndex
// system value is used
case DXIL::OpCode::FlattenedThreadIdInGroup: {
// get the node launch type
DxilModule &DM = ValCtx.DxilMod;
if (!DM.HasDxilEntryProps(F)) {
break;
}

DxilEntryProps &entryProps = DM.GetDxilEntryProps(F);
DXIL::NodeLaunchType NLT = entryProps.props.Node.LaunchType;
if (NLT == DXIL::NodeLaunchType::Thread) {
ValCtx.EmitInstrFormatError(
CI, ValidationRule::InstrInvalidSVInFunction,
{"FlattenedThreadIdInGroup", "Thread"});
}
} break;
default:
// TODO: make sure every opcode is checked.
// Skip opcodes don't need special check.
Expand Down Expand Up @@ -3463,6 +3539,62 @@ static void ValidateNodeInputRecord(Function *F, ValidationContext &ValCtx) {
}
}

static void ValidateFunctionArgs(Function& F, unsigned numUDTShaderArgs, DXIL::ShaderKind shaderKind, ValidationContext& ValCtx) {

auto ArgFormatError = [&](Function &F, Argument &arg, ValidationRule rule) {
if (arg.hasName())
ValCtx.EmitFnFormatError(&F, rule, {arg.getName().str(), F.getName()});
else
ValCtx.EmitFnFormatError(&F, rule,
{std::to_string(arg.getArgNo()), F.getName()});
};

unsigned numArgs = 0;
for (auto &arg : F.args()) {
Type *argTy = arg.getType();
if (argTy->isPointerTy())
argTy = argTy->getPointerElementType();

numArgs++;
if (numUDTShaderArgs) {
if (arg.getArgNo() >= numUDTShaderArgs) {
ArgFormatError(F, arg, ValidationRule::DeclExtraArgs);
} else if (!argTy->isStructTy()) {
switch (shaderKind) {
case DXIL::ShaderKind::Callable:
ArgFormatError(F, arg, ValidationRule::DeclParamStruct);
break;
default:
ArgFormatError(F, arg,
arg.getArgNo() == 0 ? ValidationRule::DeclPayloadStruct
: ValidationRule::DeclAttrStruct);
}
}
continue;
}

while (argTy->isArrayTy()) {
argTy = argTy->getArrayElementType();
}

if (argTy->isStructTy() && !ValCtx.isLibProfile) {
ArgFormatError(F, arg, ValidationRule::DeclFnFlattenParam);
break;
}
}

if (numArgs < numUDTShaderArgs && shaderKind != DXIL::ShaderKind::Node) {
StringRef argType[2] = {
shaderKind == DXIL::ShaderKind::Callable ? "params" : "payload",
"attributes"};
for (unsigned i = numArgs; i < numUDTShaderArgs; i++) {
ValCtx.EmitFnFormatError(
&F, ValidationRule::DeclShaderMissingArg,
{ShaderModel::GetKindName(shaderKind), F.getName(), argType[i]});
}
}
}

static void ValidateFunction(Function &F, ValidationContext &ValCtx) {
if (F.isDeclaration()) {
ValidateExternalFunction(&F, ValCtx);
Expand Down Expand Up @@ -3494,7 +3626,7 @@ static void ValidateFunction(Function &F, ValidationContext &ValCtx) {
}
}
break;
}
}
default:
break;
}
Expand All @@ -3510,62 +3642,10 @@ static void ValidateFunction(Function &F, ValidationContext &ValCtx) {
// Shader functions should return void.
if (isShader && !F.getReturnType()->isVoidTy())
ValCtx.EmitFnFormatError(&F, ValidationRule::DeclShaderReturnVoid,
{F.getName()});

auto ArgFormatError = [&](Function &F, Argument &arg, ValidationRule rule) {
if (arg.hasName())
ValCtx.EmitFnFormatError(&F, rule, {arg.getName().str(), F.getName()});
else
ValCtx.EmitFnFormatError(&F, rule,
{std::to_string(arg.getArgNo()), F.getName()});
};
{F.getName()});

// Validate parameter type.
unsigned numArgs = 0;
for (auto &arg : F.args()) {
Type *argTy = arg.getType();
if (argTy->isPointerTy())
argTy = argTy->getPointerElementType();

numArgs++;
if (numUDTShaderArgs) {
if (arg.getArgNo() >= numUDTShaderArgs) {
ArgFormatError(F, arg, ValidationRule::DeclExtraArgs);
} else if (!argTy->isStructTy()) {
switch (shaderKind) {
case DXIL::ShaderKind::Callable:
ArgFormatError(F, arg, ValidationRule::DeclParamStruct);
break;
default:
ArgFormatError(F, arg,
arg.getArgNo() == 0
? ValidationRule::DeclPayloadStruct
: ValidationRule::DeclAttrStruct);
}
}
continue;
}

while (argTy->isArrayTy()) {
argTy = argTy->getArrayElementType();
}

if (argTy->isStructTy() && !ValCtx.isLibProfile) {
ArgFormatError(F, arg, ValidationRule::DeclFnFlattenParam);
break;
}
}

if (numArgs < numUDTShaderArgs && shaderKind != DXIL::ShaderKind::Node) {
StringRef argType[2] = {
shaderKind == DXIL::ShaderKind::Callable ? "params" : "payload",
"attributes"};
for (unsigned i = numArgs; i < numUDTShaderArgs; i++) {
ValCtx.EmitFnFormatError(
&F, ValidationRule::DeclShaderMissingArg,
{ShaderModel::GetKindName(shaderKind), F.getName(), argType[i]});
}
}
ValidateFunctionArgs(F, numUDTShaderArgs, shaderKind, ValCtx);

if (ValCtx.DxilMod.HasDxilFunctionProps(&F) &&
ValCtx.DxilMod.GetDxilFunctionProps(&F).IsNode()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
; RUN: %dxv %s

; There should be no errors!

target datalayout = "e-m:e-p:32:32-i1:32-i8:32-i16:32-i32:32-i64:64-f16:32-f32:32-f64:64-n8:16:32:64"
target triple = "dxil-ms-dx"

define void @entry2() {
%1 = call i32 @dx.op.threadId.i32(i32 93, i32 1)
%2 = call i32 @dx.op.groupId.i32(i32 94, i32 2)
%3 = call i32 @dx.op.threadIdInGroup.i32(i32 95, i32 1)
%4 = call i32 @dx.op.flattenedThreadIdInGroup.i32(i32 96)
ret void
}

!llvm.ident = !{!0}
!dx.version = !{!1}
!dx.valver = !{!2}
!dx.shaderModel = !{!3}
!dx.typeAnnotations = !{!4}
!dx.entryPoints = !{!8, !9}

!0 = !{!"dxc(private) 1.7.0.14317 (main, e3c311409675)"}
!1 = !{i32 1, i32 3}
!2 = !{i32 1, i32 8}
!3 = !{!"lib", i32 6, i32 3}
!4 = !{i32 1, void ()* @entry2, !5}
!5 = !{!6}
!6 = !{i32 0, !7, !7}
!7 = !{}
!8 = !{null, !"", null, null, null}
!9 = !{void ()* @entry2, !"entry2", null, null, !10}
!10 = !{i32 8, i32 15, i32 13, i32 1, i32 15, !11, i32 16, i32 -1, i32 18, !12, i32 4, !12, i32 5, !13}
!11 = !{!"entry2", i32 0}
!12 = !{i32 1, i32 1, i32 1}
!13 = !{i32 0}

; Function Attrs: nounwind readnone
declare i32 @dx.op.threadId.i32(i32, i32) #1

; Function Attrs: nounwind readnone
declare i32 @dx.op.groupId.i32(i32, i32) #1

; Function Attrs: nounwind readnone
declare i32 @dx.op.threadIdInGroup.i32(i32, i32) #0

; Function Attrs: nounwind readnone
declare i32 @dx.op.flattenedThreadIdInGroup.i32(i32) #0

; this file comes from compiling launch_types.hlsl with:
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
; RUN: %dxv %s | FileCheck %s

target datalayout = "e-m:e-p:32:32-i1:32-i8:32-i16:32-i32:32-i64:64-f16:32-f32:32-f64:64-n8:16:32:64"
target triple = "dxil-ms-dx"

; CHECK: Function: entry3: error: Call to DXIL intrinsic ThreadId is not allowed in node shader launch type Coalescing
; CHECK: note: at '%1 = call i32 @dx.op.threadId.i32(i32 93, i32 1)' in block '#0' of function 'entry3'.
; CHECK: Function: entry3: error: Call to DXIL intrinsic GroupId is not allowed in node shader launch type Coalescing
; CHECK: note: at '%2 = call i32 @dx.op.groupId.i32(i32 94, i32 2)' in block '#0' of function 'entry3'.

define void @entry3() {
%1 = call i32 @dx.op.threadId.i32(i32 93, i32 1)
%2 = call i32 @dx.op.groupId.i32(i32 94, i32 2)
%3 = call i32 @dx.op.threadIdInGroup.i32(i32 95, i32 1)
%4 = call i32 @dx.op.flattenedThreadIdInGroup.i32(i32 96)
ret void
}

!llvm.ident = !{!0}
!dx.version = !{!1}
!dx.valver = !{!2}
!dx.shaderModel = !{!3}
!dx.typeAnnotations = !{!4}
!dx.entryPoints = !{!8, !9}

!0 = !{!"dxc(private) 1.7.0.14317 (main, e3c311409675)"}
!1 = !{i32 1, i32 3}
!2 = !{i32 1, i32 8}
!3 = !{!"lib", i32 6, i32 3}
!4 = !{i32 1, void ()* @entry3, !5}
!5 = !{!6}
!6 = !{i32 0, !7, !7}
!7 = !{}
!8 = !{null, !"", null, null, null}
!9 = !{void ()* @entry3, !"entry3", null, null, !10}
!10 = !{i32 8, i32 15, i32 13, i32 2, i32 15, !11, i32 16, i32 -1, i32 4, !12, i32 5, !13}
!11 = !{!"entry3", i32 0}
!12 = !{i32 1, i32 1, i32 1}
!13 = !{i32 0}

; Function Attrs: nounwind readnone
declare i32 @dx.op.threadId.i32(i32, i32) #1

; Function Attrs: nounwind readnone
declare i32 @dx.op.groupId.i32(i32, i32) #1

; Function Attrs: nounwind readnone
declare i32 @dx.op.threadIdInGroup.i32(i32, i32) #0

; Function Attrs: nounwind readnone
declare i32 @dx.op.flattenedThreadIdInGroup.i32(i32) #0

; this file comes from compiling launch_types.hlsl with:
Loading