From 2d8c47f147fe522678c1e21eada91b51c1fd20e4 Mon Sep 17 00:00:00 2001 From: Andrzej Janik Date: Fri, 17 May 2024 00:35:38 +0200 Subject: [PATCH] Support Meshroom (#153) --- README.md | 7 + ptx/lib/zluda_ptx_impl.bc | Bin 232464 -> 243120 bytes ptx/lib/zluda_ptx_impl.cpp | 190 +++++---- ptx/src/ast.rs | 25 +- ptx/src/emit.rs | 162 ++++++-- ptx/src/ptx.lalrpop | 87 ++++- ptx/src/raytracing.rs | 6 +- ptx/src/test/spirv_build/noreturn.ll | 19 + ptx/src/test/spirv_build/noreturn.ptx | 8 + ptx/src/test/spirv_run/call_global_ptr.ll | 71 ++++ ptx/src/test/spirv_run/call_global_ptr.ptx | 43 ++ ptx/src/test/spirv_run/isspacep.ll | 57 +++ ptx/src/test/spirv_run/isspacep.ptx | 28 ++ ptx/src/test/spirv_run/mod.rs | 2 + ptx/src/translate.rs | 172 +++++--- zluda/src/cuda.rs | 31 +- zluda/src/impl/array.rs | 65 +++- zluda/src/impl/hipfix.rs | 189 ++++++++- zluda/src/impl/mod.rs | 1 + zluda/src/impl/texobj.rs | 24 +- zluda/src/impl/texref.rs | 2 +- zluda/tests/kernel_suld.rs | 5 +- zluda/tests/kernel_sust.rs | 5 +- zluda/tests/kernel_tex.rs | 10 +- zluda/tests/linking.rs | 74 ++-- zluda/tests/mipmap_array.ptx | 47 +++ zluda/tests/mipmap_array.rs | 431 +++++++++++++++++++++ 27 files changed, 1486 insertions(+), 275 deletions(-) create mode 100644 ptx/src/test/spirv_build/noreturn.ll create mode 100644 ptx/src/test/spirv_build/noreturn.ptx create mode 100644 ptx/src/test/spirv_run/call_global_ptr.ll create mode 100644 ptx/src/test/spirv_run/call_global_ptr.ptx create mode 100644 ptx/src/test/spirv_run/isspacep.ll create mode 100644 ptx/src/test/spirv_run/isspacep.ptx create mode 100644 zluda/tests/mipmap_array.ptx create mode 100644 zluda/tests/mipmap_array.rs diff --git a/README.md b/README.md index 5be6a8ad..52781d07 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,13 @@ If an application fails to start under ZLUDA or crashes please check [Known Issu ### Applications +#### Meshroom + +Meshroom works only with on Windows due to an underlying ROCm/HIP issue. + +Meshroom 2023.3.0 might not work, it's recommended to use Meshroom freshly built from develop branch. See #79 and alicevision/Meshroom#595. Please open an issue here if you run into problems. + + #### llama.cpp If you are building llama.cpp with cmake and don't want it to crash on ZLUDA then you should use `CUDA_DOCKER_ARCH=compute_61` like this: diff --git a/ptx/lib/zluda_ptx_impl.bc b/ptx/lib/zluda_ptx_impl.bc index fec881a63b2759bcad291163e4ff3b21393bbd7b..f522728c98e45c9fb83e90c7773c2ff2d8d9e987 100644 GIT binary patch literal 243120 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

O9 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 literal 232464 zcmeEP4O~-I|G#&4+)Wr`DCowU7w`p1bbulV>IV3dSr3{PnlBq~BB4%1Mbq3EZyM?} zE%ebEsimY(SXSC&wE-#;7Mhlt+AK9BEiyIV{r}D#gWWSA?bYZ1X`hfu~TXbvZ6qnK3Q6!Z4i=b{hLAdbz2_9T`hW`b+ zz?I5s{-d-cENT^*C+hAbl=<;rNYVxk<;AXYiH&y;^N18mA9D_St!RX%%0Do^V_~sG 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 diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp index bea4202d..e5648105 100644 --- a/ptx/lib/zluda_ptx_impl.cpp +++ b/ptx/lib/zluda_ptx_impl.cpp @@ -232,23 +232,23 @@ static __device__ void image_store_pck(T value, typename Coordinates::type uint value_dword = transmute(value); if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x1 dim:1D unorm" : : "v"(value_dword), "v"(coord.x), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x1 dim:1D unorm" : : "v"(value_dword), "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x1 dim:2D unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x1 dim:2D unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x1 dim:3D unorm" : : "v"(value_dword), "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x1 dim:3D unorm" : : "v"(value_dword), "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x1 dim:1D_ARRAY unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x1 dim:1D_ARRAY unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x1 dim:2D_ARRAY unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x1 dim:2D_ARRAY unorm" : : "v"(value_dword), "v"(coord), "s"(*surface) : "memory"); } else { @@ -260,23 +260,23 @@ static __device__ void image_store_pck(T value, typename Coordinates::type uint2::Native_vec_ value_dword2 = transmute(value); if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x3 dim:1D unorm" : : "v"(value_dword2), "v"(coord.x), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x3 dim:1D unorm" : : "v"(value_dword2), "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x3 dim:2D unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x3 dim:2D unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x3 dim:3D unorm" : : "v"(value_dword2), "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x3 dim:3D unorm" : : "v"(value_dword2), "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x3 dim:1D_ARRAY unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x3 dim:1D_ARRAY unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0x3 dim:2D_ARRAY unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0x3 dim:2D_ARRAY unorm" : : "v"(value_dword2), "v"(coord), "s"(*surface) : "memory"); } else { @@ -288,23 +288,23 @@ static __device__ void image_store_pck(T value, typename Coordinates::type uint4::Native_vec_ value_dword4 = transmute(value); if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0xf dim:1D unorm" : : "v"(value_dword4), "v"(coord.x), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0xf dim:1D unorm" : : "v"(value_dword4), "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0xf dim:2D unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0xf dim:2D unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0xf dim:3D unorm" : : "v"(value_dword4), "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0xf dim:3D unorm" : : "v"(value_dword4), "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_store_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); + asm("image_store_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm" : : "v"(value_dword4), "v"(coord), "s"(*surface) : "memory"); } else { @@ -325,23 +325,23 @@ static __device__ T image_load_pck(typename Coordinates::type coord, surfac uint data; if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x1 dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x1 dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x1 dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x1 dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x1 dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x1 dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x1 dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x1 dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x1 dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x1 dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else { @@ -354,23 +354,23 @@ static __device__ T image_load_pck(typename Coordinates::type coord, surfac uint2::Native_vec_ data; if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x3 dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x3 dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x3 dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x3 dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x3 dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x3 dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x3 dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x3 dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0x3 dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0x3 dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else { @@ -383,23 +383,23 @@ static __device__ T image_load_pck(typename Coordinates::type coord, surfac uint4::Native_vec_ data; if constexpr (geo == ImageGeometry::_1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:1D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord.x), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:2D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::_3D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else { @@ -419,23 +419,23 @@ static __device__ uint4::Native_vec_ image_load_pck_full(typename Coordinates(coord)), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:3D unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(transmute(coord)), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A1D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:1D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else if constexpr (geo == ImageGeometry::A2D) { - asm volatile("image_load_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); + asm("image_load_pck %0, %1, %2 dmask:0xf dim:2D_ARRAY unorm\ns_waitcnt vmcnt(0)" : "=v"(data) : "v"(coord), "s"(*surface) : "memory"); } else { @@ -715,16 +715,26 @@ extern "C" tex_1d_f16(s32, int, tex1Dfetch_f16); tex_1d_f16(f32, float, tex1D_f16); -#define tex_2d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##2 ::Native_vec_ coord) \ - { \ - hipTextureObject_t textureObject = ptr->textureObject; \ - return tex2D(textureObject, float(coord.x), float(coord.y)).data; \ - } \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##2 ::Native_vec_ coord) \ - { \ - hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ - return tex2D(textureObject, float(coord.x), float(coord.y)).data; \ +#define tex_2d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##2 ::Native_vec_ coord) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex2D(textureObject, float(coord.x), float(coord.y)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##2 ::Native_vec_ coord, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex2DLod(textureObject, float(coord.x), float(coord.y), float(lod)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##2 ::Native_vec_ coord) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex2D(textureObject, float(coord.x), float(coord.y)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_indirect_2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##2 ::Native_vec_ coord, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex2DLod(textureObject, float(coord.x), float(coord.y), float(lod)).data; \ } __device__ half4 __ockl_image_sampleh_2D(unsigned int CONSTANT_SPACE *i, unsigned int ADDRESS_SPACE_CONSTANT *s, float2::Native_vec_ c); @@ -751,16 +761,26 @@ extern "C" tex_2d_f16(s32, int); tex_2d_f16(f32, float); -#define tex_3d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##4 ::Native_vec_ coord) \ - { \ - hipTextureObject_t textureObject = ptr->textureObject; \ - return tex3D(textureObject, float(coord.x), float(coord.y), float(coord.z)).data; \ - } \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##4 ::Native_vec_ coord) \ - { \ - hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ - return tex3D(textureObject, float(coord.x), float(coord.y), float(coord.z)).data; \ +#define tex_3d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##4 ::Native_vec_ coord) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex3D(textureObject, float(coord.x), float(coord.y), float(coord.z)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, HIP_COORD_TYPE##4 ::Native_vec_ coord, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex3DLod(textureObject, float(coord.x), float(coord.y), float(coord.z), float(lod)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##4 ::Native_vec_ coord) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex3D(textureObject, float(coord.x), float(coord.y), float(coord.z)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_indirect_3d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, HIP_COORD_TYPE##4 ::Native_vec_ coord, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex3DLod(textureObject, float(coord.x), float(coord.y), float(coord.z), float(lod)).data; \ } __device__ half4 __ockl_image_sampleh_3D(unsigned int CONSTANT_SPACE *i, unsigned int ADDRESS_SPACE_CONSTANT *s, float4::Native_vec_ c); @@ -787,16 +807,26 @@ extern "C" tex_3d_f16(s32, int); tex_3d_f16(f32, float); -#define tex_a1d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x) \ - { \ - hipTextureObject_t textureObject = ptr->textureObject; \ - return tex1DLayered(textureObject, float(x), int(layer)).data; \ - } \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x) \ - { \ - hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ - return tex1DLayered(textureObject, float(x), int(layer)).data; \ +#define tex_a1d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex1DLayered(textureObject, float(x), int(layer)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex1DLayeredLod(textureObject, float(x), int(layer), float(lod)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex1DLayered(textureObject, float(x), int(layer)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_indirect_a1d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex1DLayeredLod(textureObject, float(x), int(layer), float(lod)).data; \ } __device__ half4 __ockl_image_sampleh_1Da(unsigned int CONSTANT_SPACE *i, unsigned int ADDRESS_SPACE_CONSTANT *s, float2::Native_vec_ c); @@ -823,16 +853,26 @@ extern "C" tex_a1d_f16(s32, int); tex_a1d_f16(f32, float); -#define tex_a2d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y) \ - { \ - hipTextureObject_t textureObject = ptr->textureObject; \ - return tex2DLayered(textureObject, float(x), float(y), int(layer)).data; \ - } \ - HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y) \ - { \ - hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ - return tex2DLayered(textureObject, float(x), float(y), int(layer)).data; \ +#define tex_a2d(CHANNEL_TYPE, HIP_CHANNEL_TYPE, COORD_TYPE, HIP_COORD_TYPE) \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex2DLayered(textureObject, float(x), float(y), int(layer)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * ptr, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = ptr->textureObject; \ + return tex2DLayeredLod(textureObject, float(x), float(y), int(layer), float(lod)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_indirect_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex2DLayered(textureObject, float(x), float(y), int(layer)).data; \ + } \ + HIP_CHANNEL_TYPE##4 ::Native_vec_ FUNC(tex_level_indirect_a2d_v4_##CHANNEL_TYPE##_##COORD_TYPE)(uint64_t texobj, uint32_t layer, HIP_COORD_TYPE x, HIP_COORD_TYPE y, HIP_COORD_TYPE lod) \ + { \ + hipTextureObject_t textureObject = (hipTextureObject_t)texobj; \ + return tex2DLayeredLod(textureObject, float(x), float(y), int(layer), float(lod)).data; \ } __device__ half4 __ockl_image_sampleh_2Da(unsigned int CONSTANT_SPACE *i, unsigned int ADDRESS_SPACE_CONSTANT *s, float4::Native_vec_ c); diff --git a/ptx/src/ast.rs b/ptx/src/ast.rs index e5b5f976..55687739 100644 --- a/ptx/src/ast.rs +++ b/ptx/src/ast.rs @@ -460,8 +460,8 @@ pub enum Instruction { Membar { level: MemScope, }, - Tex(TexDetails, Arg4Tex

), - Suld(SurfaceDetails, Arg4Tex

), + Tex(TexDetails, Arg5Tex

), + Suld(SurfaceDetails, Arg5Tex

), Sust(SurfaceDetails, Arg4Sust

), Shfl(ShflMode, Arg5Shfl

), Shf(FunnelShift, Arg4

), @@ -476,6 +476,7 @@ pub enum Instruction { MatchAny(Arg3

), Red(AtomDetails, Arg2St

), Nanosleep(Arg1

), + Isspacep(StateSpace, Arg2

), Sad(ScalarType, Arg4

), } @@ -617,13 +618,6 @@ pub struct Arg4Setp { pub src2: P::Operand, } -pub struct Arg4Tex { - pub dst: P::Operand, - pub image: P::Operand, - pub layer: Option, - pub coordinates: P::Operand, -} - pub struct Arg4Sust { pub image: P::Operand, pub coordinates: P::Operand, @@ -639,6 +633,14 @@ pub struct Arg5 { pub src4: P::Operand, } +pub struct Arg5Tex { + pub dst: P::Operand, + pub image: P::Operand, + pub layer: Option, + pub coordinates: P::Operand, + pub lod: Option, +} + pub struct Arg5Setp { pub dst1: P::Id, pub dst2: Option, @@ -1317,6 +1319,7 @@ pub enum TuningDirective { MaxNtid(u32, u32, u32), ReqNtid(u32, u32, u32), MinNCtaPerSm(u32), + Noreturn, } #[repr(u8)] @@ -1382,8 +1385,8 @@ pub enum TextureGeometry { #[derive(Clone)] pub enum Initializer { Constant(ImmediateValue), - Global(ID, Type), - GenericGlobal(ID, Type), + Global(ID), + GenericGlobal(ID), Add(Box<(Initializer, Initializer)>), Array(Vec>), } diff --git a/ptx/src/emit.rs b/ptx/src/emit.rs index e2d00d98..1c88cd0e 100644 --- a/ptx/src/emit.rs +++ b/ptx/src/emit.rs @@ -403,27 +403,20 @@ unsafe fn get_llvm_const( let const2 = get_llvm_const(ctx, type_, Some(init2))?; LLVMConstAdd(const1, const2) } - (_, Some(ast::Initializer::Global(id, type_))) => { + (_, Some(ast::Initializer::Global(id))) => { let name = ctx.names.value(id)?; let b64 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::B64))?; - let mut zero = LLVMConstInt(b64, 0, 0); - let src_type = get_llvm_type(ctx, &type_)?; - let global_ptr = LLVMConstInBoundsGEP2(src_type, name, &mut zero, 1); - LLVMConstPtrToInt(global_ptr, b64) + LLVMConstPtrToInt(name, b64) } - (_, Some(ast::Initializer::GenericGlobal(id, type_))) => { + (_, Some(ast::Initializer::GenericGlobal(id))) => { let name = ctx.names.value(id)?; - let b64 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::B64))?; - let mut zero = LLVMConstInt(b64, 0, 0); - let src_type = get_llvm_type(ctx, &type_)?; - let global_ptr = LLVMConstInBoundsGEP2(src_type, name, &mut zero, 1); - // void pointers are illegal in LLVM IR let b8 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::B8))?; let b8_generic_ptr = LLVMPointerType( b8, get_llvm_address_space(&ctx.constants, ast::StateSpace::Generic)?, ); - let generic_ptr = LLVMConstAddrSpaceCast(global_ptr, b8_generic_ptr); + let generic_ptr = LLVMConstAddrSpaceCast(name, b8_generic_ptr); + let b64 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::B64))?; LLVMConstPtrToInt(generic_ptr, b64) } _ => return Err(TranslateError::todo()), @@ -551,7 +544,7 @@ fn emit_method<'a, 'input>( emit_statement(ctx, is_kernel, statement)?; } // happens if there is a post-ret trailing label - terminate_current_block_if_needed(ctx, None); + terminate_current_block_if_not_terminated(ctx, None); unsafe { LLVMPositionBuilderAtEnd(ctx.builder.get(), bb_with_variables) }; unsafe { LLVMBuildBr(ctx.builder.get(), starting_bb) }; Ok(()) @@ -593,6 +586,17 @@ fn emit_tuning_single<'a>( format!("{0},{0}", size).as_bytes(), ); } + ast::TuningDirective::Noreturn => { + let noreturn = b"noreturn"; + let attr_kind = unsafe { + LLVMGetEnumAttributeKindForName(noreturn.as_ptr().cast(), noreturn.len()) + }; + if attr_kind == 0 { + panic!(); + } + let noreturn = unsafe { LLVMCreateEnumAttribute(ctx.context.get(), attr_kind, 0) }; + unsafe { LLVMAddAttributeAtIndex(llvm_method, LLVMAttributeFunctionIndex, noreturn) }; + } } } @@ -621,6 +625,9 @@ fn emit_statement( is_kernel: bool, statement: crate::translate::ExpandedStatement, ) -> Result<(), TranslateError> { + if !matches!(statement, crate::translate::Statement::Label(..)) { + start_next_block_if_terminated(ctx); + } Ok(match statement { crate::translate::Statement::Label(label) => emit_label(ctx, label)?, crate::translate::Statement::Variable(var) => emit_function_variable(ctx, var)?, @@ -1155,6 +1162,7 @@ fn emit_instruction( ast::Instruction::Vshr(arg) => emit_inst_vshr(ctx, arg)?, ast::Instruction::Set(details, arg) => emit_inst_set(ctx, details, arg)?, ast::Instruction::Red(details, arg) => emit_inst_red(ctx, details, arg)?, + ast::Instruction::Isspacep(space, arg) => emit_inst_isspacep(ctx, *space, arg)?, ast::Instruction::Sad(type_, arg) => emit_inst_sad(ctx, *type_, arg)?, // replaced by function calls or Statement variants ast::Instruction::Activemask { .. } @@ -1180,6 +1188,70 @@ fn emit_instruction( }) } +fn emit_inst_isspacep( + ctx: &mut EmitContext, + space: ast::StateSpace, + arg: &ast::Arg2, +) -> Result<(), TranslateError> { + match space { + ast::StateSpace::Local => { + emit_inst_isspacep_impl(ctx, Some(arg.dst), arg.src, b"llvm.amdgcn.is.private\0")?; + Ok(()) + } + ast::StateSpace::Shared => { + emit_inst_isspacep_impl(ctx, Some(arg.dst), arg.src, b"llvm.amdgcn.is.shared\0")?; + Ok(()) + } + ast::StateSpace::Global => { + let builder = ctx.builder.get(); + let is_private = + emit_inst_isspacep_impl(ctx, None, arg.src, b"llvm.amdgcn.is.private\0")?; + let is_shared = + emit_inst_isspacep_impl(ctx, None, arg.src, b"llvm.amdgcn.is.shared\0")?; + let private_or_shared = + unsafe { LLVMBuildOr(builder, is_private, is_shared, LLVM_UNNAMED) }; + let i1_true = unsafe { + LLVMConstInt( + get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::Pred))?, + 1, + 0, + ) + }; + ctx.names.register_result(arg.dst, |dst| unsafe { + // I'd rathr user LLVMBuildNeg(...), but when using LLVMBuildNeg(...) in LLVM 15, + // LLVM emits this broken IR: + // %"14" = sub i1 false, %4 + LLVMBuildSub(builder, i1_true, private_or_shared, dst) + }); + Ok(()) + } + _ => Err(TranslateError::unreachable()), + } +} + +fn emit_inst_isspacep_impl( + ctx: &mut EmitContext, + dst: Option, + src: Id, + intrinsic: &[u8], +) -> Result { + let src = ctx.names.value(src)?; + let b8 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::B8))?; + let b8_generic_ptr = unsafe { + LLVMPointerType( + b8, + get_llvm_address_space(&ctx.constants, ast::StateSpace::Generic)?, + ) + }; + let src = unsafe { LLVMBuildIntToPtr(ctx.builder.get(), src, b8_generic_ptr, LLVM_UNNAMED) }; + emit_intrinsic_arg2( + ctx, + (ast::ScalarType::Pred, dst), + (ast::ScalarType::B8, ast::StateSpace::Generic, src), + intrinsic, + ) +} + fn emit_inst_sad( ctx: &mut EmitContext, type_: ast::ScalarType, @@ -1255,7 +1327,8 @@ fn emit_inst_bfind( let builder = ctx.builder.get(); let src = arg.src.get_llvm_value(&mut ctx.names)?; let llvm_dst_type = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::U32))?; - let const_0 = unsafe { LLVMConstInt(llvm_dst_type, 0, 0) }; + let llvm_src_type = get_llvm_type(ctx, &ast::Type::Scalar(details.type_))?; + let const_0 = unsafe { LLVMConstInt(llvm_src_type, 0, 0) }; let const_int_max = unsafe { LLVMConstInt(llvm_dst_type, u64::MAX, 0) }; let is_zero = unsafe { LLVMBuildICmp( @@ -1266,7 +1339,7 @@ fn emit_inst_bfind( LLVM_UNNAMED, ) }; - let mut clz_result = emit_inst_clz_impl(ctx, ast::ScalarType::U32, None, arg.src, true)?; + let mut clz_result = emit_inst_clz_impl(ctx, details.type_, None, arg.src, true)?; if !details.shift { let bits = unsafe { LLVMConstInt( @@ -1442,7 +1515,7 @@ fn emit_inst_abs( emit_intrinsic_arg2( ctx, (details.typ, Some(args.dst)), - (details.typ, args.src), + (details.typ, ast::StateSpace::Reg, args.src), intrinsic_name.as_bytes(), )?; } else { @@ -1610,7 +1683,7 @@ fn emit_inst_rsqrt( let sqrt_result = emit_intrinsic_arg2( ctx, (details.typ, None), - (details.typ, args.src), + (details.typ, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; unsafe { LLVMZludaSetFastMathFlags(sqrt_result, FastMathFlags::ApproxFunc) }; @@ -1668,7 +1741,7 @@ fn emit_inst_sqrt( let sqrt_result = emit_intrinsic_arg2( ctx, (details.type_, Some(args.dst)), - (details.type_, args.src), + (details.type_, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; unsafe { LLVMZludaSetFastMathFlags(sqrt_result, fast_math) }; @@ -2491,7 +2564,7 @@ fn emit_inst_cvt( emit_intrinsic_arg2( ctx, (type_, Some(args.dst)), - (type_, args.src), + (type_, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; } @@ -2505,7 +2578,7 @@ fn emit_inst_cvt( emit_intrinsic_arg2( ctx, (type_, Some(args.dst)), - (type_, args.src), + (type_, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; } @@ -2519,7 +2592,7 @@ fn emit_inst_cvt( emit_intrinsic_arg2( ctx, (type_, Some(args.dst)), - (type_, args.src), + (type_, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; } @@ -2533,7 +2606,7 @@ fn emit_inst_cvt( emit_intrinsic_arg2( ctx, (type_, Some(args.dst)), - (type_, args.src), + (type_, ast::StateSpace::Reg, args.src), intrinsic_fn, )?; } @@ -2699,7 +2772,7 @@ fn emit_inst_cos( let cos_value = emit_intrinsic_arg2( ctx, (ast::ScalarType::F32, Some(args.dst)), - (ast::ScalarType::F32, args.src), + (ast::ScalarType::F32, ast::StateSpace::Reg, args.src), function_name, )?; unsafe { LLVMZludaSetFastMathFlags(cos_value, FastMathFlags::ApproxFunc) }; @@ -2714,7 +2787,7 @@ fn emit_inst_sin( let cos_value = emit_intrinsic_arg2( ctx, (ast::ScalarType::F32, Some(args.dst)), - (ast::ScalarType::F32, args.src), + (ast::ScalarType::F32, ast::StateSpace::Reg, args.src), function_name, )?; unsafe { LLVMZludaSetFastMathFlags(cos_value, FastMathFlags::ApproxFunc) }; @@ -2918,7 +2991,7 @@ fn emit_inst_brev( emit_intrinsic_arg2( ctx, (type_, Some(args.dst)), - (type_, args.src), + (type_, ast::StateSpace::Reg, args.src), function_name, )?; Ok(()) @@ -2936,8 +3009,12 @@ fn emit_inst_popc( _ => return Err(TranslateError::unreachable()), }; let popc_dst = if shorten { None } else { Some(args.dst) }; - let popc_result = - emit_intrinsic_arg2(ctx, (type_, popc_dst), (type_, args.src), function_name)?; + let popc_result = emit_intrinsic_arg2( + ctx, + (type_, popc_dst), + (type_, ast::StateSpace::Reg, args.src), + function_name, + )?; if shorten { let llvm_i32 = get_llvm_type(ctx, &ast::Type::Scalar(ast::ScalarType::U32))?; ctx.names.register_result(args.dst, |dst_name| unsafe { @@ -2955,7 +3032,7 @@ fn emit_inst_ex2( let llvm_value = emit_intrinsic_arg2( ctx, (ast::ScalarType::F32, Some(args.dst)), - (ast::ScalarType::F32, args.src), + (ast::ScalarType::F32, ast::StateSpace::Reg, args.src), function_name, )?; unsafe { LLVMZludaSetFastMathFlags(llvm_value, FastMathFlags::ApproxFunc) }; @@ -2970,7 +3047,7 @@ fn emit_inst_lg2( let llvm_value = emit_intrinsic_arg2( ctx, (ast::ScalarType::F32, Some(args.dst)), - (ast::ScalarType::F32, args.src), + (ast::ScalarType::F32, ast::StateSpace::Reg, args.src), function_name, )?; unsafe { LLVMZludaSetFastMathFlags(llvm_value, FastMathFlags::ApproxFunc) }; @@ -3009,16 +3086,16 @@ fn emit_intrinsic_arg0( fn emit_intrinsic_arg2( ctx: &mut EmitContext, (dst_type, dst): (ast::ScalarType, Option), - (src_type, src): (ast::ScalarType, Id), + (src_type, src_space, src): (ast::ScalarType, ast::StateSpace, impl GetLLVMValue), intrinsic_name: &[u8], ) -> Result { let builder = ctx.builder.get(); - let mut llvm_src = ctx.names.value(src)?; + let mut llvm_src = src.get_llvm_value(&mut ctx.names)?; let dst_type = get_llvm_type(ctx, &ast::Type::Scalar(dst_type))?; let function_type = get_llvm_function_type( ctx, dst_type, - iter::once((&ast::Type::Scalar(src_type), ast::StateSpace::Reg)), + iter::once((&ast::Type::Scalar(src_type), src_space)), )?; let mut function_value = unsafe { LLVMGetNamedFunction(ctx.module.get(), intrinsic_name.as_ptr() as _) }; @@ -3508,12 +3585,15 @@ fn emit_store_var( fn emit_label(ctx: &mut EmitContext, label: Id) -> Result<(), TranslateError> { let new_block = unsafe { LLVMValueAsBasicBlock(ctx.names.value(label)?) }; - terminate_current_block_if_needed(ctx, Some(new_block)); + terminate_current_block_if_not_terminated(ctx, Some(new_block)); unsafe { LLVMPositionBuilderAtEnd(ctx.builder.get(), new_block) }; Ok(()) } -fn terminate_current_block_if_needed(ctx: &mut EmitContext, new_block: Option) { +fn terminate_current_block_if_not_terminated( + ctx: &mut EmitContext, + new_block: Option, +) { let current_block = unsafe { LLVMGetInsertBlock(ctx.builder.get()) }; if current_block == ptr::null_mut() { return; @@ -3528,6 +3608,20 @@ fn terminate_current_block_if_needed(ctx: &mut EmitContext, new_block: Option( ctx: &mut EmitContext, method: &crate::translate::Function<'input>, diff --git a/ptx/src/ptx.lalrpop b/ptx/src/ptx.lalrpop index 5ec97e15..612d3bda 100644 --- a/ptx/src/ptx.lalrpop +++ b/ptx/src/ptx.lalrpop @@ -97,6 +97,7 @@ match { ".l", ".le", ".leu", + ".level", ".lo", ".loc", ".local", @@ -116,6 +117,7 @@ match { ".ne", ".neu", ".noftz", + ".noreturn", ".num", ".or", ".param", @@ -201,6 +203,7 @@ match { "function_name", "generic", "inlined_at", + "isspacep", "ld", "ldmatrix", "lg2", @@ -283,6 +286,7 @@ ExtendedID : &'input str = { "function_name", "generic", "inlined_at", + "isspacep", "ld", "ldmatrix", "lg2", @@ -531,6 +535,8 @@ LinkingDirective: ast::LinkingDirective = { }; TuningDirective: ast::TuningDirective = { + // not a performance tuning directive but fits here in the grammar + ".noreturn" => ast::TuningDirective::Noreturn, ".maxnreg" => ast::TuningDirective::MaxNReg(ncta), ".maxntid" => ast::TuningDirective::MaxNtid(nx, 1, 1), ".maxntid" "," => ast::TuningDirective::MaxNtid(nx, ny, 1), @@ -648,8 +654,8 @@ Initializer: ast::Initializer<&'input str> = { InitializerNoAdd: ast::Initializer<&'input str> = { => ast::Initializer::Constant(val), - => ast::Initializer::Global(id, ast::Type::Struct(Vec::new())), - "generic" "(" ")" => ast::Initializer::GenericGlobal(id, ast::Type::Struct(Vec::new())), + => ast::Initializer::Global(id), + "generic" "(" ")" => ast::Initializer::GenericGlobal(id), "{" > "}" => ast::Initializer::Array(array_init) } @@ -841,6 +847,7 @@ Instruction: ast::Instruction> = { InstMatch, InstRed, InstNanosleep, + InstIsspacep, InstSad }; @@ -2067,11 +2074,23 @@ InstMembar: ast::Instruction> = { // https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#texture-instructions-tex InstTex: ast::Instruction> = { "tex" ".v4" "," "[" "," "]" => { - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates, - layer: None + layer: None, + lod: None + }; + let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; + ast::Instruction::Tex(details, args) + }, + "tex" ".level" ".v4" "," "[" "," "]" "," => { + let args = ast::Arg5Tex { + dst, + image, + coordinates, + layer: None, + lod: Some(lod) }; let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; ast::Instruction::Tex(details, args) @@ -2082,11 +2101,25 @@ InstTex: ast::Instruction> = { "tex" ".a1d" ".v4" "," "[" "," "{" "," "}" "]" => { let geometry = ast::TextureGeometry::Array1D; - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates: ast::Operand::VecPack(vec![x]), - layer: Some(layer) + layer: Some(layer), + lod: None + }; + let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; + ast::Instruction::Tex(details, args) + }, + "tex" ".level" ".a1d" ".v4" + "," "[" "," "{" "," "}" "]" "," => { + let geometry = ast::TextureGeometry::Array1D; + let args = ast::Arg5Tex { + dst, + image, + coordinates: ast::Operand::VecPack(vec![x]), + layer: Some(layer), + lod: Some(lod) }; let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; ast::Instruction::Tex(details, args) @@ -2094,11 +2127,25 @@ InstTex: ast::Instruction> = { "tex" ".a2d" ".v4" "," "[" "," "{" "," "," "," RegOrImmediate "}" "]" => { let geometry = ast::TextureGeometry::Array2D; - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates: ast::Operand::VecPack(vec![x, y]), - layer: Some(layer) + layer: Some(layer), + lod: None + }; + let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; + ast::Instruction::Tex(details, args) + }, + "tex" ".level" ".a2d" ".v4" + "," "[" "," "{" "," "," "," RegOrImmediate "}" "]" "," => { + let geometry = ast::TextureGeometry::Array2D; + let args = ast::Arg5Tex { + dst, + image, + coordinates: ast::Operand::VecPack(vec![x, y]), + layer: Some(layer), + lod: Some(lod) }; let details = ast::TexDetails { geometry, channel_type, coordinate_type, direct: false }; ast::Instruction::Tex(details, args) @@ -2108,33 +2155,36 @@ InstTex: ast::Instruction> = { // https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#surface-instructions-suld InstSuld: ast::Instruction> = { "suld" ".b" ".trap" "," "[" "," "]" => { - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates, layer: None, + lod: None, }; let details = ast::SurfaceDetails { geometry, vector, type_, direct: false, }; ast::Instruction::Suld(details, args) }, "suld" ".b" ".a1d" ".trap" "," "[" "," "{" "," "}" "]" => { let geometry = ast::TextureGeometry::Array1D; - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates: ast::Operand::VecPack(vec![x]), layer: Some(layer), + lod: None, }; let details = ast::SurfaceDetails { geometry, vector, type_, direct: false, }; ast::Instruction::Suld(details, args) }, "suld" ".b" ".a2d" ".trap" "," "[" "," "{" "," "," "," RegOrImmediate "}" "]" => { let geometry = ast::TextureGeometry::Array2D; - let args = ast::Arg4Tex { + let args = ast::Arg5Tex { dst, image, coordinates: ast::Operand::VecPack(vec![x, y]), layer: Some(layer), + lod: None, }; let details = ast::SurfaceDetails { geometry, vector, type_, direct: false, }; ast::Instruction::Suld(details, args) @@ -2380,6 +2430,20 @@ InstNanosleep: ast::Instruction> = { } } +// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#miscellaneous-instructions-nanosleep +InstIsspacep: ast::Instruction> = { + "isspacep" ".local" => { + ast::Instruction::Isspacep(ast::StateSpace::Local, a) + }, + "isspacep" ".shared" => { + ast::Instruction::Isspacep(ast::StateSpace::Shared, a) + }, + "isspacep" ".global" => { + ast::Instruction::Isspacep(ast::StateSpace::Global, a) + } +} + + // https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#integer-arithmetic-instructions-sad InstSad: ast::Instruction> = { "sad" => { @@ -2387,6 +2451,7 @@ InstSad: ast::Instruction> = { } } + NegTypeFtz: ast::ScalarType = { ".f16" => ast::ScalarType::F16, ".f16x2" => ast::ScalarType::F16x2, diff --git a/ptx/src/raytracing.rs b/ptx/src/raytracing.rs index 4c68a112..25c2e7c6 100644 --- a/ptx/src/raytracing.rs +++ b/ptx/src/raytracing.rs @@ -2996,11 +2996,12 @@ fn convert_optix_builtin_variable_and_attribute_access_single_function<'input>( } Statement::Instruction(ast::Instruction::Tex( tex, - ast::Arg4Tex { + ast::Arg5Tex { dst, image, layer, coordinates, + lod, }, )) => { if let Some(StateSpaceRemapping::ToBlock(id, ast::StateSpace::Global, offset)) = @@ -3014,11 +3015,12 @@ fn convert_optix_builtin_variable_and_attribute_access_single_function<'input>( )?; result.push(Statement::Instruction(ast::Instruction::Tex( tex, - ast::Arg4Tex { + ast::Arg5Tex { dst, image, layer, coordinates, + lod, }, ))); } else { diff --git a/ptx/src/test/spirv_build/noreturn.ll b/ptx/src/test/spirv_build/noreturn.ll new file mode 100644 index 00000000..286b289a --- /dev/null +++ b/ptx/src/test/spirv_build/noreturn.ll @@ -0,0 +1,19 @@ +target datalayout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7" +target triple = "amdgcn-amd-amdhsa" + +; Function Attrs: noreturn +define private void @noreturn(i64 %"6") #0 { +"9": + %"3" = alloca i64, align 8, addrspace(5) + %"4" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"4", align 1 + %"5" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"5", align 1 + %"8" = alloca i64, align 8, addrspace(5) + store i64 %"6", ptr addrspace(5) %"3", align 8 + %"7" = load i64, ptr addrspace(5) %"3", align 8 + store i64 %"7", ptr addrspace(5) %"8", align 8 + ret void +} + +attributes #0 = { noreturn "amdgpu-unsafe-fp-atomics"="true" "denormal-fp-math"="ieee,ieee" "denormal-fp-math-f32"="ieee,ieee" "no-trapping-math"="true" "uniform-work-group-size"="true" } diff --git a/ptx/src/test/spirv_build/noreturn.ptx b/ptx/src/test/spirv_build/noreturn.ptx new file mode 100644 index 00000000..fd34bc6a --- /dev/null +++ b/ptx/src/test/spirv_build/noreturn.ptx @@ -0,0 +1,8 @@ +.version 6.5 +.target sm_30 +.address_size 64 + +.weak .func noreturn(.param .b64 noreturn_0) +.noreturn +{ +} \ No newline at end of file diff --git a/ptx/src/test/spirv_run/call_global_ptr.ll b/ptx/src/test/spirv_run/call_global_ptr.ll new file mode 100644 index 00000000..edd07ebf --- /dev/null +++ b/ptx/src/test/spirv_run/call_global_ptr.ll @@ -0,0 +1,71 @@ +target datalayout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7" +target triple = "amdgcn-amd-amdhsa" + +@fn_ptrs = protected addrspace(1) externally_initialized global [2 x i64] [i64 0, i64 ptrtoint (ptr @incr to i64)], align 8 + +define private i64 @incr(i64 %"36") #0 { +"60": + %"21" = alloca i64, align 8, addrspace(5) + %"20" = alloca i64, align 8, addrspace(5) + %"24" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"24", align 1 + %"25" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"25", align 1 + %"51" = alloca i64, align 8, addrspace(5) + %"52" = alloca i64, align 8, addrspace(5) + %"17" = alloca i64, align 8, addrspace(5) + store i64 %"36", ptr addrspace(5) %"21", align 8 + %"37" = load i64, ptr addrspace(5) %"21", align 8 + store i64 %"37", ptr addrspace(5) %"52", align 8 + %"38" = load i64, ptr addrspace(5) %"52", align 8 + store i64 %"38", ptr addrspace(5) %"17", align 8 + %"40" = load i64, ptr addrspace(5) %"17", align 8 + %"39" = add i64 %"40", 1 + store i64 %"39", ptr addrspace(5) %"17", align 8 + %"41" = load i64, ptr addrspace(5) %"17", align 8 + store i64 %"41", ptr addrspace(5) %"51", align 8 + %"42" = load i64, ptr addrspace(5) %"51", align 8 + store i64 %"42", ptr addrspace(5) %"20", align 8 + %"43" = load i64, ptr addrspace(5) %"20", align 8 + ret i64 %"43" +} + +define protected amdgpu_kernel void @call_global_ptr(ptr addrspace(4) byref(i64) %"47", ptr addrspace(4) byref(i64) %"48") #0 { +"59": + %"22" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"22", align 1 + %"23" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"23", align 1 + %"8" = alloca i64, align 8, addrspace(5) + %"9" = alloca i64, align 8, addrspace(5) + %"10" = alloca i64, align 8, addrspace(5) + %"11" = alloca i64, align 8, addrspace(5) + %"49" = alloca i64, align 8, addrspace(5) + %"50" = alloca i64, align 8, addrspace(5) + %"26" = load i64, ptr addrspace(4) %"47", align 8 + store i64 %"26", ptr addrspace(5) %"8", align 8 + %"27" = load i64, ptr addrspace(4) %"48", align 8 + store i64 %"27", ptr addrspace(5) %"9", align 8 + %"29" = load i64, ptr addrspace(5) %"8", align 8 + %"53" = inttoptr i64 %"29" to ptr addrspace(1) + %"28" = load i64, ptr addrspace(1) %"53", align 8 + store i64 %"28", ptr addrspace(5) %"10", align 8 + %"30" = load i64, ptr addrspace(5) %"10", align 8 + store i64 %"30", ptr addrspace(5) %"49", align 8 + %"31" = load i64, ptr getelementptr inbounds (i8, ptr addrspacecast (ptr addrspace(1) @fn_ptrs to ptr), i64 8), align 8 + store i64 %"31", ptr addrspace(5) %"11", align 8 + %"18" = load i64, ptr addrspace(5) %"49", align 8 + %"32" = load i64, ptr addrspace(5) %"11", align 8 + %0 = inttoptr i64 %"32" to ptr + %"19" = call i64 %0(i64 %"18") + store i64 %"19", ptr addrspace(5) %"50", align 8 + %"33" = load i64, ptr addrspace(5) %"50", align 8 + store i64 %"33", ptr addrspace(5) %"10", align 8 + %"34" = load i64, ptr addrspace(5) %"9", align 8 + %"35" = load i64, ptr addrspace(5) %"10", align 8 + %"58" = inttoptr i64 %"34" to ptr addrspace(1) + store i64 %"35", ptr addrspace(1) %"58", align 8 + ret void +} + +attributes #0 = { "amdgpu-unsafe-fp-atomics"="true" "denormal-fp-math"="ieee,ieee" "denormal-fp-math-f32"="ieee,ieee" "no-trapping-math"="true" "uniform-work-group-size"="true" } diff --git a/ptx/src/test/spirv_run/call_global_ptr.ptx b/ptx/src/test/spirv_run/call_global_ptr.ptx new file mode 100644 index 00000000..59b1d262 --- /dev/null +++ b/ptx/src/test/spirv_run/call_global_ptr.ptx @@ -0,0 +1,43 @@ +.version 6.5 +.target sm_30 +.address_size 64 + +.weak .func (.param.u64 output) incr (.param.u64 input); + +.weak .global .align 8 .u64 fn_ptrs[2] = {0, incr}; + +.visible .entry call_global_ptr( + .param .u64 input, + .param .u64 output +) +{ + .reg .u64 in_addr; + .reg .u64 out_addr; + .reg .u64 temp; + .reg .u64 fn_ptr; + + ld.param.u64 in_addr, [input]; + ld.param.u64 out_addr, [output]; + + ld.global.u64 temp, [in_addr]; + .param.u64 incr_in; + .param.u64 incr_out; + st.param.b64 [incr_in], temp; +incr_fn_ptr: .callprototype (.param .u64 _) _ (.param .u64 _); + ld.u64 fn_ptr, [fn_ptrs+8]; + call (incr_out), fn_ptr, (incr_in), incr_fn_ptr; + ld.param.u64 temp, [incr_out]; + st.global.u64 [out_addr], temp; + ret; +} + +.weak .func (.param .u64 output) incr( + .param .u64 input +) +{ + .reg .u64 temp; + ld.param.u64 temp, [input]; + add.u64 temp, temp, 1; + st.param.u64 [output], temp; + ret; +} \ No newline at end of file diff --git a/ptx/src/test/spirv_run/isspacep.ll b/ptx/src/test/spirv_run/isspacep.ll new file mode 100644 index 00000000..08371e31 --- /dev/null +++ b/ptx/src/test/spirv_run/isspacep.ll @@ -0,0 +1,57 @@ +target datalayout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7" +target triple = "amdgcn-amd-amdhsa" + +define protected amdgpu_kernel void @isspacep(ptr addrspace(4) byref(i64) %"32", ptr addrspace(4) byref(i64) %"33") #0 { +"36": + %"10" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"10", align 1 + %"11" = alloca i1, align 1, addrspace(5) + store i1 false, ptr addrspace(5) %"11", align 1 + %"4" = alloca i64, align 8, addrspace(5) + %"5" = alloca i64, align 8, addrspace(5) + %"6" = alloca i1, align 1, addrspace(5) + %"7" = alloca i1, align 1, addrspace(5) + %"8" = alloca i32, align 4, addrspace(5) + %"9" = alloca i32, align 4, addrspace(5) + %"12" = load i64, ptr addrspace(4) %"32", align 8 + store i64 %"12", ptr addrspace(5) %"4", align 8 + %"13" = load i64, ptr addrspace(4) %"33", align 8 + store i64 %"13", ptr addrspace(5) %"5", align 8 + %"15" = load i64, ptr addrspace(5) %"4", align 8 + %0 = inttoptr i64 %"15" to ptr + %1 = call i1 @llvm.amdgcn.is.private(ptr %0) + %2 = inttoptr i64 %"15" to ptr + %3 = call i1 @llvm.amdgcn.is.shared(ptr %2) + %4 = or i1 %1, %3 + %"14" = sub i1 true, %4 + store i1 %"14", ptr addrspace(5) %"6", align 1 + %"17" = load i1, ptr addrspace(5) %"6", align 1 + %"16" = select i1 %"17", i32 1, i32 0 + store i32 %"16", ptr addrspace(5) %"8", align 4 + %"19" = load i64, ptr addrspace(5) %"4", align 8 + %5 = inttoptr i64 %"19" to ptr + %"18" = call i1 @llvm.amdgcn.is.shared(ptr %5) + store i1 %"18", ptr addrspace(5) %"7", align 1 + %"21" = load i1, ptr addrspace(5) %"7", align 1 + %"20" = select i1 %"21", i32 1, i32 0 + store i32 %"20", ptr addrspace(5) %"9", align 4 + %"22" = load i64, ptr addrspace(5) %"5", align 8 + %"23" = load i32, ptr addrspace(5) %"8", align 4 + %"34" = inttoptr i64 %"22" to ptr + store i32 %"23", ptr %"34", align 4 + %"24" = load i64, ptr addrspace(5) %"5", align 8 + %"25" = load i32, ptr addrspace(5) %"9", align 4 + %"35" = inttoptr i64 %"24" to ptr + %"38" = getelementptr inbounds i8, ptr %"35", i64 4 + store i32 %"25", ptr %"38", align 4 + ret void +} + +; Function Attrs: nounwind readnone speculatable willreturn +declare i1 @llvm.amdgcn.is.private(ptr nocapture) #1 + +; Function Attrs: nounwind readnone speculatable willreturn +declare i1 @llvm.amdgcn.is.shared(ptr nocapture) #1 + +attributes #0 = { "amdgpu-unsafe-fp-atomics"="true" "denormal-fp-math"="ieee,ieee" "denormal-fp-math-f32"="ieee,ieee" "no-trapping-math"="true" "uniform-work-group-size"="true" } +attributes #1 = { nounwind readnone speculatable willreturn } diff --git a/ptx/src/test/spirv_run/isspacep.ptx b/ptx/src/test/spirv_run/isspacep.ptx new file mode 100644 index 00000000..55d39f54 --- /dev/null +++ b/ptx/src/test/spirv_run/isspacep.ptx @@ -0,0 +1,28 @@ +.version 6.5 +.target sm_30 +.address_size 64 + +.visible .entry isspacep( + .param .u64 input, + .param .u64 output +) +{ + .reg .u64 in_addr; + .reg .u64 out_addr; + .reg .pred is_global; + .reg .pred is_shared; + + .reg .u32 is_global_u32; + .reg .u32 is_shared_u32; + + ld.param.u64 in_addr, [input]; + ld.param.u64 out_addr, [output]; + + isspacep.global is_global, in_addr; + selp.u32 is_global_u32, 1, 0, is_global; + isspacep.shared is_shared, in_addr; + selp.u32 is_shared_u32, 1, 0, is_shared; + st.u32 [out_addr], is_global_u32; + st.u32 [out_addr+4], is_shared_u32; + ret; +} diff --git a/ptx/src/test/spirv_run/mod.rs b/ptx/src/test/spirv_run/mod.rs index 5fb5a8b1..71dbd061 100644 --- a/ptx/src/test/spirv_run/mod.rs +++ b/ptx/src/test/spirv_run/mod.rs @@ -127,6 +127,7 @@ test_ptx!(cvta, [3.0f32], [3.0f32]); test_ptx!(block, [1u64], [2u64]); test_ptx!(local_align, [1u64], [1u64]); test_ptx!(call, [1u64], [2u64]); +test_ptx!(call_global_ptr, [12u64], [13u64]); // In certain situations LLVM will miscompile AMDGPU binaries. // This happens if the return type of a function is a .b8 array. // This test checks if our workaround for this bug works @@ -364,6 +365,7 @@ test_ptx!( [1923569713u64, 1923569712], [1923569713u64, 1923569712] ); +test_ptx!(isspacep, [0xDEADu32], [1u32, 0]); test_ptx!(sad, [2147483648u32, 2, 13], [2147483659u32, 2147483663]); test_ptx_warp!( diff --git a/ptx/src/translate.rs b/ptx/src/translate.rs index b06fa527..99fc3562 100644 --- a/ptx/src/translate.rs +++ b/ptx/src/translate.rs @@ -392,7 +392,7 @@ impl<'a, 'input> LinkingResolver<'a, 'input> { linking, Cow::Borrowed(decl.name()), symbol, - decl.name.is_kernel(), + decl.name.is_kernel() && is_definition, ) } @@ -591,10 +591,21 @@ impl<'input> ResolvedLinking<'input> { explicit_initializer: bool, ) -> Result { if linking == ast::LinkingDirective::None { - if self.implicit_globals.get(&name).copied() == Some((module, directive)) { - Ok(VisibilityAdjustment::Global) - } else { - Ok(VisibilityAdjustment::Module) + match self.implicit_globals.get(&name).copied() { + Some((implicit_module, implicit_directive)) => { + if implicit_module == module { + if implicit_directive == directive { + Ok(VisibilityAdjustment::Global) + } else { + // If it were something other than a declaration it would + // fail module-level symbol resolution + Ok(VisibilityAdjustment::GlobalDeclaration(None)) + } + } else { + Ok(VisibilityAdjustment::Module) + } + } + None => Ok(VisibilityAdjustment::Module), } } else { if let Some((global_module, global_directive, type_)) = self.explicit_globals.get(&name) @@ -1031,10 +1042,8 @@ fn normalize_method<'a, 'b, 'input>( normalize_method_params(&mut fn_scope, &*method.func_directive.return_arguments)?; let input_arguments = normalize_method_params(&mut fn_scope, &*method.func_directive.input_arguments)?; - if !is_kernel { - if let hash_map::Entry::Vacant(entry) = function_decls.entry(name) { - entry.insert((return_arguments.clone(), input_arguments.clone())); - } + if let hash_map::Entry::Vacant(entry) = function_decls.entry(name) { + entry.insert((return_arguments.clone(), input_arguments.clone())); } let source_name = if has_global_name { Some(Cow::Borrowed(method.func_directive.name())) @@ -1188,11 +1197,9 @@ fn expand_initializer2<'a, 'b, 'input>( ) -> Result, TranslateError> { Ok(match init { ast::Initializer::Constant(c) => ast::Initializer::Constant(c), - ast::Initializer::Global(g, type_) => { - ast::Initializer::Global(scope.get_id_in_module_scope(g)?, type_) - } - ast::Initializer::GenericGlobal(g, type_) => { - ast::Initializer::GenericGlobal(scope.get_id_in_module_scope(g)?, type_) + ast::Initializer::Global(g) => ast::Initializer::Global(scope.get_id_in_module_scope(g)?), + ast::Initializer::GenericGlobal(g) => { + ast::Initializer::GenericGlobal(scope.get_id_in_module_scope(g)?) } ast::Initializer::Add(add) => { let (init1, init2) = *add; @@ -1285,11 +1292,7 @@ fn resolve_instruction_types<'input>( .map(|directive| { Ok(match directive { TranslationDirective::Variable(linking, compiled_name, var) => { - TranslationDirective::Variable( - linking, - compiled_name, - resolve_initializers(id_defs, var)?, - ) + TranslationDirective::Variable(linking, compiled_name, var) } TranslationDirective::Method(method) => { let body = match method.body { @@ -1461,9 +1464,7 @@ fn resolve_instruction_types_method<'input>( } }, Statement::Label(i) => result.push(Statement::Label(i)), - Statement::Variable(v) => { - result.push(Statement::Variable(resolve_initializers(id_defs, v)?)) - } + Statement::Variable(v) => result.push(Statement::Variable(v)), Statement::Conditional(c) => result.push(Statement::Conditional(c)), _ => return Err(TranslateError::unreachable()), } @@ -1471,39 +1472,6 @@ fn resolve_instruction_types_method<'input>( Ok(result) } -fn resolve_initializers<'input>( - id_defs: &mut IdNameMapBuilder<'input>, - mut v: Variable, -) -> Result { - fn resolve_initializer_impl<'input>( - id_defs: &mut IdNameMapBuilder<'input>, - init: &mut ast::Initializer, - ) -> Result<(), TranslateError> { - match init { - ast::Initializer::Constant(_) => {} - ast::Initializer::Global(name, type_) - | ast::Initializer::GenericGlobal(name, type_) => { - let (src_type, _, _, _) = id_defs.get_typed(*name)?; - *type_ = src_type; - } - ast::Initializer::Add(subinit) => { - resolve_initializer_impl(id_defs, &mut (*subinit).0)?; - resolve_initializer_impl(id_defs, &mut (*subinit).1)?; - } - ast::Initializer::Array(inits) => { - for init in inits.iter_mut() { - resolve_initializer_impl(id_defs, init)?; - } - } - } - Ok(()) - } - if let Some(ref mut init) = v.initializer { - resolve_initializer_impl(id_defs, init)?; - } - Ok(v) -} - // TODO: All this garbage should be replaced with proper constant propagation or // at least ability to visit statements without moving them struct KernelConstantsVisitor { @@ -2770,9 +2738,14 @@ fn replace_instructions_with_builtins_impl<'input>( } Statement::Instruction(ast::Instruction::Tex(tex, arg)) => { let geometry = tex.geometry.as_ptx(); + let op_name = if arg.lod.is_none() { + "tex" + } else { + "tex_level" + }; let fn_name = [ ZLUDA_PTX_PREFIX, - "tex", + op_name, tex.suffix(), "_", geometry, @@ -3340,6 +3313,7 @@ fn to_llvm_module_impl2<'a, 'input>( if let Some(ref mut raytracing_state) = raytracing { translation_module = raytracing::run_on_normalized(translation_module, raytracing_state)?; } + let translation_module = return_from_noreturn(translation_module); let translation_module = extract_builtin_functions(translation_module); let translation_module = resolve_instruction_types(translation_module, functions)?; let mut translation_module = restructure_function_return_types(translation_module)?; @@ -3385,6 +3359,32 @@ fn to_llvm_module_impl2<'a, 'input>( }) } +// In PTX it's legal to have a function like this: +// .func noreturn(.param .b64 noreturn_0) +// .noreturn +// { +// } +// Which trips up LLVM. We normalize this by inserting `ret;` +fn return_from_noreturn( + mut translation_module: TranslationModule, +) -> TranslationModule { + for directive in translation_module.directives.iter_mut() { + match directive { + TranslationDirective::Method(method) => { + if let Some(ref mut body) = method.body { + if body.is_empty() && method.tuning.contains(&ast::TuningDirective::Noreturn) { + body.push(Statement::Instruction(ast::Instruction::Ret( + ast::RetData { uniform: false }, + ))); + } + } + } + TranslationDirective::Variable(..) => {} + } + } + translation_module +} + // PTX definition of param state space does not translate cleanly into AMDGPU notion of an address space: //  .param in kernel arguments matches AMDGPU constant address space // .param in function arguments and variables matches AMDGPU private address space @@ -3536,7 +3536,8 @@ fn create_metadata<'input>( match tuning { // TODO: measure ast::TuningDirective::MaxNReg(_) - | ast::TuningDirective::MinNCtaPerSm(_) => {} + | ast::TuningDirective::MinNCtaPerSm(_) + | ast::TuningDirective::Noreturn => {} ast::TuningDirective::MaxNtid(x, y, z) => { let size = x as u64 * y as u64 * z as u64; kernel_metadata.push(( @@ -3582,7 +3583,8 @@ fn insert_compilation_mode_prologue<'input>( for t in tuning.iter_mut() { match t { ast::TuningDirective::MaxNReg(_) - | ast::TuningDirective::MinNCtaPerSm(_) => {} + | ast::TuningDirective::MinNCtaPerSm(_) + | ast::TuningDirective::Noreturn => {} ast::TuningDirective::MaxNtid(_, _, z) => { *z *= 2; } @@ -6462,9 +6464,17 @@ impl ast::Instruction { ast::Instruction::Tex(details, arg) } ast::Instruction::Suld(details, arg) => { + let image_type_space = if details.direct { + (ast::Type::Texref, ast::StateSpace::Global) + } else { + ( + ast::Type::Scalar(ast::ScalarType::B64), + ast::StateSpace::Reg, + ) + }; let arg = arg.map( visitor, - (ast::Type::Surfref, ast::StateSpace::Global), + image_type_space, details.geometry, details.value_type(), ast::ScalarType::B32, @@ -6550,6 +6560,15 @@ impl ast::Instruction { ast::Instruction::Sad(type_, a) => { ast::Instruction::Sad(type_, a.map(visitor, &ast::Type::Scalar(type_), false)?) } + ast::Instruction::Isspacep(space, arg) => ast::Instruction::Isspacep( + space, + arg.map_different_types( + visitor, + &ast::Type::Scalar(ast::ScalarType::Pred), + &ast::Type::Scalar(ast::ScalarType::U64), + )?, + ), + }) } } @@ -6862,6 +6881,7 @@ impl ast::Instruction { ast::Instruction::Vshr { .. } => None, ast::Instruction::Dp4a { .. } => None, ast::Instruction::MatchAny { .. } => None, + ast::Instruction::Isspacep { .. } => None, ast::Instruction::Sub(ast::ArithDetails::Signed(_), _) => None, ast::Instruction::Sub(ast::ArithDetails::Unsigned(_), _) => None, ast::Instruction::Add(ast::ArithDetails::Signed(_), _) => None, @@ -7999,7 +8019,7 @@ fn texture_geometry_to_vec_length(geometry: ast::TextureGeometry) -> u8 { } } -impl ast::Arg4Tex { +impl ast::Arg5Tex { fn map>( self, visitor: &mut V, @@ -8007,7 +8027,7 @@ impl ast::Arg4Tex { geometry: ast::TextureGeometry, value_type: ast::Type, coordinate_type: ast::ScalarType, - ) -> Result, TranslateError> { + ) -> Result, TranslateError> { let dst = visitor.operand( ArgumentDescriptor { op: self.dst, @@ -8054,11 +8074,27 @@ impl ast::Arg4Tex { &ast::Type::Vector(coordinate_type, coord_length), ast::StateSpace::Reg, )?; - Ok(ast::Arg4Tex { + let lod = self + .lod + .map(|lod| { + visitor.operand( + ArgumentDescriptor { + op: lod, + is_dst: false, + is_memory_access: false, + non_default_implicit_conversion: None, + }, + &ast::Type::Scalar(coordinate_type), + ast::StateSpace::Reg, + ) + }) + .transpose()?; + Ok(ast::Arg5Tex { dst, image, layer, coordinates, + lod, }) } } @@ -8069,6 +8105,14 @@ impl ast::Arg4Sust { visitor: &mut V, details: &ast::SurfaceDetails, ) -> Result, TranslateError> { + let (type_, space) = if details.direct { + (ast::Type::Surfref, ast::StateSpace::Global) + } else { + ( + ast::Type::Scalar(ast::ScalarType::B64), + ast::StateSpace::Reg, + ) + }; let image = visitor.operand( ArgumentDescriptor { op: self.image, @@ -8076,8 +8120,8 @@ impl ast::Arg4Sust { is_memory_access: false, non_default_implicit_conversion: None, }, - &ast::Type::Surfref, - ast::StateSpace::Global, + &type_, + space, )?; let layer = self .layer diff --git a/zluda/src/cuda.rs b/zluda/src/cuda.rs index 1f37dbfe..f8a05848 100644 --- a/zluda/src/cuda.rs +++ b/zluda/src/cuda.rs @@ -214,6 +214,9 @@ cuda_function_declarations!( cuLinkComplete, cuLinkDestroy, cuLinkCreate_v2, + cuMipmappedArrayCreate, + cuMipmappedArrayDestroy, + cuMipmappedArrayGetLevel ] ); @@ -1242,9 +1245,7 @@ mod definitions { surface::create(pSurfObject, pResDesc) } - pub(crate) unsafe fn cuSurfObjectDestroy( - surfObject: hipSurfaceObject_t, - ) -> hipError_t { + pub(crate) unsafe fn cuSurfObjectDestroy(surfObject: hipSurfaceObject_t) -> hipError_t { surface::destroy(surfObject) } @@ -1253,7 +1254,7 @@ mod definitions { pResDesc: *const CUDA_RESOURCE_DESC, pTexDesc: *const HIP_TEXTURE_DESC, pResViewDesc: *const HIP_RESOURCE_VIEW_DESC, - ) -> hipError_t { + ) -> Result<(), CUresult> { texobj::create(pTexObject, pResDesc, pTexDesc, pResViewDesc) } @@ -1652,4 +1653,26 @@ mod definitions { ) -> Result<(), CUresult> { link::create(numOptions, options, optionValues, stateOut) } + + pub(crate) unsafe fn cuMipmappedArrayCreate( + pHandle: *mut CUmipmappedArray, + pMipmappedArrayDesc: *const HIP_ARRAY3D_DESCRIPTOR, + numMipmapLevels: ::std::os::raw::c_uint, + ) -> Result<(), CUresult> { + array::mipmapped_create(pHandle, pMipmappedArrayDesc, numMipmapLevels) + } + + pub(crate) unsafe fn cuMipmappedArrayDestroy( + hMipmappedArray: CUmipmappedArray, + ) -> hipError_t { + array::mipmapped_destroy(hMipmappedArray) + } + + pub(crate) unsafe fn cuMipmappedArrayGetLevel( + pLevelArray: *mut CUarray, + hMipmappedArray: CUmipmappedArray, + level: ::std::os::raw::c_uint, + ) -> Result<(), CUresult> { + array::mipmapped_get_level(pLevelArray, hMipmappedArray, level) + } } diff --git a/zluda/src/impl/array.rs b/zluda/src/impl/array.rs index ab2db78f..4acbb7d1 100644 --- a/zluda/src/impl/array.rs +++ b/zluda/src/impl/array.rs @@ -47,12 +47,13 @@ pub(crate) unsafe fn get_descriptor_3d( flags |= CUDA_ARRAY3D_SURFACE_LDST; let array = hipfix::array::get(array); if let (Some(array), Some(array_descriptor)) = (array.as_ref(), array_descriptor.as_mut()) { + let real_format = hipfix::get_broken_format(array).unwrap_or(array.Format); *array_descriptor = CUDA_ARRAY3D_DESCRIPTOR { Width: array.width as usize, Height: array.height as usize, Depth: array.depth as usize, NumChannels: array.NumChannels, - Format: mem::transmute(array.Format), // compatible + Format: mem::transmute(real_format), // compatible Flags: flags, }; hipError_t::hipSuccess @@ -81,3 +82,65 @@ pub(crate) unsafe fn create( Err(CUresult::CUDA_ERROR_INVALID_VALUE) } } + +pub(crate) unsafe fn mipmapped_create( + mipmapped_array: *mut CUmipmappedArray, + mipmapped_array_desc: *const HIP_ARRAY3D_DESCRIPTOR, + num_mipmap_levels: u32, +) -> Result<(), CUresult> { + if let Some(mipmapped_array_desc) = (mipmapped_array_desc).as_ref() { + let mut mipmapped_array_desc = *mipmapped_array_desc; + let (hack_flag, format) = hipfix::get_non_broken_format(mipmapped_array_desc.Format); + mipmapped_array_desc.Format = format; + let mut hip_array = ptr::null_mut(); + hip_call_cuda!(hipMipmappedArrayCreate( + &mut hip_array, + &mut mipmapped_array_desc, + num_mipmap_levels + )); + if (hip_array as usize & 0b11) != 0 { + hip_call_cuda!(hipMipmappedArrayDestroy(hip_array)); + return Err(CUresult::CUDA_ERROR_INVALID_VALUE); + } + hip_array = (hip_array as usize | hack_flag as usize) as _; + *mipmapped_array = hip_array.cast(); + Ok(()) + } else { + Err(CUresult::CUDA_ERROR_INVALID_VALUE) + } +} + +pub(crate) unsafe fn mipmapped_destroy(mipmapped_array: CUmipmappedArray) -> hipError_t { + let mipmapped_array = hipfix::array::get_mipmapped(mipmapped_array).0; + hipMipmappedArrayDestroy(mipmapped_array) +} + +pub(crate) unsafe fn mipmapped_get_level( + level_array: *mut CUarray, + mipmapped_array: CUmipmappedArray, + level: u32, +) -> Result<(), CUresult> { + let (mipmapped_array, hack_flag) = hipfix::array::get_mipmapped(mipmapped_array); + if let Some(mipmapped_array) = mipmapped_array.as_mut() { + let mut hip_array = mem::zeroed(); + hip_call_cuda!(hipMipmappedArrayGetLevel( + &mut hip_array, + mipmapped_array as *mut _, + level + )); + let hip_array_mut = hip_array.as_mut().ok_or(CUresult::CUDA_ERROR_UNKNOWN)?; + hip_array_mut.textureType = hack_flag; + if mipmapped_array.height == 0 { + // HIP returns 1 here for no good reason + hip_array_mut.height = 0; + } + if mipmapped_array.depth == 0 { + // HIP returns 1 here for no good reason + hip_array_mut.depth = 0; + } + *level_array = mem::transmute(hip_array); + Ok(()) + } else { + Err(CUresult::CUDA_ERROR_INVALID_VALUE) + } +} diff --git a/zluda/src/impl/hipfix.rs b/zluda/src/impl/hipfix.rs index 77fec003..3257d97d 100644 --- a/zluda/src/impl/hipfix.rs +++ b/zluda/src/impl/hipfix.rs @@ -3,6 +3,8 @@ use cuda_types::*; use hip_runtime_sys::*; use std::{env, ptr}; +use self::array::get_mipmapped; + use super::{function::FunctionData, stream, LiveCheck}; // For some reason HIP does not tolerate hipArraySurfaceLoadStore, even though @@ -26,8 +28,24 @@ pub(crate) fn get_non_broken_format(format: hipArray_Format) -> (u32, hipArray_F } #[must_use] -pub(crate) fn get_broken_format(broken: u32, format: hipArray_Format) -> hipArray_Format { - match (broken, format) { +pub(crate) fn get_broken_format(array: &hipArray) -> Option { + get_broken_format_impl(array.textureType, array.Format) +} + +#[must_use] +pub(crate) unsafe fn get_broken_format_mipmapped( + array: CUmipmappedArray, +) -> Result<(&'static hipMipmappedArray, Option), CUresult> { + let (hip_array, flag) = get_mipmapped(array); + let hip_array_ref = hip_array + .as_ref() + .ok_or(CUresult::CUDA_ERROR_INVALID_VALUE)?; + let format_override = get_broken_format_impl(flag, hip_array_ref.format); + Ok((hip_array_ref, format_override)) +} + +fn get_broken_format_impl(hack_flag: u32, format: hipArray_Format) -> Option { + Some(match (hack_flag, format) { (2, hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT16) => hipArray_Format::HIP_AD_FORMAT_HALF, (1, hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT16) => { hipArray_Format::HIP_AD_FORMAT_SIGNED_INT16 @@ -35,13 +53,14 @@ pub(crate) fn get_broken_format(broken: u32, format: hipArray_Format) -> hipArra (1, hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT8) => { hipArray_Format::HIP_AD_FORMAT_SIGNED_INT8 } - (_, f) => f, - } + (_, _) => return None, + }) } // memcpy3d fails when copying array1d arrays, so we mark all layered arrays by // settings LSB pub(crate) mod array { + use super::{get_broken_format, get_broken_format_mipmapped}; use crate::{ hip_call_cuda, r#impl::{memcpy3d_from_cuda, memory_type_from_cuda, FromCuda}, @@ -51,23 +70,171 @@ pub(crate) mod array { use std::{mem, ptr}; pub(crate) unsafe fn with_resource_desc( - cuda: *const CUDA_RESOURCE_DESC, - fn_: impl FnOnce(*const HIP_RESOURCE_DESC) -> T, - ) -> T { - let cuda = &*cuda; + res_desc: *const CUDA_RESOURCE_DESC, + res_desc_view: *const HIP_RESOURCE_VIEW_DESC, + fn_: impl FnOnce(*const HIP_RESOURCE_DESC, *const HIP_RESOURCE_VIEW_DESC) -> T, + ) -> Result { + let cuda = &*res_desc; if cuda.resType == CUresourcetype::CU_RESOURCE_TYPE_ARRAY { let mut cuda = *cuda; - cuda.res.array.hArray = mem::transmute(get(cuda.res.array.hArray)); - fn_((&cuda as *const CUDA_RESOURCE_DESC).cast::()) + let hip_array = get(cuda.res.array.hArray); + cuda.res.array.hArray = mem::transmute(hip_array); + if let Some(hip_array) = hip_array.as_ref() { + if let Some(new_format) = get_broken_format(hip_array) { + return if res_desc_view == ptr::null() { + let res_desc_view = HIP_RESOURCE_VIEW_DESC { + format: resource_view_format(new_format, hip_array.NumChannels)?, + width: hip_array.width as usize, + height: hip_array.height as usize, + depth: hip_array.depth as usize, + firstMipmapLevel: 0, + lastMipmapLevel: 0, + firstLayer: 0, + lastLayer: 0, + reserved: mem::zeroed(), + }; + Ok(fn_( + (&cuda as *const CUDA_RESOURCE_DESC).cast::(), + &res_desc_view, + )) + } else { + Err(CUresult::CUDA_ERROR_NOT_SUPPORTED) + }; + } + } + Ok(fn_( + (&cuda as *const CUDA_RESOURCE_DESC).cast::(), + res_desc_view, + )) + } else if cuda.resType == CUresourcetype::CU_RESOURCE_TYPE_MIPMAPPED_ARRAY { + let (hip_mipmapped_array, format_override) = + get_broken_format_mipmapped(cuda.res.mipmap.hMipmappedArray)?; + let mut cuda = *cuda; + cuda.res.mipmap.hMipmappedArray = mem::transmute(hip_mipmapped_array as *const _); + if let Some(new_format) = format_override { + return if res_desc_view == ptr::null() { + let res_desc_view = HIP_RESOURCE_VIEW_DESC { + format: resource_view_format(new_format, hip_mipmapped_array.num_channels)?, + width: hip_mipmapped_array.width as usize, + height: hip_mipmapped_array.height as usize, + depth: hip_mipmapped_array.depth as usize, + firstMipmapLevel: hip_mipmapped_array.min_mipmap_level, + lastMipmapLevel: hip_mipmapped_array.max_mipmap_level, + firstLayer: 0, + lastLayer: 0, + reserved: mem::zeroed(), + }; + Ok(fn_( + (&cuda as *const CUDA_RESOURCE_DESC).cast::(), + &res_desc_view, + )) + } else { + Err(CUresult::CUDA_ERROR_NOT_SUPPORTED) + }; + } + Ok(fn_( + (&cuda as *const CUDA_RESOURCE_DESC).cast::(), + res_desc_view, + )) } else { - fn_((cuda as *const CUDA_RESOURCE_DESC).cast::()) + Ok(fn_( + (cuda as *const CUDA_RESOURCE_DESC).cast::(), + res_desc_view, + )) } } + fn resource_view_format( + format: hipArray_Format, + num_channels: u32, + ) -> Result { + Ok(match (format, num_channels) { + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT8, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_1X8 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT8, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_2X8 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT8, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_4X8 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT8, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_1X8 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT8, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_2X8 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT8, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_4X8 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT16, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_1X16 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT16, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_2X16 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT16, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_4X16 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT16, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_1X16 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT16, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_2X16 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT16, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_4X16 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT32, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_1X32 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT32, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_2X32 + } + (hipArray_Format::HIP_AD_FORMAT_UNSIGNED_INT32, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_UINT_4X32 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT32, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_1X32 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT32, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_2X32 + } + (hipArray_Format::HIP_AD_FORMAT_SIGNED_INT32, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_SINT_4X32 + } + (hipArray_Format::HIP_AD_FORMAT_HALF, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_1X16 + } + (hipArray_Format::HIP_AD_FORMAT_HALF, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_2X16 + } + (hipArray_Format::HIP_AD_FORMAT_HALF, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_4X16 + } + (hipArray_Format::HIP_AD_FORMAT_FLOAT, 1) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_1X32 + } + (hipArray_Format::HIP_AD_FORMAT_FLOAT, 2) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_2X32 + } + (hipArray_Format::HIP_AD_FORMAT_FLOAT, 4) => { + HIPresourceViewFormat::HIP_RES_VIEW_FORMAT_FLOAT_4X32 + } + _ => return Err(CUresult::CUDA_ERROR_NOT_SUPPORTED), + }) + } + pub(crate) fn get(cuda: CUarray) -> hipArray_t { (cuda as usize & !3usize) as hipArray_t } + pub(crate) fn get_mipmapped(cuda: CUmipmappedArray) -> (hipMipmappedArray_t, u32) { + let array = (cuda as usize & !3usize) as hipMipmappedArray_t; + let broken_flag = (cuda as usize & 3usize) as u32; + (array, broken_flag) + } + pub(crate) fn to_cuda(array: hipArray_t, layered_dims: usize) -> CUarray { let a1d_layered = layered_dims as usize; ((array as usize) | a1d_layered) as CUarray diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs index 34566af5..73c6efa5 100644 --- a/zluda/src/impl/mod.rs +++ b/zluda/src/impl/mod.rs @@ -220,6 +220,7 @@ impl FromCuda for CUlibraryOption {} impl FromCuda for CUDA_KERNEL_NODE_PARAMS_v1 {} impl FromCuda for CUjitInputType {} impl FromCuda for CUDA_RESOURCE_DESC {} +impl FromCuda for CUmipmappedArray {} impl FromCuda for *mut context::Context {} impl FromCuda for *mut stream::Stream {} diff --git a/zluda/src/impl/texobj.rs b/zluda/src/impl/texobj.rs index 21eb4530..0096c747 100644 --- a/zluda/src/impl/texobj.rs +++ b/zluda/src/impl/texobj.rs @@ -1,19 +1,29 @@ +use super::hipfix; +use crate::hip_call_cuda; use cuda_types::*; use hip_runtime_sys::*; use std::ptr; -use super::hipfix; - pub(crate) unsafe fn create( p_tex_object: *mut hipTextureObject_t, p_res_desc: *const CUDA_RESOURCE_DESC, p_tex_desc: *const HIP_TEXTURE_DESC, p_res_view_desc: *const HIP_RESOURCE_VIEW_DESC, -) -> hipError_t { +) -> Result<(), CUresult> { if p_res_desc == ptr::null() { - return hipError_t::hipErrorInvalidValue; + return Err(CUresult::CUDA_ERROR_INVALID_VALUE); } - hipfix::array::with_resource_desc(p_res_desc, |p_res_desc| { - hipTexObjectCreate(p_tex_object, p_res_desc, p_tex_desc, p_res_view_desc) - }) + hipfix::array::with_resource_desc( + p_res_desc, + p_res_view_desc, + |p_res_desc, p_res_view_desc| { + hip_call_cuda!(hipTexObjectCreate( + p_tex_object, + p_res_desc, + p_tex_desc, + p_res_view_desc + )); + Ok(()) + }, + )? } diff --git a/zluda/src/impl/texref.rs b/zluda/src/impl/texref.rs index 307b5bad..b72de09b 100644 --- a/zluda/src/impl/texref.rs +++ b/zluda/src/impl/texref.rs @@ -94,7 +94,7 @@ pub(crate) unsafe fn set_array( if let Some(array) = array.as_ref() { hip_call_cuda!(hipTexRefSetFormat( texref, - hipfix::get_broken_format(array.textureType, array.Format), + hipfix::get_broken_format(array).unwrap_or(array.Format), array.NumChannels as i32, )); hip_call_cuda!(hipTexRefSetArray(texref, array, HIP_TRSA_OVERRIDE_FORMAT)); diff --git a/zluda/tests/kernel_suld.rs b/zluda/tests/kernel_suld.rs index 07fc5606..7d368a26 100644 --- a/zluda/tests/kernel_suld.rs +++ b/zluda/tests/kernel_suld.rs @@ -377,7 +377,10 @@ unsafe fn kernel_suld_impl< let x = random_size.sample(&mut rng) * sizeof_pixel; let y = random_size.sample(&mut rng); let z = random_size.sample(&mut rng); - let values = [rng.gen::(); SULD_N]; + let mut values = [SustType::default(); SULD_N]; + for value in values.iter_mut() { + *value = rng.gen::(); + } let converted_values = force_transmute(values, BYTE_FILLER3); *host_side_data.get_unchecked_mut(geo.address(size, x, y, z, sizeof_pixel)) = converted_values; assert_eq!( diff --git a/zluda/tests/kernel_sust.rs b/zluda/tests/kernel_sust.rs index 5057b563..e6a07def 100644 --- a/zluda/tests/kernel_sust.rs +++ b/zluda/tests/kernel_sust.rs @@ -419,7 +419,10 @@ unsafe fn kernel_sust_impl< let x = random_size.sample(&mut rng) * sizeof_pixel; let y = random_size.sample(&mut rng); let z = random_size.sample(&mut rng); - let values = [rng.gen::(); SUST_N]; + let mut values = [SustType::default(); SUST_N]; + for value in values.iter_mut() { + *value = rng.gen::(); + } let mut args = vec![ &x as *const _ as *const c_void, &y as *const _ as *const _, diff --git a/zluda/tests/kernel_tex.rs b/zluda/tests/kernel_tex.rs index 6b2d1d30..88e3c4ba 100644 --- a/zluda/tests/kernel_tex.rs +++ b/zluda/tests/kernel_tex.rs @@ -213,6 +213,7 @@ generate_tests!( CU_AD_FORMAT_SIGNED_INT8, CU_AD_FORMAT_SIGNED_INT16, CU_AD_FORMAT_SIGNED_INT32, + // TODO: update half crate //CU_AD_FORMAT_HALF, CU_AD_FORMAT_FLOAT ], @@ -337,13 +338,13 @@ const BYTE_FILLER2: u8 = 0xfe; unsafe fn force_transmute(f: From) -> To { if mem::size_of::() == mem::size_of::() - && mem::size_of::() == mem::size_of::() + && mem::size_of::() == mem::size_of::() { return mem::transmute_copy(&f); } - if mem::size_of::() == mem::size_of::() { + if mem::size_of::() == mem::size_of::() { if let Some(value) = ::downcast_ref::(&f) { - return mem::transmute_copy(&((value.to_f64() / f16::MAX.to_f64()) as f32)); + return mem::transmute_copy(&value.to_f32()); } if let Some(value) = ::downcast_ref::(&f) { return mem::transmute_copy(&((*value as f64 / u8::MAX as f64) as f32)); @@ -359,6 +360,9 @@ unsafe fn force_transmute(f: From) -> To { } } if mem::size_of::() == mem::size_of::() { + if let Some(_) = ::downcast_ref::(&f) { + return mem::transmute_copy(&f); + } if let Some(value) = ::downcast_ref::(&f) { return mem::transmute_copy(&f16::from_f64(*value as f64 / u8::MAX as f64)); } diff --git a/zluda/tests/linking.rs b/zluda/tests/linking.rs index 025d8bab..57ada55f 100644 --- a/zluda/tests/linking.rs +++ b/zluda/tests/linking.rs @@ -229,16 +229,6 @@ impl Directive { Directive::Shared => unimplemented!(), } } - - fn assert_exact(self) -> bool { - match self { - Directive::Kernel => false, - Directive::Method => true, - Directive::Global => false, - Directive::Const => false, - Directive::Shared => unimplemented!(), - } - } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -370,45 +360,6 @@ fn create_kernel(linking: Linking, directive: Directive, defined: bool) -> Strin kernel } -fn assert_compatible( - results: Vec<(Linking, Directive, bool, i32, Option)>, - expected: [(Linking, Directive, bool, i32, Option); 50], -) { - if results.len() != expected.len() { - panic!(); - } - let mut broken = Vec::new(); - for (result, expected) in results.into_iter().zip(IntoIterator::into_iter(expected)) { - let (linking, directive, defined, build_result, load_result) = result; - let (_, _, _, expected_build, expected_load) = expected; - if expected_build == 0 { - if build_result != 0 { - broken.push(( - linking, - directive, - defined, - (build_result, load_result), - (expected_build, expected_load), - )); - continue; - } - if expected_load == Some(0) { - if load_result != Some(0) { - broken.push(( - linking, - directive, - defined, - (build_result, load_result), - (expected_build, expected_load), - )); - continue; - } - } - } - } - assert_eq!(broken, []); -} - fn assert_compatible_compile( compiled: &[T], compiled_expected: &[T], @@ -1107,3 +1058,28 @@ unsafe fn emit_weak_fn(cuda: T) { CUresult::CUDA_SUCCESS ); } + + +cuda_driver_test!(static_entry_decl); + +unsafe fn static_entry_decl(cuda: T) { + let input1 = " + .version 6.5 + .target sm_35 + .address_size 64 + + .entry foobar(); + .entry foobar() { ret; }\0" + .to_string(); + assert_eq!(cuda.cuInit(0), CUresult::CUDA_SUCCESS); + let mut ctx = ptr::null_mut(); + assert_eq!( + cuda.cuCtxCreate_v2(&mut ctx, 0, CUdevice_v1(0)), + CUresult::CUDA_SUCCESS + ); + let mut module = mem::zeroed(); + assert_eq!( + cuda.cuModuleLoadData(&mut module, input1.as_ptr().cast()), + CUresult::CUDA_SUCCESS + ); +} diff --git a/zluda/tests/mipmap_array.ptx b/zluda/tests/mipmap_array.ptx new file mode 100644 index 00000000..9dbfd2b8 --- /dev/null +++ b/zluda/tests/mipmap_array.ptx @@ -0,0 +1,47 @@ +.version 6.5 +.target sm_30 +.address_size 64 + +.entry texture_to_surface( + .param .u64 texture_param, + .param .u64 surface_param +) +{ + .reg .u64 texture; + .reg .u64 surface; + .reg .f32 f<5>; + .reg .b16 rs<5>; + + ld.param.u64 texture, [texture_param]; + ld.param.u64 surface, [surface_param]; + + tex.2d.v4.f32.f32 {f1, f2, f3, f4}, [texture, {0f00000000, 0f00000000}]; + { cvt.rn.f16.f32 rs1, f1;} + { cvt.rn.f16.f32 rs2, f2;} + { cvt.rn.f16.f32 rs3, f3;} + { cvt.rn.f16.f32 rs4, f4;} + sust.b.2d.v4.b16.trap [surface, {0, 0}], {rs1, rs2, rs3, rs4}; + ret; +} + + +.entry read_tex_2d_mip( + .param .u64 texture_param, + .param .u64 output_param +) +{ + .reg .u64 texture; + .reg .u64 output; + .reg .f32 f<5>; + + ld.param.u64 texture, [texture_param]; + ld.param.u64 output, [output_param]; + + // 3F800000 = 1.0 + tex.level.2d.v4.f32.f32 {f1, f2, f3, f4}, [texture, {0f00000000, 0f00000000}], 0f3f800000; + st.global.f32 [output], f1; + st.global.f32 [output+4], f2; + st.global.f32 [output+8], f3; + st.global.f32 [output+12], f4; + ret; +} diff --git a/zluda/tests/mipmap_array.rs b/zluda/tests/mipmap_array.rs new file mode 100644 index 00000000..998f81e7 --- /dev/null +++ b/zluda/tests/mipmap_array.rs @@ -0,0 +1,431 @@ +use crate::common::CudaDriverFns; +use cuda_types::*; +use half::f16; +use std::{ffi::c_void, mem, ptr}; + +mod common; + +// TODO: These two tests expose various random brokenness of mipmapped array +// and texture objects. This should be turned into extensive tests like +// kernel_sust/kernel_suld/kernel_tex + +cuda_driver_test!(mipmap_texture_to_surface); + +unsafe fn mipmap_texture_to_surface(cuda: T) { + let kernel = include_str!("mipmap_array.ptx"); + let mut kernel = kernel.to_owned(); + kernel.push('\0'); + assert_eq!(cuda.cuInit(0), CUresult::CUDA_SUCCESS); + let mut ctx = ptr::null_mut(); + assert_eq!( + cuda.cuCtxCreate_v2(&mut ctx, 0, CUdevice_v1(0)), + CUresult::CUDA_SUCCESS + ); + let mut module = ptr::null_mut(); + assert_eq!( + cuda.cuModuleLoadData(&mut module, kernel.as_ptr() as _), + CUresult::CUDA_SUCCESS + ); + let mut mipmap_array = ptr::null_mut(); + let mipmap_desc = CUDA_ARRAY3D_DESCRIPTOR { + Width: 1368, + Height: 770, + Depth: 0, + Format: CUarray_format::CU_AD_FORMAT_HALF, + NumChannels: 4, + Flags: 0, + }; + assert_eq!( + cuda.cuMipmappedArrayCreate(&mut mipmap_array, &mipmap_desc, 8), + CUresult::CUDA_SUCCESS + ); + let mut array_0 = mem::zeroed(); + let mut array_1 = mem::zeroed(); + assert_eq!( + cuda.cuMipmappedArrayGetLevel(&mut array_0, mipmap_array, 0), + CUresult::CUDA_SUCCESS + ); + let mut queried_descriptor = mem::zeroed(); + assert_eq!( + cuda.cuArray3DGetDescriptor_v2(&mut queried_descriptor, array_0), + CUresult::CUDA_SUCCESS + ); + assert_eq!(mipmap_desc.Depth, queried_descriptor.Depth); + assert_eq!( + cuda.cuMipmappedArrayGetLevel(&mut array_1, mipmap_array, 1), + CUresult::CUDA_SUCCESS + ); + let mut pixels = [0x3C66u16, 0x4066, 0x4299, 4466]; + let memcpy_from_host = CUDA_MEMCPY2D { + srcXInBytes: 0, + srcY: 0, + srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST, + srcHost: pixels.as_mut_ptr() as _, + srcDevice: CUdeviceptr_v2(ptr::null_mut()), + srcArray: ptr::null_mut(), + srcPitch: 4 * mem::size_of::(), + dstXInBytes: 0, + dstY: 0, + dstMemoryType: CUmemorytype::CU_MEMORYTYPE_ARRAY, + dstHost: ptr::null_mut(), + dstDevice: CUdeviceptr_v2(ptr::null_mut()), + dstArray: array_0, + dstPitch: 0, + WidthInBytes: 4 * mem::size_of::(), + Height: 1, + }; + assert_eq!( + cuda.cuMemcpy2DUnaligned_v2(&memcpy_from_host), + CUresult::CUDA_SUCCESS + ); + let mut texture = mem::zeroed(); + let texture_resource_desc = CUDA_RESOURCE_DESC { + resType: CUresourcetype::CU_RESOURCE_TYPE_ARRAY, + res: CUDA_RESOURCE_DESC_st__bindgen_ty_1 { + array: CUDA_RESOURCE_DESC_st__bindgen_ty_1__bindgen_ty_1 { hArray: array_0 }, + }, + flags: 0, + }; + let texture_desc = CUDA_TEXTURE_DESC { + addressMode: [ + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + ], + filterMode: CUfilter_mode::CU_TR_FILTER_MODE_LINEAR, + flags: 2, + maxAnisotropy: 0, + mipmapFilterMode: CUfilter_mode::CU_TR_FILTER_MODE_POINT, + mipmapLevelBias: 0f32, + minMipmapLevelClamp: 0f32, + maxMipmapLevelClamp: 0f32, + borderColor: [0f32, 0f32, 0f32, 0f32], + reserved: mem::zeroed(), + }; + assert_eq!( + cuda.cuTexObjectCreate( + &mut texture, + &texture_resource_desc, + &texture_desc, + ptr::null() + ), + CUresult::CUDA_SUCCESS + ); + let mut surface = mem::zeroed(); + let surface_resource_desc = CUDA_RESOURCE_DESC { + resType: CUresourcetype::CU_RESOURCE_TYPE_ARRAY, + res: CUDA_RESOURCE_DESC_st__bindgen_ty_1 { + array: CUDA_RESOURCE_DESC_st__bindgen_ty_1__bindgen_ty_1 { hArray: array_1 }, + }, + flags: 0, + }; + assert_eq!( + cuda.cuSurfObjectCreate(&mut surface, &surface_resource_desc), + CUresult::CUDA_SUCCESS + ); + let mut texture_to_surface = mem::zeroed(); + assert_eq!( + cuda.cuModuleGetFunction( + &mut texture_to_surface, + module, + b"texture_to_surface\0".as_ptr().cast() + ), + CUresult::CUDA_SUCCESS + ); + let mut params = [&mut texture, &mut surface]; + assert_eq!( + cuda.cuLaunchKernel( + texture_to_surface, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ptr::null_mut(), + params.as_mut_ptr().cast(), + ptr::null_mut(), + ), + CUresult::CUDA_SUCCESS + ); + assert_eq!( + cuda.cuStreamSynchronize(ptr::null_mut()), + CUresult::CUDA_SUCCESS + ); + let mut memcpy_dst = [u16::MAX; 4]; + let memcpy_to_host = CUDA_MEMCPY2D { + srcXInBytes: 0, + srcY: 0, + srcMemoryType: CUmemorytype::CU_MEMORYTYPE_ARRAY, + srcHost: ptr::null(), + srcDevice: CUdeviceptr_v2(ptr::null_mut()), + srcArray: array_1, + srcPitch: 0, + dstXInBytes: 0, + dstY: 0, + dstMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST, + dstHost: memcpy_dst.as_mut_ptr() as _, + dstDevice: CUdeviceptr_v2(ptr::null_mut()), + dstArray: ptr::null_mut(), + dstPitch: 4 * mem::size_of::(), + WidthInBytes: 4 * mem::size_of::(), + Height: 1, + }; + assert_eq!( + cuda.cuMemcpy2DUnaligned_v2(&memcpy_to_host), + CUresult::CUDA_SUCCESS + ); + assert_eq!(&pixels, &memcpy_dst); + let texture_resource_desc = CUDA_RESOURCE_DESC { + resType: CUresourcetype::CU_RESOURCE_TYPE_MIPMAPPED_ARRAY, + res: CUDA_RESOURCE_DESC_st__bindgen_ty_1 { + mipmap: CUDA_RESOURCE_DESC_st__bindgen_ty_1__bindgen_ty_2 { + hMipmappedArray: mipmap_array, + }, + }, + flags: 0, + }; + let texture_desc = CUDA_TEXTURE_DESC { + addressMode: [ + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + ], + filterMode: CUfilter_mode::CU_TR_FILTER_MODE_LINEAR, + flags: 2, + maxAnisotropy: 0, + mipmapFilterMode: CUfilter_mode::CU_TR_FILTER_MODE_LINEAR, + mipmapLevelBias: 0f32, + minMipmapLevelClamp: 0f32, + maxMipmapLevelClamp: 7f32, + borderColor: [0f32, 0f32, 0f32, 0f32], + reserved: mem::zeroed(), + }; + let mut mipmapped_tex_obj = mem::zeroed(); + assert_eq!( + cuda.cuTexObjectCreate( + &mut mipmapped_tex_obj, + &texture_resource_desc, + &texture_desc, + ptr::null() + ), + CUresult::CUDA_SUCCESS + ); + let mut read_tex_2d_mip = mem::zeroed(); + assert_eq!( + cuda.cuModuleGetFunction( + &mut read_tex_2d_mip, + module, + b"read_tex_2d_mip\0".as_ptr().cast() + ), + CUresult::CUDA_SUCCESS + ); + let mut output_buffer = mem::zeroed(); + assert_eq!( + cuda.cuMemAlloc_v2(&mut output_buffer, 4 * mem::size_of::()), + CUresult::CUDA_SUCCESS + ); + let mut params = [ + &mut mipmapped_tex_obj as *mut _ as *mut c_void, + &mut output_buffer as *mut _ as *mut c_void, + ]; + assert_eq!( + cuda.cuLaunchKernel( + read_tex_2d_mip, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ptr::null_mut(), + params.as_mut_ptr().cast(), + ptr::null_mut(), + ), + CUresult::CUDA_SUCCESS + ); + assert_eq!( + cuda.cuStreamSynchronize(ptr::null_mut()), + CUresult::CUDA_SUCCESS + ); + let mut output = [f32::MAX; 4]; + assert_eq!( + cuda.cuMemcpyDtoH_v2( + output.as_mut_ptr().cast(), + output_buffer, + 4 * mem::size_of::() + ), + CUresult::CUDA_SUCCESS + ); + let pixels_f32 = pixels + .iter() + .copied() + .map(|x| mem::transmute::<_, f16>(x).to_f32()) + .collect::>(); + assert_eq!(&output[..], &*pixels_f32); +} + +cuda_driver_test!(mipmap_texture_to_surface2); + +unsafe fn mipmap_texture_to_surface2(cuda: T) { + let kernel = include_str!("mipmap_array.ptx"); + let mut kernel = kernel.to_owned(); + kernel.push('\0'); + assert_eq!(cuda.cuInit(0), CUresult::CUDA_SUCCESS); + let mut ctx = ptr::null_mut(); + assert_eq!( + cuda.cuCtxCreate_v2(&mut ctx, 0, CUdevice_v1(0)), + CUresult::CUDA_SUCCESS + ); + let mut module = ptr::null_mut(); + assert_eq!( + cuda.cuModuleLoadData(&mut module, kernel.as_ptr() as _), + CUresult::CUDA_SUCCESS + ); + let mut array_0 = mem::zeroed(); + let mipmap_desc = CUDA_ARRAY3D_DESCRIPTOR { + Width: 1368, + Height: 770, + Depth: 0, + Format: CUarray_format::CU_AD_FORMAT_HALF, + NumChannels: 4, + Flags: 2, + }; + assert_eq!( + cuda.cuArray3DCreate_v2(&mut array_0, &mipmap_desc), + CUresult::CUDA_SUCCESS + ); + let mut array_1 = mem::zeroed(); + let mipmap_desc = CUDA_ARRAY3D_DESCRIPTOR { + Width: 1368 / 2, + Height: 770 / 2, + Depth: 0, + Format: CUarray_format::CU_AD_FORMAT_HALF, + NumChannels: 4, + Flags: 2, + }; + assert_eq!( + cuda.cuArray3DCreate_v2(&mut array_1, &mipmap_desc), + CUresult::CUDA_SUCCESS + ); + let mut pixels = [0x3C66u16, 0x4066, 0x4299, 4466]; + let memcpy_from_host = CUDA_MEMCPY2D { + srcXInBytes: 0, + srcY: 0, + srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST, + srcHost: pixels.as_mut_ptr() as _, + srcDevice: CUdeviceptr_v2(ptr::null_mut()), + srcArray: ptr::null_mut(), + srcPitch: 4 * mem::size_of::(), + dstXInBytes: 0, + dstY: 0, + dstMemoryType: CUmemorytype::CU_MEMORYTYPE_ARRAY, + dstHost: ptr::null_mut(), + dstDevice: CUdeviceptr_v2(ptr::null_mut()), + dstArray: array_0, + dstPitch: 0, + WidthInBytes: 4 * mem::size_of::(), + Height: 1, + }; + assert_eq!( + cuda.cuMemcpy2DUnaligned_v2(&memcpy_from_host), + CUresult::CUDA_SUCCESS + ); + let mut func = mem::zeroed(); + assert_eq!( + cuda.cuModuleGetFunction(&mut func, module, b"texture_to_surface\0".as_ptr().cast()), + CUresult::CUDA_SUCCESS + ); + let mut texture = mem::zeroed(); + let texture_resource_desc = CUDA_RESOURCE_DESC { + resType: CUresourcetype::CU_RESOURCE_TYPE_ARRAY, + res: CUDA_RESOURCE_DESC_st__bindgen_ty_1 { + array: CUDA_RESOURCE_DESC_st__bindgen_ty_1__bindgen_ty_1 { hArray: array_0 }, + }, + flags: 0, + }; + let texture_desc = CUDA_TEXTURE_DESC { + addressMode: [ + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP, + ], + filterMode: CUfilter_mode::CU_TR_FILTER_MODE_LINEAR, + flags: 2, + maxAnisotropy: 0, + mipmapFilterMode: CUfilter_mode::CU_TR_FILTER_MODE_POINT, + mipmapLevelBias: 0f32, + minMipmapLevelClamp: 0f32, + maxMipmapLevelClamp: 0f32, + borderColor: [0f32, 0f32, 0f32, 0f32], + reserved: mem::zeroed(), + }; + assert_eq!( + cuda.cuTexObjectCreate( + &mut texture, + &texture_resource_desc, + &texture_desc, + ptr::null() + ), + CUresult::CUDA_SUCCESS + ); + let mut surface = mem::zeroed(); + let surface_resource_desc = CUDA_RESOURCE_DESC { + resType: CUresourcetype::CU_RESOURCE_TYPE_ARRAY, + res: CUDA_RESOURCE_DESC_st__bindgen_ty_1 { + array: CUDA_RESOURCE_DESC_st__bindgen_ty_1__bindgen_ty_1 { hArray: array_1 }, + }, + flags: 0, + }; + assert_eq!( + cuda.cuSurfObjectCreate(&mut surface, &surface_resource_desc), + CUresult::CUDA_SUCCESS + ); + let mut params = [&mut texture, &mut surface]; + assert_eq!( + cuda.cuLaunchKernel( + func, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ptr::null_mut(), + params.as_mut_ptr().cast(), + ptr::null_mut(), + ), + CUresult::CUDA_SUCCESS + ); + assert_eq!( + cuda.cuStreamSynchronize(ptr::null_mut()), + CUresult::CUDA_SUCCESS + ); + let mut memcpy_dst = [u16::MAX; 4]; + let memcpy_to_host = CUDA_MEMCPY2D { + srcXInBytes: 0, + srcY: 0, + srcMemoryType: CUmemorytype::CU_MEMORYTYPE_ARRAY, + srcHost: ptr::null(), + srcDevice: CUdeviceptr_v2(ptr::null_mut()), + srcArray: array_1, + srcPitch: 0, + dstXInBytes: 0, + dstY: 0, + dstMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST, + dstHost: memcpy_dst.as_mut_ptr() as _, + dstDevice: CUdeviceptr_v2(ptr::null_mut()), + dstArray: ptr::null_mut(), + dstPitch: 4 * mem::size_of::(), + WidthInBytes: 4 * mem::size_of::(), + Height: 1, + }; + assert_eq!( + cuda.cuMemcpy2DUnaligned_v2(&memcpy_to_host), + CUresult::CUDA_SUCCESS + ); + assert_eq!(&pixels, &memcpy_dst); +}