Files
d4r/patches/zluda/0002-dlss-runtime-cumulative.patch
T
ayo 7c5d3c9157 feat: add d4r inference engines and Windows parity tooling
Add native K/M/RR inference engines, HIP kernels, replay and validation
harnesses, and Windows DLSS setup and runtime support. Include GPU
profiling tools, build and packaging scripts, platform integration
patches, and implementation documentation.
2026-10-16 08:54:47 -04:00

2769 lines
117 KiB
Diff

diff --git a/cuda_macros/src/lib.rs b/cuda_macros/src/lib.rs
index 9188284..205f932 100644
--- a/cuda_macros/src/lib.rs
+++ b/cuda_macros/src/lib.rs
@@ -250,7 +250,7 @@ impl VisitMut for FixFnSignatures {
const MODULES: &[&str] = &[
"array", "context", "device", "driver", "event", "function", "graph", "kernel",
- "library", "link", "memory", "module", "pointer", "stream", "tex"
+ "library", "link", "memory", "module", "pointer", "stream", "surf", "tex"
];
fn normalize_fn_impl(
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
index 59bbf0f..b80262b 100644
--- a/llvm_zluda/src/compile.rs
+++ b/llvm_zluda/src/compile.rs
@@ -144,10 +144,17 @@ pub fn compile(
);
let pb_options = PassBuilderOptions::new();
+ // D4R_ZLUDA_PASSES overrides the optimization pipeline (e.g. "default<O1>")
+ // to tell translator bugs apart from optimizer-dependent miscompiles. It is
+ // not part of the module cache key: use a fresh cache when setting it.
+ let passes = std::env::var("D4R_ZLUDA_PASSES")
+ .ok()
+ .and_then(|value| std::ffi::CString::new(value).ok())
+ .unwrap_or_else(|| c"default<O3>".to_owned());
let error = unsafe {
LLVMRunPasses(
linked.get(),
- c"default<O3>".as_ptr(),
+ passes.as_ptr(),
target_machine.get(),
pb_options.get(),
)
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
index c889402..69d1faa 100644
--- a/ptx/lib/zluda_ptx_impl.cpp
+++ b/ptx/lib/zluda_ptx_impl.cpp
@@ -77,7 +77,9 @@ then cd to the directory with this file and run this simple command:
#include <utility>
#include <hip/hip_runtime.h>
#include <hip/amd_detail/amd_device_functions.h>
+#include <hip/amd_detail/amd_surface_functions.h>
#include <hip/amd_detail/amd_warp_sync_functions.h>
+#include <hip/amd_detail/ockl_image.h>
#include <hip/hip_fp8.h>
#define GLOBAL_SPACE __attribute__((address_space(1)))
@@ -87,6 +89,7 @@ then cd to the directory with this file and run this simple command:
typedef unsigned int v2u32 __attribute__((ext_vector_type(2)));
typedef int s32;
typedef int v1s32 __attribute__((ext_vector_type(1)));
+typedef short v2s16 __attribute__((ext_vector_type(2)));
typedef int v2s32 __attribute__((ext_vector_type(2)));
typedef int v4s32 __attribute__((ext_vector_type(4)));
typedef int v8s32 __attribute__((ext_vector_type(8)));
@@ -97,6 +100,7 @@ typedef float v2f32 __attribute__((ext_vector_type(2)));
typedef float v4f32 __attribute__((ext_vector_type(4)));
typedef float float8 __attribute__((ext_vector_type(8)));
typedef _Float16 f16;
+typedef _Float16 v4f16 __attribute__((ext_vector_type(4)));
typedef _Float16 f16x2 __attribute__((ext_vector_type(2)));
typedef _Float16 f16x4 __attribute__((ext_vector_type(4)));
typedef _Float16 f16x8 __attribute__((ext_vector_type(8)));
@@ -667,6 +671,12 @@ extern "C"
{
return sign; // +/- 0
}
+ // .satfinite maps infinities to the largest finite value (448 for
+ // E4M3, 57344 for E5M2); only NaN inputs produce NaN.
+ if (abs == 0x7f800000)
+ {
+ return sign | (interp == __HIP_E4M3 ? 0x7e : 0x7b);
+ }
return __hip_cvt_float_to_fp8(f, __HIP_SATFINITE, interp);
}
@@ -688,6 +698,32 @@ extern "C"
return {cvt_float_to_fp8(b, __HIP_E5M2), cvt_float_to_fp8(a, __HIP_E5M2)};
}
+ // .satfinite maps infinities to the largest finite value, but the HIP
+ // conversion turns them into NaN. Replace infinite halves with the
+ // destination format's largest finite value (exact in f16) first.
+ __device__ static __half2_raw saturate_infinite_halves(__half2 input, uint32_t max_finite_half)
+ {
+ uint32_t bits = std::bit_cast<uint32_t>(input);
+ uint32_t result = bits;
+ if ((bits & 0x7fffu) == 0x7c00u)
+ result = (result & 0xffff0000u) | (bits & 0x8000u) | max_finite_half;
+ if ((bits & 0x7fff0000u) == 0x7c000000u)
+ result = (result & 0x0000ffffu) | (bits & 0x80000000u) | (max_finite_half << 16);
+ return std::bit_cast<__half2_raw>(result);
+ }
+
+ Fp8x2 FUNC(cvt_rn_satfinite_e4m3x2_f16x2)(__half2 input)
+ {
+ auto output = __hip_cvt_halfraw2_to_fp8x2(saturate_infinite_halves(input, 0x5f00u /* 448 */), __HIP_SATFINITE, __HIP_E4M3);
+ return std::bit_cast<Fp8x2>(output);
+ }
+
+ Fp8x2 FUNC(cvt_rn_satfinite_e5m2x2_f16x2)(__half2 input)
+ {
+ auto output = __hip_cvt_halfraw2_to_fp8x2(saturate_infinite_halves(input, 0x7b00u /* 57344 */), __HIP_SATFINITE, __HIP_E5M2);
+ return std::bit_cast<Fp8x2>(output);
+ }
+
__half2 FUNC(cvt_rn_f16x2_e4m3x2)(__hip_fp8x2_e4m3 in)
{
return in;
@@ -791,6 +827,35 @@ extern "C"
return std::bit_cast<uint32_t>(ushort2::Native_vec_{lo, hi});
}
+ __device__ static void cp_async_zfill(void SHARED_SPACE *dst, void GLOBAL_SPACE *src, uint32_t size, uint32_t Bytes)
+ {
+ auto *to = static_cast<uint8_t SHARED_SPACE *>(dst);
+ auto *from = static_cast<const uint8_t GLOBAL_SPACE *>(src);
+ if (size >= Bytes)
+ {
+ __builtin_memcpy(to, from, Bytes);
+ return;
+ }
+ // Bytes at and beyond src-size are not read.
+ for (uint32_t index = 0; index < Bytes; ++index)
+ to[index] = index < size ? from[index] : uint8_t(0);
+ }
+
+ void FUNC(cp_async_zfill_4)(void SHARED_SPACE *dst, void GLOBAL_SPACE *src, uint32_t size)
+ {
+ cp_async_zfill(dst, src, size, 4);
+ }
+
+ void FUNC(cp_async_zfill_8)(void SHARED_SPACE *dst, void GLOBAL_SPACE *src, uint32_t size)
+ {
+ cp_async_zfill(dst, src, size, 8);
+ }
+
+ void FUNC(cp_async_zfill_16)(void SHARED_SPACE *dst, void GLOBAL_SPACE *src, uint32_t size)
+ {
+ cp_async_zfill(dst, src, size, 16);
+ }
+
uint2::Native_vec_ FUNC(ldmatrix_m8n8_x2_b16)(void SHARED_SPACE *address)
{
uint32_t x0 = load_single_matrix(address, 0);
@@ -816,6 +881,23 @@ extern "C"
return uint4::Native_vec_{x0, x1, x2, x3};
}
+ // movmatrix.sync.aligned.m8n8.trans.b16: lane L holds row L/4, columns
+ // 2*(L%4) and 2*(L%4)+1 of an 8x8 b16 matrix, and receives the same
+ // fragment of the transpose, i.e. elements [c][L/4] for both columns c.
+ // Element [i][j] lives in lane i*4 + j/2, half j%2.
+ uint32_t FUNC(movmatrix_m8n8_trans_b16)(uint32_t value)
+ {
+ uint32_t laneid = FUNC_CALL(sreg_laneid)();
+ uint32_t row = laneid / 4;
+ uint32_t column = (laneid % 4) * 2;
+ uint32_t low_source = column * 4 + row / 2;
+ uint32_t high_source = (column + 1) * 4 + row / 2;
+ uint32_t shift = (row % 2) * 16;
+ uint32_t low = uint32_t(__builtin_amdgcn_ds_bpermute(int(low_source << 2), int(value)));
+ uint32_t high = uint32_t(__builtin_amdgcn_ds_bpermute(int(high_source << 2), int(value)));
+ return ((low >> shift) & 0xffffu) | (((high >> shift) & 0xffffu) << 16);
+ }
+
struct byte4
{
union
@@ -1077,6 +1159,80 @@ extern "C"
}
return std::bit_cast<uint4::Native_vec_>(fallback_mma_sync_aligned<int32_t, s8x4>(a_reg, b_reg, std::bit_cast<HIP_vector_base<int32_t, 4>>(c_reg)));
}
+
+ __device__ static float4::Native_vec_ unpack_f16_accumulator(uint2::Native_vec_ packed)
+ {
+ auto low = std::bit_cast<f16x2>(packed.x);
+ auto high = std::bit_cast<f16x2>(packed.y);
+ return {float(low.x), float(low.y), float(high.x), float(high.y)};
+ }
+
+ __device__ static uint2::Native_vec_ pack_f16_accumulator(float4::Native_vec_ values)
+ {
+ f16x2 low{static_cast<f16>(values.x), static_cast<f16>(values.y)};
+ f16x2 high{static_cast<f16>(values.z), static_cast<f16>(values.w)};
+ return {std::bit_cast<uint32_t>(low), std::bit_cast<uint32_t>(high)};
+ }
+
+ uint2::Native_vec_ FUNC(mma_sync_aligned_m16n8k16_row_col_f16_f16_f16_f16)(uint4::Native_vec_ a_reg, uint2::Native_vec_ b_reg, uint2::Native_vec_ c_reg)
+ {
+ auto c = unpack_f16_accumulator(c_reg);
+ auto result = fallback_mma_sync_aligned<float, f16x2>(
+ a_reg, b_reg, HIP_vector_base<float, 4>(c.x, c.y, c.z, c.w));
+ return pack_f16_accumulator(result);
+ }
+
+ // m16n8k8 fragments are the first K half of m16n8k16 ones: A holds rows
+ // groupID and groupID+8 at columns 2*threadID+{0,1}, B holds K rows
+ // 2*threadID+{0,1}. Zero the second K half and reuse the k16 path.
+ uint2::Native_vec_ FUNC(mma_sync_aligned_m16n8k8_row_col_f16_f16_f16_f16)(uint2::Native_vec_ a_reg, uint1::Native_vec_ b_reg, uint2::Native_vec_ c_reg)
+ {
+ auto c = unpack_f16_accumulator(c_reg);
+ uint4::Native_vec_ a{a_reg.x, a_reg.y, 0u, 0u};
+ uint2::Native_vec_ b{b_reg.x, 0u};
+ auto result = fallback_mma_sync_aligned<float, f16x2>(
+ a, b, HIP_vector_base<float, 4>(c.x, c.y, c.z, c.w));
+ return pack_f16_accumulator(result);
+ }
+
+ __device__ static uint32_t e4m3x2_to_f16x2_bits(uint16_t packed)
+ {
+ __hip_fp8x2_e4m3 value;
+ value.__x = packed;
+ auto half_pair = FUNC_CALL(cvt_rn_f16x2_e4m3x2)(value);
+ return std::bit_cast<uint32_t>(half_pair);
+ }
+
+ // One K=16 half of an m16n8k32 8-bit A fragment as an m16n8k16 f16 A
+ // fragment. upper holds row groupID (4 columns from 4*threadID), lower
+ // holds row groupID+8. The f16 layout wants {upper, lower, upper, lower}
+ // rows, so the first column pair of each row goes to registers 0/1 and the
+ // second to 2/3. This permutes K the same way as e4m3_b_k16_fragment.
+ __device__ static uint4::Native_vec_ e4m3_a_k16_fragment(uint32_t upper, uint32_t lower)
+ {
+ return {e4m3x2_to_f16x2_bits(uint16_t(upper)),
+ e4m3x2_to_f16x2_bits(uint16_t(lower)),
+ e4m3x2_to_f16x2_bits(uint16_t(upper >> 16)),
+ e4m3x2_to_f16x2_bits(uint16_t(lower >> 16))};
+ }
+
+ __device__ static uint2::Native_vec_ e4m3_b_k16_fragment(uint32_t packed)
+ {
+ return {e4m3x2_to_f16x2_bits(uint16_t(packed)),
+ e4m3x2_to_f16x2_bits(uint16_t(packed >> 16))};
+ }
+
+ uint2::Native_vec_ FUNC(mma_sync_aligned_m16n8k32_row_col_f16_e4m3_e4m3_f16)(uint4::Native_vec_ a_reg, uint2::Native_vec_ b_reg, uint2::Native_vec_ c_reg)
+ {
+ auto c = unpack_f16_accumulator(c_reg);
+ auto result0 = fallback_mma_sync_aligned<float, f16x2>(
+ e4m3_a_k16_fragment(a_reg.x, a_reg.y), e4m3_b_k16_fragment(b_reg.x),
+ HIP_vector_base<float, 4>(c.x, c.y, c.z, c.w));
+ auto result1 = fallback_mma_sync_aligned<float, f16x2>(
+ e4m3_a_k16_fragment(a_reg.z, a_reg.w), e4m3_b_k16_fragment(b_reg.y),
+ HIP_vector_base<float, 4>(result0.x, result0.y, result0.z, result0.w));
+ return pack_f16_accumulator(result1);
+ }
}
template <typename T, typename A, typename B, int BOffset, typename OcklFunc>
@@ -1195,11 +1351,50 @@ extern "C"
GLOBAL_SPACE v4s32 *sampler_typed = (GLOBAL_SPACE v4s32 *)sampler;
return __llvm_amdgcn_image_sample_lz_2d_v4f32_f32(0xf, coord.x, coord.y, *image_typed, *sampler_typed, false, 0, 0);
}
+
+ static v4f32 sample_2D_level(GLOBAL_SPACE void *image, GLOBAL_SPACE void *sampler, v2f32 coord, float lod) __device__
+ {
+ __device__ v4f32 __llvm_amdgcn_image_sample_l_2d_v4f32_f32(uint32_t, float, float, float, v8s32, v4s32, bool, int, int) __asm("llvm.amdgcn.image.sample.l.2d.v4f32.f32");
+ GLOBAL_SPACE v8s32 *image_typed = (GLOBAL_SPACE v8s32 *)image;
+ GLOBAL_SPACE v4s32 *sampler_typed = (GLOBAL_SPACE v4s32 *)sampler;
+ return __llvm_amdgcn_image_sample_l_2d_v4f32_f32(0xf, coord.x, coord.y, lod, *image_typed, *sampler_typed, false, 0, 0);
+ }
+ // Integer texture coordinates address texels directly. NVIDIA compiles
+ // tex with .s32 coordinates to a texel load (TLD) that ignores the
+ // sampler's normalized-coordinate and filter modes; NGX creates every
+ // texture with normalized coordinates and reads per-pixel data this way.
+ static v4f32 fetch_2D(GLOBAL_SPACE void *image, v2s32 coord, int32_t mip) __device__
+ {
+ __device__ v4f32 __llvm_amdgcn_image_load_mip_2d_v4f32_i32(uint32_t, int32_t, int32_t, int32_t, v8s32, int, int) __asm("llvm.amdgcn.image.load.mip.2d.v4f32.i32");
+ GLOBAL_SPACE v8s32 *image_typed = (GLOBAL_SPACE v8s32 *)image;
+ return __llvm_amdgcn_image_load_mip_2d_v4f32_i32(0xf, coord.x, coord.y, mip, *image_typed, 0, 0);
+ }
+
+ static v4f32 sample_or_fetch_2D(GLOBAL_SPACE void *image, GLOBAL_SPACE void *sampler, v2f32 coord) __device__
+ {
+ return sample_2D(image, sampler, coord);
+ }
+
+ static v4f32 sample_or_fetch_2D(GLOBAL_SPACE void *image, GLOBAL_SPACE void *, v2s32 coord) __device__
+ {
+ return fetch_2D(image, coord, 0);
+ }
+
+ static v4f32 sample_or_fetch_2D_level(GLOBAL_SPACE void *image, GLOBAL_SPACE void *sampler, v2f32 coord, float lod) __device__
+ {
+ return sample_2D_level(image, sampler, coord, lod);
+ }
+
+ static v4f32 sample_or_fetch_2D_level(GLOBAL_SPACE void *image, GLOBAL_SPACE void *, v2s32 coord, float lod) __device__
+ {
+ return fetch_2D(image, coord, int32_t(lod));
+ }
+
#define tex_2d(RETURN_TYPE, COORD_TYPE) \
v4##RETURN_TYPE FUNC(texobj_2d_v4_##RETURN_TYPE##_##COORD_TYPE)(uint64_t texobj, v2##COORD_TYPE coord) \
{ \
auto [i, s] = get_image_and_sampler(texobj); \
- auto result = sample_2D(i, s, v2f32{float(coord.x), float(coord.y)}); \
+ auto result = sample_or_fetch_2D(i, s, coord); \
return v4##RETURN_TYPE{std::bit_cast<RETURN_TYPE>(result.x), std::bit_cast<RETURN_TYPE>(result.y), std::bit_cast<RETURN_TYPE>(result.z), std::bit_cast<RETURN_TYPE>(result.w)}; \
} \
v4##RETURN_TYPE FUNC(texref_2d_v4_##RETURN_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * texref, v2##COORD_TYPE coord) \
@@ -1212,6 +1407,54 @@ extern "C"
tex_2d(f32, s32);
tex_2d(s32, f32);
+ v4f16 FUNC(texobj_2d_v4_f16_s32)(uint64_t texobj, v2s32 coord)
+ {
+ auto [i, s] = get_image_and_sampler(texobj);
+ auto result = sample_or_fetch_2D(i, s, coord);
+ return {f16(result.x), f16(result.y), f16(result.z), f16(result.w)};
+ }
+ v4f16 FUNC(texref_2d_v4_f16_s32)(struct textureReference GLOBAL_SPACE *texref, v2s32 coord)
+ {
+ return FUNC_CALL(texobj_2d_v4_f16_s32)(uint64_t(texref->textureObject), coord);
+ }
+
+#define tex_2d_level(RETURN_TYPE, COORD_TYPE) \
+ v4##RETURN_TYPE FUNC(texobj_level_2d_v4_##RETURN_TYPE##_##COORD_TYPE)(uint64_t texobj, v2##COORD_TYPE coord, float lod) \
+ { \
+ auto [i, s] = get_image_and_sampler(texobj); \
+ auto result = sample_or_fetch_2D_level(i, s, coord, lod); \
+ return v4##RETURN_TYPE{std::bit_cast<RETURN_TYPE>(result.x), std::bit_cast<RETURN_TYPE>(result.y), std::bit_cast<RETURN_TYPE>(result.z), std::bit_cast<RETURN_TYPE>(result.w)}; \
+ } \
+ v4##RETURN_TYPE FUNC(texref_level_2d_v4_##RETURN_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * texref, v2##COORD_TYPE coord, float lod) \
+ { \
+ return FUNC_CALL(texobj_level_2d_v4_##RETURN_TYPE##_##COORD_TYPE)(uint64_t(texref->textureObject), coord, lod); \
+ }
+
+ tex_2d_level(f32, f32);
+ tex_2d_level(s32, s32);
+ tex_2d_level(f32, s32);
+ tex_2d_level(s32, f32);
+
+#define tex_2d_gather4(COMPONENT) \
+ v4f32 FUNC(texobj_gather4##COMPONENT##_2d_v4_f32_f32)(uint64_t texobj, v2f32 coord) \
+ { \
+ auto image = reinterpret_cast<unsigned int CONSTANT_SPACE *>(static_cast<uintptr_t>(texobj)); \
+ auto sampler = image + HIP_SAMPLER_OBJECT_OFFSET_DWORD; \
+ float2 coordinates{coord.x, coord.y}; \
+ auto result = __ockl_image_gather4##COMPONENT##_2D(image, sampler, get_native_vector(coordinates)); \
+ return v4f32{result.x, result.y, result.z, result.w}; \
+ } \
+ v4f32 FUNC(texref_gather4##COMPONENT##_2d_v4_f32_f32)(struct textureReference GLOBAL_SPACE *texref, v2f32 coord) \
+ { \
+ return FUNC_CALL(texobj_gather4##COMPONENT##_2d_v4_f32_f32)(uint64_t(texref->textureObject), coord); \
+ }
+
+ tex_2d_gather4(r);
+ tex_2d_gather4(g);
+ tex_2d_gather4(b);
+ tex_2d_gather4(a);
+#undef tex_2d_gather4
+
static v4f32 sample_3D(GLOBAL_SPACE void *image, GLOBAL_SPACE void *sampler, v4f32 coord) __device__
{
__device__ v4f32 __llvm_amdgcn_image_sample_lz_3d_v4f32_f32(uint32_t, float, float, float, v8s32, v4s32, bool, int, int) __asm("llvm.amdgcn.image.sample.lz.3d.v4f32.f32");
@@ -1219,11 +1462,23 @@ extern "C"
GLOBAL_SPACE v4s32 *sampler_typed = (GLOBAL_SPACE v4s32 *)sampler;
return __llvm_amdgcn_image_sample_lz_3d_v4f32_f32(0xf, coord.x, coord.y, coord.z, *image_typed, *sampler_typed, false, 0, 0);
}
+ static v4f32 sample_or_fetch_3D(GLOBAL_SPACE void *image, GLOBAL_SPACE void *sampler, v4f32 coord) __device__
+ {
+ return sample_3D(image, sampler, coord);
+ }
+
+ static v4f32 sample_or_fetch_3D(GLOBAL_SPACE void *image, GLOBAL_SPACE void *, v4s32 coord) __device__
+ {
+ __device__ v4f32 __llvm_amdgcn_image_load_3d_v4f32_i32(uint32_t, int32_t, int32_t, int32_t, v8s32, int, int) __asm("llvm.amdgcn.image.load.3d.v4f32.i32");
+ GLOBAL_SPACE v8s32 *image_typed = (GLOBAL_SPACE v8s32 *)image;
+ return __llvm_amdgcn_image_load_3d_v4f32_i32(0xf, coord.x, coord.y, coord.z, *image_typed, 0, 0);
+ }
+
#define tex_3d(RETURN_TYPE, COORD_TYPE) \
v4##RETURN_TYPE FUNC(texobj_3d_v4_##RETURN_TYPE##_##COORD_TYPE)(uint64_t texobj, v4##COORD_TYPE coord) \
{ \
auto [i, s] = get_image_and_sampler(texobj); \
- auto result = sample_3D(i, s, v4f32{float(coord.x), float(coord.y), float(coord.z), float(coord.w)}); \
+ auto result = sample_or_fetch_3D(i, s, coord); \
return v4##RETURN_TYPE{std::bit_cast<RETURN_TYPE>(result.x), std::bit_cast<RETURN_TYPE>(result.y), std::bit_cast<RETURN_TYPE>(result.z), std::bit_cast<RETURN_TYPE>(result.w)}; \
} \
v4##RETURN_TYPE FUNC(texref_3d_v4_##RETURN_TYPE##_##COORD_TYPE)(struct textureReference GLOBAL_SPACE * texref, v4##COORD_TYPE coord) \
@@ -1236,6 +1491,401 @@ extern "C"
tex_3d(f32, s32);
tex_3d(s32, f32);
+ // A ZLUDA surface object is a copy of HIP's image object (so the handle
+ // also works for texel fetches, as on NVIDIA), a zeroed sampler slot, then
+ // the original CUDA format: HIP integer backing alone cannot distinguish
+ // UNORM/SNORM from UINT/SINT. See zluda/src/impl/surf.rs.
+ struct D4rSurfaceHandle
+ {
+ uint64_t native; // address of the image descriptor
+ uint32_t cuda_format;
+ };
+
+ static D4rSurfaceHandle load_surface_handle(uint64_t surface_object) __device__
+ {
+ constexpr uint32_t format_offset_dwords = HIP_IMAGE_OBJECT_SIZE_DWORD + HIP_SAMPLER_OBJECT_SIZE_DWORD;
+ auto words = reinterpret_cast<GLOBAL_SPACE const uint32_t *>(static_cast<uintptr_t>(surface_object));
+ return {surface_object, words[format_offset_dwords]};
+ }
+
+ // PTX sust.p takes f32 values for UNORM/SNORM and integer values for
+ // UINT/SINT. Convert only arrays whose original CUDA descriptor was
+ // normalized; their HIP backing arrays are ordinary integer storage.
+ [[clang::noinline]]
+ static uint32_t surface_formatted_store_bits(uint32_t bits, uint32_t cuda_format,
+ int channel) __device__
+ {
+ if (cuda_format == 80) // UNORM_INT101010_2, backed by RGBA32F
+ {
+ float value = std::bit_cast<float>(bits);
+ if (!(value == value) || value < 0.0f)
+ value = 0.0f;
+ if (value > 1.0f)
+ value = 1.0f;
+ int scale = channel == 3 ? 3 : 1023;
+ int quantized = int(floorf(value * float(scale) + 0.5f));
+ return std::bit_cast<uint32_t>(float(quantized) / float(scale));
+ }
+ int scale = 0;
+ bool signed_normalized = false;
+ if (cuda_format >= 192 && cuda_format <= 194)
+ scale = 255;
+ else if (cuda_format >= 195 && cuda_format <= 197)
+ scale = 65535;
+ else if (cuda_format >= 198 && cuda_format <= 200)
+ {
+ scale = 127;
+ signed_normalized = true;
+ }
+ else if (cuda_format >= 201 && cuda_format <= 203)
+ {
+ scale = 32767;
+ signed_normalized = true;
+ }
+ if (scale == 0)
+ return bits;
+ float value = std::bit_cast<float>(bits);
+ if (!(value == value))
+ value = 0.0f;
+ if (value < (signed_normalized ? -1.0f : 0.0f))
+ value = signed_normalized ? -1.0f : 0.0f;
+ if (value > 1.0f)
+ value = 1.0f;
+ float scaled = value * float(scale);
+ int32_t integer = scaled < 0.0f
+ ? -int32_t(floorf(-scaled + 0.5f))
+ : int32_t(floorf(scaled + 0.5f));
+ return uint32_t(integer);
+ }
+
+ void FUNC(surfobj_p_2d_b32_zero)(uint64_t surface_object, v2s32 coord, uint32_t value)
+ {
+ auto wrapper = load_surface_handle(surface_object);
+ auto surface = reinterpret_cast<unsigned int ADDRESS_SPACE_CONSTANT *>(static_cast<uintptr_t>(wrapper.native));
+ int2 coordinates{coord.x, coord.y};
+ float4 pixel{std::bit_cast<float>(surface_formatted_store_bits(value, wrapper.cuda_format, 0)),
+ 0.0f, 0.0f, 0.0f};
+ auto native_pixel = __hipMapTo<float4::Native_vec_>(pixel);
+ __ockl_image_store_2D(surface, get_native_vector(coordinates), native_pixel);
+ }
+
+ void FUNC(surfobj_p_2d_v4_b32_zero)(uint64_t surface_object, v2s32 coord, v4s32 value)
+ {
+ auto wrapper = load_surface_handle(surface_object);
+ auto surface = reinterpret_cast<unsigned int ADDRESS_SPACE_CONSTANT *>(static_cast<uintptr_t>(wrapper.native));
+ int2 coordinates{coord.x, coord.y};
+ v4s32 converted{
+ int32_t(surface_formatted_store_bits(uint32_t(value.x), wrapper.cuda_format, 0)),
+ int32_t(surface_formatted_store_bits(uint32_t(value.y), wrapper.cuda_format, 1)),
+ int32_t(surface_formatted_store_bits(uint32_t(value.z), wrapper.cuda_format, 2)),
+ int32_t(surface_formatted_store_bits(uint32_t(value.w), wrapper.cuda_format, 3))};
+ float4 pixel = std::bit_cast<float4>(converted);
+ auto native_pixel = __hipMapTo<float4::Native_vec_>(pixel);
+ __ockl_image_store_2D(surface, get_native_vector(coordinates), native_pixel);
+ }
+
+ // Unformatted (sust.b) stores write raw bytes at a byte-addressed X
+ // coordinate. AMD image stores always go through the format converter, so
+ // raw channel bits are decoded into the value the converter maps back to
+ // the same bits. Channel types use hsa_ext_image_channel_type_t numbering
+ // (see __hipGetPixelAddr), orders use hsa_ext_image_channel_order_t.
+ static int surface_channel_bytes(int data_type) __device__
+ {
+ switch (data_type)
+ {
+ case 0: // SNORM_INT8
+ case 2: // UNORM_INT8
+ case 8: // SIGNED_INT8
+ case 11: // UNSIGNED_INT8
+ return 1;
+ case 1: // SNORM_INT16
+ case 3: // UNORM_INT16
+ case 9: // SIGNED_INT16
+ case 12: // UNSIGNED_INT16
+ case 14: // HALF_FLOAT
+ return 2;
+ case 10: // SIGNED_INT32
+ case 13: // UNSIGNED_INT32
+ case 15: // FLOAT
+ return 4;
+ default: // packed formats have no per-channel byte layout
+ return 0;
+ }
+ }
+
+ static int surface_channel_count(int order) __device__
+ {
+ switch (order)
+ {
+ case 3: // RG
+ case 4: // RGX
+ case 5: // RA
+ return 2;
+ case 6: // RGB
+ case 12: // SRGB
+ return 3;
+ case 7: // RGBX
+ case 8: // RGBA
+ case 9: // BGRA
+ case 10: // ARGB
+ case 11: // ABGR
+ case 13: // SRGBX
+ case 14: // SRGBA
+ case 15: // SBGRA
+ return 4;
+ default:
+ return 1;
+ }
+ }
+
+ // Maps the Nth channel in memory to its float4 lane in image load/store.
+ static int surface_memory_channel_lane(int order, int channel) __device__
+ {
+ switch (order)
+ {
+ case 0: // A
+ return 3;
+ case 5: // RA
+ return channel == 0 ? 0 : 3;
+ case 9: // BGRA
+ case 15: // SBGRA
+ return channel == 0 ? 2 : channel == 2 ? 0 : channel;
+ case 10: // ARGB
+ return channel == 0 ? 3 : channel - 1;
+ case 11: // ABGR
+ return 3 - channel;
+ default:
+ return channel;
+ }
+ }
+
+ // Exact half<->float conversions done on bits, so half subnormals survive
+ // even when the kernel is compiled with FP16 denormal flushing.
+ static float surface_half_bits_to_float(uint32_t bits) __device__
+ {
+ uint32_t sign = (bits & 0x8000u) << 16;
+ uint32_t exponent = (bits >> 10) & 0x1fu;
+ uint32_t mantissa = bits & 0x3ffu;
+ if (exponent == 0)
+ {
+ // Half subnormals are f32 normals: mantissa * 2^-24 is exact.
+ float magnitude = float(mantissa) * 0x1p-24f;
+ return std::bit_cast<float>(std::bit_cast<uint32_t>(magnitude) | sign);
+ }
+ if (exponent == 0x1f)
+ return std::bit_cast<float>(sign | 0x7f800000u | (mantissa << 13));
+ return std::bit_cast<float>(sign | ((exponent + 112u) << 23) | (mantissa << 13));
+ }
+
+ static uint32_t surface_float_to_half_bits(float value) __device__
+ {
+ uint32_t bits = std::bit_cast<uint32_t>(value);
+ uint32_t sign = (bits >> 16) & 0x8000u;
+ uint32_t exponent = (bits >> 23) & 0xffu;
+ uint32_t mantissa = bits & 0x7fffffu;
+ if (exponent == 0xff)
+ return sign | 0x7c00u | (mantissa != 0 ? 0x200u | (mantissa >> 13) : 0u);
+ int half_exponent = int(exponent) - 112;
+ if (half_exponent >= 31)
+ return sign | 0x7c00u;
+ uint32_t shift = 13;
+ if (half_exponent <= 0)
+ {
+ if (half_exponent < -10)
+ return sign;
+ mantissa |= 0x800000u;
+ shift = uint32_t(14 - half_exponent);
+ half_exponent = 0;
+ }
+ uint32_t half_mantissa = mantissa >> shift;
+ uint32_t remainder = mantissa & ((1u << shift) - 1u);
+ uint32_t halfway = 1u << (shift - 1u);
+ uint32_t result = sign | (uint32_t(half_exponent) << 10);
+ // Round to nearest even; a mantissa carry correctly bumps the exponent.
+ result += half_mantissa & (half_exponent == 0 ? 0x7ffu : 0x3ffu);
+ if (remainder > halfway || (remainder == halfway && (half_mantissa & 1u)))
+ result++;
+ return result;
+ }
+
+ // The normalized conversions below stick to FP operations that the
+ // AMDGPU backend can lower in the strict-FP (constrained) helper build;
+ // constrained min/max/rint/fptoui and i8/i16 int-to-float become
+ // unsupported library calls there. Clamping is done on integers.
+ static int32_t surface_clamp(int32_t value, int32_t low, int32_t high) __device__
+ {
+ return value < low ? low : (value > high ? high : value);
+ }
+
+ static float surface_decode_channel(uint32_t bits, int data_type) __device__
+ {
+ switch (data_type)
+ {
+ case 0: // SNORM_INT8
+ return float(surface_clamp(int32_t(int8_t(bits)), -127, 127)) / 127.0f;
+ case 1: // SNORM_INT16
+ return float(surface_clamp(int32_t(int16_t(bits)), -32767, 32767)) / 32767.0f;
+ case 2: // UNORM_INT8
+ return float(int32_t(bits & 0xffu)) / 255.0f;
+ case 3: // UNORM_INT16
+ return float(int32_t(bits & 0xffffu)) / 65535.0f;
+ case 8: // SIGNED_INT8
+ return std::bit_cast<float>(int32_t(int8_t(bits)));
+ case 9: // SIGNED_INT16
+ return std::bit_cast<float>(int32_t(int16_t(bits)));
+ case 14: // HALF_FLOAT
+ return surface_half_bits_to_float(bits);
+ default: // unsigned integers and 32-bit types store their bits as-is
+ return std::bit_cast<float>(bits);
+ }
+ }
+
+ static uint32_t surface_encode_channel(float value, int data_type) __device__
+ {
+ switch (data_type)
+ {
+ // Loaded normalized values are already within range, so rounding to
+ // nearest via floor(x + 0.5) only has to undo the load conversion.
+ case 0: // SNORM_INT8
+ return uint32_t(surface_clamp(int32_t(floorf(value * 127.0f + 0.5f)), -127, 127)) & 0xffu;
+ case 1: // SNORM_INT16
+ return uint32_t(surface_clamp(int32_t(floorf(value * 32767.0f + 0.5f)), -32767, 32767)) & 0xffffu;
+ case 2: // UNORM_INT8
+ return uint32_t(surface_clamp(int32_t(floorf(value * 255.0f + 0.5f)), 0, 255));
+ case 3: // UNORM_INT16
+ return uint32_t(surface_clamp(int32_t(floorf(value * 65535.0f + 0.5f)), 0, 65535));
+ case 14: // HALF_FLOAT
+ return surface_float_to_half_bits(value);
+ default:
+ return std::bit_cast<uint32_t>(value);
+ }
+ }
+
+ // Inverse of surface_memory_channel_lane: the memory channel feeding a
+ // float4 lane, or -1 when the lane has no channel in this order.
+ static int surface_lane_channel(int order, int lane, int channels) __device__
+ {
+ switch (order)
+ {
+ case 0: // A
+ return lane == 3 ? 0 : -1;
+ case 5: // RA
+ return lane == 0 ? 0 : (lane == 3 ? 1 : -1);
+ case 9: // BGRA
+ case 15: // SBGRA
+ return lane == 0 ? 2 : (lane == 2 ? 0 : lane);
+ case 10: // ARGB
+ return lane == 3 ? 0 : lane + 1;
+ case 11: // ABGR
+ return 3 - lane;
+ default:
+ return lane < channels ? lane : -1;
+ }
+ }
+
+ // The pixel is kept in scalar per-channel variables selected with
+ // conditionals. Byte arrays indexed at runtime turn into <16 x i8>
+ // vectors, which crash AMDGPU instruction selection (getRegClassFor from
+ // PostISelFolding) once inlined into large kernels such as rrlite_enc0.
+ static uint32_t surface_select_channel(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3,
+ int channel) __device__
+ {
+ return channel == 0 ? c0 : (channel == 1 ? c1 : (channel == 2 ? c2 : c3));
+ }
+
+ static float surface_select_lane(float4::Native_vec_ value, int lane) __device__
+ {
+ return lane == 0 ? value.x : (lane == 1 ? value.y : (lane == 2 ? value.z : value.w));
+ }
+
+ // Replaces the bytes of one channel that overlap the stored bytes.
+ static uint32_t surface_merge_channel(uint32_t bits, int channel, int channel_bytes, uint32_t data,
+ int byte_offset, int byte_count) __device__
+ {
+ for (int b = 0; b < 4; b++)
+ {
+ int relative = channel * channel_bytes + b - byte_offset;
+ if (b < channel_bytes && relative >= 0 && relative < byte_count)
+ {
+ uint32_t byte = (data >> (8 * relative)) & 0xffu;
+ bits = (bits & ~(0xffu << (8 * b))) | (byte << (8 * b));
+ }
+ }
+ return bits;
+ }
+
+ static float surface_lane_value(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3, int order,
+ int lane, int channels, int data_type) __device__
+ {
+ int channel = surface_lane_channel(order, lane, channels);
+ if (channel < 0)
+ return 0.0f;
+ return surface_decode_channel(surface_select_channel(c0, c1, c2, c3, channel), data_type);
+ }
+
+ // Stores byte_count (at most 4) little-endian bytes of data at the byte
+ // offset coord.x of row coord.y.
+ static void surface_store_raw_2d(unsigned int ADDRESS_SPACE_CONSTANT *image, v2s32 coord,
+ uint32_t data, int byte_count) __device__
+ {
+ int data_type = __ockl_image_channel_data_type_2D(image);
+ int order = __ockl_image_channel_order_2D(image);
+ int channel_bytes = surface_channel_bytes(data_type);
+ // .zero clamping drops out-of-bounds stores; negative X never hits a pixel.
+ if (channel_bytes == 0 || coord.x < 0)
+ return;
+ int channels = surface_channel_count(order);
+ int pixel_bytes = channel_bytes * channels;
+ int pixel_x = coord.x / pixel_bytes;
+ int byte_offset = coord.x - pixel_x * pixel_bytes;
+ int2 coordinates{pixel_x, coord.y};
+ auto native_coordinates = get_native_vector(coordinates);
+ uint32_t c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+ // Partial-pixel stores keep the untouched bytes of the existing pixel.
+ if (byte_offset != 0 || byte_count < pixel_bytes)
+ {
+ auto loaded = __ockl_image_load_2D(image, native_coordinates);
+ c0 = surface_encode_channel(surface_select_lane(loaded, surface_memory_channel_lane(order, 0)), data_type);
+ c1 = surface_encode_channel(surface_select_lane(loaded, surface_memory_channel_lane(order, 1)), data_type);
+ c2 = surface_encode_channel(surface_select_lane(loaded, surface_memory_channel_lane(order, 2)), data_type);
+ c3 = surface_encode_channel(surface_select_lane(loaded, surface_memory_channel_lane(order, 3)), data_type);
+ }
+ c0 = surface_merge_channel(c0, 0, channel_bytes, data, byte_offset, byte_count);
+ c1 = surface_merge_channel(c1, 1, channel_bytes, data, byte_offset, byte_count);
+ c2 = surface_merge_channel(c2, 2, channel_bytes, data, byte_offset, byte_count);
+ c3 = surface_merge_channel(c3, 3, channel_bytes, data, byte_offset, byte_count);
+ float4 value{surface_lane_value(c0, c1, c2, c3, order, 0, channels, data_type),
+ surface_lane_value(c0, c1, c2, c3, order, 1, channels, data_type),
+ surface_lane_value(c0, c1, c2, c3, order, 2, channels, data_type),
+ surface_lane_value(c0, c1, c2, c3, order, 3, channels, data_type)};
+ __ockl_image_store_2D(image, native_coordinates, __hipMapTo<float4::Native_vec_>(value));
+ }
+
+ void FUNC(surfobj_b_2d_v2_b16_zero)(uint64_t surface_object, v2s32 coord, v2s16 value)
+ {
+ auto wrapper = load_surface_handle(surface_object);
+ auto image = reinterpret_cast<unsigned int ADDRESS_SPACE_CONSTANT *>(
+ static_cast<uintptr_t>(wrapper.native));
+ uint32_t data = uint32_t(uint16_t(value.x)) | (uint32_t(uint16_t(value.y)) << 16);
+ surface_store_raw_2d(image, coord, data, 4);
+ }
+
+ void FUNC(surfobj_b_2d_b32_zero)(uint64_t surface_object, v2s32 coord, uint32_t value)
+ {
+ auto wrapper = load_surface_handle(surface_object);
+ auto image = reinterpret_cast<unsigned int ADDRESS_SPACE_CONSTANT *>(
+ static_cast<uintptr_t>(wrapper.native));
+ surface_store_raw_2d(image, coord, value, 4);
+ }
+
+ // v_exp_f32 per half; ocml's f16 exp2 becomes a libcall the backend
+ // cannot lower in constrained floating-point mode.
+ f16x2 FUNC(ex2_approx_f16x2)(f16x2 a)
+ {
+ return f16x2{static_cast<_Float16>(__builtin_amdgcn_exp2f(static_cast<float>(a.x))),
+ static_cast<_Float16>(__builtin_amdgcn_exp2f(static_cast<float>(a.y)))};
+ }
+
__device__ half __ocml_tanh_f16(half);
half FUNC(tanh_f16)(half a)
{
diff --git a/ptx/src/pass/insert_implicit_conversions.rs b/ptx/src/pass/insert_implicit_conversions.rs
index 1007bcc..f706a32 100644
--- a/ptx/src/pass/insert_implicit_conversions.rs
+++ b/ptx/src/pass/insert_implicit_conversions.rs
@@ -82,6 +82,24 @@ fn insert_implicit_conversions_impl<'input>(
data.type_ = ast::TexType::Texobj;
}
}
+ if let ExpandedStatement::Instruction(ast::Instruction::TexLevel {
+ ref mut data,
+ ref arguments,
+ }) = stmt
+ {
+ let (type_, space) = resolver.get_typed(arguments.src_ptr)?;
+ if matches!(
+ (type_, space),
+ (
+ ast::Type::Scalar(
+ ast::ScalarType::B64 | ast::ScalarType::U64 | ast::ScalarType::S64
+ ),
+ ast::StateSpace::Reg
+ )
+ ) {
+ data.type_ = ast::TexType::Texobj;
+ }
+ }
let statement = stmt.visit_map::<SpirvWord, TranslateError>(
&mut |operand,
type_state: Option<(&ast::Type, ast::StateSpace)>,
diff --git a/ptx/src/pass/insert_post_saturation.rs b/ptx/src/pass/insert_post_saturation.rs
index 0c92506..f93509f 100644
--- a/ptx/src/pass/insert_post_saturation.rs
+++ b/ptx/src/pass/insert_post_saturation.rs
@@ -133,7 +133,9 @@ fn run_instruction<'input>(
| ast::Instruction::Ex2 { .. }
| ast::Instruction::Fma {
data: ast::ArithFloat {
- saturate: false, ..
+ saturate: false,
+ relu: false,
+ ..
},
..
}
@@ -208,12 +210,29 @@ fn run_instruction<'input>(
| ast::Instruction::ReduxSync { .. }
| ast::Instruction::GridDepControl { .. }
| ast::Instruction::LdMatrix { .. }
+ | ast::Instruction::Movmatrix { .. }
| ast::Instruction::Prefetch { .. }
| ast::Instruction::Sad { .. }
| ast::Instruction::Dp2a { .. }
+ | ast::Instruction::Sust { .. }
+ | ast::Instruction::TexLevel { .. }
| ast::Instruction::Tex { .. }
| ast::Instruction::Mma { .. }
| ast::Instruction::Vshr { .. } => result.push(Statement::Instruction(instruction)),
+ ast::Instruction::Fma {
+ data:
+ ast::ArithFloat {
+ saturate: false,
+ relu: true,
+ type_,
+ ..
+ },
+ arguments: ast::FmaArgs { ref mut dst, .. },
+ } => {
+ let relu = get_post_clamp(resolver, type_, dst, true)?;
+ result.push(Statement::Instruction(instruction));
+ result.push(relu);
+ }
ast::Instruction::Add {
data:
ast::ArithDetails::Float(ast::ArithFloat {
@@ -316,6 +335,15 @@ fn get_post_saturation<'input>(
resolver: &mut GlobalStringIdentResolver2<'input>,
type_: ast::ScalarType,
old_dst: &mut SpirvWord,
+) -> Result<Statement<ast::Instruction<SpirvWord>, SpirvWord>, TranslateError> {
+ get_post_clamp(resolver, type_, old_dst, false)
+}
+
+fn get_post_clamp<'input>(
+ resolver: &mut GlobalStringIdentResolver2<'input>,
+ type_: ast::ScalarType,
+ old_dst: &mut SpirvWord,
+ relu: bool,
) -> Result<Statement<ast::Instruction<SpirvWord>, SpirvWord>, TranslateError> {
let post_sat = resolver.register_unnamed(Some((type_.into(), ast::StateSpace::Reg)));
let dst = *old_dst;
@@ -324,5 +352,6 @@ fn get_post_saturation<'input>(
dst,
src: post_sat,
type_,
+ relu,
})
}
diff --git a/ptx/src/pass/instruction_mode_to_global_mode/mod.rs b/ptx/src/pass/instruction_mode_to_global_mode/mod.rs
index e68a19d..e6f22a6 100644
--- a/ptx/src/pass/instruction_mode_to_global_mode/mod.rs
+++ b/ptx/src/pass/instruction_mode_to_global_mode/mod.rs
@@ -2179,11 +2179,14 @@ fn get_modes<T: ast::Operand>(inst: &ast::Instruction<T>) -> InstructionModes {
| ast::Instruction::ReduxSync { .. }
| ast::Instruction::GridDepControl { .. }
| ast::Instruction::LdMatrix { .. }
+ | ast::Instruction::Movmatrix { .. }
| ast::Instruction::Mma { .. }
| ast::Instruction::Prefetch { .. }
| ast::Instruction::CreatePolicyFractional { .. }
| ast::Instruction::Dp2a { .. }
+ | ast::Instruction::Sust { .. }
| ast::Instruction::Sad { .. }
+ | ast::Instruction::TexLevel { .. }
| ast::Instruction::Tex { .. }
| ast::Instruction::Copysign { .. } => InstructionModes::none(),
ast::Instruction::Add {
diff --git a/ptx/src/pass/llvm/emit.rs b/ptx/src/pass/llvm/emit.rs
index cec8af4..5ab8985 100644
--- a/ptx/src/pass/llvm/emit.rs
+++ b/ptx/src/pass/llvm/emit.rs
@@ -460,7 +460,10 @@ fn get_input_argument_type(
state_space: ast::StateSpace,
) -> Result<LLVMTypeRef, TranslateError> {
match state_space {
- ast::StateSpace::ParamEntry | ast::StateSpace::Shared => {
+ // Helper functions such as cp_async_zfill_* take global pointers.
+ ast::StateSpace::ParamEntry | ast::StateSpace::Shared | ast::StateSpace::Global
+ if !matches!(v_type, ast::Type::Texref) =>
+ {
Ok(unsafe { LLVMPointerTypeInContext(context, get_state_space(state_space)?) })
}
ast::StateSpace::Reg => get_type(context, v_type),
@@ -530,7 +533,7 @@ impl<'a> MethodEmitContext<'a> {
Statement::VectorRead(vector_read) => self.emit_vector_read(vector_read)?,
Statement::VectorWrite(vector_write) => self.emit_vector_write(vector_write)?,
Statement::SetMode(mode_reg) => self.emit_set_mode(mode_reg)?,
- Statement::FpSaturate { dst, src, type_ } => self.emit_fp_saturate(type_, dst, src)?,
+ Statement::FpSaturate { dst, src, type_, relu } => self.emit_fp_saturate(type_, dst, src, relu)?,
Statement::FpModeRequired { .. } => {}
})
}
@@ -699,10 +702,13 @@ impl<'a> MethodEmitContext<'a> {
| ast::Instruction::Nanosleep { .. }
| ast::Instruction::ReduxSync { .. }
| ast::Instruction::LdMatrix { .. }
+ | ast::Instruction::Movmatrix { .. }
| ast::Instruction::Prmt { .. }
| ast::Instruction::Mma { .. }
| ast::Instruction::Dp2a { .. }
| ast::Instruction::Tanh { .. }
+ | ast::Instruction::Sust { .. }
+ | ast::Instruction::TexLevel { .. }
| ast::Instruction::Tex { .. } => return Err(error_unreachable()),
}
}
@@ -3175,13 +3181,35 @@ impl<'a> MethodEmitContext<'a> {
type_: ast::ScalarType,
dst: SpirvWord,
src: SpirvWord,
+ relu: bool,
) -> Result<(), TranslateError> {
let llvm_type = get_scalar_type(self.context, type_);
- let zero = unsafe { LLVMConstReal(llvm_type, 0.0) };
- let one = unsafe { LLVMConstReal(llvm_type, 1.0) };
+ // Packed types (f16x2, bf16x2) need splatted constants.
+ let constant = |value: f64| unsafe {
+ if LLVMGetTypeKind(llvm_type) == LLVMTypeKind::LLVMVectorTypeKind {
+ let element = LLVMConstReal(LLVMGetElementType(llvm_type), value);
+ let mut elements = vec![element; LLVMGetVectorSize(llvm_type) as usize];
+ LLVMConstVector(elements.as_mut_ptr(), elements.len() as u32)
+ } else {
+ LLVMConstReal(llvm_type, value)
+ }
+ };
+ let zero = constant(0.0);
+ let one = constant(1.0);
+ let src = self.resolver.value(src)?;
+ if relu {
+ // PTX relu: max(x, +0) with NaN propagated, which is llvm.maximum.
+ let maximum_intrinsic = format!("llvm.maximum.{}\0", LLVMTypeDisplay(type_));
+ self.emit_intrinsic(
+ unsafe { CStr::from_bytes_with_nul_unchecked(maximum_intrinsic.as_bytes()) },
+ Some(dst),
+ vec![&type_.into()],
+ vec![(src, llvm_type), (zero, llvm_type)],
+ )?;
+ return Ok(());
+ }
let maxnum_intrinsic = format!("llvm.maximumnum.{}\0", LLVMTypeDisplay(type_));
let minnum_intrinsic = format!("llvm.minimumnum.{}\0", LLVMTypeDisplay(type_));
- let src = self.resolver.value(src)?;
let maxnum = self.emit_intrinsic(
unsafe { CStr::from_bytes_with_nul_unchecked(maxnum_intrinsic.as_bytes()) },
None,
diff --git a/ptx/src/pass/mod.rs b/ptx/src/pass/mod.rs
index 4b54c9f..15dd9ba 100644
--- a/ptx/src/pass/mod.rs
+++ b/ptx/src/pass/mod.rs
@@ -323,10 +323,12 @@ enum Statement<I, P: ast::Operand> {
ftz_f16f64: Option<bool>,
rnd_f16f64: Option<ast::RoundingMode>,
},
+ // Clamps to [0, 1] (NaN -> 0), or with relu to [0, inf) keeping NaN.
FpSaturate {
dst: SpirvWord,
src: SpirvWord,
type_: ast::ScalarType,
+ relu: bool,
},
}
@@ -632,7 +634,7 @@ impl<T: ast::Operand<Ident = SpirvWord>> Statement<ast::Instruction<T>, T> {
Statement::FunctionPointer(FunctionPointerDetails { dst, src })
}
Statement::SetMode(mode_register) => Statement::SetMode(mode_register),
- Statement::FpSaturate { dst, src, type_ } => {
+ Statement::FpSaturate { dst, src, type_, relu } => {
let dst = visitor.visit_ident(
dst,
Some((&type_.into(), ast::StateSpace::Reg)),
@@ -645,7 +647,7 @@ impl<T: ast::Operand<Ident = SpirvWord>> Statement<ast::Instruction<T>, T> {
false,
false,
)?;
- Statement::FpSaturate { dst, src, type_ }
+ Statement::FpSaturate { dst, src, type_, relu }
}
Statement::FpModeRequired {
ftz_f32,
diff --git a/ptx/src/pass/replace_instructions_with_functions.rs b/ptx/src/pass/replace_instructions_with_functions.rs
index 5bd855f..023d14d 100644
--- a/ptx/src/pass/replace_instructions_with_functions.rs
+++ b/ptx/src/pass/replace_instructions_with_functions.rs
@@ -369,6 +369,7 @@ fn run_instruction<'input>(
type_,
ctype,
dims,
+ gather_comp,
},
..
} => {
@@ -381,8 +382,53 @@ fn run_instruction<'input>(
ast::TexType::Texref => "texref",
ast::TexType::Texobj => "texobj",
};
+ let dims = match dims {
+ ast::TexDimensions::D1 => "1d",
+ ast::TexDimensions::D2 => "2d",
+ ast::TexDimensions::D3 => "3d",
+ };
+ let name = if let Some(component) = gather_comp {
+ let component = match component {
+ ast::TexGatherComponent::R => "r",
+ ast::TexGatherComponent::G => "g",
+ ast::TexGatherComponent::B => "b",
+ ast::TexGatherComponent::A => "a",
+ };
+ format!(
+ "{prefix}_gather4{component}_{dims}_v4_{dtype}_{coord}",
+ dtype = scalar_to_ptx_name(dtype),
+ coord = scalar_to_ptx_name(ctype)
+ )
+ } else {
+ format!(
+ "{prefix}_{dims}_v4_{dtype}_{coord}",
+ dtype = scalar_to_ptx_name(dtype),
+ coord = scalar_to_ptx_name(ctype)
+ )
+ };
+ to_call(resolver, fn_declarations, name.into(), i)?
+ }
+ i @ ptx_parser::Instruction::TexLevel {
+ data:
+ ast::TexData {
+ dtype,
+ type_,
+ ctype,
+ dims,
+ ..
+ },
+ ..
+ } => {
+ let dtype = match dtype {
+ ast::ScalarType::U32 => ast::ScalarType::S32,
+ t => t,
+ };
+ let prefix = match type_ {
+ ast::TexType::Texref => "texref",
+ ast::TexType::Texobj => "texobj",
+ };
let name = format!(
- "{prefix}_{dims}_v4_{dtype}_{coord}",
+ "{prefix}_level_{dims}_v4_{dtype}_{coord}",
dtype = scalar_to_ptx_name(dtype),
dims = match dims {
ast::TexDimensions::D1 => "1d",
@@ -393,6 +439,38 @@ fn run_instruction<'input>(
);
to_call(resolver, fn_declarations, name.into(), i)?
}
+ i @ ptx_parser::Instruction::Sust {
+ data: ast::SustData {
+ vector_width: Some(4),
+ formatted: true,
+ ..
+ },
+ ..
+ } => to_call(resolver, fn_declarations, "surfobj_p_2d_v4_b32_zero".into(), i)?,
+ i @ ptx_parser::Instruction::Sust {
+ data: ast::SustData {
+ vector_width: Some(2),
+ scalar_type: ast::ScalarType::B16,
+ formatted: false,
+ },
+ ..
+ } => to_call(resolver, fn_declarations, "surfobj_b_2d_v2_b16_zero".into(), i)?,
+ i @ ptx_parser::Instruction::Sust {
+ data: ast::SustData {
+ vector_width: None,
+ scalar_type: ast::ScalarType::B32,
+ formatted: false,
+ },
+ ..
+ } => to_call(resolver, fn_declarations, "surfobj_b_2d_b32_zero".into(), i)?,
+ i @ ptx_parser::Instruction::Sust {
+ data: ast::SustData {
+ vector_width: None,
+ formatted: true,
+ ..
+ },
+ ..
+ } => to_call(resolver, fn_declarations, "surfobj_p_2d_b32_zero".into(), i)?,
i @ ptx_parser::Instruction::Sqrt {
data:
ast::RcpData {
@@ -427,6 +505,14 @@ fn run_instruction<'input>(
},
..
} => to_call(resolver, fn_declarations, "ex2_approx_f32".into(), i)?,
+ i @ ptx_parser::Instruction::Ex2 {
+ data:
+ ast::TypeFtz {
+ type_: ast::ScalarType::F16x2,
+ ..
+ },
+ ..
+ } => to_call(resolver, fn_declarations, "ex2_approx_f16x2".into(), i)?,
i @ ptx_parser::Instruction::Lg2 {
data: ast::FlushToZero {
flush_to_zero: false,
@@ -436,6 +522,9 @@ fn run_instruction<'input>(
i @ ptx_parser::Instruction::Activemask { .. } => {
to_call(resolver, fn_declarations, "activemask".into(), i)?
}
+ i @ ptx_parser::Instruction::Movmatrix { .. } => {
+ to_call(resolver, fn_declarations, "movmatrix_m8n8_trans_b16".into(), i)?
+ }
i @ ptx_parser::Instruction::Bfe { data, .. } => {
let name = ["bfe_", scalar_to_ptx_name(data)].concat();
to_call(resolver, fn_declarations, name.into(), i)?
@@ -470,20 +559,20 @@ fn run_instruction<'input>(
ast::MmaDetails {
alayout,
blayout,
+ k_size,
cd_type_scalar,
ab_type_scalar,
},
..
} => {
let cd_type_name = scalar_to_ptx_name(cd_type_scalar);
- let ab_type_name = scalar_to_ptx_name(ab_type_scalar);
- let dimensions = if cd_type_scalar.kind() == ast::ScalarKind::Float {
- "m16n8k16"
- } else {
- "m16n8k32"
+ let ab_type_name = match ab_type_scalar {
+ ast::ScalarType::E4m3x2 => "e4m3".to_owned(),
+ ast::ScalarType::E5m2x2 => "e5m2".to_owned(),
+ other => scalar_to_ptx_name(other).to_owned(),
};
let name = format!(
- "mma_sync_aligned_{dimensions}_{}_{}_{cd_type_name}_{ab_type_name}_{ab_type_name}_{cd_type_name}",
+ "mma_sync_aligned_m16n8k{k_size}_{}_{}_{cd_type_name}_{ab_type_name}_{ab_type_name}_{cd_type_name}",
match alayout {
ast::MatrixLayout::Row => "row",
ast::MatrixLayout::Col => "col",
@@ -595,6 +684,27 @@ fn run_instruction<'input>(
i @ ptx_parser::Instruction::Nanosleep { .. } => {
to_call(resolver, fn_declarations, "nanosleep_u32".into(), i)?
}
+ i @ ptx_parser::Instruction::Cvt {
+ data:
+ ptx_parser::CvtDetails {
+ from: ast::ScalarType::F16x2,
+ to: to @ (ast::ScalarType::E4m3x2 | ast::ScalarType::E5m2x2),
+ mode: _,
+ },
+ arguments: _,
+ } => {
+ let to = match to {
+ ptx_parser::ScalarType::E4m3x2 => "e4m3x2",
+ ptx_parser::ScalarType::E5m2x2 => "e5m2x2",
+ _ => unreachable!(),
+ };
+ to_call(
+ resolver,
+ fn_declarations,
+ format!("cvt_rn_satfinite_{}_f16x2", to).into(),
+ i,
+ )?
+ }
i @ ptx_parser::Instruction::Cvt {
data:
ptx_parser::CvtDetails {
@@ -642,6 +752,19 @@ fn run_instruction<'input>(
i,
)?
}
+ // cp.async with a register src-size copies that many bytes and zero
+ // fills the rest of cp-size (used for padding at image borders).
+ i @ ptx_parser::Instruction::CpAsync {
+ arguments: ast::CpAsyncArgs { src_size: Some(_), .. },
+ ..
+ } => {
+ let bytes = match &i {
+ ptx_parser::Instruction::CpAsync { data, .. } => data.cp_size.as_u64(),
+ _ => return Err(error_unreachable()),
+ };
+ let name = format!("cp_async_zfill_{bytes}");
+ to_call(resolver, fn_declarations, name.into(), i)?
+ }
i @ ptx_parser::Instruction::Prmt { .. } => {
to_call(resolver, fn_declarations, "prmt_b32".into(), i)?
}
diff --git a/ptx_parser/src/ast.rs b/ptx_parser/src/ast.rs
index 53662f9..0d2ca3b 100644
--- a/ptx_parser/src/ast.rs
+++ b/ptx_parser/src/ast.rs
@@ -250,7 +250,9 @@ ptx_parser_macros::generate_instruction_type!(
src_from: {
repr: T,
space: StateSpace::Global
- }
+ },
+ // Register src-size; immediates stay in CpAsyncDetails.
+ src_size: Option<T>,
}
},
CpAsyncCommitGroup { },
@@ -450,6 +452,15 @@ ptx_parser_macros::generate_instruction_type!(
},
display: write!(f, "mov{}", data.typ)?
},
+ // movmatrix.sync.aligned.m8n8.trans.b16: warp-wide transpose of an 8x8
+ // b16 matrix held as one .b32 register per lane.
+ Movmatrix {
+ type: Type::Scalar(ScalarType::B32),
+ arguments<T>: {
+ dst: T,
+ src: T
+ }
+ },
Mul {
type: { Type::from(data.type_()) },
data: MulDetails,
@@ -945,6 +956,65 @@ ptx_parser_macros::generate_instruction_type!(
},
}
},
+ TexLevel {
+ type: !,
+ data: TexData,
+ arguments<T>: {
+ dst: {
+ repr: T,
+ type: { Type::Vector(4, data.dtype) },
+ },
+ src_ptr: {
+ repr: T,
+ type: {
+ if data.type_ == TexType::Texref {
+ Type::Texref
+ } else {
+ Type::Scalar(ScalarType::U64)
+ }
+ },
+ space: {
+ if data.type_ == TexType::Texref {
+ StateSpace::Global
+ } else {
+ StateSpace::Reg
+ }
+ },
+ },
+ src_coord: {
+ repr: T,
+ type: { data.coord_type() },
+ },
+ src_lod: {
+ repr: T,
+ type: { Type::Scalar(ScalarType::F32) },
+ },
+ }
+ },
+ Sust {
+ type: !,
+ data: SustData,
+ arguments<T>: {
+ src_surf: {
+ repr: T,
+ type: { Type::Scalar(ScalarType::U64) },
+ space: StateSpace::Reg,
+ },
+ src_coord: {
+ repr: T,
+ type: { Type::Vector(2, ScalarType::S32) },
+ },
+ src_value: {
+ repr: T,
+ type: {
+ match data.vector_width {
+ Some(width) => Type::Vector(width, data.scalar_type),
+ None => Type::Scalar(data.scalar_type),
+ }
+ },
+ },
+ }
+ },
Bfind {
type: !,
data: (),
@@ -1784,6 +1854,8 @@ pub struct ArithFloat {
pub rounding: RoundingMode,
pub flush_to_zero: Option<bool>,
pub saturate: bool,
+ // .relu (f16/bf16 fma): negative results clamp to +0, NaN stays NaN.
+ pub relu: bool,
// From PTX documentation: https://docs.nvidia.com/cuda/parallel-thread-execution/#mixed-precision-floating-point-instructions-add
// Note that an add instruction with an explicit rounding modifier is treated conservatively by
// the code optimizer. An add instruction with no rounding modifier defaults to
@@ -2823,27 +2895,35 @@ pub struct MmaDetails {
pub blayout: MatrixLayout,
pub cd_type_scalar: ScalarType,
pub ab_type_scalar: ScalarType,
+ pub k_size: u8,
}
impl MmaDetails {
pub fn dtype(&self) -> Type {
- if self.cd_type_scalar.kind() == ScalarKind::Float {
- Type::Vector(4, ScalarType::F32)
- } else {
- Type::Vector(4, ScalarType::U32)
+ match self.cd_type_scalar {
+ ScalarType::F16 => Type::Vector(2, ScalarType::U32),
+ ScalarType::F32 => Type::Vector(4, ScalarType::F32),
+ _ => Type::Vector(4, ScalarType::U32),
}
}
+ // m16n8k8 16-bit fragments are half the size of the k16 (and 8-bit k32) ones.
pub fn atype(&self) -> Type {
- Type::Vector(4, ScalarType::U32)
+ match self.k_size {
+ 8 => Type::Vector(2, ScalarType::U32),
+ _ => Type::Vector(4, ScalarType::U32),
+ }
}
pub fn btype(&self) -> Type {
- Type::Vector(2, ScalarType::U32)
+ match self.k_size {
+ 8 => Type::Vector(1, ScalarType::U32),
+ _ => Type::Vector(2, ScalarType::U32),
+ }
}
pub fn ctype(&self) -> Type {
- if self.cd_type_scalar.kind() == ScalarKind::Float {
- Type::Vector(4, ScalarType::F32)
- } else {
- Type::Vector(4, ScalarType::U32)
+ match self.cd_type_scalar {
+ ScalarType::F16 => Type::Vector(2, ScalarType::U32),
+ ScalarType::F32 => Type::Vector(4, ScalarType::F32),
+ _ => Type::Vector(4, ScalarType::U32),
}
}
}
@@ -2887,6 +2967,22 @@ pub struct TexData {
pub dtype: ScalarType,
pub ctype: ScalarType,
pub dims: TexDimensions,
+ pub gather_comp: Option<TexGatherComponent>,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub enum TexGatherComponent {
+ R,
+ G,
+ B,
+ A,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct SustData {
+ pub vector_width: Option<u8>,
+ pub scalar_type: ScalarType,
+ pub formatted: bool,
}
impl TexData {
diff --git a/ptx_parser/src/lib.rs b/ptx_parser/src/lib.rs
index d9f3d84..6820a55 100644
--- a/ptx_parser/src/lib.rs
+++ b/ptx_parser/src/lib.rs
@@ -2089,6 +2089,20 @@ derive_parser!(
Add
}
+ // https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#warp-level-matrix-transpose-instruction-movmatrix
+ // The transpose moves elements between lanes: lane L receives column L / 4 of the source
+ // matrix, so this cannot be a register move.
+ movmatrix.sync.trans.aligned.m8n8.b16 d, a => {
+ Instruction::Movmatrix {
+ arguments: MovmatrixArgs { dst: d, src: a },
+ }
+ }
+ movmatrix.sync.aligned.m8n8.trans.b16 d, a => {
+ Instruction::Movmatrix {
+ arguments: MovmatrixArgs { dst: d, src: a },
+ }
+ }
+
// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#data-movement-and-conversion-instructions-mov
mov{.vec}.type d, a => {
Instruction::Mov {
@@ -2144,9 +2158,8 @@ derive_parser!(
}
}
st.release.scope{.ss}{.level::eviction_priority}{.level::cache_hint}{.vec}.type [a], b{, cache_policy} => {
- if level_eviction_priority.is_some() || level_cache_hint || cache_policy.is_some() {
- state.errors.push(PtxError::Todo("st.release instruction with cache policy/eviction priority/cache hints".to_string()));
- }
+ // Preserve release ordering; eviction/cache hints are performance-only metadata.
+ let _ = (level_eviction_priority, level_cache_hint, cache_policy);
Instruction::St {
data: StData {
qualifier: ast::LdStQualifier::Release(scope),
@@ -2216,9 +2229,8 @@ derive_parser!(
}
}
ld.relaxed.scope{.ss}{.level::eviction_priority}{.level::cache_hint}{.level::prefetch_size}{.vec}.type d, [a]{, cache_policy} => {
- if level_eviction_priority.is_some() || level_cache_hint || level_prefetch_size.is_some() || cache_policy.is_some() {
- state.errors.push(PtxError::Todo("ld.relaxed instruction with cache policy/eviction priority/cache hints/prefetch size".to_string()));
- }
+ // Relaxed ordering remains in the AST; eviction/cache/prefetch hints only tune locality.
+ let _ = (level_eviction_priority, level_cache_hint, level_prefetch_size, cache_policy);
Instruction::Ld {
data: LdDetails {
qualifier: ast::LdStQualifier::Relaxed(scope),
@@ -2348,7 +2360,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2364,7 +2376,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2384,7 +2396,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2400,7 +2412,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2416,7 +2428,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2432,7 +2444,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: AddArgs {
@@ -2480,7 +2492,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2494,7 +2506,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2511,7 +2523,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2525,7 +2537,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2539,7 +2551,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2553,7 +2565,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: MulArgs { dst: d, src1: a, src2: b }
@@ -2679,9 +2691,15 @@ derive_parser!(
call <= { call(stream) }
// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#data-movement-and-conversion-instructions-cvt
- cvt{.ifrnd}{.ftz}{.sat}.dtype.atype d, a => {
+ cvt{.ifrnd}{.ftz}{.sat}{.satfinite}.dtype.atype d, a{, b} => {
+ if satfinite && !matches!(
+ (dtype, atype),
+ (ScalarType::E4m3x2 | ScalarType::E5m2x2, ScalarType::F32 | ScalarType::F16x2)
+ ) {
+ state.errors.push(PtxError::Todo("cvt.satfinite is only implemented for E4M3/E5M2 destinations".to_string()));
+ }
let data = ast::CvtDetails::new(&mut state.errors, ifrnd, ftz, sat, false, dtype, atype);
- let arguments = ast::CvtArgs { dst: d, src: a, src2: None };
+ let arguments = ast::CvtArgs { dst: d, src: a, src2: b };
ast::Instruction::Cvt {
data, arguments
}
@@ -2701,39 +2719,17 @@ derive_parser!(
}
// cvt.rna{.satfinite}.tf32.f32 d, a;
// cvt.frnd2{.relu}.tf32.f32 d, a;
- cvt.rn.satfinite{.relu}.f8x2type.f32 d, a, b => {
- if relu {
- state.errors.push(PtxError::Todo("cvt.rn.satfinite.f8x2 instruction with relu modifier".to_string()));
- }
- let data = ast::CvtDetails::new(&mut state.errors, Some(rn), false, false, false, f8x2type, ScalarType::F32);
- ast::Instruction::Cvt {
- data,
- arguments: ast::CvtArgs { dst: d, src: a, src2: Some(b) }
- }
- }
- // cvt.rn.satfinite{.relu}.f8x2type.f16x2 d, a;
- /*
- cvt.rn{.relu}.f16x2.f8x2type d, a => {
- if relu {
- state.errors.push(PtxError::Todo("cvt.rn.f16x2.f8x2 instruction with relu modifier".to_string()));
- }
- let data = ast::CvtDetails::new(&mut state.errors, Some(rn), false, false, ScalarType::F16x2, f8x2type);
- ast::Instruction::Cvt {
- data,
- arguments: ast::CvtArgs { dst: d, src: a, src2: None }
- }
- }
- */
-
.ifrnd: RawRoundingMode = { .rn, .rz, .rm, .rp, .rni, .rzi, .rmi, .rpi };
.frnd2: RawRoundingMode = { .rn, .rz };
RawRoundingMode = { .rn };
.dtype: ScalarType = { .u8, .u16, .u32, .u64,
.s8, .s16, .s32, .s64,
- .bf16, .f16, .f32, .f64 };
+ .bf16, .f16, .f16x2, .f32, .f64,
+ .e4m3x2, .e5m2x2 };
.atype: ScalarType = { .u8, .u16, .u32, .u64,
.s8, .s16, .s32, .s64,
- .bf16, .f16, .f32, .f64 };
+ .bf16, .f16, .f16x2, .f32, .f64,
+ .e4m3x2, .e5m2x2 };
.f8x2type: ScalarType = { .e4m3x2, .e5m2x2 };
.x2_to_type: ScalarType = { .f16x2, .bf16x2 };
.x2_from_type: ScalarType = { .e4m3x2, .e5m2x2, .f32 };
@@ -2941,7 +2937,7 @@ derive_parser!(
rounding: ast::RoundingMode::NearestEven,
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: false
+ is_fusable: false, relu: false
}
),
arguments: MadArgs { dst: d, src1: a, src2: b, src3: c }
@@ -2955,7 +2951,7 @@ derive_parser!(
rounding: rnd.into(),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: false
+ is_fusable: false, relu: false
}
),
arguments: MadArgs { dst: d, src1: a, src2: b, src3: c }
@@ -2969,7 +2965,7 @@ derive_parser!(
rounding: rnd.into(),
flush_to_zero: None,
saturate: false,
- is_fusable: false
+ is_fusable: false, relu: false
}
),
arguments: MadArgs { dst: d, src1: a, src2: b, src3: c }
@@ -2987,7 +2983,7 @@ derive_parser!(
rounding: rnd.into(),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: false
+ is_fusable: false, relu: false
},
arguments: FmaArgs { dst: d, src1: a, src2: b, src3: c }
}
@@ -2999,7 +2995,7 @@ derive_parser!(
rounding: rnd.into(),
flush_to_zero: None,
saturate: false,
- is_fusable: false
+ is_fusable: false, relu: false
},
arguments: FmaArgs { dst: d, src1: a, src2: b, src3: c }
}
@@ -3014,7 +3010,7 @@ derive_parser!(
rounding: rnd.into(),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: false
+ is_fusable: false, relu: false
},
arguments: FmaArgs { dst: d, src1: a, src2: b, src3: c }
}
@@ -3025,17 +3021,14 @@ derive_parser!(
//fma.rnd{.ftz}.relu.f16 d, a, b, c;
//fma.rnd{.ftz}.relu.f16x2 d, a, b, c;
//fma.rnd{.relu}.bf16 d, a, b, c;
- fma.rnd{.relu}.type_x2 d, a, b, c => {
- if relu {
- state.errors.push(PtxError::Todo("fma instruction with relu modifier for bf16x2/f16x2 types".to_string()));
- }
+ fma.rnd{.ftz}{.sat}{.relu}.type_x2 d, a, b, c => {
ast::Instruction::Fma {
data: ast::ArithFloat {
type_: type_x2,
rounding: rnd.into(),
- flush_to_zero: None,
- saturate: false,
- is_fusable: false
+ flush_to_zero: Some(ftz),
+ saturate: sat,
+ is_fusable: false, relu
},
arguments: FmaArgs { dst: d, src1: a, src2: b, src3: c }
}
@@ -3081,7 +3074,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -3095,7 +3088,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -3112,7 +3105,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -3126,7 +3119,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: Some(ftz),
saturate: sat,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -3140,7 +3133,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -3154,7 +3147,7 @@ derive_parser!(
rounding: rnd.map(Into::into).unwrap_or(ast::RoundingMode::NearestEven),
flush_to_zero: None,
saturate: false,
- is_fusable: rnd.is_none()
+ is_fusable: rnd.is_none(), relu: false
}
),
arguments: SubArgs { dst: d, src1: a, src2: b }
@@ -4029,9 +4022,12 @@ derive_parser!(
CpAsyncCpSize::Bytes4
});
- let src_size = src_size
+ let src_size_immediate = src_size
+ .as_ref()
.and_then(|op| op.as_immediate())
.and_then(|imm| imm.as_u64());
+ let src_size_register = if src_size_immediate.is_none() { src_size } else { None };
+ let src_size = src_size_immediate;
Instruction::CpAsync {
data: CpAsyncDetails {
@@ -4043,6 +4039,7 @@ derive_parser!(
arguments: CpAsyncArgs {
src_to: dst,
src_from: src,
+ src_size: src_size_register,
}
}
}
@@ -4198,6 +4195,7 @@ derive_parser!(
data: MmaDetails {
alayout,
blayout,
+ k_size: 16,
cd_type_scalar: ScalarType::F32,
ab_type_scalar: ScalarType::BF16,
},
@@ -4210,6 +4208,7 @@ derive_parser!(
data: MmaDetails {
alayout,
blayout,
+ k_size: 16,
cd_type_scalar: ScalarType::F32,
ab_type_scalar: ScalarType::F16,
},
@@ -4222,6 +4221,7 @@ derive_parser!(
data: MmaDetails {
alayout,
blayout,
+ k_size: 32,
cd_type_scalar: ScalarType::S32,
ab_type_scalar: ScalarType::S8,
},
@@ -4229,6 +4229,45 @@ derive_parser!(
}
}
+ mma.sync.aligned.m16n8k16.alayout.blayout.f16.f16.f16.f16 d, a, b, c => {
+ Instruction::Mma {
+ data: MmaDetails {
+ alayout,
+ blayout,
+ k_size: 16,
+ cd_type_scalar: ScalarType::F16,
+ ab_type_scalar: ScalarType::F16,
+ },
+ arguments: MmaArgs { dst: d, src1: a, src2: b, src3: c }
+ }
+ }
+
+ mma.sync.aligned.m16n8k8.alayout.blayout.f16.f16.f16.f16 d, a, b, c => {
+ Instruction::Mma {
+ data: MmaDetails {
+ alayout,
+ blayout,
+ k_size: 8,
+ cd_type_scalar: ScalarType::F16,
+ ab_type_scalar: ScalarType::F16,
+ },
+ arguments: MmaArgs { dst: d, src1: a, src2: b, src3: c }
+ }
+ }
+
+ mma.sync.aligned.m16n8k32.alayout.blayout.f16.e4m3.e4m3.f16 d, a, b, c => {
+ Instruction::Mma {
+ data: MmaDetails {
+ alayout,
+ blayout,
+ k_size: 32,
+ cd_type_scalar: ScalarType::F16,
+ ab_type_scalar: ScalarType::E4m3x2,
+ },
+ arguments: MmaArgs { dst: d, src1: a, src2: b, src3: c }
+ }
+ }
+
.alayout: MatrixLayout = {.row};
.blayout: MatrixLayout = {.col};
@@ -4405,43 +4444,117 @@ derive_parser!(
}
// https://docs.nvidia.com/cuda/parallel-thread-execution/#texture-instructions-tex
- tex.1d.v4.dtype.ctype d, [a, c] => {
+ // PTX base mode selects mip level zero. ZLUDA's texture-object lowering
+ // uses the AMD image.sample.lz intrinsic, which has the same behavior.
+ tex{.base}.1d.v4.dtype.ctype d, [a, c] => {
+ let _ = base;
Instruction::Tex {
data: TexData {
dtype,
ctype,
dims: TexDimensions::D1,
- type_: TexType::Texref
+ type_: TexType::Texref,
+ gather_comp: None,
},
arguments: TexArgs { dst: d, src_ptr: a, src_coord: c }
}
}
- tex.2d.v4.dtype.ctype d, [a, c] => {
+ tex{.base}.2d.v4.dtype.ctype d, [a, c] => {
+ let _ = base;
Instruction::Tex {
data: TexData {
dtype,
ctype,
dims: TexDimensions::D2,
- type_: TexType::Texref
+ type_: TexType::Texref,
+ gather_comp: None,
},
arguments: TexArgs { dst: d, src_ptr: a, src_coord: c }
}
}
- tex.3d.v4.dtype.ctype d, [a, c] => {
+ tex{.base}.3d.v4.dtype.ctype d, [a, c] => {
+ let _ = base;
Instruction::Tex {
data: TexData {
dtype,
ctype,
dims: TexDimensions::D3,
- type_: TexType::Texref
+ type_: TexType::Texref,
+ gather_comp: None,
},
arguments: TexArgs { dst: d, src_ptr: a, src_coord: c }
}
}
- .dtype: ScalarType = { .u32, .s32, .f32 };
+ // Explicit LOD sampling maps to the AMD image.sample.l intrinsic.
+ tex.level.2d.v4.dtype.ctype d, [a, c], lod => {
+ Instruction::TexLevel {
+ data: TexData {
+ dtype,
+ ctype,
+ dims: TexDimensions::D2,
+ type_: TexType::Texref,
+ gather_comp: None,
+ },
+ arguments: TexLevelArgs { dst: d, src_ptr: a, src_coord: c, src_lod: lod }
+ }
+ }
+
+ .dtype: ScalarType = { .u32, .s32, .f32, .f16 };
.ctype: ScalarType = { .s32, .f32 };
+ // NVIDIA texture gathers return four float samples from the bilerp footprint.
+ tld4.comp.2d.v4.f32.f32 d, [a, c] => {
+ let gather_comp = match comp {
+ TexGatherChannel::R => ast::TexGatherComponent::R,
+ TexGatherChannel::G => ast::TexGatherComponent::G,
+ TexGatherChannel::B => ast::TexGatherComponent::B,
+ TexGatherChannel::A => ast::TexGatherComponent::A,
+ };
+ Instruction::Tex {
+ data: TexData {
+ dtype: ScalarType::F32,
+ ctype: ScalarType::F32,
+ dims: TexDimensions::D2,
+ type_: TexType::Texref,
+ gather_comp: Some(gather_comp),
+ },
+ arguments: TexArgs { dst: d, src_ptr: a, src_coord: c }
+ }
+ }
+
+ .comp: TexGatherChannel = { .r, .g, .b, .a };
+ // This is the formatted 2D store used by the captured DLSS auto-exposure kernels.
+ sust.p.2d.b32.zero [a, b], c => {
+ Instruction::Sust {
+ data: SustData { vector_width: None, scalar_type: ScalarType::B32, formatted: true },
+ arguments: SustArgs { src_surf: a, src_coord: b, src_value: c }
+ }
+ }
+
+ sust.p.2d.v4.b32.zero [a, b], c => {
+ Instruction::Sust {
+ data: SustData { vector_width: Some(4), scalar_type: ScalarType::B32, formatted: true },
+ arguments: SustArgs { src_surf: a, src_coord: b, src_value: c }
+ }
+ }
+
+ // Unformatted 32-bit store used by the DLTSS model kernels.
+ sust.b.2d.b32.zero [a, b], c => {
+ Instruction::Sust {
+ data: SustData { vector_width: None, scalar_type: ScalarType::B32, formatted: false },
+ arguments: SustArgs { src_surf: a, src_coord: b, src_value: c }
+ }
+ }
+
+ // Unformatted packed-half store used by the RRLite model kernels.
+ sust.b.2d.v2.b16.zero [a, b], c => {
+ Instruction::Sust {
+ data: SustData { vector_width: Some(2), scalar_type: ScalarType::B16, formatted: false },
+ arguments: SustArgs { src_surf: a, src_coord: b, src_value: c }
+ }
+ }
+
// https://docs.nvidia.com/cuda/parallel-thread-execution/
match.any.sync.type d, a, membermask => {
Instruction::MatchSync {
diff --git a/ptx_parser_macros/src/lib.rs b/ptx_parser_macros/src/lib.rs
index 51ad91e..e67e14d 100644
--- a/ptx_parser_macros/src/lib.rs
+++ b/ptx_parser_macros/src/lib.rs
@@ -33,7 +33,7 @@ struct OpcodeDefinitions {
}
impl OpcodeDefinitions {
- fn new(opcode: &IdentLike, definitions: Vec<SingleOpcodeDefinition>) -> Self {
+ fn new(_opcode: &IdentLike, definitions: Vec<SingleOpcodeDefinition>) -> Self {
let mut selections = vec![None; definitions.len()];
let mut generation = 0usize;
loop {
@@ -102,11 +102,13 @@ impl OpcodeDefinitions {
}
}
if !selected_something {
- panic!(
- "Failed to generate pattern selection for `{}`. State: {:?}",
- opcode,
- selections.into_iter().rev().collect::<Vec<_>>()
- );
+ // If required modifiers cannot uniquely separate the remaining
+ // alternatives, let their full parsers disambiguate by suffix
+ // and argument shape at runtime.
+ for index in unselected {
+ selections[index] = Some((None, generation));
+ }
+ break;
}
generation += 1;
}
@@ -504,7 +506,20 @@ fn emit_parse_function(
.to_tokens(&mut selectors);
let mut has_default_selector = false;
for selection_layer in def.block_selection.iter() {
+ let default_definitions = selection_layer
+ .iter()
+ .filter_map(|(selection_key, selected_definition)| {
+ if selection_key.is_none() {
+ Some(*selected_definition)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
for (selection_key, selected_definition) in selection_layer {
+ if selection_key.is_none() {
+ continue;
+ }
let def_parser = emit_definition_parser(type_name, (opcode,*selected_definition), &def.definitions[*selected_definition]);
match selection_key {
Some(selection_keys) => {
@@ -516,16 +531,27 @@ fn emit_parse_function(
}
.to_tokens(&mut selectors);
}
- None => {
- has_default_selector = true;
- quote! {
- else {
- #def_parser
- }
- }
- .to_tokens(&mut selectors);
+ None => unreachable!(),
+ }
+ }
+ if !default_definitions.is_empty() {
+ has_default_selector = true;
+ let attempts = default_definitions.iter().map(|selected_definition| {
+ let def_parser = emit_definition_parser(
+ type_name,
+ (opcode, *selected_definition),
+ &def.definitions[*selected_definition],
+ );
+ quote! {
+ |stream: &mut PtxParser<'a, 'input>| { Ok({ #def_parser }) }
+ }
+ });
+ quote! {
+ else {
+ alt((#(#attempts,)* )).parse_next(stream)?
}
}
+ .to_tokens(&mut selectors);
}
}
if !has_default_selector {
diff --git a/zluda/src/impl/array.rs b/zluda/src/impl/array.rs
index 70bc9e6..96d01ed 100644
--- a/zluda/src/impl/array.rs
+++ b/zluda/src/impl/array.rs
@@ -1,14 +1,69 @@
use hip_runtime_sys::*;
+use std::collections::HashMap;
+use std::sync::{Mutex, OnceLock};
+
+fn original_formats() -> &'static Mutex<HashMap<usize, u32>> {
+ static FORMATS: OnceLock<Mutex<HashMap<usize, u32>>> = OnceLock::new();
+ FORMATS.get_or_init(|| Mutex::new(HashMap::new()))
+}
+
+pub(crate) fn original_format(array: hipArray_t) -> Result<Option<u32>, hipErrorCode_t> {
+ original_formats()
+ .lock()
+ .map(|formats| formats.get(&(array as usize)).copied())
+ .map_err(|_| hipErrorCode_t::Unknown)
+}
pub(crate) unsafe fn create_v2(
handle: *mut hipArray_t,
desc: *const HIP_ARRAY_DESCRIPTOR,
) -> hipError_t {
- hipArrayCreate(handle, desc)
+ // HIP's legacy descriptor enum omits CUDA's packed and normalized formats.
+ // Use equivalent integer channel storage for normalized 8/16-bit arrays;
+ // the texture object's normalized-float read mode supplies the conversion.
+ // For packed UNORM 10:10:10:2, use RGBA32F storage. The copy path expands
+ // public four-byte pixels to backing float channels.
+ let descriptor = &*desc;
+ let (format, channels) = match descriptor.Format.0 {
+ 80 if descriptor.NumChannels == 4 => (hipArray_Format(32), 4), // RGBA32F backing
+ 192 => (hipArray_Format(1), 1), // UNORM_INT8X1
+ 193 => (hipArray_Format(1), 2), // UNORM_INT8X2
+ 194 => (hipArray_Format(1), 4), // UNORM_INT8X4
+ 195 => (hipArray_Format(2), 1), // UNORM_INT16X1
+ 196 => (hipArray_Format(2), 2), // UNORM_INT16X2
+ 197 => (hipArray_Format(2), 4), // UNORM_INT16X4
+ 198 => (hipArray_Format(8), 1), // SNORM_INT8X1
+ 199 => (hipArray_Format(8), 2), // SNORM_INT8X2
+ 200 => (hipArray_Format(8), 4), // SNORM_INT8X4
+ 201 => (hipArray_Format(9), 1), // SNORM_INT16X1
+ 202 => (hipArray_Format(9), 2), // SNORM_INT16X2
+ 203 => (hipArray_Format(9), 4), // SNORM_INT16X4
+ _ => return hipArrayCreate(handle, desc),
+ };
+ let backing = HIP_ARRAY_DESCRIPTOR {
+ Width: descriptor.Width,
+ Height: descriptor.Height,
+ Format: format,
+ NumChannels: channels,
+ };
+ hipArrayCreate(handle, &backing)?;
+ if let Ok(mut formats) = original_formats().lock() {
+ formats.insert(*handle as usize, descriptor.Format.0);
+ Ok(())
+ } else {
+ let _ = hipArrayDestroy(*handle);
+ Err(hipErrorCode_t::Unknown)
+ }
}
pub(crate) unsafe fn destroy(array: hipArray_t) -> hipError_t {
- hipArrayDestroy(array)
+ hipArrayDestroy(array)?;
+ original_formats()
+ .lock()
+ .map(|mut formats| {
+ formats.remove(&(array as usize));
+ })
+ .map_err(|_| hipErrorCode_t::Unknown)
}
pub(crate) unsafe fn _3d_create_v2(
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
index aea11d5..b441dde 100644
--- a/zluda/src/impl/function.rs
+++ b/zluda/src/impl/function.rs
@@ -1,4 +1,7 @@
-use cuda_types::cuda::{CUfunction, CUfunction_attribute, CUkernel};
+use cuda_types::cuda::{
+ CUfunction, CUfunction_attribute, CUkernel, CU_LAUNCH_PARAM_BUFFER_POINTER_AS_INT,
+ CU_LAUNCH_PARAM_BUFFER_SIZE_AS_INT, CU_LAUNCH_PARAM_END_AS_INT,
+};
use dark_api::FunctionArgInfo;
use hip_runtime_sys::*;
use std::mem;
@@ -86,10 +89,45 @@ pub(crate) fn launch_kernel(
kernel_params: *mut *mut ::core::ffi::c_void,
extra: *mut *mut ::core::ffi::c_void,
) -> hipError_t {
- // TODO: fix constants in extra
- if !extra.is_null() {
- return hipError_t::ErrorNotSupported;
- }
+ let mut translated_extra_storage = [std::ptr::null_mut::<std::ffi::c_void>(); 8];
+ let extra_for_hip = if extra.is_null() {
+ extra
+ } else {
+ let mut input_index = 0usize;
+ let mut output_index = 0usize;
+ let mut terminated = false;
+ while input_index < translated_extra_storage.len()
+ && output_index < translated_extra_storage.len()
+ {
+ let token = unsafe { *extra.add(input_index) } as usize;
+ if token == CU_LAUNCH_PARAM_END_AS_INT as usize {
+ translated_extra_storage[output_index] = 3usize as *mut std::ffi::c_void;
+ terminated = true;
+ break;
+ }
+ if token == CU_LAUNCH_PARAM_BUFFER_POINTER_AS_INT as usize
+ || token == CU_LAUNCH_PARAM_BUFFER_SIZE_AS_INT as usize
+ {
+ if input_index + 1 >= translated_extra_storage.len()
+ || output_index + 1 >= translated_extra_storage.len()
+ {
+ return hipError_t::ErrorInvalidValue;
+ }
+ translated_extra_storage[output_index] =
+ unsafe { *extra.add(input_index) };
+ translated_extra_storage[output_index + 1] =
+ unsafe { *extra.add(input_index + 1) };
+ input_index += 2;
+ output_index += 2;
+ continue;
+ }
+ return hipError_t::ErrorNotSupported;
+ }
+ if !terminated {
+ return hipError_t::ErrorInvalidValue;
+ }
+ translated_extra_storage.as_mut_ptr()
+ };
unsafe {
hipModuleLaunchKernel(
f.base,
@@ -102,7 +140,7 @@ pub(crate) fn launch_kernel(
shared_mem_bytes,
stream,
kernel_params,
- extra,
+ extra_for_hip,
)
}
}
diff --git a/zluda/src/impl/memory.rs b/zluda/src/impl/memory.rs
index 800fad6..b724942 100644
--- a/zluda/src/impl/memory.rs
+++ b/zluda/src/impl/memory.rs
@@ -1,5 +1,5 @@
use crate::r#impl::{
- context,
+ array, context,
driver::{self},
};
use cuda_types::cuda::{CUerror, CUresult, CUresultConsts};
@@ -279,8 +279,193 @@ pub(crate) unsafe fn alloc_pitch_v2(
Ok(())
}
+fn packed_1010102_component(value: f32, scale: u32) -> u32 {
+ let clamped = if value.is_nan() {
+ 0.0
+ } else {
+ value.clamp(0.0, 1.0)
+ };
+ (clamped * scale as f32 + 0.5).floor() as u32
+}
+
+unsafe fn packed_1010102_external_to_host(memcpy: hip_Memcpy2D, packed: &mut [u8]) -> CUresult {
+ let width = memcpy.WidthInBytes;
+ match memcpy.srcMemoryType {
+ hipMemoryType::hipMemoryTypeHost => {
+ if memcpy.srcHost.is_null()
+ || memcpy
+ .srcXInBytes
+ .checked_add(width)
+ .is_none_or(|end| end > memcpy.srcPitch)
+ {
+ return Err(CUerror::INVALID_VALUE);
+ }
+ for row in 0..memcpy.Height {
+ let source_row = memcpy.srcY.checked_add(row).ok_or(CUerror::INVALID_VALUE)?;
+ let offset = source_row
+ .checked_mul(memcpy.srcPitch)
+ .and_then(|value| value.checked_add(memcpy.srcXInBytes))
+ .ok_or(CUerror::INVALID_VALUE)?;
+ ptr::copy_nonoverlapping(
+ memcpy.srcHost.cast::<u8>().add(offset),
+ packed.as_mut_ptr().add(row * width),
+ width,
+ );
+ }
+ }
+ hipMemoryType::hipMemoryTypeDevice | hipMemoryType::hipMemoryTypeUnified => {
+ let mut staging = memcpy;
+ staging.dstMemoryType = hipMemoryType::hipMemoryTypeHost;
+ staging.dstHost = packed.as_mut_ptr().cast();
+ staging.dstXInBytes = 0;
+ staging.dstY = 0;
+ staging.dstPitch = width;
+ hipMemcpyParam2D(&staging)?;
+ }
+ _ => return Err(CUerror::NOT_SUPPORTED),
+ }
+ Ok(())
+}
+
+unsafe fn packed_1010102_host_to_external(memcpy: hip_Memcpy2D, packed: &[u8]) -> CUresult {
+ let width = memcpy.WidthInBytes;
+ match memcpy.dstMemoryType {
+ hipMemoryType::hipMemoryTypeHost => {
+ if memcpy.dstHost.is_null()
+ || memcpy
+ .dstXInBytes
+ .checked_add(width)
+ .is_none_or(|end| end > memcpy.dstPitch)
+ {
+ return Err(CUerror::INVALID_VALUE);
+ }
+ for row in 0..memcpy.Height {
+ let destination_row = memcpy.dstY.checked_add(row).ok_or(CUerror::INVALID_VALUE)?;
+ let offset = destination_row
+ .checked_mul(memcpy.dstPitch)
+ .and_then(|value| value.checked_add(memcpy.dstXInBytes))
+ .ok_or(CUerror::INVALID_VALUE)?;
+ ptr::copy_nonoverlapping(
+ packed.as_ptr().add(row * width),
+ memcpy.dstHost.cast::<u8>().add(offset),
+ width,
+ );
+ }
+ }
+ hipMemoryType::hipMemoryTypeDevice | hipMemoryType::hipMemoryTypeUnified => {
+ let mut staging = memcpy;
+ staging.srcMemoryType = hipMemoryType::hipMemoryTypeHost;
+ staging.srcHost = packed.as_ptr().cast();
+ staging.srcXInBytes = 0;
+ staging.srcY = 0;
+ staging.srcPitch = width;
+ hipMemcpyParam2D(&staging)?;
+ }
+ _ => return Err(CUerror::NOT_SUPPORTED),
+ }
+ Ok(())
+}
+
pub(crate) unsafe fn copy_2d_v2(memcpy: hip_Memcpy2D) -> CUresult {
- hipMemcpyParam2D(&memcpy)?;
+ let src_packed = memcpy.srcMemoryType == hipMemoryType::hipMemoryTypeArray
+ && array::original_format(memcpy.srcArray)? == Some(80);
+ let dst_packed = memcpy.dstMemoryType == hipMemoryType::hipMemoryTypeArray
+ && array::original_format(memcpy.dstArray)? == Some(80);
+ if !src_packed && !dst_packed {
+ hipMemcpyParam2D(&memcpy)?;
+ return Ok(());
+ }
+ if memcpy.WidthInBytes % 4 != 0
+ || (src_packed && memcpy.srcXInBytes % 4 != 0)
+ || (dst_packed && memcpy.dstXInBytes % 4 != 0)
+ {
+ return Err(CUerror::INVALID_VALUE);
+ }
+ let backing_width = memcpy
+ .WidthInBytes
+ .checked_mul(4)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ let backing_bytes = backing_width
+ .checked_mul(memcpy.Height)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ if src_packed && dst_packed {
+ let mut backing = memcpy;
+ backing.srcXInBytes = memcpy
+ .srcXInBytes
+ .checked_mul(4)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ backing.dstXInBytes = memcpy
+ .dstXInBytes
+ .checked_mul(4)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ backing.WidthInBytes = backing_width;
+ hipMemcpyParam2D(&backing)?;
+ return Ok(());
+ }
+ // A packed array and a different array format have incompatible pixels.
+ if (src_packed && memcpy.dstMemoryType == hipMemoryType::hipMemoryTypeArray)
+ || (dst_packed && memcpy.srcMemoryType == hipMemoryType::hipMemoryTypeArray)
+ {
+ return Err(CUerror::INVALID_VALUE);
+ }
+ let mut backing = Vec::<f32>::new();
+ backing
+ .try_reserve_exact(backing_bytes / 4)
+ .map_err(|_| CUerror::OUT_OF_MEMORY)?;
+ backing.resize(backing_bytes / 4, 0.0);
+ let mut packed = Vec::<u8>::new();
+ let packed_bytes = memcpy
+ .WidthInBytes
+ .checked_mul(memcpy.Height)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ packed
+ .try_reserve_exact(packed_bytes)
+ .map_err(|_| CUerror::OUT_OF_MEMORY)?;
+ packed.resize(packed_bytes, 0);
+ if dst_packed {
+ packed_1010102_external_to_host(memcpy, &mut packed)?;
+ for (pixel, word) in packed.chunks_exact(4).enumerate() {
+ let bits = u32::from_le_bytes(word.try_into().unwrap());
+ backing[pixel * 4] = (bits & 1023) as f32 / 1023.0;
+ backing[pixel * 4 + 1] = ((bits >> 10) & 1023) as f32 / 1023.0;
+ backing[pixel * 4 + 2] = ((bits >> 20) & 1023) as f32 / 1023.0;
+ backing[pixel * 4 + 3] = (bits >> 30) as f32 / 3.0;
+ }
+ let mut transfer = memcpy;
+ transfer.srcMemoryType = hipMemoryType::hipMemoryTypeHost;
+ transfer.srcHost = backing.as_ptr().cast();
+ transfer.srcXInBytes = 0;
+ transfer.srcY = 0;
+ transfer.srcPitch = backing_width;
+ transfer.dstXInBytes = memcpy
+ .dstXInBytes
+ .checked_mul(4)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ transfer.WidthInBytes = backing_width;
+ hipMemcpyParam2D(&transfer)?;
+ } else {
+ let mut transfer = memcpy;
+ transfer.srcXInBytes = memcpy
+ .srcXInBytes
+ .checked_mul(4)
+ .ok_or(CUerror::INVALID_VALUE)?;
+ transfer.dstMemoryType = hipMemoryType::hipMemoryTypeHost;
+ transfer.dstHost = backing.as_mut_ptr().cast();
+ transfer.dstXInBytes = 0;
+ transfer.dstY = 0;
+ transfer.dstPitch = backing_width;
+ transfer.WidthInBytes = backing_width;
+ hipMemcpyParam2D(&transfer)?;
+ for (pixel, word) in packed.chunks_exact_mut(4).enumerate() {
+ let channels = &backing[pixel * 4..pixel * 4 + 4];
+ let bits = packed_1010102_component(channels[0], 1023)
+ | packed_1010102_component(channels[1], 1023) << 10
+ | packed_1010102_component(channels[2], 1023) << 20
+ | packed_1010102_component(channels[3], 3) << 30;
+ word.copy_from_slice(&bits.to_le_bytes());
+ }
+ packed_1010102_host_to_external(memcpy, &packed)?;
+ }
Ok(())
}
diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs
index 0cf20cd..5329d32 100644
--- a/zluda/src/impl/mod.rs
+++ b/zluda/src/impl/mod.rs
@@ -15,6 +15,7 @@ pub(super) mod memory;
pub(super) mod module;
pub(super) mod pointer;
pub(super) mod stream;
+pub(super) mod surf;
pub(super) mod tex;
#[cfg(debug_assertions)]
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
index c9d4bbd..a6adeef 100644
--- a/zluda/src/impl/module.rs
+++ b/zluda/src/impl/module.rs
@@ -185,6 +185,23 @@ fn get_best_ptx_and_compile(
_ => {}
})
};
+ if let Some(dump_dir) = std::env::var_os("D4R_ZLUDA_DUMP_PTX_DIR") {
+ let dump_dir = std::path::PathBuf::from(dump_dir);
+ let _ = fs::create_dir_all(&dump_dir);
+ for (index, source) in ptx_modules.iter().enumerate() {
+ let stem = format!("module-{index:04}");
+ let _ = fs::write(dump_dir.join(format!("{stem}.ptx")), source.as_bytes());
+ let diagnostic = match ptx_parser::parse_module_checked(source.as_ref()) {
+ Ok(ast) => format!(
+ "checked parse: success\ndirectives={}\ninvalid_directives={}\n",
+ ast.directives.len(),
+ ast.invalid_directives
+ ),
+ Err(errors) => format!("checked parse: {} errors\n{errors:#?}\n", errors.len()),
+ };
+ let _ = fs::write(dump_dir.join(format!("{stem}.parse.txt")), diagnostic);
+ }
+ }
let maybe_module = ptx_modules
.iter()
.rev() // TODO: actually sort by SM
@@ -293,6 +310,43 @@ fn get_gcn_arch<'a>(props: &'a hipDeviceProp_tR0600) -> Result<&'a str, CUerror>
gcn_arch.to_str().map_err(|_| CUerror::UNKNOWN)
}
+// The git SHA alone does not change for uncommitted translator or helper
+// changes, so stale compiled modules would be served after a rebuild. Mix in
+// the size and modification time of the loaded ZLUDA library itself.
+fn zluda_build_version() -> &'static str {
+ static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
+ VERSION.get_or_init(|| {
+ let sha = env!("VERGEN_GIT_SHA");
+ match loaded_library_fingerprint() {
+ Some(fingerprint) => format!("{sha}+{fingerprint}"),
+ None => sha.to_string(),
+ }
+ })
+}
+
+#[cfg(not(windows))]
+fn loaded_library_fingerprint() -> Option<String> {
+ use std::os::unix::fs::MetadataExt;
+ let mut info: libc::Dl_info = unsafe { mem::zeroed() };
+ let address = zluda_build_version as *const libc::c_void;
+ if unsafe { libc::dladdr(address, &mut info) } == 0 || info.dli_fname.is_null() {
+ return None;
+ }
+ let path = unsafe { CStr::from_ptr(info.dli_fname) }.to_str().ok()?;
+ let metadata = fs::metadata(path).ok()?;
+ Some(format!(
+ "{:x}.{:x}.{:x}",
+ metadata.len(),
+ metadata.mtime(),
+ metadata.mtime_nsec()
+ ))
+}
+
+#[cfg(windows)]
+fn loaded_library_fingerprint() -> Option<String> {
+ None
+}
+
fn get_cache_key<'a, 'b>(
isa: &'a str,
text: &str,
@@ -307,7 +361,7 @@ fn get_cache_key<'a, 'b>(
Some(zluda_cache::ModuleKey {
hash: blake3::hash(text.as_bytes()).to_hex(),
compiler_version: "builtin",
- zluda_version: env!("VERGEN_GIT_SHA"),
+ zluda_version: zluda_build_version(),
device: isa,
backend_key: serialized_attributes,
last_access: zluda_cache::ModuleCache::time_now(),
@@ -334,6 +388,24 @@ fn compile_and_cache(
ast: ptx_parser::Module,
cache_with_key: &mut Option<(zluda_cache::ModuleCache, zluda_cache::ModuleKey)>,
) -> Result<(Vec<u8>, u32, Option<Metadata32Bit>), CUerror> {
+ let d4r_dump_dir = std::env::var_os("D4R_ZLUDA_DUMP_DIR").map(std::path::PathBuf::from);
+ let d4r_compiler_hook = |data: &Vec<u8>, stage: String| {
+ if let Some(dir) = d4r_dump_dir.as_ref() {
+ let _ = fs::create_dir_all(dir);
+ let filename = match stage.as_str() {
+ "linked.ll" => "linked.ll",
+ "opt.ll" => "opt.ll",
+ "asm" => "asm.s",
+ "o" => "object.o",
+ _ => return,
+ };
+ let _ = fs::write(dir.join(filename), data);
+ }
+ };
+ let compiler_hook: Option<&dyn Fn(&Vec<u8>, String)> =
+ d4r_dump_dir
+ .as_ref()
+ .map(|_| &d4r_compiler_hook as &dyn Fn(&Vec<u8>, String));
let llvm_module = ptx::to_llvm_module(
ast,
ptx::Attributes {
@@ -341,7 +413,10 @@ fn compile_and_cache(
},
|_| {},
)
- .map_err(|_| CUerror::UNKNOWN)?;
+ .map_err(|error| {
+ eprintln!("[zluda] PTX translation failed: {error:?}");
+ CUerror::UNKNOWN
+ })?;
let ptx_impl = llvm_module.linked_bitcode();
let sm_version = llvm_module.metadata.sm_version;
let metadata32 = llvm_module.metadata32.as_ref().map(Metadata32Bit::new);
@@ -353,9 +428,16 @@ fn compile_and_cache(
llvm_module.attributes_ir,
llvm_module.metadata,
llvm_module.metadata32,
- None,
+ compiler_hook,
)
- .map_err(|_| CUerror::UNKNOWN)?;
+ .map_err(|error| {
+ eprintln!("[zluda] AMDGPU compilation failed: {error}");
+ CUerror::UNKNOWN
+ })?;
+ if let Some(dir) = d4r_dump_dir.as_ref() {
+ let _ = fs::create_dir_all(dir);
+ let _ = fs::write(dir.join("module.hsaco"), &elf_module);
+ }
if let Some((cache, key)) = cache_with_key {
key.last_access = zluda_cache::ModuleCache::time_now();
cache.insert_module(key, &elf_module);
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
index ed2df7a..10f3609 100644
--- a/zluda/src/lib.rs
+++ b/zluda/src/lib.rs
@@ -200,6 +200,9 @@ cuda_macros::cuda_function_declarations!(
cuStreamQuery,
cuStreamSynchronize,
cuStreamWaitEvent,
+ cuSurfObjectCreate,
+ cuSurfObjectDestroy,
+ cuSurfObjectGetResourceDesc,
cuTexObjectCreate,
cuTexObjectDestroy,
cuTexRefSetAddress_v2,
diff --git a/zluda_common/src/lib.rs b/zluda_common/src/lib.rs
index be0a72a..a7863c5 100644
--- a/zluda_common/src/lib.rs
+++ b/zluda_common/src/lib.rs
@@ -301,6 +301,7 @@ from_cuda_transmute!(
cusparseIndexBase_t => rocsparse_index_base,
cusparseMatDescr_t => rocsparse_mat_descr,
CUtexObject => hipTextureObject_t,
+ CUsurfObject => hipSurfaceObject_t,
CUDA_RESOURCE_DESC => HIP_RESOURCE_DESC,
CUDA_TEXTURE_DESC => HIP_TEXTURE_DESC,
CUDA_RESOURCE_VIEW_DESC => HIP_RESOURCE_VIEW_DESC,
diff --git a/ptx/examples/d4r_emit.rs b/ptx/examples/d4r_emit.rs
new file mode 100644
index 0000000..d89069d
--- /dev/null
+++ b/ptx/examples/d4r_emit.rs
@@ -0,0 +1,37 @@
+use std::{env, fs, path::PathBuf};
+use ptx_parser::parse_module_checked;
+
+fn main() {
+ let input = env::args().nth(1).expect("PTX path");
+ let outdir = PathBuf::from(env::args().nth(2).unwrap_or_else(|| "/tmp/d4r-autoexposure-emitted".into()));
+ fs::create_dir_all(&outdir).expect("create output directory");
+ let bytes = fs::read(&input).expect("read PTX");
+ let text = std::str::from_utf8(&bytes).expect("PTX UTF-8").trim_end_matches('\0');
+ let ast = parse_module_checked(text).unwrap_or_else(|e| panic!("PTX parse failed: {e:?}"));
+ let module = ptx::to_llvm_module(ast, ptx::Attributes { clock_rate: 2_500_000 }, |_| {})
+ .unwrap_or_else(|e| panic!("PTX translation failed: {e:?}"));
+ fs::write(outdir.join("ptx.ll"), module.llvm_ir.print_module_to_string().to_bytes()).expect("write translated IR");
+ let linked_helpers = module.linked_bitcode();
+ let hook = |data: &Vec<u8>, stage: String| {
+ let filename = match stage.as_str() {
+ "linked.ll" => "linked.ll",
+ "opt.ll" => "opt.ll",
+ "asm" => "asm.s",
+ "o" => "object.o",
+ other => panic!("unexpected stage: {other}"),
+ };
+ fs::write(outdir.join(filename), data).expect("write compiler stage");
+ };
+ let elf = llvm_zluda::compile(
+ &module.context,
+ "gfx1101",
+ module.llvm_ir,
+ linked_helpers,
+ module.attributes_ir,
+ module.metadata,
+ module.metadata32,
+ Some(&hook),
+ ).unwrap_or_else(|e| panic!("AMDGPU code generation failed: {e}"));
+ fs::write(outdir.join("module.hsaco"), &elf).expect("write linked code object");
+ println!("emitted AMDGPU code object: {}", outdir.join("module.hsaco").display());
+}
diff --git a/ptx_parser/examples/d4r_inspect.rs b/ptx_parser/examples/d4r_inspect.rs
new file mode 100644
index 0000000..92bdaa9
--- /dev/null
+++ b/ptx_parser/examples/d4r_inspect.rs
@@ -0,0 +1,19 @@
+use std::{env, fs};
+use ptx_parser::{parse_module_checked, parse_module_unchecked};
+
+fn main() {
+ for path in env::args().skip(1) {
+ let bytes = fs::read(&path).expect("read PTX");
+ let text = std::str::from_utf8(&bytes).expect("PTX UTF-8").trim_end_matches('\0');
+ let unchecked = parse_module_unchecked(text);
+ println!("{}: version={:?} sm={} directives={} invalid_directives={}",
+ path, unchecked.ptx_version, unchecked.sm_version, unchecked.directives.len(), unchecked.invalid_directives);
+ match parse_module_checked(text) {
+ Ok(module) => println!(" checked: OK directives={}", module.directives.len()),
+ Err(errors) => {
+ println!(" checked: {} parser errors", errors.len());
+ for error in errors { println!(" {error:?}"); }
+ }
+ }
+ }
+}
diff --git a/zluda/src/impl/surf.rs b/zluda/src/impl/surf.rs
new file mode 100644
index 0000000..5ef0f53
--- /dev/null
+++ b/zluda/src/impl/surf.rs
@@ -0,0 +1,113 @@
+use crate::r#impl::array;
+use hip_runtime_sys::*;
+use std::collections::HashMap;
+use std::sync::{Mutex, OnceLock};
+
+// Device layout of a ZLUDA surface object. It starts with a copy of HIP's
+// image object so the handle is also a valid texture object for integer
+// (texel) fetches: NVIDIA shares descriptors between the two, and NGX reads
+// in-place-updated arrays through surface handles with tex. The CUDA format
+// follows the (zeroed) sampler slot.
+const IMAGE_OBJECT_BYTES: usize = 48; // HIP_IMAGE_OBJECT_SIZE_DWORD * 4
+const SAMPLER_OBJECT_BYTES: usize = 32; // HIP_SAMPLER_OBJECT_SIZE_DWORD * 4
+const FORMAT_OFFSET: usize = IMAGE_OBJECT_BYTES + SAMPLER_OBJECT_BYTES;
+const HANDLE_BYTES: usize = FORMAT_OFFSET + 16;
+
+struct SurfaceInfo {
+ native: usize,
+ descriptor: Vec<u8>,
+}
+
+fn surface_resources() -> &'static Mutex<HashMap<usize, SurfaceInfo>> {
+ static RESOURCES: OnceLock<Mutex<HashMap<usize, SurfaceInfo>>> = OnceLock::new();
+ RESOURCES.get_or_init(|| Mutex::new(HashMap::new()))
+}
+
+pub(crate) unsafe fn object_create(
+ surface: &mut hipSurfaceObject_t,
+ resource: *const HIP_RESOURCE_DESC,
+) -> hipError_t {
+ let descriptor = match resource.as_ref() {
+ Some(value) => value,
+ None => return Err(hipErrorCode_t::r#InvalidValue),
+ };
+ let cuda_format = if descriptor.resType == HIPresourcetype::HIP_RESOURCE_TYPE_ARRAY {
+ array::original_format(descriptor.res.array.hArray)?.unwrap_or(0)
+ } else {
+ 0
+ };
+ let mut native = std::ptr::null_mut();
+ hipCreateSurfaceObject(&mut native, resource.cast::<hipResourceDesc>())?;
+ let mut device_handle = std::ptr::null_mut();
+ if let Err(error) = hipMalloc(&mut device_handle, HANDLE_BYTES) {
+ let _ = hipDestroySurfaceObject(native);
+ return Err(error);
+ }
+ let mut tail = [0u32; (HANDLE_BYTES - IMAGE_OBJECT_BYTES) / 4];
+ tail[(FORMAT_OFFSET - IMAGE_OBJECT_BYTES) / 4] = cuda_format;
+ let written = hipMemcpyDtoD(
+ hipDeviceptr_t(device_handle),
+ hipDeviceptr_t(native.cast()),
+ IMAGE_OBJECT_BYTES,
+ )
+ .and_then(|()| {
+ hipMemcpyHtoD(
+ hipDeviceptr_t(device_handle.cast::<u8>().add(IMAGE_OBJECT_BYTES).cast()),
+ tail.as_mut_ptr().cast(),
+ HANDLE_BYTES - IMAGE_OBJECT_BYTES,
+ )
+ });
+ if let Err(error) = written {
+ let _ = hipFree(device_handle);
+ let _ = hipDestroySurfaceObject(native);
+ return Err(error);
+ }
+ *surface = device_handle.cast();
+ let key = device_handle as usize;
+ let bytes = std::slice::from_raw_parts(
+ (descriptor as *const HIP_RESOURCE_DESC).cast::<u8>(),
+ std::mem::size_of::<HIP_RESOURCE_DESC>(),
+ )
+ .to_vec();
+ if let Ok(mut resources) = surface_resources().lock() {
+ resources.insert(key, SurfaceInfo { native: native as usize, descriptor: bytes });
+ Ok(())
+ } else {
+ let _ = hipFree(device_handle);
+ let _ = hipDestroySurfaceObject(native);
+ Err(hipErrorCode_t::r#Unknown)
+ }
+}
+
+pub(crate) unsafe fn object_destroy(surface: hipSurfaceObject_t) -> hipError_t {
+ let info = surface_resources()
+ .lock()
+ .map_err(|_| hipErrorCode_t::Unknown)?
+ .remove(&(surface as usize))
+ .ok_or(hipErrorCode_t::InvalidValue)?;
+ let native_result = hipDestroySurfaceObject(info.native as hipSurfaceObject_t);
+ let free_result = hipFree(surface.cast());
+ native_result?;
+ free_result
+}
+
+pub(crate) unsafe fn object_get_resource_desc(
+ output: &mut HIP_RESOURCE_DESC,
+ surface: hipSurfaceObject_t,
+) -> hipError_t {
+ let resources = match surface_resources().lock() {
+ Ok(value) => value,
+ Err(_) => return Err(hipErrorCode_t::r#Unknown),
+ };
+ let bytes = match resources.get(&(surface as usize)) {
+ Some(value) if value.descriptor.len() == std::mem::size_of::<HIP_RESOURCE_DESC>() =>
+ &value.descriptor,
+ _ => return Err(hipErrorCode_t::r#InvalidValue),
+ };
+ std::ptr::copy_nonoverlapping(
+ bytes.as_ptr(),
+ (output as *mut HIP_RESOURCE_DESC).cast::<u8>(),
+ bytes.len(),
+ );
+ Ok(())
+}