Skip to main content

hrx/
lib.rs

1//! GPU execution and Loom compilation without a build-time native dependency.
2//!
3//! Native libraries are verified and loaded on first use. See [`bundle`] for
4//! offline setup.
5mod access_graph;
6pub mod artifacts;
7#[path = "benchmark_statistics.rs"]
8pub mod benchmark;
9mod buffer_pool;
10pub mod bundle;
11mod dependency_frontier;
12pub mod fabric;
13pub mod image;
14pub mod inference;
15pub mod jobs;
16pub mod plan_cache;
17pub mod residency;
18mod runtime;
19mod target;
20pub mod tensor;
21pub use access_graph::{AccessGraph, AccessView};
22pub use buffer_pool::{BufferPool, PooledBuffer};
23pub use runtime::*;
24/// Coordinated GPU/NPU execution with inferred memory dependencies.
25pub mod execution;
26#[path = "loom/model.rs"]
27pub mod model;
28pub use execution::{Access, Completion, Runtime};
29/// Low-level GPU execution. Kernel dispatch and external memory access require
30/// the caller to establish their safety and synchronization contracts.
31pub mod gpu {
32    pub use crate::runtime::*;
33}
34pub use target::{TARGET_KEY, Target};
35pub mod loom;
36#[cfg(feature = "npu")]
37pub mod npu;
38
39/// The README's example is compiled with the crate, so an API change that would
40/// invalidate it fails the build instead of reaching a reader.
41#[cfg(doctest)]
42#[doc = include_str!("../README.md")]
43pub struct Readme;
44
45/// A runtime, validation, provisioning or compiler failure.
46#[derive(Debug, thiserror::Error)]
47#[non_exhaustive]
48pub enum Error {
49    /// A mapped allocation or exhausted prepared run capacity prevents submission.
50    #[error("resource busy: {0}")]
51    Busy(String),
52    /// Hardware or the selected native runtime cannot provide a requested capability.
53    #[error("unsupported capability: {0}")]
54    Unsupported(String),
55    /// A device failure left memory contents or completion uncertain.
56    #[error("device execution failed: {0}")]
57    DeviceLost(String),
58    /// A native backend failure with its original status code.
59    #[error("{backend} {operation} failed ({code}): {message}")]
60    Backend {
61        /// Native backend name.
62        backend: &'static str,
63        /// Operation that failed.
64        operation: &'static str,
65        /// Native status or command state.
66        code: i32,
67        /// Native diagnostic text. A boxed slice keeps the success-path Result
68        /// layout within the pre-existing native error size.
69        message: Box<str>,
70    },
71    /// A retained asynchronous failure, shared by completion observers.
72    #[error("{source}")]
73    Execution {
74        /// Original structured failure, including backend and node context.
75        #[source]
76        source: std::sync::Arc<Error>,
77    },
78    /// Work was cancelled before completion.
79    #[error("execution cancelled")]
80    Cancelled,
81    /// Invalid input or a contextual diagnostic.
82    #[error("{0}")]
83    Message(String),
84    /// Structured diagnostics from a failed Loom compiler invocation.
85    #[error("Loom compilation failed: {message}")]
86    Compile {
87        /// Rendered diagnostic summary.
88        message: String,
89        /// Complete structured diagnostics.
90        diagnostics: Vec<loom::Diagnostic>,
91    },
92    /// An operating-system failure, preserving its error kind and source.
93    #[error("{0}")]
94    Io(#[from] std::io::Error),
95    /// Invalid JSON, preserving the parser's location and source.
96    #[error("{0}")]
97    Json(#[from] serde_json::Error),
98    /// A dynamic library or symbol could not be loaded.
99    #[error("{0}")]
100    Library(#[from] libloading::Error),
101    /// A network provisioning failure.
102    #[error("{0}")]
103    Download(#[source] Box<ureq::Error>),
104    /// Legacy native status retained for source compatibility.
105    /// The native 0.8 runtime reports structured errors through other variants.
106    #[error("{context}: {message}")]
107    Runtime {
108        /// Operation that failed.
109        context: String,
110        /// Native HRX status code.
111        code: i32,
112        /// Owned native status message.
113        message: String,
114    },
115    /// Adds context while preserving an underlying failure.
116    #[error("{context}: {source}")]
117    Context {
118        /// Operation that failed.
119        context: String,
120        #[source]
121        /// Underlying failure.
122        source: Box<Error>,
123    },
124}
125impl Error {
126    /// Attach context without flattening the underlying error.
127    pub fn context(self, context: impl Into<String>) -> Self {
128        Self::Context {
129            context: context.into(),
130            source: Box::new(self),
131        }
132    }
133}
134/// The result of an HRX operation.
135pub type Result<T> = std::result::Result<T, Error>;
136
137pub mod runner;
138
139// Initialization failures remain retryable; only a successful value is memoized.
140pub(crate) fn cached_init<T: Clone>(
141    cache: &std::sync::Mutex<Option<T>>,
142    initialize: impl FnOnce() -> Result<T>,
143) -> Result<T> {
144    let mut cached = cache
145        .lock()
146        .map_err(|_| Error::Message("initialization cache poisoned".into()))?;
147    if let Some(value) = cached.as_ref() {
148        return Ok(value.clone());
149    }
150    let value = initialize()?;
151    *cached = Some(value.clone());
152    Ok(value)
153}
154
155#[cfg(test)]
156mod initialization_tests {
157    #[test]
158    fn failures_retry_and_concurrent_success_initializes_once() {
159        use std::sync::{
160            Mutex,
161            atomic::{AtomicUsize, Ordering},
162        };
163        let cache = Mutex::new(None);
164        assert!(
165            super::cached_init(&cache, || Err::<usize, _>(super::Error::Message(
166                "transient".into()
167            )))
168            .is_err()
169        );
170        let calls = AtomicUsize::new(0);
171        std::thread::scope(|scope| {
172            for _ in 0..8 {
173                scope.spawn(|| {
174                    assert_eq!(
175                        super::cached_init(&cache, || {
176                            calls.fetch_add(1, Ordering::Relaxed);
177                            Ok(42)
178                        })
179                        .unwrap(),
180                        42
181                    );
182                });
183            }
184        });
185        assert_eq!(calls.load(Ordering::Relaxed), 1);
186    }
187}