rusticl: restructure program build to prepare for parallelization

v2: include restructuring of link and compile, break out more functions
v3: split out naming changes to later commit

Reviewed-by: Karol Herbst <kherbst@redhat.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36497>
This commit is contained in:
Seán de Búrca 2025-08-02 11:15:58 -07:00 committed by Marge Bot
parent 833481b6ab
commit 56cc1b1c96
2 changed files with 88 additions and 74 deletions

View file

@ -309,7 +309,7 @@ fn build_program(
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let p = Program::ref_from_raw(program)?;
let p = Program::arc_from_raw(program)?;
let devs = validate_devices(device_list, num_devices, &p.devs)?;
// SAFETY: The requirements on `ProgramCB::try_new` match the requirements
@ -321,25 +321,15 @@ fn build_program(
return Err(CL_INVALID_OPERATION);
}
// CL_BUILD_PROGRAM_FAILURE if there is a failure to build the program executable. This error
// will be returned if clBuildProgram does not return until the build has completed.
let options = c_string_to_string(options);
let res = p.build(&devs, &options);
if let Some(cb) = cb_opt {
cb.call(p);
}
let res = p.build(devs, options, cb_opt);
//• CL_INVALID_BINARY if program is created with clCreateProgramWithBinary and devices listed in device_list do not have a valid program binary loaded.
//• CL_INVALID_BUILD_OPTIONS if the build options specified by options are invalid.
//• CL_INVALID_OPERATION if the build of a program executable for any of the devices listed in device_list by a previous call to clBuildProgram for program has not completed.
//• CL_INVALID_OPERATION if program was not created with clCreateProgramWithSource, clCreateProgramWithIL or clCreateProgramWithBinary.
if res {
Ok(())
} else {
Err(CL_BUILD_PROGRAM_FAILURE)
}
res
}
#[cl_entrypoint(clCompileProgram)]
@ -354,7 +344,7 @@ fn compile_program(
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let p = Program::ref_from_raw(program)?;
let p = Program::arc_from_raw(program)?;
let devs = validate_devices(device_list, num_devices, &p.devs)?;
// SAFETY: The requirements on `ProgramCB::try_new` match the requirements
@ -408,23 +398,13 @@ fn compile_program(
return Err(CL_INVALID_OPERATION);
}
// CL_COMPILE_PROGRAM_FAILURE if there is a failure to compile the program source. This error
// will be returned if clCompileProgram does not return until the compile has completed.
let options = c_string_to_string(options);
let res = p.compile(&devs, &options, &headers);
if let Some(cb) = cb_opt {
cb.call(p);
}
let res = p.compile(devs, options, headers, cb_opt);
// • CL_INVALID_COMPILER_OPTIONS if the compiler options specified by options are invalid.
// • CL_INVALID_OPERATION if the compilation or build of a program executable for any of the devices listed in device_list by a previous call to clCompileProgram or clBuildProgram for program has not completed.
if res {
Ok(())
} else {
Err(CL_COMPILE_PROGRAM_FAILURE)
}
res
}
pub fn link_program(
@ -459,31 +439,11 @@ pub fn link_program(
// CL_INVALID_OPERATION if the compilation or build of a program executable for any of the
// devices listed in device_list by a previous call to clCompileProgram or clBuildProgram for
// program has not completed.
for d in &devs {
if progs
.iter()
.map(|p| p.status(d))
.any(|s| s != CL_BUILD_SUCCESS as cl_build_status)
{
return Err(CL_INVALID_OPERATION);
}
if progs.iter().any(|p| !p.all_devices_succeeded(&devs)) {
return Err(CL_INVALID_OPERATION);
}
// CL_LINK_PROGRAM_FAILURE if there is a failure to link the compiled binaries and/or libraries.
let res = Program::link(c, &devs, &progs, c_string_to_string(options));
let code = if devs
.iter()
.map(|d| res.status(d))
.all(|s| s == CL_BUILD_SUCCESS as cl_build_status)
{
CL_SUCCESS as cl_int
} else {
CL_LINK_PROGRAM_FAILURE
};
if let Some(cb) = cb_opt {
cb.call(&res);
}
let (res, code) = Program::link(c, devs, progs, c_string_to_string(options), cb_opt)?;
Ok((res.into_cl(), code))

View file

@ -1,4 +1,5 @@
use crate::api::icd::*;
use crate::api::types::ProgramCB;
use crate::core::context::*;
use crate::core::device::*;
use crate::core::kernel::*;
@ -567,14 +568,17 @@ impl Program {
.any(|k| Arc::strong_count(k) > 1)
}
pub fn build(&self, devs: &[&'static Device], options: &str) -> bool {
pub fn build(
self: Arc<Self>,
devs: Vec<&'static Device>,
options: String,
callback: Option<ProgramCB>,
) -> CLResult<()> {
let lib = options.contains("-create-library");
let mut info = self.build_info();
let mut res = true;
for dev in devs {
if !self.do_compile(dev, options, &[], &mut info) {
res = false;
for &dev in &devs {
if !self.do_compile(dev, &options, &[], &mut info) {
continue;
}
@ -594,17 +598,29 @@ impl Program {
// Don't request validation of the SPIR-V, as we've just done that
// as part of compilation.
Self::do_link(d, &spirvs, lib, None);
if d.status == CL_BUILD_ERROR {
res = false
}
}
info.rebuild_kernels(devs, self.is_src());
info.rebuild_kernels(&devs, self.is_src());
debug_logging(self, devs);
// The callback must be called after we've dropped any mutex locks we're
// holding.
drop(info);
res
if let Some(callback) = callback {
callback.call(&self);
}
debug_logging(&self, &devs);
if !self.all_devices_succeeded(&devs) {
// clBuildProgram returns CL_BUILD_PROGRAM_FAILURE if there is a
// failure to build the program executable. This error will be
// returned if clBuildProgram does not return until the build
// has completed.
return Err(CL_BUILD_PROGRAM_FAILURE);
}
Ok(())
}
fn do_compile(
@ -694,28 +710,46 @@ impl Program {
}
}
pub fn compile(&self, devs: &[&Device], options: &str, headers: &[HeaderProgram]) -> bool {
let mut res = true;
for dev in devs {
res &= self.do_compile(dev, options, headers, &mut self.build_info());
pub fn compile(
self: Arc<Self>,
devs: Vec<&Device>,
options: String,
headers: Vec<HeaderProgram>,
callback: Option<ProgramCB>,
) -> CLResult<()> {
for &dev in &devs {
self.do_compile(dev, &options, &headers, &mut self.build_info());
}
debug_logging(self, devs);
if let Some(callback) = callback {
callback.call(&self);
}
res
debug_logging(&self, &devs);
if !self.all_devices_succeeded(&devs) {
// clCompileProgram returns CL_COMPILE_PROGRAM_FAILURE if there is a
// failure to compile the program source. This error will be
// returned if clCompileProgram does not return until the compile
// has completed.
return Err(CL_COMPILE_PROGRAM_FAILURE);
}
Ok(())
}
pub fn link(
context: Arc<Context>,
devs: &[&'static Device],
progs: &[Arc<Program>],
devs: Vec<&'static Device>,
progs: Vec<Arc<Program>>,
options: String,
) -> Arc<Program> {
callback: Option<ProgramCB>,
) -> CLResult<(Arc<Program>, cl_int)> {
let mut builds = HashMap::new();
let mut locks: Vec<_> = progs.iter().map(|p| p.build_info()).collect();
let lib = options.contains("-create-library");
for &d in devs {
for &d in &devs {
let bins: Vec<_> = locks
.iter_mut()
.map(|l| l.dev_build(d).spirv.as_ref().unwrap())
@ -741,19 +775,31 @@ impl Program {
};
// Pre build nir kernels
build.rebuild_kernels(devs, false);
build.rebuild_kernels(&devs, false);
let res = Arc::new(Self {
base: CLObjectBase::new(RusticlTypes::Program),
context: context,
devs: devs.to_owned(),
devs: devs.clone(),
src: ProgramSourceType::Linked,
build: Mutex::new(build),
});
if let Some(callback) = callback {
callback.call(&res);
}
debug_logging(&res, &devs);
res
let status = if res.all_devices_succeeded(&devs) {
CL_SUCCESS as cl_int
} else {
// clLinkProgram returns CL_LINK_PROGRAM_FAILURE if there is a
// failure to link the compiled binaries and/or libraries.
CL_LINK_PROGRAM_FAILURE
};
Ok((res, status))
}
/// Performs linking of the provided SPIR-V binaries.
@ -795,6 +841,14 @@ impl Program {
};
}
/// Returns `true` if build succeeded for each of the provided devices,
/// false otherwise.
pub fn all_devices_succeeded(&self, devices: &[&Device]) -> bool {
devices
.iter()
.all(|&device| self.status(device) == CL_BUILD_SUCCESS as cl_build_status)
}
pub fn is_bin(&self) -> bool {
matches!(self.src, ProgramSourceType::Binary)
}