decuda 0.1.1

CUDA to HIP, SYCL, OpenCL, and Rust GPU migration tool — automatic source-code translator for porting CUDA C++ kernels to AMD ROCm HIP, Intel oneAPI SYCL, Khronos OpenCL, and Rust GPU (cust / rust-gpu)
Documentation
//! Database of CUDA runtime / driver APIs and how each maps to the supported
//! targets. The database is loaded once at startup; entries can be queried by
//! API name and target backend.

#![allow(dead_code)]

use std::collections::BTreeMap;

use once_cell::sync::Lazy;

use crate::cli::Target;

/// Per-API metadata.
#[derive(Debug, Clone)]
pub struct ApiInfo {
    pub header: &'static str,
    pub hip: Option<&'static str>,
    pub sycl: Option<&'static str>,
    pub rust: Option<&'static str>,
    pub opencl: Option<&'static str>,
    pub note: &'static str,
}

impl ApiInfo {
    pub fn supports(&self, t: Target) -> bool {
        self.mapping(t).is_some()
    }

    pub fn mapping(&self, t: Target) -> Option<&'static str> {
        match t {
            Target::Hip => self.hip,
            Target::Sycl => self.sycl,
            Target::Rust => self.rust,
            Target::Opencl => self.opencl,
            Target::All => None,
        }
    }

    pub fn summarize(&self, t: Target) -> String {
        if let Some(m) = self.mapping(t) {
            format!("-> {t:?} = {m}")
        } else {
            format!("(no mapping for {t:?})")
        }
    }
}

pub type ApiMap = BTreeMap<&'static str, ApiInfo>;

pub fn database() -> &'static ApiMap {
    &DATABASE
}

