use super::{result, sys};
use core::ffi::{CStr, c_char};
use std::borrow::Cow;
use std::ffi::CString;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
pub use result::HiprtcError;
#[derive(Debug, Clone)]
pub struct Hsaco(pub(crate) HsacoKind, pub(crate) GfxVersion);
impl Hsaco {
pub fn from_file<P: Into<PathBuf>>(path: P, gfx_version: GfxVersion) -> Self {
Self(HsacoKind::File(path.into()), gfx_version)
}
pub fn from_binary(data: Vec<u8>, gfx_version: GfxVersion) -> Self {
Self(HsacoKind::Binary(data), gfx_version)
}
pub fn as_bytes(&self) -> std::io::Result<Cow<'_, [u8]>> {
match &self.0 {
HsacoKind::Binary(b) => Ok(Cow::Borrowed(b)),
HsacoKind::File(p) => Ok(Cow::Owned(std::fs::read(p)?)),
}
}
pub fn gfx_version(&self) -> GfxVersion {
self.1
}
pub fn kind(&self) -> &HsacoKind {
&self.0
}
}
#[derive(Debug, Clone)]
pub enum HsacoKind {
Binary(Vec<u8>),
File(PathBuf),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GfxVersion {
Gfx942,
Gfx950,
Gfx1100,
Gfx1101,
Gfx1102,
Gfx1200,
Gfx1201,
}
impl GfxVersion {
pub fn as_str(&self) -> &'static str {
match self {
Self::Gfx942 => "gfx942",
Self::Gfx950 => "gfx950",
Self::Gfx1100 => "gfx1100",
Self::Gfx1101 => "gfx1101",
Self::Gfx1102 => "gfx1102",
Self::Gfx1200 => "gfx1200",
Self::Gfx1201 => "gfx1201",
}
}
}
impl fmt::Display for GfxVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for GfxVersion {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let head = s.trim().split(':').next().unwrap_or("");
match head.to_lowercase().as_str() {
"gfx942" => Ok(Self::Gfx942),
"gfx950" => Ok(Self::Gfx950),
"gfx1100" => Ok(Self::Gfx1100),
"gfx1101" => Ok(Self::Gfx1101),
"gfx1102" => Ok(Self::Gfx1102),
"gfx1200" => Ok(Self::Gfx1200),
"gfx1201" => Ok(Self::Gfx1201),
_ => Err(format!(
"Unknown or unsupported AMD GPU architecture: '{s}'"
)),
}
}
}
pub fn compile_hsaco<S: AsRef<str>>(
src: S,
gfx_version: GfxVersion,
) -> Result<Hsaco, CompileError> {
compile_hsaco_with_opts(src, gfx_version, Default::default())
}
pub fn compile_hsaco_with_opts<S: AsRef<str>>(
src: S,
gfx_version: GfxVersion,
mut opts: CompileOptions,
) -> Result<Hsaco, CompileError> {
if opts.arch.is_none() {
opts.arch = Some(gfx_version.as_str().to_string());
}
let rocm_include = {
let root = std::env::var("ROCM_PATH").unwrap_or_else(|_| "/opt/rocm".to_string());
format!("{root}/include")
};
if !opts.include_paths.contains(&rocm_include) {
opts.include_paths.push(rocm_include);
}
let prog = Program::create(src, opts.name.as_deref())?;
prog.compile(gfx_version, opts)
}
pub(crate) struct Program {
prog: sys::hiprtcProgram,
_src: CString,
_name: Option<CString>,
}
impl Program {
pub(crate) fn create<S: AsRef<str>>(src: S, name: Option<&str>) -> Result<Self, CompileError> {
let src = CString::new(src.as_ref().as_bytes())
.expect("program code cannot contain null terminators");
let name =
name.map(|s| CString::new(s).expect("program name cannot contain null terminators"));
let prog =
result::create_program(&src, name.as_deref()).map_err(CompileError::CreationError)?;
Ok(Self {
prog,
_src: src,
_name: name,
})
}
pub(crate) fn compile(
self,
gfx_version: GfxVersion,
opts: CompileOptions,
) -> Result<Hsaco, CompileError> {
let options = opts.build();
result::compile_program(self.prog, &options).map_err(|e| {
let log_raw = result::get_program_log(self.prog).unwrap_or_default();
let log_ptr = log_raw.as_ptr() as *const c_char;
let log = unsafe { CStr::from_ptr(log_ptr) }.to_owned();
CompileError::CompileError {
hiprtc: e,
options,
log,
}
})?;
let bytes_c = result::get_hsaco(self.prog).map_err(CompileError::GetHsacoError)?;
let bytes: Vec<u8> = unsafe {
let mut v = std::mem::ManuallyDrop::new(bytes_c);
Vec::from_raw_parts(v.as_mut_ptr() as *mut u8, v.len(), v.capacity())
};
Ok(Hsaco(HsacoKind::Binary(bytes), gfx_version))
}
}
impl Drop for Program {
fn drop(&mut self) {
let prog = std::mem::replace(&mut self.prog, std::ptr::null_mut());
if !prog.is_null() {
result::destroy_program(prog).unwrap();
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompileError {
CreationError(HiprtcError),
CompileError {
hiprtc: HiprtcError,
options: Vec<String>,
log: CString,
},
GetLogError(HiprtcError),
GetHsacoError(HiprtcError),
DestroyError(HiprtcError),
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for CompileError {}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct CompileOptions {
pub opt_level: Option<u8>,
pub fast_math: Option<bool>,
pub defines: Vec<String>,
pub include_paths: Vec<String>,
pub arch: Option<String>,
pub name: Option<String>,
pub options: Vec<String>,
}
impl CompileOptions {
pub(crate) fn build(&self) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if let Some(level) = self.opt_level {
out.push(format!("-O{level}"));
}
match self.fast_math {
Some(true) => out.push("-ffast-math".into()),
Some(false) => out.push("-fno-fast-math".into()),
None => {}
}
for def in &self.defines {
out.push(format!("-D{def}"));
}
for path in &self.include_paths {
out.push(format!("-I{path}"));
}
if let Some(arch) = &self.arch {
out.push(format!("--offload-arch={arch}"));
}
for opt in &self.options {
out.push(opt.clone());
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gfx_from_str_canonical() {
assert_eq!(
"gfx1100".parse::<GfxVersion>().unwrap(),
GfxVersion::Gfx1100
);
}
#[test]
fn gfx_from_str_uppercase() {
assert_eq!("GFX942".parse::<GfxVersion>().unwrap(), GfxVersion::Gfx942);
}
#[test]
fn gfx_from_str_strips_feature_suffix() {
let parsed = "gfx1100:sramecc-:xnack-".parse::<GfxVersion>().unwrap();
assert_eq!(parsed, GfxVersion::Gfx1100);
}
#[test]
fn gfx_from_str_unknown_errs() {
assert!("gfx9999".parse::<GfxVersion>().is_err());
}
#[test]
fn compile_options_empty() {
let opts = CompileOptions::default();
assert!(opts.build().is_empty());
}
#[test]
fn compile_options_opt_level() {
let opts = CompileOptions {
opt_level: Some(3),
..Default::default()
};
assert_eq!(opts.build(), vec!["-O3"]);
}
#[test]
fn compile_options_fast_math() {
let opts = CompileOptions {
fast_math: Some(true),
..Default::default()
};
assert_eq!(opts.build(), vec!["-ffast-math"]);
let opts = CompileOptions {
fast_math: Some(false),
..Default::default()
};
assert_eq!(opts.build(), vec!["-fno-fast-math"]);
}
#[test]
fn compile_options_multi() {
let opts = CompileOptions {
opt_level: Some(2),
defines: vec!["NDEBUG".into(), "N=16".into()],
include_paths: vec!["/opt/rocm/include".into()],
arch: Some("gfx1100".into()),
..Default::default()
};
assert_eq!(
opts.build(),
vec![
"-O2",
"-DNDEBUG",
"-DN=16",
"-I/opt/rocm/include",
"--offload-arch=gfx1100",
]
);
}
}