Skip to main content

oxihuman_export/
opencl_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! OpenCL kernel stub export.
6
7/// An OpenCL kernel argument.
8pub struct ClKernelArg {
9    pub type_name: String,
10    pub name: String,
11    pub is_global: bool,
12}
13
14/// An OpenCL kernel.
15pub struct ClKernel {
16    pub name: String,
17    pub args: Vec<ClKernelArg>,
18    pub body: String,
19}
20
21/// An OpenCL program export.
22pub struct OpenClExport {
23    pub kernels: Vec<ClKernel>,
24    pub pragmas: Vec<String>,
25}
26
27/// Create a new OpenCL export.
28pub fn new_opencl_export() -> OpenClExport {
29    OpenClExport {
30        kernels: Vec::new(),
31        pragmas: vec!["#pragma OPENCL EXTENSION cl_khr_fp64 : enable".to_string()],
32    }
33}
34
35/// Add a kernel stub.
36pub fn add_cl_kernel(exp: &mut OpenClExport, name: &str, body: &str) {
37    exp.kernels.push(ClKernel {
38        name: name.to_string(),
39        args: Vec::new(),
40        body: body.to_string(),
41    });
42}
43
44/// Add an argument to the last kernel.
45pub fn add_cl_kernel_arg(
46    exp: &mut OpenClExport,
47    type_name: &str,
48    arg_name: &str,
49    is_global: bool,
50) -> bool {
51    if let Some(k) = exp.kernels.last_mut() {
52        k.args.push(ClKernelArg {
53            type_name: type_name.to_string(),
54            name: arg_name.to_string(),
55            is_global,
56        });
57        true
58    } else {
59        false
60    }
61}
62
63/// Kernel count.
64pub fn cl_kernel_count(exp: &OpenClExport) -> usize {
65    exp.kernels.len()
66}
67
68/// Find a kernel by name.
69pub fn find_cl_kernel<'a>(exp: &'a OpenClExport, name: &str) -> Option<&'a ClKernel> {
70    exp.kernels.iter().find(|k| k.name == name)
71}
72
73/// Render OpenCL source.
74pub fn render_opencl_source(exp: &OpenClExport) -> String {
75    let mut s = String::new();
76    for p in &exp.pragmas {
77        s.push_str(p);
78        s.push('\n');
79    }
80    for k in &exp.kernels {
81        let args: Vec<String> = k
82            .args
83            .iter()
84            .map(|a| {
85                if a.is_global {
86                    format!("__global {} {}", a.type_name, a.name)
87                } else {
88                    format!("{} {}", a.type_name, a.name)
89                }
90            })
91            .collect();
92        s.push_str(&format!(
93            "__kernel void {}({}) {{\n  {}\n}}\n",
94            k.name,
95            args.join(", "),
96            k.body
97        ));
98    }
99    s
100}
101
102/// Validate (at least one kernel).
103pub fn validate_opencl_export(exp: &OpenClExport) -> bool {
104    !exp.kernels.is_empty()
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn new_export_empty() {
113        let exp = new_opencl_export();
114        assert_eq!(cl_kernel_count(&exp), 0 /* empty */);
115    }
116
117    #[test]
118    fn add_kernel_increments() {
119        let mut exp = new_opencl_export();
120        add_cl_kernel(&mut exp, "my_kernel", "int id = get_global_id(0);");
121        assert_eq!(cl_kernel_count(&exp), 1 /* one kernel */);
122    }
123
124    #[test]
125    fn add_arg_to_kernel() {
126        let mut exp = new_opencl_export();
127        add_cl_kernel(&mut exp, "k", "");
128        let ok = add_cl_kernel_arg(&mut exp, "float*", "data", true);
129        assert!(ok /* added */);
130        assert_eq!(exp.kernels[0].args.len(), 1 /* one arg */);
131    }
132
133    #[test]
134    fn add_arg_no_kernel_fails() {
135        let mut exp = new_opencl_export();
136        assert!(!add_cl_kernel_arg(&mut exp, "int", "x", false) /* no kernel */);
137    }
138
139    #[test]
140    fn find_kernel_by_name() {
141        let mut exp = new_opencl_export();
142        add_cl_kernel(&mut exp, "sum", "");
143        assert!(find_cl_kernel(&exp, "sum").is_some() /* found */);
144    }
145
146    #[test]
147    fn find_missing_none() {
148        let exp = new_opencl_export();
149        assert!(find_cl_kernel(&exp, "x").is_none() /* not found */);
150    }
151
152    #[test]
153    fn render_contains_kernel_keyword() {
154        let mut exp = new_opencl_export();
155        add_cl_kernel(&mut exp, "k", "");
156        let src = render_opencl_source(&exp);
157        assert!(src.contains("__kernel") /* keyword */);
158    }
159
160    #[test]
161    fn render_contains_global_arg() {
162        let mut exp = new_opencl_export();
163        add_cl_kernel(&mut exp, "k", "");
164        add_cl_kernel_arg(&mut exp, "float*", "buf", true);
165        let src = render_opencl_source(&exp);
166        assert!(src.contains("__global") /* global qualifier */);
167    }
168
169    #[test]
170    fn validate_empty_fails() {
171        let exp = new_opencl_export();
172        assert!(!validate_opencl_export(&exp) /* empty */);
173    }
174}