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.
2769 lines
117 KiB
Diff
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(())
|
|
+}
|