File size: 5,194 Bytes
a8baeed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | //! Explicit OpenCL kernel signatures and metadata.
//!
//! Each kernel is formally declared with its name, parameter types, work item constraints,
//! and memory access patterns.
pub struct KernelSignature {
pub name: &'static str,
pub params: &'static [(&'static str, &'static str)],
pub min_work_items: usize,
pub safety_notes: &'static str,
}
pub const NEGATE_U8_SIGNATURE: KernelSignature = KernelSignature {
name: "negate_u8",
params: &[
("input", "__global const uchar*"),
("output", "__global uchar*"),
("element_count", "const ulong"),
],
min_work_items: 1,
safety_notes: "Kernel guards gid >= count. Input and output must not overlap. \
Canonical representation: input values must be 0x00 or 0x01. \
Output always produces 0x00 or 0x01.",
};
pub const ADD_U8_SIGNATURE: KernelSignature = KernelSignature {
name: "add_u8",
params: &[
("a", "__global const uchar*"),
("b", "__global const uchar*"),
("output", "__global uchar*"),
("count", "const uint"),
],
min_work_items: 1,
safety_notes: "Kernel guards gid >= count. Input buffers a, b must not overlap with output. \
Performs wrapping u8 addition: output[i] = (a[i] + b[i]) mod 256. \
On canonical boolean buffers (0x00, 0x01), produces values 0, 1, or 2.",
};
/// Registry of all OpenCL kernels in the functor system.
pub const KERNEL_REGISTRY: &[&KernelSignature] = &[&NEGATE_U8_SIGNATURE, &ADD_U8_SIGNATURE];
/// Validate kernel signature match at runtime.
/// Returns Ok if all registered kernels have valid signatures.
pub fn validate_registry() -> Result<(), String> {
for kernel_sig in KERNEL_REGISTRY {
if kernel_sig.name.is_empty() {
return Err("kernel name cannot be empty".to_string());
}
if kernel_sig.params.is_empty() {
return Err(format!("kernel '{}' must have parameters", kernel_sig.name));
}
for (param_name, param_type) in kernel_sig.params {
if param_name.is_empty() || param_type.is_empty() {
return Err(format!("kernel '{}': invalid parameter", kernel_sig.name));
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kernel_registry_is_valid() {
validate_registry().expect("kernel registry validation failed");
}
#[test]
fn negate_signature_is_correct() {
assert_eq!(NEGATE_U8_SIGNATURE.name, "negate_u8");
assert_eq!(NEGATE_U8_SIGNATURE.params.len(), 3);
assert_eq!(
NEGATE_U8_SIGNATURE.params[0],
("input", "__global const uchar*")
);
assert_eq!(NEGATE_U8_SIGNATURE.params[1], ("output", "__global uchar*"));
assert_eq!(
NEGATE_U8_SIGNATURE.params[2],
("element_count", "const ulong")
);
}
#[test]
fn add_signature_is_correct() {
assert_eq!(ADD_U8_SIGNATURE.name, "add_u8");
assert_eq!(ADD_U8_SIGNATURE.params.len(), 4);
assert_eq!(ADD_U8_SIGNATURE.params[0], ("a", "__global const uchar*"));
assert_eq!(ADD_U8_SIGNATURE.params[1], ("b", "__global const uchar*"));
assert_eq!(ADD_U8_SIGNATURE.params[2], ("output", "__global uchar*"));
assert_eq!(ADD_U8_SIGNATURE.params[3], ("count", "const uint"));
}
/// Every registered signature must describe the OpenCL source that is compiled:
/// the kernel name and each `type name` parameter, in declaration order.
#[test]
fn registry_matches_kernel_sources() {
let sources = [
(&NEGATE_U8_SIGNATURE, super::super::negate::KERNEL_SRC),
(&ADD_U8_SIGNATURE, super::super::add::KERNEL_SRC),
];
assert_eq!(
sources.len(),
KERNEL_REGISTRY.len(),
"a registered kernel has no source check"
);
for (sig, src) in sources {
// Compare with all whitespace removed: "const uint count" == "constuintcount".
let squashed: String = src.chars().filter(|c| !c.is_whitespace()).collect();
let header = format!("__kernelvoid{}(", sig.name);
let start = squashed
.find(&header)
.unwrap_or_else(|| panic!("kernel '{}' not found in its source", sig.name));
let rest = &squashed[start + header.len()..];
let declared: Vec<&str> = rest[..rest.find(')').expect("unterminated parameter list")]
.split(',')
.collect();
let expected: Vec<String> = sig
.params
.iter()
.map(|(name, ty)| format!("{ty}{name}").replace(' ', ""))
.collect();
assert_eq!(declared, expected, "kernel '{}' parameters", sig.name);
}
}
#[test]
fn all_kernels_have_min_work_items() {
for kernel_sig in KERNEL_REGISTRY {
assert!(
kernel_sig.min_work_items >= 1,
"kernel '{}' must allow at least 1 work item",
kernel_sig.name
);
}
}
}
|