llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-backend-spir-v Author: Kaitlin Peng (kmpeng) <details> <summary>Changes</summary> Fixes llvm/offload-test-suite#<!-- -->1037 on the Clang side. SPIR-V has no multi-component 64-bit image format, so `[RW]Buffer<uint/int64_t2>` was being lowered to an `Unknown` image format, and the offloader then backed it with the Vulkan `R64G64` format. Since `R64G64` is an optional format and most (if not all) devices do not support it for texel buffers, we got all 0 outputs. This PR changes the lowering to pack `uint/int64_t2` typed buffer elements into 4-component 32-bit images and bitcast during load/store. This approach mirrors DirectX, where `uint/int64_t2` is backed by 4x32-bit channels and reinterpreted in the shader. The PR also adds `RWBuffer<uint/int64_t2>` load/store tests to `Unsigned/SignedBufferLoadStore.ll`. --- Full diff: https://github.com/llvm/llvm-project/pull/208626.diff 5 Files Affected: - (modified) clang/lib/CodeGen/Targets/SPIR.cpp (+8) - (modified) clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl (+2-2) - (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+43-14) - (modified) llvm/test/CodeGen/SPIRV/hlsl-resources/SignedBufferLoadStore.ll (+26) - (modified) llvm/test/CodeGen/SPIRV/hlsl-resources/UnsignedBufferLoadStore.ll (+26) ``````````diff diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp index 88e7c7f9482a4..2c92fbde4f3c3 100644 --- a/clang/lib/CodeGen/Targets/SPIR.cpp +++ b/clang/lib/CodeGen/Targets/SPIR.cpp @@ -890,6 +890,14 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getSPIRVImageTypeFromHLSLResource( "The element type for a SPIR-V resource must be a scalar integer or " "floating point type."); + // SPIR-V has no 64-bit multi-component image format, so pack a 2-component + // 64-bit typed buffer into a 4-component 32-bit image. The backend + // reinterprets it with OpBitcast on load and store. + if (SampledType->isIntegerTy(64) && NumChannels == 2) { + SampledType = llvm::Type::getInt32Ty(Ctx); + NumChannels = 4; + } + // These parameters correspond to the operands to the OpTypeImage SPIR-V // instruction. See // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpTypeImage. diff --git a/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl b/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl index aebee894f79d5..6b967bb1e3e3a 100644 --- a/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl +++ b/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl @@ -23,11 +23,11 @@ RWBuffer<uint4> rwb_uint4; // 64-bit integers // CHECK: %"class.hlsl::RWBuffer.7" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 41) } RWBuffer<int64_t> rwb_i64; -// CHECK: %"class.hlsl::RWBuffer.8" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 0) } +// CHECK: %"class.hlsl::RWBuffer.8" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) } RWBuffer<int64_t2> rwb_i64_2; // CHECK: %"class.hlsl::RWBuffer.9" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 40) } RWBuffer<uint64_t> rwb_u64; -// CHECK: %"class.hlsl::RWBuffer.10" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 0) } +// CHECK: %"class.hlsl::RWBuffer.10" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 30) } RWBuffer<uint64_t2> rwb_u64_2; // Floats diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp index 11128eadef95f..15bb399c2ca09 100644 --- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp @@ -2073,6 +2073,24 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const { Register IdxReg = IntPtrDef->getOperand(3).getReg(); if (HandleType->getOpcode() == SPIRV::OpTypeImage) { + SPIRVTypeInst SampledType = + GR.getSPIRVTypeForVReg(HandleType->getOperand(1).getReg()); + SPIRVTypeInst StoreValCompType = + GR.getScalarOrVectorComponentType(GR.getSPIRVTypeForVReg(StoreVal)); + if (StoreValCompType && StoreValCompType != SampledType) { + // A wide element (e.g. int64_t2) is emulated with a narrower packed + // image. This bitcasts the value to match the format. + SPIRVTypeInst PackedType = widenTypeToVec4(SampledType, I); + Register PackedReg = + MRI->createVirtualRegister(GR.getRegClass(PackedType)); + BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpBitcast)) + .addDef(PackedReg) + .addUse(GR.getSPIRVTypeID(PackedType)) + .addUse(StoreVal) + .constrainAllUses(TII, TRI, RBI); + StoreVal = PackedReg; + } + auto BMI = BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageWrite)) .addUse(NewHandleReg) @@ -6286,22 +6304,20 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch( }; uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType); - if (ResultSize == 4) { - auto BMI = - BuildMI(*Pos.getParent(), Pos, Loc, - TII.get(IsFetch ? SPIRV::OpImageFetch : SPIRV::OpImageRead)) - .addDef(ResVReg) - .addUse(GR.getSPIRVTypeID(ResType)) - .addUse(ImageReg) - .addUse(IdxReg); - AddOperands(BMI); - BMI.constrainAllUses(TII, TRI, RBI); - return true; - } + // A wide element (e.g. int64_t2) is emulated with a narrower packed image, so + // its sampled type is different from the result. + SPIRVTypeInst SampledType = + GR.getSPIRVTypeForVReg(ImageType->getOperand(1).getReg()); + bool IsPacked = SampledType != GR.getScalarOrVectorComponentType(ResType); + SPIRVTypeInst ReadType = + widenTypeToVec4(IsPacked ? SampledType : ResType, Pos); + bool ReadTypeMatchesResult = ReadType == ResType; + // Read directly into the result, or into a temporary to bitcast/extract. + Register ReadReg = ReadTypeMatchesResult + ? ResVReg + : MRI->createVirtualRegister(GR.getRegClass(ReadType)); - SPIRVTypeInst ReadType = widenTypeToVec4(ResType, Pos); - Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType)); auto BMI = BuildMI(*Pos.getParent(), Pos, Loc, TII.get(IsFetch ? SPIRV::OpImageFetch : SPIRV::OpImageRead)) @@ -6312,6 +6328,19 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch( AddOperands(BMI); BMI.constrainAllUses(TII, TRI, RBI); + if (ReadTypeMatchesResult) + return true; + + if (IsPacked) { + // Reinterpret the packed vector as the wide result type. + BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpBitcast)) + .addDef(ResVReg) + .addUse(GR.getSPIRVTypeID(ResType)) + .addUse(ReadReg) + .constrainAllUses(TII, TRI, RBI); + return true; + } + if (ResultSize == 1) { BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpCompositeExtract)) .addDef(ResVReg) diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/SignedBufferLoadStore.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/SignedBufferLoadStore.ll index d15f88b5dd799..56ee2fd5ab011 100644 --- a/llvm/test/CodeGen/SPIRV/hlsl-resources/SignedBufferLoadStore.ll +++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/SignedBufferLoadStore.ll @@ -2,10 +2,13 @@ ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %} @.str.b0 = private unnamed_addr constant [3 x i8] c"B0\00", align 1 [email protected] = private unnamed_addr constant [3 x i8] c"B1\00", align 1 ; CHECK-DAG: [[uint:%[0-9]+]] = OpTypeInt 32 0 +; CHECK-DAG: [[ulong:%[0-9]+]] = OpTypeInt 64 0 ; CHECK-DAG: [[v2int:%[0-9]+]] = OpTypeVector [[uint]] 2 ; CHECK-DAG: [[v4int:%[0-9]+]] = OpTypeVector [[uint]] 4 +; CHECK-DAG: [[v2ulong:%[0-9]+]] = OpTypeVector [[ulong]] 2 ; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[uint]] 0 ; CHECK-DAG: [[one:%[0-9]+]] = OpConstant [[uint]] 1 ; CHECK-DAG: [[twenty:%[0-9]+]] = OpConstant [[uint]] 20 @@ -13,6 +16,9 @@ ; CHECK-DAG: [[ImageType:%[0-9]+]] = OpTypeImage [[uint]] Buffer 2 0 0 2 Unknown ; CHECK-DAG: [[ImagePtr:%[0-9]+]] = OpTypePointer UniformConstant [[ImageType]] ; CHECK-DAG: [[Var:%[0-9]+]] = OpVariable [[ImagePtr]] UniformConstant +; CHECK-DAG: [[PackedImageType:%[0-9]+]] = OpTypeImage [[uint]] Buffer 2 0 0 2 Rgba32i +; CHECK-DAG: [[PackedImagePtr:%[0-9]+]] = OpTypePointer UniformConstant [[PackedImageType]] +; CHECK-DAG: [[PackedVar:%[0-9]+]] = OpVariable [[PackedImagePtr]] UniformConstant ; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) ; CHECK: OpFunction @@ -133,5 +139,25 @@ bb_both: ret void } +; A 64-bit 2-component element has no representable image format, so it is packed into a 4-component 32-bit image. +; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) +; CHECK: OpFunction +define void @main_int64_vector2() local_unnamed_addr #0 { +entry: +; CHECK: [[H:%[0-9]+]] = OpLoad [[PackedImageType]] [[PackedVar]] + %s_h.i = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_21t(i32 4, i32 5, i32 1, i32 0, ptr nonnull @.str.b1) + +; CHECK: [[R:%[0-9]+]] = OpImageRead [[v4int]] [[H]] [[one]] SignExtend +; CHECK: [[V:%[0-9]+]] = OpBitcast [[v2ulong]] [[R]] + %0 = tail call noundef nonnull align 16 dereferenceable(16) ptr @llvm.spv.resource.getpointer.p0.tspirv.SignedImage_i32_5_2_0_0_2_21t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) %s_h.i, i32 1) + %1 = load <2 x i64>, ptr %0, align 16 + +; CHECK: [[W:%[0-9]+]] = OpBitcast [[v4int]] [[V]] +; CHECK: OpImageWrite {{%[0-9]+}} [[zero]] [[W]] SignExtend + %2 = tail call noundef nonnull align 16 dereferenceable(16) ptr @llvm.spv.resource.getpointer.p0.tspirv.SignedImage_i32_5_2_0_0_2_21t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) %s_h.i, i32 0) + store <2 x i64> %1, ptr %2, align 16 + ret void +} + attributes #0 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } attributes #1 = { mustprogress nocallback nofree nosync nounwind willreturn memory(none) } diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/UnsignedBufferLoadStore.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnsignedBufferLoadStore.ll index d038c5ce2574d..59602d1be77bf 100644 --- a/llvm/test/CodeGen/SPIRV/hlsl-resources/UnsignedBufferLoadStore.ll +++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnsignedBufferLoadStore.ll @@ -2,10 +2,13 @@ ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %} @.str.b0 = private unnamed_addr constant [3 x i8] c"B0\00", align 1 [email protected] = private unnamed_addr constant [3 x i8] c"B1\00", align 1 ; CHECK-DAG: [[uint:%[0-9]+]] = OpTypeInt 32 0 +; CHECK-DAG: [[ulong:%[0-9]+]] = OpTypeInt 64 0 ; CHECK-DAG: [[v2int:%[0-9]+]] = OpTypeVector [[uint]] 2 ; CHECK-DAG: [[v4int:%[0-9]+]] = OpTypeVector [[uint]] 4 +; CHECK-DAG: [[v2ulong:%[0-9]+]] = OpTypeVector [[ulong]] 2 ; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[uint]] 0 ; CHECK-DAG: [[one:%[0-9]+]] = OpConstant [[uint]] 1 ; CHECK-DAG: [[twenty:%[0-9]+]] = OpConstant [[uint]] 20 @@ -13,6 +16,9 @@ ; CHECK-DAG: [[ImageType:%[0-9]+]] = OpTypeImage [[uint]] Buffer 2 0 0 2 Unknown ; CHECK-DAG: [[ImagePtr:%[0-9]+]] = OpTypePointer UniformConstant [[ImageType]] ; CHECK-DAG: [[Var:%[0-9]+]] = OpVariable [[ImagePtr]] UniformConstant +; CHECK-DAG: [[PackedImageType:%[0-9]+]] = OpTypeImage [[uint]] Buffer 2 0 0 2 Rgba32ui +; CHECK-DAG: [[PackedImagePtr:%[0-9]+]] = OpTypePointer UniformConstant [[PackedImageType]] +; CHECK-DAG: [[PackedVar:%[0-9]+]] = OpVariable [[PackedImagePtr]] UniformConstant ; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) ; CHECK: OpFunction @@ -133,5 +139,25 @@ bb_both: ret void } +; A 64-bit 2-component element has no representable image format, so it is packed into a 4-component 32-bit image. +; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) +; CHECK: OpFunction +define void @main_uint64_vector2() local_unnamed_addr #0 { +entry: +; CHECK: [[H:%[0-9]+]] = OpLoad [[PackedImageType]] [[PackedVar]] + %s_h.i = tail call target("spirv.Image", i32, 5, 2, 0, 0, 2, 30) @llvm.spv.resource.handlefrombinding.tspirv.Image_i32_5_2_0_0_2_30t(i32 4, i32 5, i32 1, i32 0, ptr nonnull @.str.b1) + +; CHECK: [[R:%[0-9]+]] = OpImageRead [[v4int]] [[H]] [[one]] +; CHECK: [[V:%[0-9]+]] = OpBitcast [[v2ulong]] [[R]] + %0 = tail call noundef nonnull align 16 dereferenceable(16) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_30t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 30) %s_h.i, i32 1) + %1 = load <2 x i64>, ptr %0, align 16 + +; CHECK: [[W:%[0-9]+]] = OpBitcast [[v4int]] [[V]] +; CHECK: OpImageWrite {{%[0-9]+}} [[zero]] [[W]] + %2 = tail call noundef nonnull align 16 dereferenceable(16) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_30t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 30) %s_h.i, i32 0) + store <2 x i64> %1, ptr %2, align 16 + ret void +} + attributes #0 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } attributes #1 = { mustprogress nocallback nofree nosync nounwind willreturn memory(none) } `````````` </details> https://github.com/llvm/llvm-project/pull/208626 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
