Skip to main content

tenferro_xla/pjrt/
plugin.rs

1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use libloading::Library;
5
6use crate::{Error, Result};
7
8use super::sys::{GetPjrtApiFn, PJRT_Api};
9
10/// Dynamically loaded PJRT plugin.
11///
12/// # Examples
13///
14/// ```
15/// use tenferro_xla::{Error, PjrtPlugin};
16///
17/// let err = PjrtPlugin::load_from_env("__TENFERRO_XLA_DOCS_UNSET").unwrap_err();
18/// assert!(matches!(err, Error::MissingEnv { .. }));
19/// ```
20pub struct PjrtPlugin {
21    path: PathBuf,
22    _api: *const PJRT_Api,
23    _library: Library,
24}
25
26impl fmt::Debug for PjrtPlugin {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("PjrtPlugin")
29            .field("path", &self.path)
30            .finish_non_exhaustive()
31    }
32}
33
34impl PjrtPlugin {
35    /// Load a PJRT plugin path from an environment variable.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use tenferro_xla::{Error, PjrtPlugin};
41    ///
42    /// let err = PjrtPlugin::load_from_env("__TENFERRO_XLA_DOCS_UNSET").unwrap_err();
43    /// assert!(matches!(err, Error::MissingEnv { .. }));
44    /// ```
45    ///
46    /// # Errors
47    ///
48    /// Returns `Error::MissingEnv` when `var` is unset, or `Error::PluginLoad`
49    /// with the typed dynamic-library source when loading or symbol lookup
50    /// fails.
51    pub fn load_from_env(var: &'static str) -> Result<Self> {
52        let path = super::plugin_path_from_env(var)?;
53        Self::load_path(path)
54    }
55
56    /// Load a PJRT plugin from an explicit dynamic-library path.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use tenferro_xla::{Error, PjrtPlugin};
62    ///
63    /// let err = PjrtPlugin::load_path("/definitely/missing/pjrt.so").unwrap_err();
64    /// assert!(matches!(err, Error::PluginLoad { .. }));
65    /// ```
66    ///
67    /// # Errors
68    ///
69    /// Returns `Error::PluginLoad` with the original dynamic-library error as
70    /// its source when the file cannot be opened or `GetPjrtApi` is missing,
71    /// or `Error::PjrtCall` when the plugin returns a null API table.
72    pub fn load_path(path: impl Into<PathBuf>) -> Result<Self> {
73        let path = path.into();
74        // SAFETY: Loading a dynamic library is inherently unsafe. The library is
75        // retained in `Self` for at least as long as the API pointer is exposed.
76        let library = unsafe { Library::new(&path) }.map_err(|source| Error::PluginLoad {
77            path: path.clone(),
78            source: Box::new(source),
79        })?;
80        // SAFETY: OpenXLA PJRT plugins export `GetPjrtApi` with this signature.
81        // The returned table is owned by the plugin and remains valid while the
82        // dynamic library is loaded.
83        let api = unsafe {
84            let symbol: libloading::Symbol<'_, GetPjrtApiFn> =
85                library
86                    .get(b"GetPjrtApi")
87                    .map_err(|source| Error::PluginLoad {
88                        path: path.clone(),
89                        source: Box::new(source),
90                    })?;
91            symbol()
92        };
93        if api.is_null() {
94            return Err(Error::PjrtCall {
95                call: "GetPjrtApi",
96                message: format!("plugin at {path:?} returned a null API table"),
97            });
98        }
99        Ok(Self {
100            path,
101            _api: api,
102            _library: library,
103        })
104    }
105
106    /// Return the path used to load the plugin.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use std::path::Path;
112    /// use tenferro_xla::PjrtPlugin;
113    ///
114    /// let _path_type = std::any::type_name::<&Path>();
115    /// let _method: fn(&PjrtPlugin) -> &Path = PjrtPlugin::path;
116    /// ```
117    pub fn path(&self) -> &Path {
118        &self.path
119    }
120
121    pub(crate) fn api(&self) -> *const PJRT_Api {
122        self._api
123    }
124}