use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::{
fmt::Display,
marker::PhantomData,
sync::atomic::{AtomicI8, Ordering},
};
use ruda_core::format::format_str;
use ruda_core::ir::StorageType;
use crate::runtime::{
compiler::{CompilationError, Compiler, RudaTask},
config::{RudaRuntimeConfig, RuntimeConfig, compilation::CompilationLogLevel},
id::KernelId,
server::{RudaDim, ExecutionMode},
};
pub trait KernelMetadata: Send + Sync + 'static {
fn name(&self) -> &'static str {
core::any::type_name::<Self>()
}
fn id(&self) -> KernelId;
fn address_type(&self) -> StorageType;
}
pub use ruda_core::kernel::{BufferIOAttr, KernelIo, KernelArg, KernelDefinition, KernelOptions, ScalarKernelArg, Visibility};
pub struct CompiledKernel<C: Compiler> {
pub io: Option<ruda_core::kernel::KernelIo>,
pub entrypoint_name: String,
pub debug_name: Option<&'static str>,
pub source: String,
pub repr: Option<C::Representation>,
pub ruda_dim: RudaDim,
pub debug_info: Option<DebugInformation>,
}
#[derive(new)]
pub struct DebugInformation {
pub lang_tag: &'static str,
pub id: KernelId,
}
pub trait RudaKernel: KernelMetadata {
fn define(&self) -> KernelDefinition;
fn source(&self) -> Option<PrecompiledSource> { None }
}
pub struct PrecompiledSource {
pub source: String,
pub entrypoint_name: String,
pub lang: &'static str,
}
impl KernelMetadata for Box<dyn RudaKernel> {
fn name(&self) -> &'static str { self.as_ref().name() }
fn id(&self) -> KernelId { self.as_ref().id() }
fn address_type(&self) -> StorageType { self.as_ref().address_type() }
}
impl RudaKernel for Box<dyn RudaKernel> {
fn define(&self) -> KernelDefinition { self.as_ref().define() }
fn source(&self) -> Option<PrecompiledSource> { self.as_ref().source() }
}
pub struct KernelTask<C: Compiler, K: RudaKernel> {
kernel_definition: K,
_compiler: PhantomData<fn() -> C>,
}
pub struct RudaTaskKernel<C: Compiler> {
pub task: Box<dyn RudaTask<C>>,
}
impl<C: Compiler, K: RudaKernel> KernelTask<C, K> {
pub fn new(kernel_definition: K) -> Self {
Self {
kernel_definition,
_compiler: PhantomData,
}
}
fn compile_definition(
&self,
definition: KernelDefinition,
compiler: &mut C,
options: &C::CompilationOptions,
mode: ExecutionMode,
addr_type: StorageType,
) -> Result<CompiledKernel<C>, CompilationError> {
let entrypoint_name = definition.options.kernel_name.clone();
let ruda_dim = definition.ruda_dim;
if let Some(source) = self.kernel_definition.source() {
if source.lang != compiler.lang_tag() {
return Err(CompilationError::Generic {
reason: alloc::format!("kernel `{}` carries {} source, but this compiler expects {}",
self.name(), source.lang, compiler.lang_tag()),
backtrace: ruda_core::backtrace::BackTrace::capture(),
});
}
return Ok(CompiledKernel {
io: None,
entrypoint_name: source.entrypoint_name,
debug_name: Some(self.kernel_definition.name()),
source: source.source,
repr: None,
ruda_dim,
debug_info: None,
});
}
let repr = compiler.compile(definition, options, mode, addr_type)?;
Ok(CompiledKernel {
io: compiler.buffer_io(&repr),
entrypoint_name,
debug_name: Some(self.kernel_definition.name()),
source: repr.to_string(),
repr: Some(repr),
ruda_dim,
debug_info: None,
})
}
}
impl<C: Compiler, K: RudaKernel> RudaTask<C> for KernelTask<C, K> {
fn kernel_definition(&self) -> Option<KernelDefinition> {
if self.kernel_definition.source().is_some() { None } else { Some(self.kernel_definition.define()) }
}
fn compile(
&self,
compiler: &mut C,
compilation_options: &C::CompilationOptions,
mode: ExecutionMode,
addr_type: StorageType,
) -> Result<CompiledKernel<C>, CompilationError> {
let gpu_ir = crate::runtime::compiler::catch_expansion(|| self.kernel_definition.define())?;
self.compile_definition(gpu_ir, compiler, compilation_options, mode, addr_type)
}
fn compile_recorded(
&self,
compiler: &mut C,
options: &C::CompilationOptions,
mode: ExecutionMode,
addr_type: StorageType,
recording: &mut crate::runtime::compiler::CompilationRecording,
) -> Result<CompiledKernel<C>, CompilationError> {
let definition = crate::runtime::compiler::catch_expansion(|| self.kernel_definition.define())?;
recording.defined(&definition);
let compiled = self.compile_definition(definition, compiler, options, mode, addr_type)?;
recording.source(&compiled.source);
Ok(compiled)
}
}
impl<C: Compiler, K: RudaKernel> KernelMetadata for KernelTask<C, K> {
fn id(&self) -> KernelId {
self.kernel_definition.id()
}
fn name(&self) -> &'static str {
self.kernel_definition.name()
}
fn address_type(&self) -> StorageType {
self.kernel_definition.address_type()
}
}
impl<C: Compiler> KernelMetadata for Box<dyn RudaTask<C>> {
fn id(&self) -> KernelId {
self.as_ref().id()
}
fn name(&self) -> &'static str {
self.as_ref().name()
}
fn address_type(&self) -> StorageType {
self.as_ref().address_type()
}
}
static COMPILATION_LEVEL: AtomicI8 = AtomicI8::new(-1);
fn compilation_level() -> u8 {
let compilation_level = COMPILATION_LEVEL.load(Ordering::Relaxed);
if compilation_level == -1 {
let val = match RudaRuntimeConfig::get().compilation.logger.level {
CompilationLogLevel::Full => 2,
CompilationLogLevel::Disabled => 0,
CompilationLogLevel::Basic => 1,
};
COMPILATION_LEVEL.store(val, Ordering::Relaxed);
val as u8
} else {
compilation_level as u8
}
}
impl<C: Compiler> Display for CompiledKernel<C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match compilation_level() {
2 => self.format_full(f),
_ => self.format_basic(f),
}
}
}
impl<C: Compiler> CompiledKernel<C> {
fn format_basic(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("[Compiling kernel]")?;
if let Some(name) = self.debug_name {
if name.len() <= 32 {
f.write_fmt(format_args!(" {name}"))?;
} else {
f.write_fmt(format_args!(" {}", name.split('<').next().unwrap_or("")))?;
}
}
Ok(())
}
fn format_full(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("[START_KERNEL_COMPILATION]")?;
if let Some(name) = self.debug_name {
if name.len() <= 32 {
f.write_fmt(format_args!("\nname: {name}"))?;
} else {
let name = format_str(name, &[('<', '>')], false);
f.write_fmt(format_args!("\nname: {name}"))?;
}
}
if let Some(info) = &self.debug_info {
f.write_fmt(format_args!("\nid: {:#?}", info.id))?;
}
f.write_fmt(format_args!(
"
source:
```{}
{}
```
[END_KERNEL_COMPILATION]
",
self.debug_info
.as_ref()
.map(|info| info.lang_tag)
.unwrap_or(""),
self.source
))
}
}