Skip to content

Commit 7efd6ee

Browse files
authored
[bug] Fix crash in dxilgen when buffer subscript gep feeds an lcssa phi (microsoft#5570)
[bug] Fix crash in dxilgen when buffer subscript gep feeds an lcssa phi This change fixes a crash in the dxilgen pass when lowering buffer subscripts. These subscripts feed into a gep instruction that needs to be replaced with the computed index and offset for the dxil buffer load intrinsics. When processing all the users of the subscripts the code would crash on any phi node user. We can handle single-value phis by propagating through to the users of the phi. The single-value phis can be inserted by the lcssa pass. After fixing this initial crash it triggered another error later in the compiler for a similar issue. The lowered handle used by the buffer load was also being fed through an lcssa phi by a later run of that pass. This would trigger a dxil validation error because handles are not allowed in phis. Since it is a single-value phi we can again propagate the value to the phi users to avoid this error. The test case was reduced from a real-world shader and only occurred when compiling with optimization disabled (Od).
1 parent 578114e commit 7efd6ee

File tree

6 files changed

+222
-0
lines changed

6 files changed

+222
-0
lines changed

lib/HLSL/DxilNoOptLegalize.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
#include "llvm/IR/Instructions.h"
1313
#include "dxc/HLSL/DxilGenerationPass.h"
1414
#include "dxc/HLSL/DxilNoops.h"
15+
#include "dxc/Support/Global.h"
1516
#include "llvm/IR/Operator.h"
1617
#include "llvm/Analysis/DxilValueCache.h"
18+
#include "llvm/Analysis/InstructionSimplify.h"
1719

1820
using namespace llvm;
1921

@@ -120,6 +122,15 @@ class DxilNoOptSimplifyInstructions : public ModulePass {
120122
I->eraseFromParent();
121123
Changed = true;
122124
}
125+
} else if (PHINode* Phi = dyn_cast<PHINode>(I)) {
126+
// Replace all simple phi values (such as those inserted by lcssa) with the
127+
// value itself. This avoids phis in places they are not expected because
128+
// the normal simplify passes will clean them up.
129+
if (Value *NewPhi = llvm::SimplifyInstruction(Phi, M.getDataLayout())) {
130+
Phi->replaceAllUsesWith(NewPhi);
131+
Phi->eraseFromParent();
132+
Changed = true;
133+
}
123134
}
124135
}
125136
}

