edgefirst_tflite/library.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 Au-Zone Technologies. All Rights Reserved.
3
4//! Safe wrapper around the `TFLite` shared-library handle.
5
6use std::fmt;
7use std::path::{Path, PathBuf};
8
9use crate::error::{Error, Result};
10
11/// Handle to a loaded `TFLite` shared library.
12///
13/// `Library` wraps the FFI function table produced by `libloading` and
14/// `bindgen`, providing safe construction via auto-discovery or an explicit
15/// filesystem path.
16///
17/// # Examples
18///
19/// ```no_run
20/// use edgefirst_tflite::Library;
21///
22/// // Auto-discover TFLite library
23/// let lib = Library::new()?;
24///
25/// // Or load from a specific path
26/// let lib = Library::from_path("/usr/lib/libtensorflowlite_c.so")?;
27/// # Ok::<(), edgefirst_tflite::Error>(())
28/// ```
29pub struct Library {
30 inner: edgefirst_tflite_sys::tensorflowlite_c,
31 path: Option<PathBuf>,
32}
33
34impl Library {
35 /// Discover and load the `TFLite` shared library automatically.
36 ///
37 /// This probes well-known versioned and unversioned library paths using
38 /// the [`edgefirst_tflite_sys::discovery`] module.
39 ///
40 /// # Errors
41 ///
42 /// Returns an [`Error`] if no compatible `TFLite` library can be found.
43 pub fn new() -> Result<Self> {
44 let (inner, path) =
45 edgefirst_tflite_sys::discovery::discover_with_path().map_err(Error::from)?;
46 Ok(Self {
47 inner,
48 path: Some(path),
49 })
50 }
51
52 /// Load the `TFLite` shared library from a specific `path`.
53 ///
54 /// # Errors
55 ///
56 /// Returns an [`Error`] if the library cannot be loaded from `path` or
57 /// required symbols are missing.
58 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
59 let raw = path.as_ref();
60 let inner = edgefirst_tflite_sys::discovery::load(raw).map_err(Error::from)?;
61 // Canonicalise so reopen() works even if the working directory
62 // changes later. Fall back to the original for soname-only paths.
63 let resolved = if raw.is_file() {
64 std::fs::canonicalize(raw).unwrap_or_else(|_| raw.to_path_buf())
65 } else {
66 raw.to_path_buf()
67 };
68 Ok(Self {
69 inner,
70 path: Some(resolved),
71 })
72 }
73
74 /// Returns a reference to the underlying FFI function table.
75 ///
76 /// This is an escape hatch for advanced users who need direct access to
77 /// the raw `tensorflowlite_c` bindings.
78 #[must_use]
79 pub fn as_sys(&self) -> &edgefirst_tflite_sys::tensorflowlite_c {
80 &self.inner
81 }
82
83 /// Re-open the underlying shared library, incrementing the OS refcount.
84 ///
85 /// This is used internally to keep the main `TFLite` library alive for
86 /// built-in delegates (e.g., XNNPACK) whose function pointers live in
87 /// the main library rather than a separate delegate `.so`.
88 pub(crate) fn reopen(&self) -> Result<libloading::Library> {
89 let path = self
90 .path
91 .as_ref()
92 .ok_or_else(|| Error::invalid_argument("library path not available for reopen"))?;
93 // SAFETY: Re-opening the same shared library increments the OS
94 // refcount. The path is known-valid because it was successfully
95 // loaded during construction.
96 unsafe { libloading::Library::new(path.as_os_str()) }.map_err(Error::from)
97 }
98}
99
100// SAFETY: `Library` holds a `tensorflowlite_c` struct whose fields are
101// function pointers (all `Send + Sync`) and a `libloading::Library` (which
102// is `Send + Sync`). The TFLite C API has no thread affinity — function
103// pointers resolved from a loaded library are safe to call from any thread.
104unsafe impl Send for Library {}
105
106// SAFETY: All access through `&Library` is via immutable function-pointer
107// calls (`as_sys()` returns `&tensorflowlite_c`). No interior mutability.
108unsafe impl Sync for Library {}
109
110impl fmt::Debug for Library {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.debug_struct("Library")
113 .field("inner", &"tensorflowlite_c { .. }")
114 .finish()
115 }
116}