/// All known CUDA runtime API entries.
///
/// Entries marked `None` for a backend mean: no automatic translation; the
/// rewriter preserves the original token and emits a warning.
static DATABASE: Lazy<ApiMap> = Lazy::new(|| {
    let mut m: ApiMap = BTreeMap::new();

    // ---- Memory management -------------------------------------------------
    insert(&mut m, "cudaMalloc", api("cuda_runtime.h",
        Some("hipMalloc"), None, None, None,
        "opencl: use clCreateBuffer + clEnqueueWriteBuffer"));
    insert(&mut m, "cudaFree", api("cuda_runtime.h",
        Some("hipFree"), None, None, None,
        "opencl: use clReleaseMemObject"));
    insert(&mut m, "cudaMemcpy", api("cuda_runtime.h",
        Some("hipMemcpy"), None, None, None,
        "opencl: clEnqueueCopyBuffer / clEnqueueReadBuffer"));
    insert(&mut m, "cudaMemcpyAsync", api("cuda_runtime.h",
        Some("hipMemcpyAsync"), None, None, None,
        "async memcpy needs a queue on every backend"));
    insert(&mut m, "cudaMemset", api("cuda_runtime.h",
        Some("hipMemset"), None, None, None, ""));
    insert(&mut m, "cudaMemcpyDeviceToHost", header_only("cuda_runtime.h"));
    insert(&mut m, "cudaMemcpyHostToDevice", header_only("cuda_runtime.h"));
    insert(&mut m, "cudaMemcpyDeviceToDevice", header_only("cuda_runtime.h"));
    insert(&mut m, "cudaHostAlloc", api("cuda_runtime.h",
        Some("hipHostMalloc"), None, None, None,
        "opencl: use clCreateBuffer with CL_MEM_USE_HOST_PTR"));
    insert(&mut m, "cudaMallocHost", api("cuda_runtime.h",
        Some("hipHostMalloc"), None, None, None,
        "opencl: clCreateBuffer with CL_MEM_ALLOC_HOST_PTR"));
    insert(&mut m, "cudaMallocManaged", api("cuda_runtime.h",
        Some("hipMallocManaged"), None, None, None,
        "sycl: sycl::malloc_shared; opencl: clSVMAlloc (different signatures)"));
    insert(&mut m, "cudaFreeHost", api("cuda_runtime.h",
        Some("hipFreeHost"), None, None, None,
        "sycl: sycl::free; opencl: clReleaseMemObject (different semantics)"));
    insert(&mut m, "cudaMemcpyToSymbol", api("cuda_runtime.h",
        Some("hipMemcpyToSymbol"), None, None, None,
        "sycl/opencl: no symbol-table equivalent; use constant memory"));
    insert(&mut m, "cudaMemcpyFromSymbol", api("cuda_runtime.h",
        Some("hipMemcpyFromSymbol"), None, None, None,
        "sycl/opencl: no symbol-table equivalent; use constant memory"));

    // ---- Streams & events ---------------------------------------------------
    insert(&mut m, "cudaStreamCreate", api("cuda_runtime.h",
        Some("hipStreamCreate"), None, None, None,
        "sycl/opencl: this maps to creating a queue / command queue"));
    insert(&mut m, "cudaStreamDestroy", api("cuda_runtime.h",
        Some("hipStreamDestroy"), None, None, None, ""));
    insert(&mut m, "cudaStreamSynchronize", api("cuda_runtime.h",
        Some("hipStreamSynchronize"), None, None, None,
        "sycl: queue.wait(); opencl: clFinish / clWaitForEvents"));
    insert(&mut m, "cudaEventCreate", api("cuda_runtime.h",
        Some("hipEventCreate"), None, None, None, ""));
    insert(&mut m, "cudaEventRecord", api("cuda_runtime.h",
        Some("hipEventRecord"), None, None, None, ""));
    insert(&mut m, "cudaEventSynchronize", api("cuda_runtime.h",
        Some("hipEventSynchronize"), None, None, None, ""));
    insert(&mut m, "cudaEventElapsedTime", api("cuda_runtime.h",
        Some("hipEventElapsedTime"), None, None, None, ""));
    insert(&mut m, "cudaEventDestroy", api("cuda_runtime.h",
        Some("hipEventDestroy"), None, None, None, ""));

    // ---- Device & error handling -------------------------------------------
    insert(&mut m, "cudaGetDeviceCount", api("cuda_runtime.h",
        Some("hipGetDeviceCount"), None, None, None, ""));
    insert(&mut m, "cudaGetDevice", api("cuda_runtime.h",
        Some("hipGetDevice"), None, None, None, ""));
    insert(&mut m, "cudaSetDevice", api("cuda_runtime.h",
        Some("hipSetDevice"), None, None, None, ""));
    insert(&mut m, "cudaGetLastError", api("cuda_runtime.h",
        Some("hipGetLastError"), None, None, None, ""));
    insert(&mut m, "cudaPeekAtLastError", api("cuda_runtime.h",
        Some("hipPeekAtLastError"), None, None, None, ""));
    insert(&mut m, "cudaGetErrorName", api("cuda_runtime.h",
        Some("hipGetErrorName"), None, None, None, ""));
    insert(&mut m, "cudaGetErrorString", api("cuda_runtime.h",
        Some("hipGetErrorString"), None, None, None, ""));
    insert(&mut m, "cudaDeviceSynchronize", api("cuda_runtime.h",
        Some("hipDeviceSynchronize"), None, None, None,
        "sycl: queue.wait_and_throw(); opencl: clFinish (require queue object)"));
    insert(&mut m, "cudaDeviceReset", api("cuda_runtime.h",
        Some("hipDeviceReset"), None, None, None,
        "sycl/opencl: no direct equivalent; device reset is implicit"));
    insert(&mut m, "cudaDeviceGetAttribute", api("cuda_runtime.h",
        Some("hipDeviceGetAttribute"), None, None, None,
        "sycl: device.get_info; opencl: clGetDeviceInfo (different signatures)"));

    // ---- Types (lookup only) -----------------------------------------------
    insert(&mut m, "cudaError_t", type_alias("cuda_runtime.h",
        Some("hipError_t"), Some("sycl::errc"), Some("cust::CUresult"), Some("cl_int")));
    insert(&mut m, "cudaStream_t", type_alias("cuda_runtime.h",
        Some("hipStream_t"), Some("sycl::queue"), Some("cust::Stream"), Some("cl_command_queue")));
    insert(&mut m, "cudaEvent_t", type_alias("cuda_runtime.h",
        Some("hipEvent_t"), Some("sycl::event"), None, Some("cl_event")));
    insert(&mut m, "dim3", type_alias("",
        Some("dim3"), Some("sycl::range<3>"), Some("(u32, u32, u32)"), Some("[usize; 3]")));

    // ---- Math intrinsics (mostly 1:1) --------------------------------------
    for name in [
        "__sinf", "__cosf", "__tanf", "__expf", "__logf", "__log2f", "__log10f",
        "__powf", "__sqrtf", "__fdividef", "__fmaf_rn", "__fmul_rn",
        "__fmadd_rd", "__float2int_rn", "__float2uint_rn",
    ] {
        insert(&mut m, name, math_intrinsic(name));
    }
    m
});

