; RUN: opt -S -dxil-resource-type -dxil-resource-access -disable-verify \ ; RUN: -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s ; The file contains examples of hlsl snippets that will generate invalid ; looking resource access, either through code-gen or by optimization. ; These can be legalized by replacing the handles with indicies into the ; same global resource. ; NOTE: The below resources are generated with: ; ; RWBuffer In : register(u0); ; RWStructuredBuffer Out0 : register(u1); ; RWStructuredBuffer Out1 : register(u2); ; RWStructuredBuffer OutArr[]; ; cbuffer c { ; bool cond; ; }; %__cblayout_c = type <{ i32 }> @.str = internal unnamed_addr constant [3 x i8] c"In\00", align 1 @.str.2 = internal unnamed_addr constant [5 x i8] c"Out0\00", align 1 @.str.3 = internal unnamed_addr constant [5 x i8] c"Out1\00", align 1 @c.cb = local_unnamed_addr global target("dx.CBuffer", %__cblayout_c) poison @c.str = internal unnamed_addr constant [2 x i8] c"c\00", align 1 @OutArr.str = internal unnamed_addr constant [7 x i8] c"OutArr\00", align 1 ; Local select into global resource array: ; ; RWStructuredBuffer Out = cond ? OutArr[0] : OutArr[1]; ; Out[GI] = WaveActiveMax(In[GI]); ; ; This will ensure that the index is propogated through phi branching ; correctly. We see that two different handles are generated in the different ; branches, so we will ensure that the handle generation is removed from ; the branches and that the `phi i32` node is used to distinguish the index. ; Then that index is used to generate the handle. ; ; CHECK-LABEL: @select_global_resource_array() define void @select_global_resource_array() { entry: %c.cb_h.i.i = tail call target("dx.CBuffer", %__cblayout_c) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_s___cblayout_cst(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str) store target("dx.CBuffer", %__cblayout_c) %c.cb_h.i.i, ptr @c.cb, align 4 %c.cb = load target("dx.CBuffer", %__cblayout_c), ptr @c.cb, align 4 %0 = call ptr addrspace(2) @llvm.dx.resource.getpointer.p2.tdx.CBuffer_s___cblayout_cst(target("dx.CBuffer", %__cblayout_c) %c.cb, i32 0) %1 = load i32, ptr addrspace(2) %0, align 4 %loadedv.i = trunc nuw i32 %1 to i1 br i1 %loadedv.i, label %cond.true.i, label %cond.false.i cond.true.i: ; CHECK: cond.true.i: ; CHECK-NEXT: br label %cond.end.i %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) br label %cond.end.i cond.false.i: ; CHECK: cond.false.i: ; CHECK-NEXT: br label %cond.end.i %3 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) br label %cond.end.i cond.end.i: ; CHECK: cond.end.i ; CHECK-NEXT: %[[HANDLE_IDX:.*]] = phi i32 [ 0, %cond.true.i ], [ 1, %cond.false.i ] ; CHECK: %[[TID:.*]] = tail call i32 @llvm.dx.flattened.thread.id.in.group() ; CHECK: %[[WAVE_MAX:.*]] = tail call i32 @llvm.dx.wave.reduce.max.i32(i32 %{{.*}}) ; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) ; CHECK-SAME: @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[HANDLE_IDX]], ptr nonnull @OutArr.str) ; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[TID]], i32 0, i32 %[[WAVE_MAX]]) ; CHECK-NEXT: ret void %cond.i.sroa.speculated = phi target("dx.RawBuffer", i32, 1, 0) [ %2, %cond.true.i ], [ %3, %cond.false.i ] %4 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str) %5 = tail call i32 @llvm.dx.flattened.thread.id.in.group() %6 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %4, i32 %5) %7 = load i32, ptr %6, align 4 %hlsl.wave.active.max.i = tail call i32 @llvm.dx.wave.reduce.max.i32(i32 %7) %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %cond.i.sroa.speculated, i32 %5) store i32 %hlsl.wave.active.max.i, ptr %8, align 4 ret void } ; Using a local array of global resources ; ; RWStructuredBuffer Outs[2] = {OutArr[0], OutArr[1]}; ; Outs[cond ? 0 : 1][GI] = In[GI]; ; ; This will ensure that the index is propogated through the select of two ; resources. So we want to check that the `select i1` is based on the handle ; indices, and that this index is used to generate the handle. ; ; CHECK-LABEL: @local_array_of_global_resources() define void @local_array_of_global_resources() { entry: %0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str) %c.cb_h.i.i = tail call target("dx.CBuffer", %__cblayout_c) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_s___cblayout_cst(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str) store target("dx.CBuffer", %__cblayout_c) %c.cb_h.i.i, ptr @c.cb, align 4 %1 = tail call i32 @llvm.dx.flattened.thread.id.in.group() %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) %3 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) %4 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %0, i32 %1) %5 = load i32, ptr %4, align 4 %c.cb = load target("dx.CBuffer", %__cblayout_c), ptr @c.cb, align 4 %6 = call ptr addrspace(2) @llvm.dx.resource.getpointer.p2.tdx.CBuffer_s___cblayout_cst(target("dx.CBuffer", %__cblayout_c) %c.cb, i32 0) %7 = load i32, ptr addrspace(2) %6, align 4 %loadedv.i = trunc nuw i32 %7 to i1 ; CHECK: %[[TID:.*]] = tail call i32 @llvm.dx.flattened.thread.id.in.group() ; CHECK: %[[HANDLE_IDX:.*]] = select i1 %loadedv.i, i32 0, i32 1 ; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) ; CHECK-SAME: @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[HANDLE_IDX]], ptr nonnull @OutArr.str) ; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[TID]], i32 0, i32 {{.*}}) %.sroa.speculated = select i1 %loadedv.i, target("dx.RawBuffer", i32, 1, 0) %2, target("dx.RawBuffer", i32, 1, 0) %3 %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %.sroa.speculated, i32 %1) store i32 %5, ptr %8, align 4 ret void }