decuda — CUDA to HIP / SYCL / OpenCL / Rust GPU Migration Tool
Migrate CUDA C++ source code to HIP, SYCL, OpenCL, or Rust GPU automatically. decuda is a command-line tool that performs the mechanical rewrites — kernel launch syntax, runtime API names, built-in variables, header includes, qualifiers — and flags everything that needs human judgement in a structured migration report.
CUDA is the dominant GPU programming model, but vendor lock-in to NVIDIA hardware limits portability. decuda helps you port CUDA kernels to:
- AMD ROCm HIP — near source-compatible, minimal changes needed
- Intel oneAPI SYCL — C++ cross-platform GPU programming
- Khronos OpenCL — open standard for heterogeneous computing
- Rust GPU (
cust/rust-gpu) — Rust-native GPU compute
It is a starting point for migration, not a finished translator. The philosophy is mechanical, not semantic: do the syntactic rewrites that are safe, and surface everything else as warnings so a human engineer can make the right call.
Table of Contents
- Supported CUDA Constructs
- Quick Start
- Usage
- How It Works
- Examples
- CUDA API Database
- Limitations
- Comparison with Other Tools
- License
Supported CUDA Constructs
| Construct | HIP | SYCL | Rust | OpenCL |
|---|---|---|---|---|
__global__ / __device__ qualifiers |
auto | auto | auto | auto |
Kernel launch kernel<<<g,b>>>(...) |
auto | auto | auto | auto |
threadIdx / blockIdx / blockDim |
auto | auto | auto | auto |
__syncthreads() / __syncwarp(...) |
auto | auto | auto | auto |
atomicAdd / atomicCAS / ... |
auto | auto | auto | auto |
__shared__ / __constant__ |
auto | auto | auto | auto |
cudaMalloc / cudaMemcpy / ... |
auto | warn | warn | warn |
cudaStreamCreate / events |
auto | warn | warn | warn |
cudaError_t / dim3 aliases |
auto | auto | auto | auto |
__launch_bounds__ |
warn | warn | warn | warn |
| Inline PTX / texture references | warn | warn | warn | warn |
| Thrust / CUB / cuBLAS wrappers | warn | warn | warn | warn |
auto = automatic rewrite, warn = flagged in the migration report, preserved as-is in output.
Quick Start
# Migrate a single CUDA file to all four backends.
# Migrate to HIP only.
# Migrate an entire directory of .cu / .cuh files.
Usage
migrate — translate CUDA to target GPU language
| Flag | Description |
|---|---|
-i, --input |
Input .cu/.cuh file or directory |
-o, --output |
Output directory; each target writes into <output>/<target>/ |
-t, --target |
hip, sycl, rust, opencl, or all (default: all) |
--dry-run |
Parse and plan only; do not write output files |
-v, --verbose |
Emit progress to stderr |
--filter <substr> |
Only process files whose path contains <substr> |
inspect — show the IR extracted from a CUDA file
list-apis — list all CUDA APIs in the database
Output layout
Each target writes its output under <output>/<target>/, preserving the
directory layout of the input:
| Target | File extension | Output directory |
|---|---|---|
hip |
.hip.cpp |
<output>/hip/ |
sycl |
.sycl.cpp |
<output>/sycl/ |
rust |
.rs |
<output>/rust/ |
opencl |
.cl |
<output>/opencl/ |
A migration-report.json.<timestamp> is written next to the output
directories, capturing per-file warnings (unsupported APIs, mismatched
qualifiers, …). A human-readable summary is printed to stdout.
How It Works
decuda processes CUDA source in four stages:
-
Pre-process — Replace CUDA's
kernel<<<g,b>>>(args)launch syntax with an ordinary function call (__decuda_launch(...)) so the source is parseable by a stock C++ grammar (tree-sitter-cpp). Comments and string literals are skipped so a launch inside a comment never shifts byte positions. -
Validate — Parse the preprocessed source with
tree-sitter-cpp. Parse errors are surfaced but the AST is unused (tree-sitter doesn't understand CUDA-specific identifiers). -
Harvest IR — Walk the original source with targeted regex sweeps for: kernel launches, function/storage qualifiers (
__global__,__shared__, ...), built-in variables (threadIdx, ...), synchronization intrinsics (__syncthreads), CUDA runtime API calls (cudaMalloc,cudaStreamCreate, ...), atomic intrinsics, and header includes. Each match becomes an IR node with a byte span. -
Emit — Compute per-node replacements for the target backend and apply them as byte-precise span substitutions in source order, tracking the cumulative byte shift so each edit lands at its original semantic position. Overlapping edits (e.g. a
cuda_runtimeruntime call landing inside an#include <cuda_runtime.h>line) are detected and the earlier one wins.
Examples
The examples/cu/ directory contains 14 CUDA input files covering basic
through advanced constructs:
| File | Constructs exercised |
|---|---|
saxpy.cu |
kernels, launches, malloc/free, shared memory |
histogram.cu |
atomics, shared bins, grid-stride, warp intrinsics |
transpose.cu |
2D dim3 grid/block, shared-memory tile |
stream_pipeline.cu |
streams, events, async memcpy, smem+stream launches |
device_helpers.cu |
__device__ helpers, inline hints, __constant__, __launch_bounds__ |
reduction.cu |
warp-shuffle reduction, __shfl_sync, tree reduction, atomicAdd |
stencil_3d.cu |
3D 7-point stencil, shared-memory halo, 3D dim3 |
device_management.cu |
multi-GPU, error handling, pinned memory |
managed_memory.cu |
cudaMallocManaged, __managed__, unified memory |
warp_primitives.cu |
__shfl_sync, __ballot_sync, __any_sync, __all_sync |
rich.cu |
atomics, shared mem, warp intrinsics, 2D launches, constant mem |
launch_in_comment.cu |
launches inside comments/strings (preprocessor test) |
headers_only.cu |
every include-replacement path |
empty.cu |
no CUDA constructs (banner + verbatim source) |
Regenerate all outputs:
See examples/README.md for the full layout.
CUDA API Database
The CUDA-API database is in src/cuda_db.rs. Adding a new API entry is a
one-liner:
insert;
The four Option<&str> are per-target mappings. None means "no mapping;
preserve verbatim and warn." Run decuda list-apis to see all entries.
Limitations
- Kernel bodies are copied verbatim — qualifiers, built-in variables,
and
cudaXxxcalls inside are rewritten, but algorithmic structure stays as-is. SYCL, Rust, and OpenCL kernels typically need a manual rewrite of the body (e.g. converting__shared__ float buf[32]to a SYCLlocal_accessor). - 2D/3D grid sizes —
dim3grid/block sizes are translated literally; SYCL/Rust/OpenCL output uses{grid}as a singlesize_t. The user must translatedim3tosycl::range<3>/(grid_x, grid_y, grid_z)etc. - Host code is not rewritten end-to-end —
cuda_runtime.his mapped to the closest target header, but you'll still need to set up the SYCL queue, OpenCL context+queue,cust::Cudadevice handle, etc. - Math intrinsics (
__sinf,__expf,__fmul_rn, ...) are kept as names — the HIP/OpenCL/Rust equivalents are usually bit-identical.
Comparison with Other Tools
| Feature | decuda | HIPIFY (AMD) | SYCLomatic (Intel) | cu2clang |
|---|---|---|---|---|
| HIP target | yes | yes | — | — |
| SYCL target | yes | — | yes | — |
| OpenCL target | yes | — | — | yes |
| Rust GPU target | yes | — | — | — |
| Multi-target in one run | yes | no | no | no |
| Migration report | JSON + stdout | YAML | YAML | — |
| Dry-run mode | yes | yes | yes | — |
| Directory walker | yes | yes | yes | no |
| Open source | yes | yes | yes | yes |
decuda's unique advantage: one tool, four targets, one run. HIPIFY only targets HIP, SYCLomatic only targets SYCL, and cu2clang only targets OpenCL. decuda emits all four in a single invocation with a unified migration report.
License
Apache-2.0.