diff --git a/Cargo.lock b/Cargo.lock index ff6e08e3..042c416a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1326,6 +1326,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "larod" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "larod-sys", + "log", + "memmap2", + "thiserror", + "trybuild", +] + +[[package]] +name = "larod-sys" +version = "0.1.0" +dependencies = [ + "bindgen", + "pkg-config", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1459,6 +1480,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -2349,6 +2379,12 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "target-triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" + [[package]] name = "tempdir" version = "0.3.7" @@ -2371,6 +2407,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -2687,6 +2732,21 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + [[package]] name = "tungstenite" version = "0.21.0" @@ -2954,6 +3014,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index e5d42629..bf201dde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ licensekey = { path = "crates/licensekey" } licensekey-sys = { path = "crates/licensekey-sys" } mdb = { path = "crates/mdb" } mdb-sys = { path = "crates/mdb-sys" } +larod = { path = "crates/larod" } +larod-sys = { path = "crates/larod-sys" } [workspace.package] edition = "2021" diff --git a/crates/larod-sys/Cargo.toml b/crates/larod-sys/Cargo.toml new file mode 100644 index 00000000..9e518dde --- /dev/null +++ b/crates/larod-sys/Cargo.toml @@ -0,0 +1,11 @@ +[package] +build = "build.rs" +name = "larod-sys" +version = "0.1.0" +edition.workspace = true + +[build-dependencies] +bindgen = { workspace = true } +pkg-config = { workspace = true } + +[dependencies] diff --git a/crates/larod-sys/build.rs b/crates/larod-sys/build.rs new file mode 100644 index 00000000..771449c1 --- /dev/null +++ b/crates/larod-sys/build.rs @@ -0,0 +1,26 @@ +use std::{env, path}; + +fn populated_bindings(dst: &path::PathBuf) { + let library = pkg_config::Config::new().probe("liblarod").unwrap(); + let mut bindings = bindgen::Builder::default() + .header("wrapper.h") + .allowlist_recursively(false) + .allowlist_function("^(larod.*)$") + .allowlist_type("^(_?larod.*)$") + .default_enum_style(bindgen::EnumVariation::Rust { + non_exhaustive: true, + }) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .layout_tests(false); + for path in library.include_paths { + bindings = bindings.clang_args(&["-F", path.to_str().unwrap()]); + } + bindings.generate().unwrap().write_to_file(dst).unwrap(); +} + +fn main() { + let dst = path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"); + if env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default() != "x86_64" { + populated_bindings(&dst); + } +} diff --git a/crates/larod-sys/src/bindings.rs b/crates/larod-sys/src/bindings.rs new file mode 100644 index 00000000..6e5497ab --- /dev/null +++ b/crates/larod-sys/src/bindings.rs @@ -0,0 +1,568 @@ +/* automatically generated by rust-bindgen 0.69.4 */ + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodDevice { + _unused: [u8; 0], +} +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum larodAccess { + LAROD_ACCESS_INVALID = 0, + LAROD_ACCESS_PRIVATE = 1, + LAROD_ACCESS_PUBLIC = 2, +} +#[repr(i32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum larodErrorCode { + LAROD_ERROR_NONE = 0, + LAROD_ERROR_JOB = -1, + LAROD_ERROR_LOAD_MODEL = -2, + LAROD_ERROR_FD = -3, + LAROD_ERROR_MODEL_NOT_FOUND = -4, + LAROD_ERROR_PERMISSION = -5, + LAROD_ERROR_CONNECTION = -6, + LAROD_ERROR_CREATE_SESSION = -7, + LAROD_ERROR_KILL_SESSION = -8, + LAROD_ERROR_INVALID_CHIP_ID = -9, + LAROD_ERROR_INVALID_ACCESS = -10, + LAROD_ERROR_DELETE_MODEL = -11, + LAROD_ERROR_TENSOR_MISMATCH = -12, + LAROD_ERROR_VERSION_MISMATCH = -13, + LAROD_ERROR_ALLOC = -14, + LAROD_ERROR_MAX_ERRNO = 1024, +} +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum larodTensorDataType { + LAROD_TENSOR_DATA_TYPE_INVALID = 0, + LAROD_TENSOR_DATA_TYPE_UNSPECIFIED = 1, + LAROD_TENSOR_DATA_TYPE_BOOL = 2, + LAROD_TENSOR_DATA_TYPE_UINT8 = 3, + LAROD_TENSOR_DATA_TYPE_INT8 = 4, + LAROD_TENSOR_DATA_TYPE_UINT16 = 5, + LAROD_TENSOR_DATA_TYPE_INT16 = 6, + LAROD_TENSOR_DATA_TYPE_UINT32 = 7, + LAROD_TENSOR_DATA_TYPE_INT32 = 8, + LAROD_TENSOR_DATA_TYPE_UINT64 = 9, + LAROD_TENSOR_DATA_TYPE_INT64 = 10, + LAROD_TENSOR_DATA_TYPE_FLOAT16 = 11, + LAROD_TENSOR_DATA_TYPE_FLOAT32 = 12, + LAROD_TENSOR_DATA_TYPE_FLOAT64 = 13, + LAROD_TENSOR_DATA_TYPE_MAX = 14, +} +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum larodTensorLayout { + LAROD_TENSOR_LAYOUT_INVALID = 0, + LAROD_TENSOR_LAYOUT_UNSPECIFIED = 1, + LAROD_TENSOR_LAYOUT_NHWC = 2, + LAROD_TENSOR_LAYOUT_NCHW = 3, + LAROD_TENSOR_LAYOUT_420SP = 4, + LAROD_TENSOR_LAYOUT_MAX = 5, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodError { + pub code: larodErrorCode, + pub msg: *const ::std::os::raw::c_char, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodTensorDims { + pub dims: [usize; 12usize], + pub len: usize, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodTensorPitches { + pub pitches: [usize; 12usize], + pub len: usize, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodModel { + _unused: [u8; 0], +} +pub type larodLoadModelCallback = ::std::option::Option< + unsafe extern "C" fn( + model: *mut larodModel, + userData: *mut ::std::os::raw::c_void, + error: *mut larodError, + ), +>; +pub type larodRunJobCallback = ::std::option::Option< + unsafe extern "C" fn(userData: *mut ::std::os::raw::c_void, error: *mut larodError), +>; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodConnection { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodJobRequest { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodTensor { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodMap { + _unused: [u8; 0], +} +extern "C" { + pub fn larodClearError(error: *mut *mut larodError); +} +extern "C" { + pub fn larodConnect(conn: *mut *mut larodConnection, error: *mut *mut larodError) -> bool; +} +extern "C" { + pub fn larodDisconnect(conn: *mut *mut larodConnection, error: *mut *mut larodError) -> bool; +} +extern "C" { + pub fn larodGetNumSessions( + conn: *mut larodConnection, + numSessions: *mut u64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetDevice( + conn: *const larodConnection, + name: *const ::std::os::raw::c_char, + instance: u32, + error: *mut *mut larodError, + ) -> *const larodDevice; +} +extern "C" { + pub fn larodGetDeviceName( + dev: *const larodDevice, + error: *mut *mut larodError, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn larodGetDeviceInstance( + dev: *const larodDevice, + instance: *mut u32, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodListDevices( + conn: *mut larodConnection, + numDevices: *mut usize, + error: *mut *mut larodError, + ) -> *mut *const larodDevice; +} +extern "C" { + pub fn larodLoadModel( + conn: *mut larodConnection, + fd: ::std::os::raw::c_int, + dev: *const larodDevice, + access: larodAccess, + name: *const ::std::os::raw::c_char, + params: *const larodMap, + error: *mut *mut larodError, + ) -> *mut larodModel; +} +extern "C" { + pub fn larodLoadModelAsync( + conn: *mut larodConnection, + inFd: ::std::os::raw::c_int, + dev: *const larodDevice, + access: larodAccess, + name: *const ::std::os::raw::c_char, + params: *const larodMap, + callback: larodLoadModelCallback, + userData: *mut ::std::os::raw::c_void, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetModel( + conn: *mut larodConnection, + modelId: u64, + error: *mut *mut larodError, + ) -> *mut larodModel; +} +extern "C" { + pub fn larodGetModels( + conn: *mut larodConnection, + numModels: *mut usize, + error: *mut *mut larodError, + ) -> *mut *mut larodModel; +} +extern "C" { + pub fn larodDestroyModel(model: *mut *mut larodModel); +} +extern "C" { + pub fn larodDestroyModels(models: *mut *mut *mut larodModel, numModels: usize); +} +extern "C" { + pub fn larodDeleteModel( + conn: *mut larodConnection, + model: *mut larodModel, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetModelId(model: *const larodModel, error: *mut *mut larodError) -> u64; +} +extern "C" { + pub fn larodGetModelDevice( + model: *const larodModel, + error: *mut *mut larodError, + ) -> *const larodDevice; +} +extern "C" { + pub fn larodGetModelSize(model: *const larodModel, error: *mut *mut larodError) -> usize; +} +extern "C" { + pub fn larodGetModelName( + model: *const larodModel, + error: *mut *mut larodError, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn larodGetModelAccess( + model: *const larodModel, + error: *mut *mut larodError, + ) -> larodAccess; +} +extern "C" { + pub fn larodGetModelNumInputs(model: *const larodModel, error: *mut *mut larodError) -> usize; +} +extern "C" { + pub fn larodGetModelNumOutputs(model: *const larodModel, error: *mut *mut larodError) -> usize; +} +extern "C" { + pub fn larodGetModelInputByteSizes( + model: *const larodModel, + numInputs: *mut usize, + error: *mut *mut larodError, + ) -> *mut usize; +} +extern "C" { + pub fn larodGetModelOutputByteSizes( + model: *const larodModel, + numOutputs: *mut usize, + error: *mut *mut larodError, + ) -> *mut usize; +} +extern "C" { + pub fn larodCreateModelInputs( + model: *const larodModel, + numTensors: *mut usize, + error: *mut *mut larodError, + ) -> *mut *mut larodTensor; +} +extern "C" { + pub fn larodCreateModelOutputs( + model: *const larodModel, + numTensors: *mut usize, + error: *mut *mut larodError, + ) -> *mut *mut larodTensor; +} +extern "C" { + pub fn larodAllocModelInputs( + conn: *mut larodConnection, + model: *const larodModel, + fdPropFlags: u32, + numTensors: *mut usize, + params: *mut larodMap, + error: *mut *mut larodError, + ) -> *mut *mut larodTensor; +} +extern "C" { + pub fn larodAllocModelOutputs( + conn: *mut larodConnection, + model: *const larodModel, + fdPropFlags: u32, + numTensors: *mut usize, + params: *mut larodMap, + error: *mut *mut larodError, + ) -> *mut *mut larodTensor; +} +extern "C" { + pub fn larodCreateTensors( + numTensors: usize, + error: *mut *mut larodError, + ) -> *mut *mut larodTensor; +} +extern "C" { + pub fn larodDestroyTensors( + conn: *mut larodConnection, + tensors: *mut *mut *mut larodTensor, + numTensors: usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetTensorDims( + tensor: *mut larodTensor, + dims: *const larodTensorDims, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorDims( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> *const larodTensorDims; +} +extern "C" { + pub fn larodSetTensorPitches( + tensor: *mut larodTensor, + pitches: *const larodTensorPitches, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorPitches( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> *const larodTensorPitches; +} +extern "C" { + pub fn larodSetTensorDataType( + tensor: *mut larodTensor, + dataType: larodTensorDataType, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorDataType( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> larodTensorDataType; +} +extern "C" { + pub fn larodSetTensorLayout( + tensor: *mut larodTensor, + layout: larodTensorLayout, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorLayout( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> larodTensorLayout; +} +extern "C" { + pub fn larodSetTensorFd( + tensor: *mut larodTensor, + fd: ::std::os::raw::c_int, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorFd( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn larodSetTensorFdSize( + tensor: *mut larodTensor, + size: usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorFdSize( + tensor: *const larodTensor, + size: *mut usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetTensorFdOffset( + tensor: *mut larodTensor, + offset: i64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorFdOffset(tensor: *const larodTensor, error: *mut *mut larodError) -> i64; +} +extern "C" { + pub fn larodTrackTensor( + conn: *mut larodConnection, + tensor: *mut larodTensor, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetTensorFdProps( + tensor: *mut larodTensor, + fdPropFlags: u32, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorFdProps( + tensor: *const larodTensor, + fdPropFlags: *mut u32, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodGetTensorName( + tensor: *const larodTensor, + error: *mut *mut larodError, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn larodGetTensorByteSize( + tensor: *const larodTensor, + byteSize: *mut usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodCreateMap(error: *mut *mut larodError) -> *mut larodMap; +} +extern "C" { + pub fn larodDestroyMap(map: *mut *mut larodMap); +} +extern "C" { + pub fn larodMapSetStr( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + value: *const ::std::os::raw::c_char, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodMapSetInt( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + value: i64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodMapSetIntArr2( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + value0: i64, + value1: i64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodMapSetIntArr4( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + value0: i64, + value1: i64, + value2: i64, + value3: i64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodMapGetStr( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + error: *mut *mut larodError, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn larodMapGetInt( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + value: *mut i64, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodMapGetIntArr2( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + error: *mut *mut larodError, + ) -> *const i64; +} +extern "C" { + pub fn larodMapGetIntArr4( + map: *mut larodMap, + key: *const ::std::os::raw::c_char, + error: *mut *mut larodError, + ) -> *const i64; +} +extern "C" { + pub fn larodCreateJobRequest( + model: *const larodModel, + inputTensors: *mut *mut larodTensor, + numInputs: usize, + outputTensors: *mut *mut larodTensor, + numOutputs: usize, + params: *mut larodMap, + error: *mut *mut larodError, + ) -> *mut larodJobRequest; +} +extern "C" { + pub fn larodDestroyJobRequest(jobReq: *mut *mut larodJobRequest); +} +extern "C" { + pub fn larodSetJobRequestModel( + jobReq: *mut larodJobRequest, + model: *const larodModel, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetJobRequestInputs( + jobReq: *mut larodJobRequest, + tensors: *mut *mut larodTensor, + numTensors: usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetJobRequestOutputs( + jobReq: *mut larodJobRequest, + tensors: *mut *mut larodTensor, + numTensors: usize, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetJobRequestPriority( + jobReq: *mut larodJobRequest, + priority: u8, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodSetJobRequestParams( + jobReq: *mut larodJobRequest, + params: *const larodMap, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodRunJob( + conn: *mut larodConnection, + jobReq: *const larodJobRequest, + error: *mut *mut larodError, + ) -> bool; +} +extern "C" { + pub fn larodRunJobAsync( + conn: *mut larodConnection, + jobReq: *const larodJobRequest, + callback: larodRunJobCallback, + userData: *mut ::std::os::raw::c_void, + error: *mut *mut larodError, + ) -> bool; +} diff --git a/crates/larod-sys/src/lib.rs b/crates/larod-sys/src/lib.rs new file mode 100644 index 00000000..36fd376f --- /dev/null +++ b/crates/larod-sys/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(not(target_arch = "x86_64"))] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +#[cfg(target_arch = "x86_64")] +include!("bindings.rs"); diff --git a/crates/larod-sys/wrapper.h b/crates/larod-sys/wrapper.h new file mode 100644 index 00000000..bda0612d --- /dev/null +++ b/crates/larod-sys/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml new file mode 100644 index 00000000..8dda0351 --- /dev/null +++ b/crates/larod/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "larod" +version = "0.1.0" +edition.workspace = true + +[dependencies] +larod-sys = { workspace = true } +log.workspace = true +thiserror.workspace = true +memmap2 = "0.9.5" + +[dev-dependencies] +env_logger.workspace = true +anyhow.workspace = true +trybuild = "1.0.101" + +[features] +device-tests = [] + +[[example]] +name = "basic" \ No newline at end of file diff --git a/crates/larod/README.md b/crates/larod/README.md new file mode 100644 index 00000000..424a8199 --- /dev/null +++ b/crates/larod/README.md @@ -0,0 +1,16 @@ +# Device Tests + +```bash +cargo test --tests -p larod --features device-tests --target aarch64-unknown-linux-gnu -- --nocapture +``` + +# Compilation Tests +Because this library is a wrapper around a C library, the `unsafe` keyword is used extensively. One of the nice things about Rust is the compile time guarantees around lifetimes. In developing this library, we intend to produce an API that upholds Rusts compile time guarantees and prevents compilation when the developer is trying to do something unsafe. However, testing this to verify we're doing what we intend to do is tricky, because we can't really run a test if we can't compile it (by design). + +So, the trybuild crate is used along with a special test organization to intentially write test Rust programs that don't compile. The trybuild crate can then test and verify that the code does indeed not compile, and with the expected error. The trybuild crate will look for Rust files and compile them and match the comipler error to an expected error. Since it will look for a local file, these tests must be run on the development host, and cannot be shipped to a remote camera along with other device tests. So, `AXIS_DEVICE_IP` must be unset, the target architecture must be the native host architecture, and we must request that normally ignored tests be run. + +An example invocation is: +```bash +unset AXIS_DEVICE_IP +cargo test --tests -p larod compiler_tests::lifetimes -- --ignored --nocapture +``` \ No newline at end of file diff --git a/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.rs b/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.rs new file mode 100644 index 00000000..1ec97299 --- /dev/null +++ b/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.rs @@ -0,0 +1,33 @@ +use larod::{Error, ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; +fn main() { + env_logger::builder().is_test(true).try_init(); + let session = Session::new(); + let mut preprocessor = match Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session) + { + Ok(p) => p, + Err(Error::LarodError(e)) => { + log::error!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + log::error!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); + } + let tensors = preprocessor.input_tensors(); + drop(preprocessor); + if let Some(tensors) = tensors { + log::info!("input_tensor size: {}", tensors.len()); + for t in tensors.iter() { + log::info!("first_tensor dims {:?}", t.dims()); + } + } +} diff --git a/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.stderr b/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.stderr new file mode 100644 index 00000000..dc8507f1 --- /dev/null +++ b/crates/larod/compiler_tests/lifetimes/tensor_lifetimes.stderr @@ -0,0 +1,12 @@ +error[E0505]: cannot move out of `preprocessor` because it is borrowed + --> compiler_tests/lifetimes/tensor_lifetimes.rs:26:10 + | +5 | let mut preprocessor = match Preprocessor::builder() + | ---------------- binding `preprocessor` declared here +... +25 | let tensors = preprocessor.input_tensors(); + | ------------ borrow of `preprocessor` occurs here +26 | drop(preprocessor); + | ^^^^^^^^^^^^ move out of `preprocessor` occurs here +27 | if let Some(tensors) = tensors { + | ------- borrow later used here diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs new file mode 100644 index 00000000..287da3c6 --- /dev/null +++ b/crates/larod/examples/basic.rs @@ -0,0 +1,60 @@ +use anyhow::Context; +use larod::{ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; +use std::{fs, iter::Iterator, path::Path}; + +fn get_file(name: &str) -> anyhow::Result { + let path = Path::new(name); + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(path)?; + fs::remove_file(path)?; + Ok(file) +} + +fn main() -> anyhow::Result<()> { + env_logger::init(); + let session = Session::new(); + let devices = session + .devices() + .context("could not list available devices")?; + println!("Devices:"); + for d in devices { + println!( + "{} ({})", + d.name().expect("Couldn't get device name"), + d.instance().expect("Couldn't get device instance") + ); + } + let mut preprocessor = Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session)?; + preprocessor + .create_model_inputs() + .context("Error while creating model inputs")?; + if let Some(tensors) = preprocessor.input_tensors_mut() { + log::info!("input_tensor size: {}", tensors.len()); + for t in tensors.iter_mut() { + log::info!("first_tensor layout {:?}", t.layout()); + log::info!("first_tensor dims {:?}", t.dims()); + log::info!("first_tensor pitches {:?}", t.pitches()); + let pitches = t.pitches()?; + let file = get_file("/tmp/acap-rs-larod-preproc-1")?; + file.set_len(pitches[0] as u64)?; + t.set_buffer(file)?; + } + } + let Some(input_tensor) = preprocessor + .input_tensors_mut() + .and_then(|tc| tc.first_mut()) + else { + return Err(anyhow::anyhow!("preprocessor has no input tensors")); + }; + + Ok(()) +} diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs new file mode 100644 index 00000000..412d794c --- /dev/null +++ b/crates/larod/src/lib.rs @@ -0,0 +1,1745 @@ +//! A safe warpper around the larod-sys bindings to the larod C library. +//! +//! +//! Example +//! ```rust +//! use larod::Session; +//! let session = Session::new(); +//! let devices = session.devices(); +//! ``` +//! +//! # Gotchas +//! Many of the C functions return either a bool or a pointer to some object. +//! Additionally, one of the out arguments is a pointer to a larodError +//! object. If the normal return type is true, or not NULL in the case of a +//! pointer, the pointer to the larodError struct is expected to be NULL. This +//! represents two potentially conflicting indicators of whether the function +//! succeeded. +//! +//! Crucially, objects pointed to by returned pointers *AND* a non-NULL pointer +//! to a larodError struct need to be dealocated. That is handled appropriately +//! by constructing the LarodError struct if the larodError pointer is non-NULL +//! and the impl Drop for LarodError will dealocate the object appropriately. +//! +//! ## Tensors +//! The larod library supports [creating tensors](https://axiscommunications.github.io/acap-documentation/docs/api/src/api/larod/html/larod_8h.html#aededa9e269d87d0f1b7636a007760cb2). +//! However, it seems that calling that function, as well as [larodCreateModelInputs](https://axiscommunications.github.io/acap-documentation/docs/api/src/api/larod/html/larod_8h.html#adefd8c496e10eddced5be85d93aceb13), +//! allocates some structure on the heap. So, when [larodDestroyTensors](https://axiscommunications.github.io/acap-documentation/docs/api/src/api/larod/html/larod_8h.html#afac99dfef68ffe3d513008aaac354ae0) +//! is called, it deallocates any memory or file descriptors associated with +//! each tensor, but also the container storing the pointers to the tensors. +//! This makes it all but impossible to create a container in Rust storing +//! information about individual tensors and pass something to liblarod to +//! properly deallocate those tensors. This is because C and Rust may use +//! different allocators and objects should be deallocated by the same allocator +//! use for their allocation in the first place. +//! +//! # TODOs: +//! - [ ] [larodDisconnect](https://axiscommunications.github.io/acap-documentation/docs/api/src/api/larod/html/larod_8h.html#ab8f97b4b4d15798384ca25f32ca77bba) +//! indicates it may fail to "kill a session." What are the implications if it fails to kill a session? Can we clear the sessions? + +use core::slice; +pub use larod_sys::larodAccess as LarodAccess; +use larod_sys::*; +use memmap2::{Mmap, MmapMut}; +use std::{ + ffi::{c_char, CStr, CString}, + fmt::Display, + fs::File, + marker::PhantomData, + ops, + os::fd::{AsFd, AsRawFd}, + ptr::{self}, +}; + +type Result = std::result::Result; + +macro_rules! try_func { + ($func:ident $(,)?) => {{ + let mut error: *mut larodError = ptr::null_mut(); + let success = $func(&mut error); + if error.is_null() { + (success, None) + } else { + (success, Some(Error::LarodError(LarodError{inner: error}))) + } + }}; + ($func:ident, $($arg:expr),+ $(,)?) => {{ + let mut error: *mut larodError = ptr::null_mut(); + let success = $func($( $arg ),+, &mut error); + if error.is_null() { + (success, None) + } else { + (success, Some(Error::LarodError(LarodError{inner: error}))) + } + + }} +} + +// Most larod functions require a `NULL`` pointer to a larodError AND may +// produce either a `NULL`` output pointer or `false` if an error occurs. This +// results in two potential indicators of whether the function succeeded. If we +// get a`true` output, we expect the larodError to be a pointer to `NULL` still. +// In the possibly rare event that a function succeeds but the larodError +// pointer is not `NULL`, we need to deallocate that memory by calling +// `larodClearError`. The `try_func` macro always checks to see if the +// larodError pointer is `NULL` and return a `LarodError` if not. Doing so will +// call `larodClearError` when it is ultimately dropped. +#[derive(Debug)] +pub struct LarodError { + inner: *mut larodError, +} + +impl LarodError { + pub fn msg(&self) -> Result { + if self.inner.is_null() { + Err(Error::NullLarodPointer) + } else { + let msg_slice = unsafe { CStr::from_ptr((*self.inner).msg).to_str() }; + match msg_slice { + Ok(m) => Ok(m.into()), + Err(e) => { + log::error!("larodError.msg contained invalid UTF-8: {:?}", e); + Err(Error::InvalidLarodMessage) + } + } + } + } + + pub fn code(&self) -> larodErrorCode { + unsafe { (*self.inner).code } + } +} + +unsafe impl Send for LarodError {} +unsafe impl Sync for LarodError {} + +impl std::error::Error for LarodError {} + +impl Display for LarodError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.msg().unwrap_or("unknown error message".into()) + ) + } +} + +impl Drop for LarodError { + fn drop(&mut self) { + if !self.inner.is_null() { + unsafe { larodClearError(&mut self.inner) } + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + LarodError(#[from] LarodError), + #[error("liblarod returned an unexpected null pointer")] + NullLarodPointer, + #[error("message string returned from liblarod is not valid UTF-8")] + InvalidLarodMessage, + #[error("liblarod returned a pointer to invalid data")] + PointerToInvalidData, + #[error("could not allocate memory for CString")] + CStringAllocation, + #[error("invalid combination of configuration parameters for preprocessor")] + PreprocessorError(PreProcError), + #[error("missing error data from liblarod")] + MissingLarodError, + #[error(transparent)] + IOError(std::io::Error), + #[error("attempted operation without satisfying all required dependencies")] + UnsatisfiedDependencies, +} + +// impl LarodError { +// /// Convert from liblarod larodError to LarodError +// /// If larodError is not NULL, it must be dealocated by calling larodClearError +// fn from(e: *mut larodError) -> Self { +// if e.is_null() { +// Self::default() +// } else { +// let le = unsafe { *e }; +// let msg: String = unsafe { +// CStr::from_ptr(le.msg) +// .to_str() +// .unwrap_or("Error message invalid") +// .into() +// }; +// let code: LarodErrorCode = le.code.into(); +// // unsafe { +// // larodClearError(&mut e); +// // } +// Self { msg, code } +// } +// } +// } + +/// A type representing a larodMap. +pub struct LarodMap { + raw: *mut larodMap, +} + +impl LarodMap { + /// Create a new larodMap object + pub fn new() -> Result { + let (map, maybe_error): (*mut larodMap, Option) = + unsafe { try_func!(larodCreateMap) }; + if !map.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateMap allocated a map AND returned an error!" + ); + Ok(Self { raw: map }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Add a string to a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_string("key", "value").expect("Error setting string value for larodMap"); + /// ``` + pub fn set_string(&mut self, k: &str, v: &str) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(Error::CStringAllocation); + }; + let Ok(value_cstr) = CString::new(v.as_bytes()) else { + return Err(Error::CStringAllocation); + }; + let (success, maybe_error): (bool, Option) = unsafe { + try_func!( + larodMapSetStr, + self.raw, + key_cstr.as_ptr(), + value_cstr.as_ptr(), + ) + }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodMapSetStr indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Add an integer to a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int("key", 45).expect("Error setting integer value for larodMap"); + /// ``` + pub fn set_int(&mut self, k: &str, v: i64) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(Error::CStringAllocation); + }; + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodMapSetInt, self.raw, key_cstr.as_ptr(), v) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodMapSetInt indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Add an integer array of two items to a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int_arr2("key", (45, 64)).expect("Error setting integer array for larodMap"); + /// ``` + pub fn set_int_arr2(&mut self, k: &str, v: (i64, i64)) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(Error::CStringAllocation); + }; + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodMapSetIntArr2, self.raw, key_cstr.as_ptr(), v.0, v.1) }; + + if success { + debug_assert!( + maybe_error.is_none(), + "larodMapSetIntArr2 indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Add an integer array of 4 items to a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int_arr4("key", (45, 64, 36, 23)).expect("Error setting integer array for larodMap"); + /// ``` + pub fn set_int_arr4(&mut self, k: &str, v: (i64, i64, i64, i64)) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(Error::CStringAllocation); + }; + let (success, maybe_error): (bool, Option) = unsafe { + try_func!( + larodMapSetIntArr4, + self.raw, + key_cstr.as_ptr(), + v.0, + v.1, + v.2, + v.3 + ) + }; + + if success { + debug_assert!( + maybe_error.is_none(), + "larodMapSetIntArr4 indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Get a string from a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_string("key", "value").expect("Error setting string value for larodMap"); + /// let returned_string = map.get_string("key").expect("Unable to return value for \"key\""); + /// ``` + pub fn get_string(&self, k: &str) -> Result { + let Ok(key_cstr) = CString::new(k) else { + return Err(Error::CStringAllocation); + }; + let (c_str_ptr, maybe_error): (*const c_char, Option) = + unsafe { try_func!(larodMapGetStr, self.raw, key_cstr.as_ptr()) }; + let c_str = unsafe { CStr::from_ptr(c_str_ptr) }; + if let Ok(rs) = c_str.to_str() { + debug_assert!( + maybe_error.is_none(), + "larodMapGetStr returned a string AND returned an error!" + ); + Ok(String::from(rs)) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Get an integer array of 4 items from a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int("key", 45).expect("Error setting integer array for larodMap"); + /// let value = map.get_int("key").expect("Unable to get array values for \"key\""); + /// ``` + pub fn get_int(&self, k: &str) -> Result { + let Ok(key_cstr) = CString::new(k) else { + return Err(Error::CStringAllocation); + }; + let mut v: i64 = 0; + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodMapGetInt, self.raw, key_cstr.as_ptr(), &mut v) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodMapGetInt indicated success AND returned an error!" + ); + Ok(v) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Get an integer array of 4 items from a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int_arr2("key", (45, 64)).expect("Error setting integer array for larodMap"); + /// let returned_array = map.get_int_arr2("key").expect("Unable to get array values for \"key\""); + /// ``` + pub fn get_int_arr2(&self, k: &str) -> Result<&[i64; 2]> { + let Ok(key_cstr) = CString::new(k) else { + return Err(Error::CStringAllocation); + }; + let (out_arr, maybe_error) = + unsafe { try_func!(larodMapGetIntArr2, self.raw, key_cstr.as_ptr()) }; + if !out_arr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodMapGetInt indicated success AND returned an error!" + ); + unsafe { + slice::from_raw_parts(out_arr, 2) + .try_into() + .or(Err(Error::PointerToInvalidData)) + } + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + /// Get an integer array of 4 items from a larodMap object. + /// Example + /// ```rust + /// use larod::LarodMap; + /// + /// let map = LarodMap::new().expect("Error creating map"); + /// map.set_int_arr4("key", (45, 64, 36, 23)).expect("Error setting integer array for larodMap"); + /// let returned_array = map.get_int_arr4("key").expect("Unable to get array values for \"key\""); + /// ``` + pub fn get_int_arr4(&self, k: &str) -> Result<&[i64; 4]> { + let Ok(key_cstr) = CString::new(k) else { + return Err(Error::CStringAllocation); + }; + let (out_arr, maybe_error) = + unsafe { try_func!(larodMapGetIntArr4, self.raw, key_cstr.as_ptr()) }; + if !out_arr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodMapGetIntArr4 indicated success AND returned an error!" + ); + unsafe { + slice::from_raw_parts(out_arr, 4) + .try_into() + .or(Err(Error::PointerToInvalidData)) + } + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +impl std::ops::Drop for LarodMap { + fn drop(&mut self) { + unsafe { + larodDestroyMap(&mut self.raw); + } + } +} + +// #[derive(Eq, PartialEq, Hash)] +// pub struct Tensor<'a> { +// ptr: *mut *mut larodTensor, +// phantom: PhantomData<&'a Session>, +// } + +pub struct LarodTensorContainer<'a> { + ptr: *mut *mut larodTensor, + tensors: Vec>, + num_tensors: usize, +} + +impl<'a> LarodTensorContainer<'a> { + pub fn as_slice(&self) -> &[Tensor] { + self.tensors.as_slice() + } + + pub fn iter(&self) -> impl Iterator { + self.tensors.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator> { + self.tensors.iter_mut() + } + + pub fn len(&self) -> usize { + self.tensors.len() + } +} + +impl<'a> ops::Deref for LarodTensorContainer<'a> { + type Target = [Tensor<'a>]; + + #[inline] + fn deref(&self) -> &[Tensor<'a>] { + self.tensors.as_slice() + } +} + +impl<'a> ops::DerefMut for LarodTensorContainer<'a> { + #[inline] + fn deref_mut(&mut self) -> &mut [Tensor<'a>] { + self.tensors.as_mut_slice() + } +} + +pub struct Tensor<'a> { + ptr: *mut larodTensor, + buffer: Option, + mmap: Option, + phantom: PhantomData<&'a Session>, +} + +/// A structure representing a larodTensor. +impl<'a> Tensor<'a> { + fn as_ptr(&self) -> *const larodTensor { + self.ptr.cast_const() + } + + fn as_mut_ptr(&self) -> *mut larodTensor { + self.ptr + } + + pub fn name(&self) -> Result<&str> { + let (c_str_ptr, maybe_error) = unsafe { try_func!(larodGetTensorName, self.ptr) }; + if !c_str_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetTensorName indicated success AND returned an error!" + ); + let c_str = unsafe { CStr::from_ptr(c_str_ptr) }; + if let Ok(s) = c_str.to_str() { + Ok(s) + } else { + Err(Error::PointerToInvalidData) + } + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + pub fn byte_size() {} + + pub fn dims(&self) -> Result<&[usize]> { + let (dims, maybe_error) = unsafe { try_func!(larodGetTensorDims, self.ptr) }; + if !dims.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetTensorDims indicated success AND returned an error!" + ); + // let d = unsafe { + // (*dims) + // .dims + // .into_iter() + // .take((*dims).len) + // .collect::>() + // }; + let (left, _) = unsafe { (*dims).dims.split_at((*dims).len) }; + Ok(left) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + pub fn set_dims(&self, dims: &[usize]) -> Result<()> { + let mut dim_array: [usize; 12] = [0; 12]; + for (idx, dim) in dims.iter().take(12).enumerate() { + dim_array[idx] = *dim; + } + let dims_struct = larodTensorDims { + dims: dim_array, + len: dims.len(), + }; + let (success, maybe_error) = + unsafe { try_func!(larodSetTensorDims, self.ptr, &dims_struct) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodSetTensorDims indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + pub fn pitches(&self) -> Result<&[usize]> { + let (pitches_raw, maybe_error) = + unsafe { try_func!(larodGetTensorPitches, self.ptr.cast_const()) }; + if !pitches_raw.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetTensorPitches indicated success AND returned an error!" + ); + // let d = unsafe { + // (*dims) + // .dims + // .into_iter() + // .take((*dims).len) + // .collect::>() + // }; + let (left, _) = unsafe { (*pitches_raw).pitches.split_at((*pitches_raw).len) }; + Ok(left) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + pub fn set_pitches() {} + pub fn data_type() {} + pub fn set_data_type() {} + pub fn layout(&self) -> Result { + let (layout, maybe_error) = + unsafe { try_func!(larodGetTensorLayout, self.ptr.cast_const()) }; + if maybe_error.is_none() { + Ok(layout) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + pub fn set_layout() {} + pub fn fd(&self) -> Option> { + self.buffer.as_ref().map(|f| f.as_fd()) + } + + /// Use a memory mapped file as a buffer for this tensor. + /// The method name here differs a bit from the larodSetTensorFd, + /// but aligns better with the need for the tensor to own the + /// file descriptor it is using as a buffer. + pub fn set_buffer(&mut self, file: File) -> Result<()> { + let (success, maybe_error) = + unsafe { try_func!(larodSetTensorFd, self.ptr, file.as_raw_fd()) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodSetTensorFd indicated success AND returned an error!" + ); + self.buffer = Some(file); + unsafe { + match MmapMut::map_mut(self.buffer.as_ref().unwrap()) { + Ok(m) => { + self.mmap = Some(m); + } + Err(e) => { + return Err(Error::IOError(e)); + } + }; + } + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + pub fn fd_size() {} + pub fn set_fd_size() {} + pub fn fd_offset() {} + pub fn set_fd_offset() {} + pub fn fd_props() {} + pub fn set_fd_props() {} + pub fn as_slice(&self) -> Option<&[u8]> { + self.mmap.as_deref() + } + pub fn copy_from_slice(&mut self, slice: &[u8]) { + if let Some(mmap) = self.mmap.as_mut() { + mmap.copy_from_slice(slice); + } + } + // pub fn destroy(mut self, session: &Session) -> Result<()> { + // let (success, maybe_error) = + // unsafe { try_func!(larodDestroyTensors, session.conn, &mut self.ptr, 1) }; + // if success { + // debug_assert!( + // maybe_error.is_none(), + // "larodDestroyTensors indicated success AND returned an error!" + // ); + // Ok(()) + // } else { + // Err(maybe_error.unwrap_or(Error::MissingLarodError)) + // } + // } +} + +impl<'a> From<*mut larodTensor> for Tensor<'a> { + fn from(value: *mut larodTensor) -> Self { + Self { + ptr: value, + buffer: None, + mmap: None, + phantom: PhantomData, + } + } +} + +/// A type representing a larodDevice. +/// The lifetime of LarodDevice is explicitly tied to the lifetime of a +/// [Session]. So using a LarodDevice after the Session it was acquired from +/// will cause compilation to fail. +/// ```compile_fail +/// use larod::Session; +/// let sess = Session::new(); +/// let first_device = sess +/// .devices() +/// .expect("unable to get devices") +/// .pop() +/// .expect("empty devices list!"); +/// drop(sess); +/// println!("{:?}", first_device.name()); +/// ``` +#[derive(Debug)] +pub struct LarodDevice<'a> { + // The caller does not get ownership of the returned pointer and must not + // attempt to free it. The lifetime of the memory pointed to expires when + // conn closes. + ptr: *const larodDevice, + phantom: PhantomData<&'a Session>, +} + +impl<'a> LarodDevice<'a> { + /// Get the name of a larodDevice. + pub fn name(&self) -> Result { + unsafe { + let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, self.ptr); + if !c_char_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetDeviceName returned an object pointer AND returned an error!" + ); + let c_name = CStr::from_ptr(c_char_ptr); + c_name + .to_str() + .map(String::from) + .map_err(|_e| Error::InvalidLarodMessage) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + } + + /// Get the instance of a larodDevice. + /// From the larod documentation + /// > *In case there are multiple identical devices that are available in the service, they are distinguished by an instance number, with the first instance starting from zero.* + pub fn instance(&self) -> Result { + unsafe { + let mut instance: u32 = 0; + let (success, maybe_error) = try_func!(larodGetDeviceInstance, self.ptr, &mut instance); + if success { + debug_assert!( + maybe_error.is_none(), + "larodGetDeviceInstance returned success AND returned an error!" + ); + Ok(instance) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + } +} + +pub trait LarodModel<'a> { + fn create_model_inputs(&mut self) -> Result<()>; + fn num_inputs(&self) -> usize; + fn input_tensors(&self) -> Option<&LarodTensorContainer<'a>>; + fn input_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>>; + fn create_model_outputs(&mut self) -> Result<()>; + fn num_outputs(&self) -> usize; + fn output_tensors(&self) -> Option<&LarodTensorContainer<'a>>; + fn output_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>>; + fn create_job(&self) -> Result>; +} + +#[derive(Default)] +pub enum ImageFormat { + #[default] + NV12, + RGBInterleaved, + RGBPlanar, +} + +#[derive(Default)] +pub enum PreProcBackend { + #[default] + LibYUV, + ACE, + VProc, + OpenCLDLPU, + OpenCLGPU, + RemoteLibYuv, + RemoteOpenCLDLPU, + RemoteOpenCLGPU, +} + +#[derive(Debug, Default)] +pub enum InferenceChip { + #[default] + TFLiteCPU, + TFLiteDLPU, +} + +#[derive(Debug, Default)] +pub enum PreProcError { + #[default] + UnsupportedOperation, +} + +struct Resolution { + width: u32, + height: u32, +} + +#[derive(Default)] +pub struct PreprocessorBuilder { + backend: PreProcBackend, + input_size: Option, + crop: Option<(i64, i64, i64, i64)>, + output_size: Option, + input_format: ImageFormat, + output_format: ImageFormat, +} + +impl PreprocessorBuilder { + pub fn new() -> Self { + PreprocessorBuilder::default() + } + + pub fn backend(mut self, backend: PreProcBackend) -> Self { + self.backend = backend; + self + } + + /// Crop a portion of the input stream + /// (X offset, Y offset, Width, Height) + pub fn crop(mut self, crop: (i64, i64, i64, i64)) -> Self { + self.crop = Some(crop); + self + } + + /// Scale the input image width and height to the desired output width and + /// height. The aspect ratio is not preserved. Size indicates the desired + /// final output size. + pub fn output_size(mut self, width: u32, height: u32) -> Self { + self.output_size = Some(Resolution { width, height }); + self + } + + pub fn input_size(mut self, width: u32, height: u32) -> Self { + self.input_size = Some(Resolution { width, height }); + self + } + + pub fn input_format(mut self, format: ImageFormat) -> Self { + self.input_format = format; + self + } + + pub fn output_format(mut self, format: ImageFormat) -> Self { + self.output_format = format; + self + } + + pub fn load(self, session: &Session) -> Result> { + let mut map = LarodMap::new()?; + match self.input_format { + ImageFormat::NV12 => map.set_string("image.input.format", "nv12")?, + ImageFormat::RGBInterleaved => { + if !matches!( + self.backend, + PreProcBackend::LibYUV | PreProcBackend::RemoteLibYuv + ) { + return Err(Error::PreprocessorError(PreProcError::UnsupportedOperation)); + } else { + map.set_string("image.input.format", "rgb-interleaved")?; + } + } + ImageFormat::RGBPlanar => { + if !matches!( + self.backend, + PreProcBackend::LibYUV | PreProcBackend::RemoteLibYuv + ) { + return Err(Error::PreprocessorError(PreProcError::UnsupportedOperation)); + } else { + map.set_string("image.input.format", "rgb-planar")?; + } + } + } + match self.output_format { + ImageFormat::NV12 => { + if matches!( + self.backend, + PreProcBackend::LibYUV | PreProcBackend::RemoteLibYuv + ) { + map.set_string("image.output.format", "nv12")?; + } else { + return Err(Error::PreprocessorError(PreProcError::UnsupportedOperation)); + } + } + ImageFormat::RGBInterleaved => { + if matches!(self.backend, PreProcBackend::VProc) { + return Err(Error::PreprocessorError(PreProcError::UnsupportedOperation)); + } else { + map.set_string("image.output.format", "rgb-interleaved")?; + } + } + ImageFormat::RGBPlanar => { + if matches!( + self.backend, + PreProcBackend::LibYUV | PreProcBackend::VProc | PreProcBackend::RemoteLibYuv + ) { + map.set_string("image.output.format", "rgb-planar")?; + } else { + return Err(Error::PreprocessorError(PreProcError::UnsupportedOperation)); + } + } + } + if let Some(s) = self.input_size { + map.set_int_arr2( + "image.input.size", + (i64::from(s.width), i64::from(s.height)), + )?; + } + + let mut crop_map: Option = None; + if let Some(crop) = self.crop { + crop_map = Some(LarodMap::new()?); + crop_map + .as_mut() + .unwrap() + .set_int_arr4("image.input.crop", crop)?; + } + + if let Some(s) = self.output_size { + map.set_int_arr2( + "image.output.size", + (i64::from(s.width), i64::from(s.height)), + )?; + } + + let device_name = match self.backend { + PreProcBackend::LibYUV => "cpu-proc", + PreProcBackend::ACE => "axis-ace-proc", + PreProcBackend::VProc => "ambarella-cvflow-proc", + PreProcBackend::OpenCLDLPU => "axis-a8-dlpu-proc", + PreProcBackend::OpenCLGPU => "axis-a8-gpu-proc", + PreProcBackend::RemoteLibYuv => "remote-cpu-proc", + PreProcBackend::RemoteOpenCLDLPU => "remote-axis-a8-dlpu-proc", + PreProcBackend::RemoteOpenCLGPU => "remote-axis-a8-gpu-proc", + }; + let (device, maybe_device_error) = unsafe { + try_func!( + larodGetDevice, + session.conn, + CString::new(device_name) + .map_err(|_| Error::CStringAllocation)? + .as_ptr(), + 0 + ) + }; + if device.is_null() { + return Err(maybe_device_error.unwrap_or(Error::MissingLarodError)); + } + debug_assert!( + maybe_device_error.is_none(), + "larodGetDevice indicated success AND returned an error!" + ); + let (model_ptr, maybe_error) = unsafe { + try_func!( + larodLoadModel, + session.conn, + -1, + device, + LarodAccess::LAROD_ACCESS_PRIVATE, + CString::new("") + .map_err(|_| Error::CStringAllocation)? + .as_ptr(), + map.raw + ) + }; + if !model_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodLoadModel indicated success AND returned an error!" + ); + Ok(Preprocessor { + session, + ptr: model_ptr, + input_tensors: None, + num_inputs: 0, + output_tensors: None, + num_outputs: 0, + crop: crop_map, + }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +pub struct Preprocessor<'a> { + session: &'a Session, + ptr: *mut larodModel, + input_tensors: Option>, + num_inputs: usize, + output_tensors: Option>, + num_outputs: usize, + crop: Option, +} + +impl<'a> Preprocessor<'a> { + pub fn builder() -> PreprocessorBuilder { + PreprocessorBuilder::new() + } +} + +impl<'a> LarodModel<'a> for Preprocessor<'a> { + fn create_model_inputs(&mut self) -> Result<()> { + let (tensors_ptr, maybe_error) = + unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; + if !tensors_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelInputs indicated success AND returned an error!" + ); + let tensors_raw: &[*mut larodTensor] = + unsafe { slice::from_raw_parts_mut(tensors_ptr, self.num_inputs) }; + let tensors: Vec = tensors_raw + .iter() + .map(|t_raw| Tensor::from(*t_raw)) + .collect(); + self.input_tensors = Some(LarodTensorContainer { + ptr: tensors_ptr, + tensors, + num_tensors: self.num_inputs, + }); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn create_model_outputs(&mut self) -> Result<()> { + let (tensors_ptr, maybe_error) = + unsafe { try_func!(larodCreateModelOutputs, self.ptr, &mut self.num_outputs) }; + if !tensors_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelOutputs indicated success AND returned an error!" + ); + let tensors_raw: &[*mut larodTensor] = + unsafe { slice::from_raw_parts_mut(tensors_ptr, self.num_outputs) }; + let tensors: Vec = tensors_raw + .iter() + .map(|t_raw| Tensor::from(*t_raw)) + .collect(); + self.output_tensors = Some(LarodTensorContainer { + ptr: tensors_ptr, + tensors, + num_tensors: self.num_outputs, + }); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn num_inputs(&self) -> usize { + self.num_inputs + } + + fn input_tensors(&self) -> Option<&LarodTensorContainer<'a>> { + self.input_tensors.as_ref() + } + + fn input_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>> { + self.input_tensors.as_mut() + } + + fn num_outputs(&self) -> usize { + self.num_outputs + } + + fn output_tensors(&self) -> Option<&LarodTensorContainer<'a>> { + self.output_tensors.as_ref() + } + + fn output_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>> { + self.output_tensors.as_mut() + } + + fn create_job(&self) -> Result> { + if self.input_tensors.is_none() || self.output_tensors.is_none() { + return Err(Error::UnsatisfiedDependencies); + } + let (job_ptr, maybe_error) = unsafe { + try_func!( + larodCreateJobRequest, + self.ptr, + self.input_tensors.as_ref().unwrap().ptr, + self.num_inputs, + self.output_tensors.as_ref().unwrap().ptr, + self.num_outputs, + self.crop + .as_ref() + .map_or(ptr::null_mut::(), |m| m.raw) + ) + }; + if !job_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateJobRequest indicated success AND returned an error!" + ); + Ok(JobRequest { + raw: job_ptr, + session: self.session, + }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +impl<'a> Drop for Preprocessor<'a> { + fn drop(&mut self) { + if let Some(ref mut tensor_container) = self.input_tensors { + log::debug!("Dropping Preprocessor input tensors!"); + unsafe { + try_func!( + larodDestroyTensors, + self.session.conn, + &mut tensor_container.ptr, + tensor_container.num_tensors + ) + }; + } + unsafe { larodDestroyModel(&mut self.ptr) }; + } +} + +pub struct JobRequest<'a> { + raw: *mut larodJobRequest, + session: &'a Session, +} + +impl<'a> JobRequest<'a> { + pub fn run(&self) -> Result<()> { + let (success, maybe_error) = unsafe { try_func!(larodRunJob, self.session.conn, self.raw) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodRunJob indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +impl<'a> Drop for JobRequest<'a> { + fn drop(&mut self) { + unsafe { + larodDestroyJobRequest(&mut self.raw); + } + } +} + +// #[derive(Default)] +// pub struct ModelBuilder { +// file_path: Option, +// device: InferenceChip, +// crop: Option<(u32, u32, u32, u32)>, +// } + +// impl ModelBuilder { +// pub fn new() -> Self { +// ModelBuilder::default() +// } + +// pub fn source_file(mut self, path: PathBuf) -> Self { +// self.file_path = Some(path); +// self +// } + +// pub fn on_chip(mut self, device: InferenceChip) -> Self { +// self.device = device; +// self +// } + +// pub fn with_crop(mut self, crop: (u32, u32, u32, u32)) -> Self { +// self.crop = Some(crop); +// self +// } + +// pub fn load(self, session: Session) -> Model {} +// } + +pub struct InferenceModel<'a> { + session: &'a Session, + ptr: *mut larodModel, + input_tensors: Option>, + num_inputs: usize, + output_tensors: Option>, + num_outputs: usize, +} + +impl<'a> InferenceModel<'a> { + pub fn id() -> Result<()> { + Ok(()) + } + pub fn chip() -> Result<()> { + Ok(()) + } + pub fn device() -> Result<()> { + Ok(()) + } + pub fn size() -> Result<()> { + Ok(()) + } + pub fn name() -> Result<()> { + Ok(()) + } + pub fn access() -> Result<()> { + Ok(()) + } + pub fn num_inputs() -> Result<()> { + Ok(()) + } + pub fn num_outputs() -> Result<()> { + Ok(()) + } + + pub fn create_model_outputs() -> Result<()> { + Ok(()) + } +} + +impl<'a> LarodModel<'a> for InferenceModel<'a> { + fn create_model_inputs(&mut self) -> Result<()> { + let (tensors_ptr, maybe_error) = + unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; + if !tensors_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelInputs indicated success AND returned an error!" + ); + let tensors_raw: &[*mut larodTensor] = + unsafe { slice::from_raw_parts_mut(tensors_ptr, self.num_inputs) }; + let tensors: Vec = tensors_raw + .iter() + .map(|t_raw| Tensor::from(*t_raw)) + .collect(); + self.input_tensors = Some(LarodTensorContainer { + ptr: tensors_ptr, + tensors, + num_tensors: self.num_inputs, + }); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn create_model_outputs(&mut self) -> Result<()> { + let (tensors_ptr, maybe_error) = + unsafe { try_func!(larodCreateModelOutputs, self.ptr, &mut self.num_outputs) }; + if !tensors_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelInputs indicated success AND returned an error!" + ); + let tensors_raw: &[*mut larodTensor] = + unsafe { slice::from_raw_parts_mut(tensors_ptr, self.num_outputs) }; + let tensors: Vec = tensors_raw + .iter() + .map(|t_raw| Tensor::from(*t_raw)) + .collect(); + self.output_tensors = Some(LarodTensorContainer { + ptr: tensors_ptr, + tensors, + num_tensors: self.num_outputs, + }); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn num_inputs(&self) -> usize { + self.num_inputs + } + + fn input_tensors(&self) -> Option<&LarodTensorContainer<'a>> { + self.input_tensors.as_ref() + } + + fn input_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>> { + self.input_tensors.as_mut() + } + + fn num_outputs(&self) -> usize { + self.num_outputs + } + + fn output_tensors(&self) -> Option<&LarodTensorContainer<'a>> { + self.output_tensors.as_ref() + } + + fn output_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>> { + self.output_tensors.as_mut() + } + + fn create_job(&self) -> Result> { + if self.input_tensors.is_none() || self.output_tensors.is_none() { + return Err(Error::UnsatisfiedDependencies); + } + let (job_ptr, maybe_error) = unsafe { + try_func!( + larodCreateJobRequest, + self.ptr, + self.input_tensors.as_ref().unwrap().ptr, + self.num_inputs, + self.output_tensors.as_ref().unwrap().ptr, + self.num_outputs, + self.params + .as_ref() + .map_or(ptr::null_mut::(), |m| m.raw) + ) + }; + if !job_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateJobRequest indicated success AND returned an error!" + ); + Ok(JobRequest { + raw: job_ptr, + session: self.session, + }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +impl<'a> Drop for InferenceModel<'a> { + fn drop(&mut self) { + if let Some(ref mut tensor_container) = self.input_tensors { + unsafe { + try_func!( + larodDestroyTensors, + self.session.conn, + &mut tensor_container.ptr, + tensor_container.num_tensors + ) + }; + } + unsafe { larodDestroyModel(&mut self.ptr) }; + } +} + +pub struct SessionBuilder {} + +impl SessionBuilder { + pub fn new() -> SessionBuilder { + SessionBuilder {} + } + pub fn build(&self) -> Result { + let mut conn: *mut larodConnection = ptr::null_mut(); + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodConnect, &mut conn) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodConnect indicated success AND returned an error!" + ); + Ok(Session { conn }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } +} + +impl Default for SessionBuilder { + fn default() -> Self { + SessionBuilder::new() + } +} + +pub struct Session { + conn: *mut larodConnection, +} + +// Using a session builder might not be necessary. +// There's little to configure when starting a session. +impl Session { + /// Constructs a new `Session`. + /// + /// # Panics + /// + /// Use `Session::builder()` if you wish to handle the failure as an `Error` + /// instead of panicking. + pub fn new() -> Session { + SessionBuilder::new().build().expect("Session::new()") + } + pub fn builder() -> SessionBuilder { + SessionBuilder::new() + } + pub fn disconnect(&mut self) -> Result<()> { + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodDisconnect, &mut self.conn) }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodDisconnect indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + pub fn num_sessions() -> Result<()> { + Ok(()) + } + + /// Returns a reference to an available device + pub fn device(&self, name: &str, instance: u32) -> Result { + let Ok(name_cstr) = CString::new(name) else { + return Err(Error::CStringAllocation); + }; + let (device_ptr, maybe_error) = + unsafe { try_func!(larodGetDevice, self.conn, name_cstr.as_ptr(), instance) }; + if !device_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetDevice indicated success AND returned an error!" + ); + Ok(LarodDevice { + ptr: device_ptr, + phantom: PhantomData, + }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + pub fn list_chips() -> Result<()> { + Ok(()) + } + + /// Get a reference to a HashMap of name LarodDevice pairs. + pub fn devices(&self) -> Result> { + let mut num_devices: usize = 0; + let (dev_ptr, maybe_error) = + unsafe { try_func!(larodListDevices, self.conn, &mut num_devices) }; + if dev_ptr.is_null() { + return Err(maybe_error.unwrap_or(Error::MissingLarodError)); + } + let raw_devices = + unsafe { slice::from_raw_parts::<*const larodDevice>(dev_ptr, num_devices) }; + + let devices: Vec = raw_devices + .iter() + .map(|ptr| LarodDevice { + ptr: *ptr, + phantom: PhantomData, + }) + .collect(); + + Ok(devices) + } + + pub fn models() -> Result<()> { + Ok(()) + } + pub fn delete_model(&self) -> Result<()> { + Ok(()) + } + pub fn alloc_model_inputs() -> Result<()> { + Ok(()) + } + pub fn alloc_model_outputs() -> Result<()> { + Ok(()) + } + pub fn destroy_tensors() -> Result<()> { + Ok(()) + } + // pub fn track_tensor(&self, tensor: &Tensor) -> Result<()> { + // let (success, maybe_error) = + // unsafe { try_func!(larodTrackTensor, self.conn, tensor.as_mut_ptr()) }; + // if success { + // debug_assert!( + // maybe_error.is_none(), + // "larodTrackTensor indicated success AND returned an error!" + // ); + // Ok(()) + // } else { + // Err(maybe_error.unwrap_or(Error::MissingLarodError)) + // } + // } + pub fn run_job() -> Result<()> { + Ok(()) + } + pub fn run_inference() -> Result<()> { + Ok(()) + } + pub fn chip_id() -> Result<()> { + Ok(()) + } + pub fn chip_type() -> Result<()> { + Ok(()) + } +} + +impl Default for Session { + fn default() -> Self { + SessionBuilder::default() + .build() + .expect("Session::default()") + } +} + +impl std::ops::Drop for Session { + fn drop(&mut self) { + log::debug!("Dropping Session!"); + // unsafe { + // try_func!(larodDisconnect, &mut self.conn); + // } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(all(target_arch = "aarch64", feature = "device-tests"))] + mod device_test { + use super::*; + + #[test] + fn it_creates_larod_map() { + let _ = env_logger::builder().is_test(true).try_init(); + assert!(LarodMap::new().is_ok()); + } + + #[test] + fn it_drops_map() { + let _ = env_logger::builder().is_test(true).try_init(); + let map = LarodMap::new().unwrap(); + std::mem::drop(map); + } + + #[test] + fn larod_map_can_set_str() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_string("test_key", "test_value").unwrap(); + } + + #[test] + fn larod_map_can_get_str() { + let mut map = LarodMap::new().unwrap(); + map.set_string("test_key", "this_value").unwrap(); + let s = map.get_string("test_key").unwrap(); + assert_eq!(s, String::from("this_value")); + } + + #[test] + fn larod_map_can_set_int() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int("test_key", 10).unwrap(); + } + + #[test] + fn larod_map_can_get_int() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int("test_key", 9).unwrap(); + let i = map.get_int("test_key").unwrap(); + assert_eq!(i, 9); + } + + #[test] + fn larod_map_can_set_2_tuple() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int_arr2("test_key", (1, 2)).unwrap(); + } + #[test] + fn larod_map_can_get_2_tuple() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int_arr2("test_key", (5, 6)).unwrap(); + let arr = map.get_int_arr2("test_key").unwrap(); + assert_eq!(arr[0], 5); + assert_eq!(arr[1], 6); + } + + #[test] + fn larod_map_can_set_4_tuple() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int_arr4("test_key", (1, 2, 3, 4)).unwrap(); + } + + #[test] + fn larod_map_can_get_4_tuple() { + let _ = env_logger::builder().is_test(true).try_init(); + let mut map = LarodMap::new().unwrap(); + map.set_int_arr4("test_key", (1, 2, 3, 4)).unwrap(); + let arr = map.get_int_arr4("test_key").unwrap(); + assert_eq!(arr[0], 1); + assert_eq!(arr[1], 2); + assert_eq!(arr[2], 3); + assert_eq!(arr[3], 4); + } + + #[test] + fn it_establishes_session() { + let _ = env_logger::builder().is_test(true).try_init(); + Session::new(); + } + + #[test] + fn it_lists_devices() { + let _ = env_logger::builder().is_test(true).try_init(); + let sess = Session::new(); + let devices = sess.devices().unwrap(); + for device in devices { + log::info!( + "device: {}, id: {}, addr: {:?}", + device.name().unwrap(), + device.instance().unwrap(), + unsafe { std::ptr::addr_of!(*device.ptr) }, + ); + } + } + + #[test] + fn it_creates_and_destroys_preprocessor() { + let _ = env_logger::builder().is_test(true).try_init(); + let session = Session::new(); + let mut preprocessor = match Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session) + { + Ok(p) => p, + Err(Error::LarodError(e)) => { + log::error!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + log::error!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); + } + log::info!("Number of model inputs: {}", preprocessor.num_inputs); + } + + #[test] + fn model_errors_with_no_tensors() { + let _ = env_logger::builder().is_test(true).try_init(); + let session = Session::new(); + let mut preprocessor = match Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session) + { + Ok(p) => p, + Err(Error::LarodError(e)) => { + log::error!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + log::error!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); + } + log::info!("Number of model inputs: {}", preprocessor.num_inputs); + } + + #[test] + fn preprocessor_is_safe_without_model_tensors() { + let _ = env_logger::builder().is_test(true).try_init(); + let session = Session::new(); + let preprocessor = match Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session) + { + Ok(p) => p, + Err(Error::LarodError(e)) => { + log::error!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + log::error!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + log::info!("Number of model inputs: {}", preprocessor.num_inputs); + if let Some(tensors) = preprocessor.input_tensors() { + log::info!("input_tensor size: {}", tensors.len()); + for t in tensors.iter() { + log::info!("first_tensor dims {:?}", t.dims()); + } + } + } + + #[test] + fn preprocessor_can_iterate_model_tensors() { + let _ = env_logger::builder().is_test(true).try_init(); + let session = Session::new(); + let mut preprocessor = match Preprocessor::builder() + .input_format(ImageFormat::NV12) + .input_size(1920, 1080) + .output_size(1920, 1080) + .backend(PreProcBackend::LibYUV) + .load(&session) + { + Ok(p) => p, + Err(Error::LarodError(e)) => { + log::error!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + log::error!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); + } + log::info!("Number of model inputs: {}", preprocessor.num_inputs); + if let Some(tensors) = preprocessor.input_tensors() { + log::info!("input_tensor size: {}", tensors.len()); + for t in tensors.iter() { + log::info!("first_tensor dims {:?}", t.dims()); + } + } + } + } + + mod compiler_tests { + #[test] + #[ignore] + fn lifetimes() { + let t = trybuild::TestCases::new(); + t.compile_fail("compiler_tests/lifetimes/*.rs"); + } + } +}