blob: 15c4a773d6a45d3617f0c96784ef06ed176384f6 [file]
use crate::api::icd::*;
use crate::core::context::*;
use crate::core::device::*;
use crate::impl_cl_type_trait;
use mesa_rust::compiler::clc::*;
use mesa_rust::compiler::nir::*;
use mesa_rust::util::disk_cache::*;
use mesa_rust_gen::*;
use rusticl_opencl_gen::*;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ffi::CString;
use std::mem::size_of;
use std::ptr;
use std::slice;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::Once;
const BIN_HEADER_SIZE_V1: usize =
// 1. format version
size_of::<u32>() +
// 2. spirv len
size_of::<u32>() +
// 3. binary_type
size_of::<cl_program_binary_type>();
const BIN_HEADER_SIZE: usize = BIN_HEADER_SIZE_V1;
// kernel cache
static mut DISK_CACHE: Option<DiskCache> = None;
static DISK_CACHE_ONCE: Once = Once::new();
fn get_disk_cache() -> &'static Option<DiskCache> {
unsafe {
DISK_CACHE_ONCE.call_once(|| {
DISK_CACHE = DiskCache::new("rusticl", "rusticl", 0);
});
&DISK_CACHE
}
}
#[repr(C)]
pub struct Program {
pub base: CLObjectBase<CL_INVALID_PROGRAM>,
pub context: Arc<Context>,
pub devs: Vec<Arc<Device>>,
pub src: CString,
pub il: Vec<u8>,
pub kernel_count: AtomicU32,
spec_constants: Mutex<Vec<spirv::SpecConstant>>,
build: Mutex<ProgramBuild>,
}
impl_cl_type_trait!(cl_program, Program, CL_INVALID_PROGRAM);
struct ProgramBuild {
builds: HashMap<Arc<Device>, ProgramDevBuild>,
kernels: Vec<String>,
}
struct ProgramDevBuild {
spirv: Option<spirv::SPIRVBin>,
status: cl_build_status,
options: String,
log: String,
bin_type: cl_program_binary_type,
}
fn prepare_options(options: &str, dev: &Device) -> Vec<CString> {
let mut options = options.to_owned();
if !options.contains("-cl-std=CL") {
options.push_str(" -cl-std=CL");
options.push_str(dev.clc_version.api_str());
}
if !dev.image_supported() {
options.push_str(" -U__IMAGE_SUPPORT__");
}
options.push_str(" -D__OPENCL_VERSION__=");
options.push_str(dev.cl_version.clc_str());
let mut res = Vec::new();
// we seperate on a ' ' unless we hit a "
let mut sep = ' ';
let mut old = 0;
for (i, c) in options.char_indices() {
if c == '"' {
if sep == ' ' {
sep = '"';
} else {
sep = ' ';
}
}
if c == '"' || c == sep {
// beware of double seps
if old != i {
res.push(&options[old..i]);
}
old = i + c.len_utf8();
}
}
// add end of the string
res.push(&options[old..]);
res.iter()
.map(|&a| match a {
"-cl-denorms-are-zero" => "-fdenormal-fp-math=positive-zero",
_ => a,
})
.map(CString::new)
.map(Result::unwrap)
.collect()
}
impl Program {
pub fn new(context: &Arc<Context>, devs: &[Arc<Device>], src: CString) -> Arc<Program> {
let builds = devs
.iter()
.map(|d| {
(
d.clone(),
ProgramDevBuild {
spirv: None,
status: CL_BUILD_NONE,
log: String::from(""),
options: String::from(""),
bin_type: CL_PROGRAM_BINARY_TYPE_NONE,
},
)
})
.collect();
Arc::new(Self {
base: CLObjectBase::new(),
context: context.clone(),
devs: devs.to_vec(),
src: src,
il: Vec::new(),
kernel_count: AtomicU32::new(0),
spec_constants: Mutex::new(Vec::new()),
build: Mutex::new(ProgramBuild {
builds: builds,
kernels: Vec::new(),
}),
})
}
pub fn from_bins(
context: Arc<Context>,
devs: Vec<Arc<Device>>,
bins: &[&[u8]],
) -> Arc<Program> {
let mut builds = HashMap::new();
let mut kernels = HashSet::new();
for (d, b) in devs.iter().zip(bins) {
let mut ptr = b.as_ptr();
let bin_type;
let spirv;
unsafe {
// 1. version
let version = ptr.cast::<u32>().read();
ptr = ptr.add(size_of::<u32>());
match version {
1 => {
// 2. size of the spirv
let spirv_size = ptr.cast::<u32>().read();
ptr = ptr.add(size_of::<u32>());
// 3. binary_type
bin_type = ptr.cast::<cl_program_binary_type>().read();
ptr = ptr.add(size_of::<cl_program_binary_type>());
// 4. the spirv
assert!(b.as_ptr().add(BIN_HEADER_SIZE_V1) == ptr);
assert!(b.len() == BIN_HEADER_SIZE_V1 + spirv_size as usize);
spirv = Some(spirv::SPIRVBin::from_bin(slice::from_raw_parts(
ptr,
spirv_size as usize,
)));
}
_ => panic!("unknown version"),
}
}
if let Some(spirv) = &spirv {
for k in spirv.kernels() {
kernels.insert(k);
}
}
builds.insert(
d.clone(),
ProgramDevBuild {
spirv: spirv,
status: CL_BUILD_SUCCESS as cl_build_status,
log: String::from(""),
options: String::from(""),
bin_type: bin_type,
},
);
}
Arc::new(Self {
base: CLObjectBase::new(),
context: context,
devs: devs,
src: CString::new("").unwrap(),
il: Vec::new(),
kernel_count: AtomicU32::new(0),
spec_constants: Mutex::new(Vec::new()),
build: Mutex::new(ProgramBuild {
builds: builds,
kernels: kernels.into_iter().collect(),
}),
})
}
pub fn from_spirv(context: Arc<Context>, spirv: &[u8]) -> Arc<Program> {
let mut builds = HashMap::new();
for d in &context.devs {
let spirv = Some(spirv::SPIRVBin::from_bin(spirv));
builds.insert(
d.clone(),
ProgramDevBuild {
spirv: spirv,
status: CL_BUILD_SUCCESS as cl_build_status,
log: String::from(""),
options: String::from(""),
bin_type: CL_PROGRAM_BINARY_TYPE_INTERMEDIATE,
},
);
}
Arc::new(Self {
base: CLObjectBase::new(),
devs: context.devs.clone(),
context: context,
src: CString::new("").unwrap(),
il: spirv.to_vec(),
kernel_count: AtomicU32::new(0),
spec_constants: Mutex::new(Vec::new()),
build: Mutex::new(ProgramBuild {
builds: builds,
kernels: Vec::new(),
}),
})
}
fn build_info(&self) -> MutexGuard<ProgramBuild> {
self.build.lock().unwrap()
}
fn dev_build_info<'a>(
l: &'a mut MutexGuard<ProgramBuild>,
dev: &Arc<Device>,
) -> &'a mut ProgramDevBuild {
l.builds.get_mut(dev).unwrap()
}
pub fn status(&self, dev: &Arc<Device>) -> cl_build_status {
Self::dev_build_info(&mut self.build_info(), dev).status
}
pub fn log(&self, dev: &Arc<Device>) -> String {
Self::dev_build_info(&mut self.build_info(), dev)
.log
.clone()
}
pub fn bin_type(&self, dev: &Arc<Device>) -> cl_program_binary_type {
Self::dev_build_info(&mut self.build_info(), dev).bin_type
}
pub fn options(&self, dev: &Arc<Device>) -> String {
Self::dev_build_info(&mut self.build_info(), dev)
.options
.clone()
}
// we need to precalculate the size
pub fn bin_sizes(&self) -> Vec<usize> {
let mut lock = self.build_info();
let mut res = Vec::new();
for d in &self.devs {
let info = Self::dev_build_info(&mut lock, d);
res.push(
info.spirv
.as_ref()
.map_or(0, |s| s.to_bin().len() + BIN_HEADER_SIZE),
);
}
res
}
pub fn binaries(&self, vals: &[u8]) -> Vec<*mut u8> {
// if the application didn't provide any pointers, just return the length of devices
if vals.is_empty() {
return vec![std::ptr::null_mut(); self.devs.len()];
}
// vals is an array of pointers where we should write the device binaries into
if vals.len() != self.devs.len() * size_of::<*const u8>() {
panic!("wrong size")
}
let ptrs: &[*mut u8] = unsafe {
slice::from_raw_parts(vals.as_ptr().cast(), vals.len() / size_of::<*mut u8>())
};
let mut lock = self.build_info();
for (i, d) in self.devs.iter().enumerate() {
let mut ptr = ptrs[i];
let info = Self::dev_build_info(&mut lock, d);
let spirv = info.spirv.as_ref().unwrap().to_bin();
unsafe {
// 1. binary format version
ptr.cast::<u32>().write(1);
ptr = ptr.add(size_of::<u32>());
// 2. size of the spirv
ptr.cast::<u32>().write(spirv.len() as u32);
ptr = ptr.add(size_of::<u32>());
// 3. binary_type
ptr.cast::<cl_program_binary_type>().write(info.bin_type);
ptr = ptr.add(size_of::<cl_program_binary_type>());
// 4. the spirv
assert!(ptrs[i].add(BIN_HEADER_SIZE) == ptr);
ptr::copy_nonoverlapping(spirv.as_ptr(), ptr, spirv.len());
}
}
ptrs.to_vec()
}
pub fn args(&self, dev: &Arc<Device>, kernel: &str) -> Vec<spirv::SPIRVKernelArg> {
Self::dev_build_info(&mut self.build_info(), dev)
.spirv
.as_ref()
.unwrap()
.args(kernel)
}
pub fn kernels(&self) -> Vec<String> {
self.build_info().kernels.clone()
}
pub fn active_kernels(&self) -> bool {
self.kernel_count.load(Ordering::Relaxed) != 0
}
pub fn build(&self, dev: &Arc<Device>, options: String) -> bool {
// program binary
let is_il = !self.il.is_empty();
if self.src.as_bytes().is_empty() && !is_il {
return true;
}
let mut info = self.build_info();
let d = Self::dev_build_info(&mut info, dev);
let lib = options.contains("-create-library");
let args = prepare_options(&options, dev);
if !is_il {
let (spirv, log) = spirv::SPIRVBin::from_clc(
&self.src,
&args,
&Vec::new(),
get_disk_cache(),
dev.cl_features(),
dev.address_bits(),
);
d.log = log;
if spirv.is_none() {
d.status = CL_BUILD_ERROR;
return false;
}
d.spirv = spirv;
}
d.options = options;
let spirvs = [d.spirv.as_ref().unwrap()];
let (spirv, log) = spirv::SPIRVBin::link(&spirvs, lib);
d.log.push_str(&log);
d.spirv = spirv;
if d.spirv.is_some() {
d.bin_type = if lib {
CL_PROGRAM_BINARY_TYPE_LIBRARY
} else {
CL_PROGRAM_BINARY_TYPE_EXECUTABLE
};
d.status = CL_BUILD_SUCCESS as cl_build_status;
let mut kernels = d.spirv.as_ref().unwrap().kernels();
info.kernels.append(&mut kernels);
true
} else {
d.status = CL_BUILD_ERROR;
false
}
}
pub fn compile(
&self,
dev: &Arc<Device>,
options: String,
headers: &[spirv::CLCHeader],
) -> bool {
// program binary
let is_il = !self.il.is_empty();
if self.src.as_bytes().is_empty() && !is_il {
return true;
}
let mut info = self.build_info();
let d = Self::dev_build_info(&mut info, dev);
if is_il {
d.bin_type = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT;
return true;
}
let args = prepare_options(&options, dev);
let (spirv, log) = spirv::SPIRVBin::from_clc(
&self.src,
&args,
headers,
get_disk_cache(),
dev.cl_features(),
dev.address_bits(),
);
d.spirv = spirv;
d.log = log;
d.options = options;
if d.spirv.is_some() {
d.status = CL_BUILD_SUCCESS as cl_build_status;
d.bin_type = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT;
true
} else {
d.status = CL_BUILD_ERROR;
false
}
}
pub fn link(
context: Arc<Context>,
devs: &[Arc<Device>],
progs: &[Arc<Program>],
options: String,
) -> Arc<Program> {
let devs: Vec<Arc<Device>> = devs.iter().map(|d| (*d).clone()).collect();
let mut builds = HashMap::new();
let mut kernels = HashSet::new();
let mut locks: Vec<_> = progs.iter().map(|p| p.build_info()).collect();
let lib = options.contains("-create-library");
for d in &devs {
let bins: Vec<_> = locks
.iter_mut()
.map(|l| Self::dev_build_info(l, d).spirv.as_ref().unwrap())
.collect();
let (spirv, log) = spirv::SPIRVBin::link(&bins, lib);
let status;
let bin_type;
if let Some(spirv) = &spirv {
for k in spirv.kernels() {
kernels.insert(k);
}
status = CL_BUILD_SUCCESS as cl_build_status;
bin_type = if lib {
CL_PROGRAM_BINARY_TYPE_LIBRARY
} else {
CL_PROGRAM_BINARY_TYPE_EXECUTABLE
};
} else {
status = CL_BUILD_ERROR;
bin_type = CL_PROGRAM_BINARY_TYPE_NONE;
};
builds.insert(
d.clone(),
ProgramDevBuild {
spirv: spirv,
status: status,
log: log,
options: String::from(""),
bin_type: bin_type,
},
);
}
Arc::new(Self {
base: CLObjectBase::new(),
context: context,
devs: devs,
src: CString::new("").unwrap(),
il: Vec::new(),
kernel_count: AtomicU32::new(0),
spec_constants: Mutex::new(Vec::new()),
build: Mutex::new(ProgramBuild {
builds: builds,
kernels: kernels.into_iter().collect(),
}),
})
}
pub(super) fn hash_key(&self, dev: &Arc<Device>, name: &str) -> Option<cache_key> {
if let Some(cache) = dev.screen().shader_cache() {
let mut lock = self.build_info();
let info = Self::dev_build_info(&mut lock, dev);
assert_eq!(info.status, CL_BUILD_SUCCESS as cl_build_status);
let spirv = info.spirv.as_ref().unwrap();
let mut bin = spirv.to_bin().to_vec();
bin.extend_from_slice(name.as_bytes());
Some(cache.gen_key(&bin))
} else {
None
}
}
pub fn devs_with_build(&self) -> Vec<&Arc<Device>> {
let mut lock = self.build_info();
self.devs
.iter()
.filter(|d| {
let info = Self::dev_build_info(&mut lock, d);
info.status == CL_BUILD_SUCCESS as cl_build_status
})
.collect()
}
pub fn attribute_str(&self, kernel: &str, d: &Arc<Device>) -> String {
let mut lock = self.build_info();
let info = Self::dev_build_info(&mut lock, d);
let attributes_strings = [
info.spirv.as_ref().unwrap().vec_type_hint(kernel),
info.spirv.as_ref().unwrap().local_size(kernel),
info.spirv.as_ref().unwrap().local_size_hint(kernel),
];
let attributes_strings: Vec<_> = attributes_strings
.iter()
.flatten()
.map(String::as_str)
.collect();
attributes_strings.join(",")
}
pub fn to_nir(&self, kernel: &str, d: &Arc<Device>) -> NirShader {
let mut lock = self.build_info();
let info = Self::dev_build_info(&mut lock, d);
assert_eq!(info.status, CL_BUILD_SUCCESS as cl_build_status);
info.spirv
.as_ref()
.unwrap()
.to_nir(
kernel,
d.screen
.nir_shader_compiler_options(pipe_shader_type::PIPE_SHADER_COMPUTE),
&d.lib_clc,
&mut [],
d.address_bits(),
)
.unwrap()
}
pub fn is_binary(&self) -> bool {
self.src.to_bytes().is_empty() && self.il.is_empty()
}
}