use fuel_ir::backend::{BackendCapabilities, FitStatus};
use fuel_ir::{HostBuffer, HostBufferRef, Result};
pub trait HostStorage {
fn as_host_buffer_ref(&self) -> Result<HostBufferRef<'_>>;
fn into_host_buffer(self) -> Result<HostBuffer>
where
Self: Sized,
{
Ok(self.as_host_buffer_ref()?.to_owned())
}
}
pub trait BackendStorage: Send + Sync + std::fmt::Debug {
fn len_bytes(&self) -> usize;
}
pub trait BackendCapabilityProvider {
fn capabilities(&self) -> BackendCapabilities;
}
pub trait BackendRuntime {
fn available_bytes(&self) -> Option<u64>;
fn total_bytes(&self) -> Option<u64>;
fn would_fit(&self, size: u64) -> FitStatus {
match (self.available_bytes(), self.total_bytes()) {
(Some(a), _) if size > a => FitStatus::WontFit,
(Some(a), Some(t)) if t > 0 => {
let post_used = t.saturating_sub(a.saturating_sub(size));
if (post_used as f64) / (t as f64) > 0.85 {
FitStatus::Tight
} else {
FitStatus::Comfortable
}
}
_ => FitStatus::Unknown,
}
}
fn as_backend_streams(&self) -> Option<&dyn BackendStreams> {
None
}
}
pub trait BackendStreams: BackendRuntime {
fn pending_work_count(&self) -> Option<u32>;
fn slot_capacity(&self) -> u32;
fn flush(&self) -> Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use fuel_ir::backend::{BackendCapabilities, SubstrateClass};
use fuel_ir::dispatch::OpKind;
use fuel_ir::{BackendId, DType, DeviceLocation};
use std::collections::HashSet;
#[test]
fn capability_provider_is_implementable() {
struct DummyDevice;
impl BackendCapabilityProvider for DummyDevice {
fn capabilities(&self) -> BackendCapabilities {
BackendCapabilities {
backend_id: BackendId::Cpu,
device_location: DeviceLocation::Cpu,
op_dtype_support: HashSet::new(),
required_alignment: 1,
access_granularity_bits: 8,
transfer_paths: Vec::new(),
storage_substrate: SubstrateClass::HostBytes,
compute_throughput_flops_per_ns: 1.0,
mem_bandwidth_bytes_per_ns: 4.0,
}
}
}
let d = DummyDevice;
let caps = d.capabilities();
assert_eq!(caps.backend_id, BackendId::Cpu);
let _ = (OpKind::MatMul, DType::F32); }
struct MockRuntime {
available: Option<u64>,
total: Option<u64>,
}
impl BackendRuntime for MockRuntime {
fn available_bytes(&self) -> Option<u64> {
self.available
}
fn total_bytes(&self) -> Option<u64> {
self.total
}
}
#[test]
fn would_fit_wont_fit_when_size_exceeds_available() {
let r = MockRuntime {
available: Some(1_000),
total: Some(10_000),
};
assert_eq!(r.would_fit(1_001), FitStatus::WontFit);
}
#[test]
fn would_fit_comfortable_when_post_alloc_under_threshold() {
let r = MockRuntime {
available: Some(80),
total: Some(100),
};
assert_eq!(r.would_fit(10), FitStatus::Comfortable);
}
#[test]
fn would_fit_tight_when_post_alloc_above_threshold() {
let r = MockRuntime {
available: Some(20),
total: Some(100),
};
assert_eq!(r.would_fit(10), FitStatus::Tight);
}
#[test]
fn would_fit_unknown_on_none_signals() {
let r1 = MockRuntime {
available: None,
total: Some(100),
};
let r2 = MockRuntime {
available: Some(50),
total: None,
};
let r3 = MockRuntime {
available: None,
total: None,
};
assert_eq!(r1.would_fit(10), FitStatus::Unknown);
assert_eq!(r2.would_fit(10), FitStatus::Unknown);
assert_eq!(r3.would_fit(10), FitStatus::Unknown);
}
#[test]
fn would_fit_unknown_on_zero_total() {
let r = MockRuntime {
available: Some(0),
total: Some(0),
};
assert_eq!(r.would_fit(0), FitStatus::Unknown);
}
#[test]
fn would_fit_zero_byte_alloc() {
let r = MockRuntime {
available: Some(50),
total: Some(100),
};
assert_eq!(r.would_fit(0), FitStatus::Comfortable);
}
#[test]
fn base_runtime_is_not_streams_by_default() {
let r = MockRuntime {
available: Some(50),
total: Some(100),
};
assert!(
r.as_backend_streams().is_none(),
"a backend with no queue concept must report None via as_backend_streams",
);
}
#[test]
fn streams_handle_reachable_through_runtime_upcast() {
struct StreamingHandle {
inflight: u32,
}
impl BackendRuntime for StreamingHandle {
fn available_bytes(&self) -> Option<u64> {
None
}
fn total_bytes(&self) -> Option<u64> {
None
}
fn as_backend_streams(&self) -> Option<&dyn BackendStreams> {
Some(self)
}
}
impl BackendStreams for StreamingHandle {
fn pending_work_count(&self) -> Option<u32> {
Some(self.inflight)
}
fn slot_capacity(&self) -> u32 {
4
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
let handle = StreamingHandle { inflight: 3 };
let as_runtime: &dyn BackendRuntime = &handle;
let streams = as_runtime
.as_backend_streams()
.expect("streaming handle exposes BackendStreams");
assert_eq!(streams.pending_work_count(), Some(3));
assert_eq!(streams.slot_capacity(), 4);
streams.flush().expect("flush is Ok");
}
}