lib/HLSL/HLOperationLower.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7602,6 +7602,20 @@ void TranslateStructBufSubscriptUser(
76027602
handle, ResKind, bufIdx, baseOffset, status, OP, DL);
76037603
}
76047604
BCI->eraseFromParent();
7605+
} else if (PHINode* Phi = dyn_cast<PHINode>(user)) {
7606+
if (Phi->getNumIncomingValues() != 1) {
7607+
dxilutil::EmitErrorOnInstruction(Phi, "Phi not supported for buffer subscript");
7608+
return;
7609+
}
7610+
// Since the phi only has a single value we can safely process its
7611+
// users to translate the subscript. These single-value phis are
7612+
// inserted by the lcssa pass.
7613+
for (auto U = Phi->user_begin(); U != Phi->user_end();) {
7614+
Value *PhiUser = *(U++);
7615+
TranslateStructBufSubscriptUser(cast<Instruction>(PhiUser),
7616+
handle, ResKind, bufIdx, baseOffset, status, OP, DL);
7617+
}
7618+
Phi->eraseFromParent();
76057619
} else {
76067620
// should only used by GEP
76077621
GetElementPtrInst *GEP = cast<GetElementPtrInst>(user);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
; RUN: opt -S -dxil-o0-simplify-inst %s | FileCheck %s
2+
3+
; Make sure we remove a single-value phi node.
4+
5+
; CHECK: %add = add i32 1, 2
6+
; CHECK-NOT: phi
7+
; CHECK: %mul = mul i32 %add, 2
8+
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"
9+
target triple = "dxil-ms-dx"
10+
11+
define void @foo() {
12+
entry:
13+
%add = add i32 1, 2
14+
br label %exit
15+
16+
exit:
17+
%lcssa = phi i32 [ %add, %entry ]
18+
%mul = mul i32 %lcssa, 2
19+
ret void
20+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
; RUN: opt -S -dxil-o0-simplify-inst %s | FileCheck %s
2+
3+
; Make sure we remove a single-value phi node of dxil handles.
4+
; Phis of handles are invalid dxil but we can make it valid by replacing
5+
; the phi with its one incoming value.
6+
7+
; CHECK: %handle = call %dx.types.Handle @dx.op.createHandle
8+
; CHECK-NOT: phi
9+
; CHECK: %load = call %dx.types.ResRet.i32 @dx.op.bufferLoad.i32(i32 68, %dx.types.Handle %handle, i32 0, i32 0)
10+
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"
11+
target triple = "dxil-ms-dx"
12+
13+
%dx.types.Handle = type { i8* }
14+
%dx.types.ResRet.i32 = type { i32, i32, i32, i32, i32 }
15+
16+
define void @main() {
17+
entry:
18+
%handle = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 0, i32 3, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex)
19+
br label %exit
20+
21+
exit:
22+
%lcssa = phi %dx.types.Handle [ %handle, %entry ]
23+
%load = call %dx.types.ResRet.i32 @dx.op.bufferLoad.i32(i32 68, %dx.types.Handle %lcssa, i32 0, i32 0)
24+
ret void
25+
}
26+
27+
; Function Attrs: nounwind readonly
28+
declare %dx.types.Handle @dx.op.createHandle(i32, i8, i32, i32, i1) #2
29+
30+
; Function Attrs: nounwind readonly
31+
declare %dx.types.ResRet.i32 @dx.op.bufferLoad.i32(i32, %dx.types.Handle, i32, i32) #2
32+
33+
attributes #0 = { nounwind readnone }
34+
attributes #1 = { nounwind }
35+
attributes #2 = { nounwind readonly }
36+
37+
!llvm.ident = !{!0}
38+
!dx.version = !{!1}
39+
!dx.valver = !{!2}
40+
!dx.shaderModel = !{!3}
41+
!dx.resources = !{!4}
42+
!dx.viewIdState = !{!8}
43+
!dx.entryPoints = !{!9}
44+
45+
!0 = !{!"dxc(private) 1.7.0.14024 (main, 1d6b5627a)"}
46+
!1 = !{i32 1, i32 0}
47+
!2 = !{i32 1, i32 7}
48+
!3 = !{!"vs", i32 6, i32 0}
49+
!4 = !{!5, null, null, null}
50+
!5 = !{!6}
51+
!6 = !{i32 0, i32* undef, !"", i32 0, i32 3, i32 1, i32 12, i32 0, !7}
52+
!7 = !{i32 1, i32 4}
53+
!8 = !{[3 x i32] [i32 1, i32 1, i32 1]}
54+
!9 = !{void ()* @main, !"main", !10, !4, !17}
55+
!10 = !{!11, !15, null}
56+
!11 = !{!12}
57+
!12 = !{i32 0, !"A", i8 5, i8 0, !13, i8 0, i32 1, i8 1, i32 0, i8 0, !14}
58+
!13 = !{i32 0}
59+
!14 = !{i32 3, i32 1}
60+
!15 = !{!16}
61+
!16 = !{i32 0, !"Z", i8 5, i8 0, !13, i8 1, i32 1, i8 1, i32 0, i8 0, !14}
62+
!17 = !{i32 0, i64 17}
63+
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
; RUN: opt -S -dxilgen %s | FileCheck %s
2+
3+
; Make sure we can correctly generate the buffer load when
4+
; its gep goes through a single-value phi node.
5+
6+
; CHECK: exit:
7+
; CHECK-NEXT: call %dx.types.ResRet.i32 @dx.op.rawBufferLoad.i32
8+
9+
10+
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"
11+
target triple = "dxil-ms-dx"
12+
13+
%ConstantBuffer = type opaque
14+
%"class.StructuredBuffer<S>" = type { %struct.S }
15+
%struct.S = type { i32 }
16+
%dx.types.Handle = type { i8* }
17+
%dx.types.ResourceProperties = type { i32, i32 }
18+
@"\01?buf@@3V?$StructuredBuffer@US@@@@A" = external global %"class.StructuredBuffer<S>", align 4
19+
20+
@"$Globals" = external constant %ConstantBuffer
21+
22+
; Function Attrs: nounwind
23+
define void @main(<4 x float>* noalias) #0 {
24+
entry:
25+
%a = load %"class.StructuredBuffer<S>", %"class.StructuredBuffer<S>"* @"\01?buf@@3V?$StructuredBuffer@US@@@@A"
26+
%b = call %dx.types.Handle @"dx.hl.createhandle..%dx.types.Handle (i32, %\22class.StructuredBuffer<S>\22)"(i32 0, %"class.StructuredBuffer<S>" %a)
27+
%c = call %dx.types.Handle @"dx.hl.annotatehandle..%dx.types.Handle (i32, %dx.types.Handle, %dx.types.ResourceProperties, %\22class.StructuredBuffer<S>\22)"(i32 11, %dx.types.Handle %b, %dx.types.ResourceProperties { i32 524, i32 4 }, %"class.StructuredBuffer<S>" zeroinitializer)
28+
%d = call %struct.S* @"dx.hl.subscript.[].rn.%struct.S* (i32, %dx.types.Handle, i32)"(i32 0, %dx.types.Handle %c, i32 0)
29+
%gep = getelementptr inbounds %struct.S, %struct.S* %d, i32 0, i32 0
30+
br label %exit
31+
32+
exit:
33+
%lcssa = phi i32* [ %gep, %entry ]
34+
%struct_buffer_load = load i32, i32* %lcssa, align 4
35+
36+
ret void
37+
}
38+
39+
; Function Attrs: nounwind readnone
40+
declare %struct.S* @"dx.hl.subscript.[].rn.%struct.S* (i32, %dx.types.Handle, i32)"(i32, %dx.types.Handle, i32) #0
41+
42+
; Function Attrs: nounwind readnone
43+
declare %dx.types.Handle @"dx.hl.createhandle..%dx.types.Handle (i32, %\22class.StructuredBuffer<S>\22)"(i32, %"class.StructuredBuffer<S>") #0
44+
45+
; Function Attrs: nounwind readnone
46+
declare %dx.types.Handle @"dx.hl.annotatehandle..%dx.types.Handle (i32, %dx.types.Handle, %dx.types.ResourceProperties, %\22class.StructuredBuffer<S>\22)"(i32, %dx.types.Handle, %dx.types.ResourceProperties, %"class.StructuredBuffer<S>") #0
47+
48+
49+
attributes #0 = { nounwind }
50+
51+
!llvm.module.flags = !{!0}
52+
!pauseresume = !{!1}
53+
!llvm.ident = !{!2}
54+
!dx.version = !{!3}
55+
!dx.valver = !{!4}
56+
!dx.shaderModel = !{!5}
57+
!dx.typeAnnotations = !{!6}
58+
!dx.entryPoints = !{!13}
59+
!dx.fnprops = !{!17}
60+
!dx.options = !{!18, !19}
61+
62+
!0 = !{i32 2, !"Debug Info Version", i32 3}
63+
!1 = !{!"hlsl-hlemit", !"hlsl-hlensure"}
64+
!2 = !{!"dxc(private) 1.7.0.14024 (main, 1d6b5627a)"}
65+
!3 = !{i32 1, i32 0}
66+
!4 = !{i32 1, i32 7}
67+
!5 = !{!"ps", i32 6, i32 0}
68+
!6 = !{i32 1, void (<4 x float>*)* @main, !7}
69+
!7 = !{!8, !10}
70+
!8 = !{i32 0, !9, !9}
71+
!9 = !{}
72+
!10 = !{i32 1, !11, !12}
73+
!11 = !{i32 4, !"SV_Target", i32 7, i32 9}
74+
!12 = !{i32 0}
75+
!13 = !{void (<4 x float>*)* @main, !"main", null, !14, null}
76+
!14 = !{null, null, !15, null}
77+
!15 = !{!16}
78+
!16 = !{i32 0, %ConstantBuffer* @"$Globals", !"$Globals", i32 0, i32 -1, i32 1, i32 0, null}
79+
!17 = !{void (<4 x float>*)* @main, i32 0, i1 false}
80+
!18 = !{i32 152}
81+
!19 = !{i32 -1}
82+
!20 = !DILocation(line: 2, column: 3, scope: !21)
83+
!21 = !DISubprogram(name: "main", scope: !22, file: !22, line: 1, type: !23, isLocal: false, isDefinition: true, scopeLine: 1, flags: DIFlagPrototyped, isOptimized: false, function: void (<4 x float>*)* @main)
84+
!22 = !DIFile(filename: ".\5Ct2.hlsl", directory: "")
85+
!23 = !DISubroutineType(types: !9)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: dxc /Tvs_6_0 /Od %s | FileCheck %s
2+
3+
// This is a regression test for a crash in dxilgen when compiling
4+
// with optimization disabled. It triggered a crash because the
5+
// gep used by the subscript operator was fed through an lcssa
6+
// phi for a use out of the loop and phis are not allowed users
7+
// of the gep for the subscript operator.
8+
9+
// CHECK: call %dx.types.ResRet.i32 @dx.op.bufferLoad.i32
10+
11+
struct S { uint bits; };
12+
StructuredBuffer<S> buf : register(t3);
13+
14+
S get(uint idx)
15+
{
16+
return buf[idx];
17+
}
18+
19+
[RootSignature("DescriptorTable(SRV(t0, numDescriptors=10))")]
20+
uint main(uint C : A) : Z
21+
{
22+
int i = 0;
23+
S s;
24+
do {
25+
s = get(i);
26+
} while (i < C);
27+
28+
return s.bits;
29+
}

0 commit comments

Comments
 (0)