fn insert(m: &mut ApiMap, name: &'static str, info: ApiInfo) {
    m.insert(name, info);
}

const fn api(
    header: &'static str,
    hip: Option<&'static str>,
    sycl: Option<&'static str>,
    rust: Option<&'static str>,
    opencl: Option<&'static str>,
    note: &'static str,
) -> ApiInfo {
    ApiInfo {
        header,
        hip,
        sycl,
        rust,
        opencl,
        note,
    }
}

fn header_only(header: &'static str) -> ApiInfo {
    ApiInfo {
        header,
        hip: Some("hipMemcpyKind"),
        sycl: None,
        rust: None,
        opencl: None,
        note: "enum constant; backend value is structural",
    }
}

fn type_alias(
    header: &'static str,
    hip: Option<&'static str>,
    sycl: Option<&'static str>,
    rust: Option<&'static str>,
    opencl: Option<&'static str>,
) -> ApiInfo {
    ApiInfo {
        header,
        hip,
        sycl,
        rust,
        opencl,
        note: "type alias; no automatic code rewrite",
    }
}

fn math_intrinsic(name: &'static str) -> ApiInfo {
    ApiInfo {
        header: "device_functions.h",
        hip: Some(name),
        sycl: Some("sycl::native::"),
        rust: Some(name),
        opencl: Some(name.trim_start_matches('_')),
        note: "math intrinsic; mostly identical across HIP/OpenCL/Rust",
    }
}

/// Look up an API entry, returning None if the name is not in the database.
pub fn lookup(name: &str) -> Option<&'static ApiInfo> {
    DATABASE.get(name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cuda_malloc_maps_to_hip() {
        let e = lookup("cudaMalloc").unwrap();
        assert_eq!(e.hip, Some("hipMalloc"));
    }

    #[test]
    fn cuda_event_t_maps_everywhere() {
        let e = lookup("cudaEvent_t").unwrap();
        assert!(e.hip.is_some());
        assert!(e.sycl.is_some());
        assert!(e.rust.is_none());
        assert!(e.opencl.is_some());
    }

    #[test]
    fn unknown_apis_return_none() {
        assert!(lookup("cudaDefinitelyNotARealApi").is_none());
    }

    #[test]
    fn cuda_device_synchronize_maps_to_hip() {
        let e = lookup("cudaDeviceSynchronize").unwrap();
        assert_eq!(e.hip, Some("hipDeviceSynchronize"));
        assert!(e.sycl.is_none(), "SYCL requires queue object — semantic");
        assert!(e.opencl.is_none(), "OpenCL requires command queue — semantic");
    }

    #[test]
    fn cuda_malloc_managed_maps_to_hip() {
        let e = lookup("cudaMallocManaged").unwrap();
        assert_eq!(e.hip, Some("hipMallocManaged"));
    }

    #[test]
    fn cuda_free_host_maps_to_hip() {
        let e = lookup("cudaFreeHost").unwrap();
        assert_eq!(e.hip, Some("hipFreeHost"));
    }

    #[test]
    fn cuda_memcpy_to_from_symbol_maps_to_hip() {
        let to = lookup("cudaMemcpyToSymbol").unwrap();
        assert_eq!(to.hip, Some("hipMemcpyToSymbol"));
        let from = lookup("cudaMemcpyFromSymbol").unwrap();
        assert_eq!(from.hip, Some("hipMemcpyFromSymbol"));
    }

    #[test]
    fn cuda_device_reset_and_attribute_map_to_hip() {
        let reset = lookup("cudaDeviceReset").unwrap();
        assert_eq!(reset.hip, Some("hipDeviceReset"));
        let attr = lookup("cudaDeviceGetAttribute").unwrap();
        assert_eq!(attr.hip, Some("hipDeviceGetAttribute"));
    }
}