#![allow(dead_code)]
use oxicuda_blas::GpuFloat;
use oxicuda_ptx::builder::BodyBuilder;
use oxicuda_ptx::ir::{PtxType, Register};
pub(crate) fn ptx_suffix<T: GpuFloat>() -> &'static str {
let s = T::PTX_TYPE.as_ptx_str();
s.strip_prefix('.').unwrap_or(s)
}
pub(crate) fn load_global_float<T: GpuFloat>(b: &mut BodyBuilder<'_>, addr: Register) -> Register {
if T::PTX_TYPE == PtxType::F32 {
b.load_global_f32(addr)
} else {
b.load_global_f64(addr)
}
}
pub(crate) fn store_global_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
addr: Register,
val: Register,
) {
if T::PTX_TYPE == PtxType::F32 {
b.store_global_f32(addr, val);
} else {
b.store_global_f64(addr, val);
}
}
pub(crate) fn fma_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
a: Register,
bv: Register,
c: Register,
) -> Register {
if T::PTX_TYPE == PtxType::F32 {
b.fma_f32(a, bv, c)
} else {
b.fma_f64(a, bv, c)
}
}
pub(crate) fn mul_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
a: Register,
bv: Register,
) -> Register {
if T::PTX_TYPE == PtxType::F32 {
let dst = b.alloc_reg(PtxType::F32);
b.raw_ptx(&format!("mul.rn.f32 {dst}, {a}, {bv};"));
dst
} else {
let dst = b.alloc_reg(PtxType::F64);
b.raw_ptx(&format!("mul.rn.f64 {dst}, {a}, {bv};"));
dst
}
}
pub(crate) fn add_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
a: Register,
bv: Register,
) -> Register {
if T::PTX_TYPE == PtxType::F32 {
b.add_f32(a, bv)
} else {
b.add_f64(a, bv)
}
}
pub(crate) fn load_float_imm<T: GpuFloat>(b: &mut BodyBuilder<'_>, val: f64) -> Register {
if T::PTX_TYPE == PtxType::F32 {
let r = b.alloc_reg(PtxType::F32);
let bits = (val as f32).to_bits();
b.raw_ptx(&format!("mov.b32 {r}, 0F{bits:08X};"));
r
} else {
let r = b.alloc_reg(PtxType::F64);
let bits = val.to_bits();
b.raw_ptx(&format!("mov.b64 {r}, 0D{bits:016X};"));
r
}
}
pub(crate) fn reinterpret_bits_to_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
bits: Register,
) -> Register {
if T::PTX_TYPE == PtxType::F32 {
let bits32 = b.alloc_reg(PtxType::U32);
b.raw_ptx(&format!("cvt.u32.u64 {bits32}, {bits};"));
let fval = b.alloc_reg(PtxType::F32);
b.raw_ptx(&format!("mov.b32 {fval}, {bits32};"));
fval
} else {
let fval = b.alloc_reg(PtxType::F64);
b.raw_ptx(&format!("mov.b64 {fval}, {bits};"));
fval
}
}
pub(crate) fn emit_shfl_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
mode: &str,
val: Register,
offset: &str,
c: &str,
) -> Register {
if T::PTX_TYPE == PtxType::F64 {
let lo = b.alloc_reg(PtxType::B32);
let hi = b.alloc_reg(PtxType::B32);
b.raw_ptx(&format!("mov.b64 {{{lo}, {hi}}}, {val};"));
let lo_shuf = b.alloc_reg(PtxType::B32);
let hi_shuf = b.alloc_reg(PtxType::B32);
b.raw_ptx(&format!(
"shfl.sync.{mode}.b32 {lo_shuf}, {lo}, {offset}, {c}, 0xFFFFFFFF;"
));
b.raw_ptx(&format!(
"shfl.sync.{mode}.b32 {hi_shuf}, {hi}, {offset}, {c}, 0xFFFFFFFF;"
));
let packed = b.alloc_reg(PtxType::F64);
b.raw_ptx(&format!("mov.b64 {packed}, {{{lo_shuf}, {hi_shuf}}};"));
packed
} else {
let shuffled = b.alloc_reg(T::PTX_TYPE);
b.raw_ptx(&format!(
"shfl.sync.{mode}.b32 {shuffled}, {val}, {offset}, {c}, 0xFFFFFFFF;"
));
shuffled
}
}
pub(crate) fn emit_warp_reduce_sum<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
val: Register,
) -> Register {
let suffix = ptx_suffix::<T>();
let mut current = val;
for offset in [16u32, 8, 4, 2, 1] {
let shuffled = emit_shfl_float::<T>(b, "down", current.clone(), &offset.to_string(), "31");
let sum = b.alloc_reg(T::PTX_TYPE);
b.raw_ptx(&format!("add.{suffix} {sum}, {current}, {shuffled};"));
current = sum;
}
current
}
pub(crate) fn emit_atomic_add_float<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
addr: Register,
val: Register,
) {
let suffix = ptx_suffix::<T>();
let _old = b.alloc_reg(T::PTX_TYPE);
b.raw_ptx(&format!(
"atom.global.add.{suffix} {_old}, [{addr}], {val};"
));
}
pub(crate) fn emit_guarded_load<T: GpuFloat>(
b: &mut BodyBuilder<'_>,
base_addr: Register,
idx: Register,
bound: Register,
) -> Register {
let elem_bytes = T::SIZE as u32;
let zero = load_float_imm::<T>(b, 0.0);
let pred = b.alloc_reg(PtxType::Pred);
b.raw_ptx(&format!("setp.lo.u32 {pred}, {idx}, {bound};"));
let addr = b.byte_offset_addr(base_addr, idx, elem_bytes);
let loaded = load_global_float::<T>(b, addr);
let result = b.alloc_reg(T::PTX_TYPE);
let suffix = ptx_suffix::<T>();
b.raw_ptx(&format!(
"selp.{suffix} {result}, {loaded}, {zero}, {pred};"
));
result
}
#[cfg(test)]
pub(crate) mod test_support {
pub(crate) enum PtxasOutcome {
Ok,
Rejected(String),
Unavailable,
}
pub(crate) fn assemble_sm86(name: &str, ptx: &str) -> PtxasOutcome {
use std::io::Write;
let path = std::env::temp_dir().join(format!("oxicuda_sparse_{name}.ptx"));
{
let mut f = std::fs::File::create(&path).expect("test: create temp PTX file");
f.write_all(ptx.as_bytes())
.expect("test: write temp PTX file");
}
match std::process::Command::new("ptxas")
.arg("-arch=sm_86")
.arg(&path)
.arg("-o")
.arg("/dev/null")
.output()
{
Ok(out) if out.status.success() => PtxasOutcome::Ok,
Ok(out) => PtxasOutcome::Rejected(String::from_utf8_lossy(&out.stderr).into_owned()),
Err(_) => PtxasOutcome::Unavailable,
}
}
pub(crate) fn assert_assembles_and_clean(kernel: &str, ptx: &str) {
assert!(
!ptx.contains("shfl.sync.down.b64")
&& !ptx.contains("shfl.sync.up.b64")
&& !ptx.contains("shfl.sync.bfly.b64")
&& !ptx.contains("shfl.sync.idx.b64"),
"{kernel}: emitted an illegal .b64 warp shuffle (ptxas only accepts .b32):\n{ptx}"
);
match assemble_sm86(kernel, ptx) {
PtxasOutcome::Ok | PtxasOutcome::Unavailable => {}
PtxasOutcome::Rejected(stderr) => {
panic!("ptxas rejected the {kernel} kernel:\n{stderr}\nPTX:\n{ptx}")
}
}
}
}