Skip to content

C/C++ SDK

The WAVE C API uses opaque handles for devices, host buffers, and compiled kernels.

From the repository root, build the static library with CMake:

Terminal window
cmake -S sdk/cpp -B sdk/cpp/build
cmake --build sdk/cpp/build

This produces libwave.a. Link it into your project and include the public header:

#include <wave/wave.h>

In CMakeLists.txt:

target_link_libraries(my_app PRIVATE wave)

Call wave_detect_device() to obtain a device handle. The handle is an opaque pointer that you pass to subsequent API calls:

wave_device_t *dev = wave_detect_device();
if (!dev) {
fprintf(stderr, "No GPU found: %s\n", wave_last_error());
return 1;
}
printf("vendor: %d\n", wave_device_vendor(dev));
printf("name: %s\n", wave_device_name(dev));

wave_device_vendor() returns a wave_vendor_t enum value. wave_device_name() returns a null-terminated string owned by the device handle.

ValueMeaning
WAVE_VENDOR_APPLEApple GPU; Metal
WAVE_VENDOR_NVIDIANVIDIA GPU
WAVE_VENDOR_AMDAMD GPU
WAVE_VENDOR_INTELIntel GPU
WAVE_VENDOR_EMULATORSoftware emulator

Buffers are created through typed factory functions. Each returns an opaque wave_buffer_t*:

float host_data[] = {1.0f, 2.0f, 3.0f, 4.0f};
// From existing host data
wave_buffer_t *a = wave_create_buffer_f32(host_data, 4);
// Zero-initialized buffer
wave_buffer_t *out = wave_create_zeros_f32(1024);

A NULL return indicates an allocation failure; check wave_last_error() for details.

Pass kernel source code and a wave_lang_t constant to wave_compile():

const char *source =
"import wave_gpu\n"
"\n"
"@wave_gpu.kernel\n"
"def vector_add(a: f32[:], b: f32[:], out: f32[:], n: u32):\n"
" tid = thread_id()\n"
" if tid < n:\n"
" out[tid] = a[tid] + b[tid]\n";
wave_kernel_t *kern = wave_compile(source, WAVE_LANG_PYTHON);
if (!kern) {
fprintf(stderr, "Compile error: %s\n", wave_last_error());
return 1;
}
ValueLanguage
WAVE_LANG_PYTHONPython
WAVE_LANG_RUSTRust
WAVE_LANG_CPPC++
WAVE_LANG_TYPESCRIPTTypeScript

wave_launch() passes --memory-file, --workgroup, and --reg options that the current wave-emu CLI does not accept. Emulator launches fail, and GPU device vendors are rejected directly. Use the Python quickstart or Rust SDK for execution.

This call illustrates the arguments and error handling:

uint32_t n = 1024;
wave_buffer_t *buffers[] = { a, b, out };
uint32_t scalars[] = { n };
uint32_t grid[] = { n / 256, 1, 1 };
uint32_t workgroup[] = { 256, 1, 1 };
int rc = wave_launch(kern, dev, buffers, 3, scalars, 1, grid, workgroup);
if (rc != 0) {
fprintf(stderr, "Launch failed: %s\n", wave_last_error());
}

wave_launch returns 0 on success and -1 on failure.

Copy buffer contents into a caller-allocated array with wave_read_buffer_f32():

float result[1024];
int rc = wave_read_buffer_f32(out, result, 1024);
if (rc != 0) {
fprintf(stderr, "Read failed: %s\n", wave_last_error());
}
printf("result[0] = %f\n", result[0]);

The function returns 0 on success and -1 on error. The caller is responsible for providing a buffer large enough to hold count elements.

Free each handle when it is no longer needed:

wave_free_kernel(kern);
wave_free_buffer(a);
wave_free_buffer(b);
wave_free_buffer(out);
wave_free_device(dev);

Fallible functions return NULL for pointer results or -1 for integer results. Call wave_last_error() immediately after a failure to retrieve a human-readable error message:

wave_kernel_t *kern = wave_compile(bad_source, WAVE_LANG_PYTHON);
if (!kern) {
const char *msg = wave_last_error();
fprintf(stderr, "error: %s\n", msg);
}

wave_last_error() returns a pointer to a thread-local string. The pointer is valid until the next WAVE API call on the same thread.

See the C/C++ API reference for function signatures.