From 151cd8c7ca0f3bf5e014ff8120f02146a8c962d4 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 25 Oct 2024 10:02:24 -0400 Subject: [PATCH 01/51] Add larod-sys crate --- Cargo.lock | 15 +++++++++++++++ Cargo.toml | 2 ++ crates/larod-sys/Cargo.toml | 11 +++++++++++ crates/larod-sys/build.rs | 23 +++++++++++++++++++++++ crates/larod-sys/src/bindings.rs | 3 +++ crates/larod-sys/src/lib.rs | 14 ++++++++++++++ crates/larod-sys/wrapper.h | 1 + crates/larod/Cargo.toml | 8 ++++++++ crates/larod/src/lib.rs | 14 ++++++++++++++ 9 files changed, 91 insertions(+) create mode 100644 crates/larod-sys/Cargo.toml create mode 100644 crates/larod-sys/build.rs create mode 100644 crates/larod-sys/src/bindings.rs create mode 100644 crates/larod-sys/src/lib.rs create mode 100644 crates/larod-sys/wrapper.h create mode 100644 crates/larod/Cargo.toml create mode 100644 crates/larod/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 15d29792..07a109e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1236,6 +1236,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "larod" +version = "0.1.0" +dependencies = [ + "larod-sys", +] + +[[package]] +name = "larod-sys" +version = "0.1.0" +dependencies = [ + "bindgen", + "pkg-config", +] + [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 3d718f16..0bda46b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,8 @@ axstorage = { path = "crates/axstorage" } axstorage-sys = { path = "crates/axstorage-sys" } bbox = { path = "crates/bbox" } bbox-sys = { path = "crates/bbox-sys" } +larod = { path = "crates/larod" } +larod-sys = { path = "crates/larod-sys" } licensekey = { path = "crates/licensekey" } licensekey-sys = { path = "crates/licensekey-sys" } mdb = { path = "crates/mdb" } 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..4485f5ec --- /dev/null +++ b/crates/larod-sys/build.rs @@ -0,0 +1,23 @@ +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.*)$") + .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..2efefc0a --- /dev/null +++ b/crates/larod-sys/src/bindings.rs @@ -0,0 +1,3 @@ +/* automatically generated by rust-bindgen 0.69.4 */ + + diff --git a/crates/larod-sys/src/lib.rs b/crates/larod-sys/src/lib.rs new file mode 100644 index 00000000..b93cf3ff --- /dev/null +++ b/crates/larod-sys/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} 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..f00f6840 --- /dev/null +++ b/crates/larod/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "larod" +version = "0.1.0" +edition.workspace = true + +[dependencies] + +larod-sys = { workspace = true } diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs new file mode 100644 index 00000000..b93cf3ff --- /dev/null +++ b/crates/larod/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 8d3defa45e1c55e3fe16b45773312632e225e3b2 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 25 Oct 2024 11:31:09 -0400 Subject: [PATCH 02/51] Add bindings. --- crates/larod-sys/src/bindings.rs | 552 ++++++++++++++++++++++++++++++- 1 file changed, 551 insertions(+), 1 deletion(-) diff --git a/crates/larod-sys/src/bindings.rs b/crates/larod-sys/src/bindings.rs index 2efefc0a..6ea38066 100644 --- a/crates/larod-sys/src/bindings.rs +++ b/crates/larod-sys/src/bindings.rs @@ -1,3 +1,553 @@ /* automatically generated by rust-bindgen 0.69.4 */ - +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct larodDevice { + _unused: [u8; 0], +} +pub const larodAccess_LAROD_ACCESS_INVALID: larodAccess = 0; +pub const larodAccess_LAROD_ACCESS_PRIVATE: larodAccess = 1; +pub const larodAccess_LAROD_ACCESS_PUBLIC: larodAccess = 2; +pub type larodAccess = ::std::os::raw::c_uint; +pub const larodErrorCode_LAROD_ERROR_NONE: larodErrorCode = 0; +pub const larodErrorCode_LAROD_ERROR_JOB: larodErrorCode = -1; +pub const larodErrorCode_LAROD_ERROR_LOAD_MODEL: larodErrorCode = -2; +pub const larodErrorCode_LAROD_ERROR_FD: larodErrorCode = -3; +pub const larodErrorCode_LAROD_ERROR_MODEL_NOT_FOUND: larodErrorCode = -4; +pub const larodErrorCode_LAROD_ERROR_PERMISSION: larodErrorCode = -5; +pub const larodErrorCode_LAROD_ERROR_CONNECTION: larodErrorCode = -6; +pub const larodErrorCode_LAROD_ERROR_CREATE_SESSION: larodErrorCode = -7; +pub const larodErrorCode_LAROD_ERROR_KILL_SESSION: larodErrorCode = -8; +pub const larodErrorCode_LAROD_ERROR_INVALID_CHIP_ID: larodErrorCode = -9; +pub const larodErrorCode_LAROD_ERROR_INVALID_ACCESS: larodErrorCode = -10; +pub const larodErrorCode_LAROD_ERROR_DELETE_MODEL: larodErrorCode = -11; +pub const larodErrorCode_LAROD_ERROR_TENSOR_MISMATCH: larodErrorCode = -12; +pub const larodErrorCode_LAROD_ERROR_VERSION_MISMATCH: larodErrorCode = -13; +pub const larodErrorCode_LAROD_ERROR_ALLOC: larodErrorCode = -14; +pub const larodErrorCode_LAROD_ERROR_POWER_NOT_AVAILABLE: larodErrorCode = -15; +pub const larodErrorCode_LAROD_ERROR_MAX_ERRNO: larodErrorCode = 1024; +pub type larodErrorCode = ::std::os::raw::c_int; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INVALID: larodTensorDataType = 0; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UNSPECIFIED: larodTensorDataType = 1; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_BOOL: larodTensorDataType = 2; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT8: larodTensorDataType = 3; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT8: larodTensorDataType = 4; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT16: larodTensorDataType = 5; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT16: larodTensorDataType = 6; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT32: larodTensorDataType = 7; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT32: larodTensorDataType = 8; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT64: larodTensorDataType = 9; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT64: larodTensorDataType = 10; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT16: larodTensorDataType = 11; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT32: larodTensorDataType = 12; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT64: larodTensorDataType = 13; +pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_MAX: larodTensorDataType = 14; +pub type larodTensorDataType = ::std::os::raw::c_uint; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_INVALID: larodTensorLayout = 0; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_UNSPECIFIED: larodTensorLayout = 1; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_NHWC: larodTensorLayout = 2; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_NCHW: larodTensorLayout = 3; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_420SP: larodTensorLayout = 4; +pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_MAX: larodTensorLayout = 5; +pub type larodTensorLayout = ::std::os::raw::c_uint; +#[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; +} From 709d58ce5fd6f7d68ad25925bde154eeb3b244b9 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Sat, 26 Oct 2024 11:59:06 -0400 Subject: [PATCH 03/51] Drop back to ACAP SDK 1.3. Get larod crate fleshed out a little to do testing. --- .devcontainer/Dockerfile | 8 ++-- .devhost/install-rust.sh | 4 +- .devhost/install-sdk.sh | 4 +- .devhost/install-venv.sh | 10 ++--- crates/larod-sys/src/lib.rs | 17 ++----- crates/larod/src/lib.rs | 89 ++++++++++++++++++++++++++++++++++--- 6 files changed, 101 insertions(+), 31 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 44fdcbf9..f9193a62 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,12 +2,12 @@ ARG REPO=axisecp ARG SDK=acap-native-sdk ARG UBUNTU_VERSION=22.04 # Keep in sync with `install-sdk.sh`. -ARG VERSION=1.15 +ARG VERSION=1.3 ARG BASE_IMAGE=debian:bookworm-20240423 -FROM --platform=linux/amd64 ${REPO}/${SDK}:${VERSION}-aarch64-ubuntu${UBUNTU_VERSION} AS sdk-aarch64 -FROM --platform=linux/amd64 ${REPO}/${SDK}:${VERSION}-armv7hf-ubuntu${UBUNTU_VERSION} AS sdk-armv7hf -FROM --platform=linux/amd64 ${BASE_IMAGE} +FROM --platform=linux/arm64 ${REPO}/${SDK}:${VERSION}-aarch64-ubuntu${UBUNTU_VERSION} AS sdk-aarch64 +FROM --platform=linux/arm64 ${REPO}/${SDK}:${VERSION}-armv7hf-ubuntu${UBUNTU_VERSION} AS sdk-armv7hf +FROM --platform=linux/arm64 ${BASE_IMAGE} COPY --from=sdk-aarch64 /opt/axis/acapsdk/axis-acap-manifest-tools /opt/axis/acapsdk/axis-acap-manifest-tools COPY --from=sdk-aarch64 /opt/axis/acapsdk/environment-setup-cortexa53-crypto-poky-linux /opt/axis/acapsdk/environment-setup-cortexa53-crypto-poky-linux diff --git a/.devhost/install-rust.sh b/.devhost/install-rust.sh index c0b7baf8..0f2ebf8c 100755 --- a/.devhost/install-rust.sh +++ b/.devhost/install-rust.sh @@ -3,9 +3,9 @@ set -eux curl \ --output /tmp/rustup-init \ - "https://static.rust-lang.org/rustup/archive/1.26.0/x86_64-unknown-linux-gnu/rustup-init" + "https://static.rust-lang.org/rustup/archive/1.26.0/aarch64-unknown-linux-gnu/rustup-init" -echo "0b2f6c8f85a3d02fde2efc0ced4657869d73fccfce59defb4e8d29233116e6db /tmp/rustup-init" \ +echo "673e336c81c65e6b16dcdede33f4cc9ed0f08bde1dbe7a935f113605292dc800 /tmp/rustup-init" \ | sha256sum -c - chmod +x /tmp/rustup-init diff --git a/.devhost/install-sdk.sh b/.devhost/install-sdk.sh index 56bab1e8..36f0d660 100755 --- a/.devhost/install-sdk.sh +++ b/.devhost/install-sdk.sh @@ -5,7 +5,7 @@ set -eux DIRECTORY="${1}" # Keep version in sync with `Dockerfile`. -docker run axisecp/acap-native-sdk:1.15-armv7hf-ubuntu22.04 tar \ +docker run axisecp/acap-native-sdk:1.3-armv7hf-ubuntu22.04 tar \ --create \ --directory /opt/ \ --file - \ @@ -18,7 +18,7 @@ docker run axisecp/acap-native-sdk:1.15-armv7hf-ubuntu22.04 tar \ --strip-components 1 # Keep version in sync with `Dockerfile`. -docker run axisecp/acap-native-sdk:1.15-aarch64-ubuntu22.04 tar \ +docker run axisecp/acap-native-sdk:1.3-aarch64-ubuntu22.04 tar \ --create \ --directory /opt/ \ --file - \ diff --git a/.devhost/install-venv.sh b/.devhost/install-venv.sh index 6c079cd8..40374e42 100755 --- a/.devhost/install-venv.sh +++ b/.devhost/install-venv.sh @@ -21,15 +21,15 @@ PIP_CONSTRAINT=constraints.txt pip install --requirement requirements.txt # automatically. curl \ --location \ - --output /tmp/node-v18.16.1-linux-x64.tar.gz \ - "https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-x64.tar.gz" + --output /tmp/node-v18.16.1-linux-arm64.tar.gz \ + "https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-arm64.tar.gz" -echo "59582f51570d0857de6333620323bdeee5ae36107318f86ce5eca24747cabf5b /tmp/node-v18.16.1-linux-x64.tar.gz" \ +echo "555b5c521e068acc976e672978ba0f5b1a0c030192b50639384c88143f4460bc /tmp/node-v18.16.1-linux-arm64.tar.gz" \ | sha256sum -c - -tar -xf "/tmp/node-v18.16.1-linux-x64.tar.gz" --strip-components 1 -C "${VIRTUAL_ENV}" +tar -xf "/tmp/node-v18.16.1-linux-arm64.tar.gz" --strip-components 1 -C "${VIRTUAL_ENV}" -rm /tmp/node-v18.16.1-linux-x64.tar.gz +rm /tmp/node-v18.16.1-linux-arm64.tar.gz # Install `devcontainer` into venv npm install -g @devcontainers/cli@0.65.0 diff --git a/crates/larod-sys/src/lib.rs b/crates/larod-sys/src/lib.rs index b93cf3ff..36fd376f 100644 --- a/crates/larod-sys/src/lib.rs +++ b/crates/larod-sys/src/lib.rs @@ -1,14 +1,5 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#[cfg(not(target_arch = "x86_64"))] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[cfg(target_arch = "x86_64")] +include!("bindings.rs"); diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index b93cf3ff..21864083 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1,14 +1,93 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use larod_sys::*; +use std::{ffi::CStr, ptr}; + +type Result = std::result::Result; + +// Define our error types. These may be customized for our error handling cases. +// Now we will be able to write our own errors, defer to an underlying error +// implementation, or do something in between. +#[derive(Debug, Clone)] +struct LarodError { + msg: String, + code: LarodErrorCode, +} + +impl From<*mut larodError> for LarodError { + fn from(e: *mut larodError) -> Self { + let le = unsafe { *e }; + let msg: String = unsafe { CStr::from_ptr(le.msg).to_str().unwrap().into() }; + let code: LarodErrorCode = le.code.into(); + Self { msg, code } + } +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone)] +enum LarodErrorCode { + NONE, + JOB, + LOAD_MODEL, + FD, + MODEL_NOT_FOUND, + PERMISSION, + CONNECTION, + CREATE_SESSION, + KILL_SESSION, + INVALID_CHIP_ID, + INVALID_ACCESS, + DELETE_MODEL, + TENSOR_MISMATCH, + VERSION_MISMATCH, + ALLOC, + POWER_NOT_AVAILABLE, + MAX_ERRNO, +} + +impl From for LarodErrorCode { + fn from(code: larodErrorCode) -> LarodErrorCode { + match code { + larodErrorCode_LAROD_ERROR_NONE => LarodErrorCode::NONE, + larodErrorCode_LAROD_ERROR_JOB => LarodErrorCode::JOB, + larodErrorCode_LAROD_ERROR_LOAD_MODEL => LarodErrorCode::LOAD_MODEL, + larodErrorCode_LAROD_ERROR_FD => LarodErrorCode::FD, + larodErrorCode_LAROD_ERROR_MODEL_NOT_FOUND => LarodErrorCode::MODEL_NOT_FOUND, + larodErrorCode_LAROD_ERROR_PERMISSION => LarodErrorCode::PERMISSION, + larodErrorCode_LAROD_ERROR_CONNECTION => LarodErrorCode::CONNECTION, + larodErrorCode_LAROD_ERROR_CREATE_SESSION => LarodErrorCode::CREATE_SESSION, + larodErrorCode_LAROD_ERROR_KILL_SESSION => LarodErrorCode::KILL_SESSION, + larodErrorCode_LAROD_ERROR_INVALID_CHIP_ID => LarodErrorCode::INVALID_CHIP_ID, + larodErrorCode_LAROD_ERROR_INVALID_ACCESS => LarodErrorCode::INVALID_ACCESS, + larodErrorCode_LAROD_ERROR_DELETE_MODEL => LarodErrorCode::DELETE_MODEL, + larodErrorCode_LAROD_ERROR_TENSOR_MISMATCH => LarodErrorCode::TENSOR_MISMATCH, + larodErrorCode_LAROD_ERROR_VERSION_MISMATCH => LarodErrorCode::VERSION_MISMATCH, + larodErrorCode_LAROD_ERROR_ALLOC => LarodErrorCode::ALLOC, + larodErrorCode_LAROD_ERROR_POWER_NOT_AVAILABLE => LarodErrorCode::POWER_NOT_AVAILABLE, + larodErrorCode_LAROD_ERROR_MAX_ERRNO => LarodErrorCode::MAX_ERRNO, + _ => unreachable!(), + } + } +} + +fn create_map() -> Result<*mut larodMap> { + let mut error: *mut larodError = ptr::null_mut(); + let map: *mut larodMap = unsafe { larodCreateMap(&mut error) }; + println!("map is_null? {:?}", map.is_null()); + println!("error is_null? {:?}", error.is_null()); + let e: LarodError = error.into(); + if !map.is_null() && matches!(e.code, LarodErrorCode::NONE) { + Ok(map) + } else { + Err(e) + } } #[cfg(test)] mod tests { use super::*; + use std::ptr; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn it_creates_larod_map() { + assert!(create_map().is_ok()); } } From e158693dfd171db6c14277d52e348aa4ef4b984e Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Sun, 27 Oct 2024 22:15:25 -0400 Subject: [PATCH 04/51] Swap back to x86_64 --- .devcontainer/Dockerfile | 22 ++++++++++++---------- .devhost/install-rust.sh | 4 ++-- .devhost/install-venv.sh | 10 +++++----- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f9193a62..3af97780 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -5,9 +5,9 @@ ARG UBUNTU_VERSION=22.04 ARG VERSION=1.3 ARG BASE_IMAGE=debian:bookworm-20240423 -FROM --platform=linux/arm64 ${REPO}/${SDK}:${VERSION}-aarch64-ubuntu${UBUNTU_VERSION} AS sdk-aarch64 -FROM --platform=linux/arm64 ${REPO}/${SDK}:${VERSION}-armv7hf-ubuntu${UBUNTU_VERSION} AS sdk-armv7hf -FROM --platform=linux/arm64 ${BASE_IMAGE} +FROM --platform=linux/amd64 ${REPO}/${SDK}:${VERSION}-aarch64-ubuntu${UBUNTU_VERSION} AS sdk-aarch64 +FROM --platform=linux/amd64 ${REPO}/${SDK}:${VERSION}-armv7hf-ubuntu${UBUNTU_VERSION} AS sdk-armv7hf +FROM --platform=linux/amd64 ${BASE_IMAGE} COPY --from=sdk-aarch64 /opt/axis/acapsdk/axis-acap-manifest-tools /opt/axis/acapsdk/axis-acap-manifest-tools COPY --from=sdk-aarch64 /opt/axis/acapsdk/environment-setup-cortexa53-crypto-poky-linux /opt/axis/acapsdk/environment-setup-cortexa53-crypto-poky-linux @@ -25,16 +25,10 @@ ENV \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-args=--sysroot=${SYSROOT_AARCH64}" \ CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc" \ CXX_aarch64_unknown_linux_gnu="aarch64-linux-gnu-g++" \ - PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}/usr/lib/pkgconfig:${SYSROOT_AARCH64}/usr/share/pkgconfig" \ - PKG_CONFIG_PATH_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}/usr/lib/pkgconfig:${SYSROOT_AARCH64}/usr/share/pkgconfig" \ - PKG_CONFIG_SYSROOT_DIR_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}" \ CARGO_TARGET_THUMBV7NEON_UNKNOWN_LINUX_GNUEABIHF_LINKER="arm-linux-gnueabihf-gcc" \ CARGO_TARGET_THUMBV7NEON_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="-C link-args=--sysroot=${SYSROOT_ARMV7HF}" \ CC_thumbv7neon_unknown_linux_gnueabihf="arm-linux-gnueabihf-gcc" \ - CXX_thumbv7neon_unknown_linux_gnueabihf="arm-linux-gnueabihf-g++" \ - PKG_CONFIG_LIBDIR_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}/usr/lib/pkgconfig:${SYSROOT_ARMV7HF}/usr/share/pkgconfig" \ - PKG_CONFIG_PATH_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}/usr/lib/pkgconfig:${SYSROOT_ARMV7HF}/usr/share/pkgconfig" \ - PKG_CONFIG_SYSROOT_DIR_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}" + CXX_thumbv7neon_unknown_linux_gnueabihf="arm-linux-gnueabihf-g++" COPY .devhost/install-system-packages.sh ./ RUN ./install-system-packages.sh \ @@ -61,6 +55,14 @@ RUN --mount=type=bind,target=/context \ && ./install-venv.sh $VIRTUAL_ENV \ && chmod a+w -R $VIRTUAL_ENV +ENV \ + PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}/usr/lib/pkgconfig:${SYSROOT_AARCH64}/usr/share/pkgconfig" \ + PKG_CONFIG_PATH_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}/usr/lib/pkgconfig:${SYSROOT_AARCH64}/usr/share/pkgconfig" \ + PKG_CONFIG_SYSROOT_DIR_aarch64_unknown_linux_gnu="${SYSROOT_AARCH64}" \ + PKG_CONFIG_LIBDIR_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}/usr/lib/pkgconfig:${SYSROOT_ARMV7HF}/usr/share/pkgconfig" \ + PKG_CONFIG_PATH_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}/usr/lib/pkgconfig:${SYSROOT_ARMV7HF}/usr/share/pkgconfig" \ + PKG_CONFIG_SYSROOT_DIR_thumbv7neon_unknown_linux_gnueabihf="${SYSROOT_ARMV7HF}" + # If neither `CARGO_HOME` nor `HOME` is set when launching a container, then cargo will try to # download crates to this directory. If launched with the `--user` option then this will fail. # TODO: Replace the example in the README with something that does not mount any volumes. diff --git a/.devhost/install-rust.sh b/.devhost/install-rust.sh index 0f2ebf8c..c0b7baf8 100755 --- a/.devhost/install-rust.sh +++ b/.devhost/install-rust.sh @@ -3,9 +3,9 @@ set -eux curl \ --output /tmp/rustup-init \ - "https://static.rust-lang.org/rustup/archive/1.26.0/aarch64-unknown-linux-gnu/rustup-init" + "https://static.rust-lang.org/rustup/archive/1.26.0/x86_64-unknown-linux-gnu/rustup-init" -echo "673e336c81c65e6b16dcdede33f4cc9ed0f08bde1dbe7a935f113605292dc800 /tmp/rustup-init" \ +echo "0b2f6c8f85a3d02fde2efc0ced4657869d73fccfce59defb4e8d29233116e6db /tmp/rustup-init" \ | sha256sum -c - chmod +x /tmp/rustup-init diff --git a/.devhost/install-venv.sh b/.devhost/install-venv.sh index 40374e42..6c079cd8 100755 --- a/.devhost/install-venv.sh +++ b/.devhost/install-venv.sh @@ -21,15 +21,15 @@ PIP_CONSTRAINT=constraints.txt pip install --requirement requirements.txt # automatically. curl \ --location \ - --output /tmp/node-v18.16.1-linux-arm64.tar.gz \ - "https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-arm64.tar.gz" + --output /tmp/node-v18.16.1-linux-x64.tar.gz \ + "https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-x64.tar.gz" -echo "555b5c521e068acc976e672978ba0f5b1a0c030192b50639384c88143f4460bc /tmp/node-v18.16.1-linux-arm64.tar.gz" \ +echo "59582f51570d0857de6333620323bdeee5ae36107318f86ce5eca24747cabf5b /tmp/node-v18.16.1-linux-x64.tar.gz" \ | sha256sum -c - -tar -xf "/tmp/node-v18.16.1-linux-arm64.tar.gz" --strip-components 1 -C "${VIRTUAL_ENV}" +tar -xf "/tmp/node-v18.16.1-linux-x64.tar.gz" --strip-components 1 -C "${VIRTUAL_ENV}" -rm /tmp/node-v18.16.1-linux-arm64.tar.gz +rm /tmp/node-v18.16.1-linux-x64.tar.gz # Install `devcontainer` into venv npm install -g @devcontainers/cli@0.65.0 From 024e491c855ecbf94e202d95d96716da9b19fd88 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Sun, 27 Oct 2024 22:15:41 -0400 Subject: [PATCH 05/51] Start implementing LarodMap --- crates/larod/src/lib.rs | 142 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 21864083..56367655 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1,5 +1,8 @@ use larod_sys::*; -use std::{ffi::CStr, ptr}; +use std::{ + ffi::{CStr, CString}, + ptr, +}; type Result = std::result::Result; @@ -68,16 +71,101 @@ impl From for LarodErrorCode { } } -fn create_map() -> Result<*mut larodMap> { - let mut error: *mut larodError = ptr::null_mut(); - let map: *mut larodMap = unsafe { larodCreateMap(&mut error) }; - println!("map is_null? {:?}", map.is_null()); - println!("error is_null? {:?}", error.is_null()); - let e: LarodError = error.into(); - if !map.is_null() && matches!(e.code, LarodErrorCode::NONE) { - Ok(map) - } else { - Err(e) +pub struct LarodMap { + raw: *mut larodMap, +} + +impl LarodMap { + fn new() -> Result { + let mut error: *mut larodError = ptr::null_mut(); + let map: *mut larodMap = unsafe { larodCreateMap(&mut error) }; + println!("map is_null? {:?}", map.is_null()); + println!("error is_null? {:?}", error.is_null()); + let e: LarodError = error.into(); + if !map.is_null() && matches!(e.code, LarodErrorCode::NONE) { + Ok(Self { raw: map }) + } else { + Err(e) + } + } + + fn set_string(&mut self, k: &str, v: &str) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let Ok(value_cstr) = CString::new(v.as_bytes()) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string value CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let success = + unsafe { larodMapSetStr(self.raw, key_cstr.as_ptr(), value_cstr.as_ptr(), &mut error) }; + if success { + Ok(()) + } else { + Err(error.into()) + } + } + fn set_int(&mut self, k: &str, v: i64) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let success = unsafe { larodMapSetInt(self.raw, key_cstr.as_ptr(), v, &mut error) }; + if success { + Ok(()) + } else { + Err(error.into()) + } + } + fn set_int_arr2(&mut self, k: &str, v: (i64, i64)) -> Result<()> { + let Ok(key_cstr) = CString::new(k.as_bytes()) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let success = + unsafe { larodMapSetIntArr2(self.raw, key_cstr.as_ptr(), v.0, v.1, &mut error) }; + if success { + Ok(()) + } else { + Err(error.into()) + } + } + 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(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let success = unsafe { + larodMapSetIntArr4(self.raw, key_cstr.as_ptr(), v.0, v.1, v.2, v.3, &mut error) + }; + if success { + Ok(()) + } else { + Err(error.into()) + } + } +} + +impl std::ops::Drop for LarodMap { + fn drop(&mut self) { + unsafe { + larodDestroyMap(&mut self.raw); + } } } @@ -88,6 +176,36 @@ mod tests { #[test] fn it_creates_larod_map() { - assert!(create_map().is_ok()); + assert!(LarodMap::new().is_ok()); + } + + #[test] + fn it_drops_map() { + let map = LarodMap::new().unwrap(); + std::mem::drop(map); + } + + #[test] + fn larod_map_can_set_str() { + let mut map = LarodMap::new().unwrap(); + map.set_string("test_key", "test_value").unwrap(); + } + + #[test] + fn larod_map_can_set_int() { + let mut map = LarodMap::new().unwrap(); + map.set_int("test_key", 10).unwrap(); + } + + #[test] + fn larod_map_can_set_2_tuple() { + let mut map = LarodMap::new().unwrap(); + map.set_int_arr2("test_key", (1, 2)).unwrap(); + } + + #[test] + fn larod_map_can_set_4_tuple() { + let mut map = LarodMap::new().unwrap(); + map.set_int_arr4("test_key", (1, 2, 3, 4)).unwrap(); } } From 56d372043b5c48f2e9af3dac54d03d7b72d137d7 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Mon, 28 Oct 2024 22:21:01 -0400 Subject: [PATCH 06/51] Implement get_string, get_int_arr2, get_int_arr4. --- crates/larod/src/lib.rs | 93 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 56367655..25fdb502 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1,7 +1,8 @@ +use core::slice; use larod_sys::*; use std::{ ffi::{CStr, CString}, - ptr, + ptr::{self, slice_from_raw_parts}, }; type Result = std::result::Result; @@ -43,6 +44,7 @@ enum LarodErrorCode { VERSION_MISMATCH, ALLOC, POWER_NOT_AVAILABLE, + INVALID_TYPE, MAX_ERRNO, } @@ -159,6 +161,95 @@ impl LarodMap { Err(error.into()) } } + + fn get_string(&self, k: &str) -> Result { + let Ok(key_cstr) = CString::new(k) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let s = unsafe { CStr::from_ptr(larodMapGetStr(self.raw, key_cstr.as_ptr(), &mut error)) }; + let Ok(rs) = s.to_str() else { + return Err(LarodError { + msg: String::from("Returned string is not valid UTF-8"), + code: LarodErrorCode::INVALID_TYPE, + }); + }; + Ok(String::from(rs)) + } + fn get_int(&self, k: &str) -> Result { + let Ok(key_cstr) = CString::new(k) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let mut v: i64 = 0; + let success = unsafe { larodMapGetInt(self.raw, key_cstr.as_ptr(), &mut v, &mut error) }; + if success { + Ok(v) + } else { + Err(error.into()) + } + } + fn get_int_arr2(&self, k: &str) -> Result<&[i64; 2]> { + let Ok(key_cstr) = CString::new(k) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let maybe_int_arr = unsafe { + let ip = larodMapGetIntArr2(self.raw, key_cstr.as_ptr(), &mut error); + if ip.is_null() { + return Err(LarodError { + msg: String::from("Could not get integer array from LarodMap"), + code: LarodErrorCode::INVALID_TYPE, + }); + } else { + slice::from_raw_parts(ip, 2).try_into() + } + }; + let Ok(int_arr) = maybe_int_arr else { + return Err(LarodError { + msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), + code: LarodErrorCode::INVALID_TYPE, + }); + }; + Ok(int_arr) + } + + fn get_int_arr4(&self, k: &str) -> Result<&[i64; 4]> { + let Ok(key_cstr) = CString::new(k) else { + return Err(LarodError { + msg: String::from("Could not allocate set_string key CString"), + code: LarodErrorCode::ALLOC, + }); + }; + let mut error: *mut larodError = ptr::null_mut(); + let maybe_int_arr = unsafe { + let ip = larodMapGetIntArr4(self.raw, key_cstr.as_ptr(), &mut error); + if ip.is_null() { + return Err(LarodError { + msg: String::from("Could not get integer array from LarodMap"), + code: LarodErrorCode::INVALID_TYPE, + }); + } else { + slice::from_raw_parts(ip, 4).try_into() + } + }; + let Ok(int_arr) = maybe_int_arr else { + return Err(LarodError { + msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), + code: LarodErrorCode::INVALID_TYPE, + }); + }; + Ok(int_arr) + } } impl std::ops::Drop for LarodMap { From 3d510b6045f97d7c42b2da0fc49d7d2f901ec82d Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 29 Oct 2024 23:44:24 -0400 Subject: [PATCH 07/51] - Improved error handling to always deallocate a larodError object if the pointer to it is non-NULL. - Added some documentation. - Mimicked the try_from macro from the axevent crate to DRY the code. --- crates/larod/src/lib.rs | 231 +++++++++++++++++++++++++++------------- 1 file changed, 157 insertions(+), 74 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 25fdb502..c6ea5073 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1,27 +1,69 @@ +//! A safe warpper around the larod-sys bindings to the larod C library. +//! +//! # Gotchas +//! Many of the C functions return either a bool or a pointer to some object. +//! Additionally, one of the out arguments is the pointer to the larodError +//! struct. 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 copying the larodError data into a Rust LarodError struct and +//! dealocating the larodError object if it is non-NULL. + use core::slice; use larod_sys::*; use std::{ - ffi::{CStr, CString}, + ffi::{c_char, CStr, CString}, ptr::{self, slice_from_raw_parts}, }; type Result = std::result::Result; +macro_rules! try_func { + ($func:ident $(,)?) => {{ + let mut error: *mut larodError = ptr::null_mut(); + let success = $func(&mut error); + (success, LarodError::from(error)) + }}; + ($func:ident, $($arg:expr),+ $(,)?) => {{ + let mut error: *mut larodError = ptr::null_mut(); + let success = $func($( $arg ),+, &mut error); + (success, LarodError::from(error)) + }} +} + // Define our error types. These may be customized for our error handling cases. // Now we will be able to write our own errors, defer to an underlying error // implementation, or do something in between. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct LarodError { msg: String, code: LarodErrorCode, } +/// Convert from liblarod larodError to LarodError +/// If larodError is not NULL, it must be dealocated by calling larodClearError impl From<*mut larodError> for LarodError { - fn from(e: *mut larodError) -> Self { - let le = unsafe { *e }; - let msg: String = unsafe { CStr::from_ptr(le.msg).to_str().unwrap().into() }; - let code: LarodErrorCode = le.code.into(); - Self { msg, code } + fn from(mut 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 } + } } } @@ -48,6 +90,12 @@ enum LarodErrorCode { MAX_ERRNO, } +impl Default for LarodErrorCode { + fn default() -> Self { + LarodErrorCode::NONE + } +} + impl From for LarodErrorCode { fn from(code: larodErrorCode) -> LarodErrorCode { match code { @@ -79,15 +127,15 @@ pub struct LarodMap { impl LarodMap { fn new() -> Result { - let mut error: *mut larodError = ptr::null_mut(); - let map: *mut larodMap = unsafe { larodCreateMap(&mut error) }; - println!("map is_null? {:?}", map.is_null()); - println!("error is_null? {:?}", error.is_null()); - let e: LarodError = error.into(); - if !map.is_null() && matches!(e.code, LarodErrorCode::NONE) { - Ok(Self { raw: map }) - } else { + let (map, e): (*mut larodMap, LarodError) = unsafe { try_func!(larodCreateMap) }; + if map.is_null() { Err(e) + } else { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodCreateMap allocated a map AND returned an error!" + ); + Ok(Self { raw: map }) } } @@ -104,13 +152,22 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let success = - unsafe { larodMapSetStr(self.raw, key_cstr.as_ptr(), value_cstr.as_ptr(), &mut error) }; + let (success, e): (bool, LarodError) = unsafe { + try_func!( + larodMapSetStr, + self.raw, + key_cstr.as_ptr(), + value_cstr.as_ptr(), + ) + }; if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapSetStr indicated success AND returned an error!" + ); Ok(()) } else { - Err(error.into()) + Err(e) } } fn set_int(&mut self, k: &str, v: i64) -> Result<()> { @@ -120,12 +177,16 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let success = unsafe { larodMapSetInt(self.raw, key_cstr.as_ptr(), v, &mut error) }; + let (success, e): (bool, LarodError) = + unsafe { try_func!(larodMapSetInt, self.raw, key_cstr.as_ptr(), v) }; if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapSetInt indicated success AND returned an error!" + ); Ok(()) } else { - Err(error.into()) + Err(e) } } fn set_int_arr2(&mut self, k: &str, v: (i64, i64)) -> Result<()> { @@ -135,13 +196,17 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let success = - unsafe { larodMapSetIntArr2(self.raw, key_cstr.as_ptr(), v.0, v.1, &mut error) }; + let (success, e): (bool, LarodError) = + unsafe { try_func!(larodMapSetIntArr2, self.raw, key_cstr.as_ptr(), v.0, v.1) }; + if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapSetIntArr2 indicated success AND returned an error!" + ); Ok(()) } else { - Err(error.into()) + Err(e) } } fn set_int_arr4(&mut self, k: &str, v: (i64, i64, i64, i64)) -> Result<()> { @@ -151,14 +216,26 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let success = unsafe { - larodMapSetIntArr4(self.raw, key_cstr.as_ptr(), v.0, v.1, v.2, v.3, &mut error) + let (success, e): (bool, LarodError) = unsafe { + try_func!( + larodMapSetIntArr4, + self.raw, + key_cstr.as_ptr(), + v.0, + v.1, + v.2, + v.3 + ) }; + if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapSetIntArr4 indicated success AND returned an error!" + ); Ok(()) } else { - Err(error.into()) + Err(e) } } @@ -169,15 +246,21 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let s = unsafe { CStr::from_ptr(larodMapGetStr(self.raw, key_cstr.as_ptr(), &mut error)) }; - let Ok(rs) = s.to_str() else { + let (c_str_ptr, e): (*const c_char, LarodError) = + 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!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapGetStr returned a string AND returned an error!" + ); + Ok(String::from(rs)) + } else { return Err(LarodError { msg: String::from("Returned string is not valid UTF-8"), code: LarodErrorCode::INVALID_TYPE, }); - }; - Ok(String::from(rs)) + } } fn get_int(&self, k: &str) -> Result { let Ok(key_cstr) = CString::new(k) else { @@ -186,13 +269,17 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); let mut v: i64 = 0; - let success = unsafe { larodMapGetInt(self.raw, key_cstr.as_ptr(), &mut v, &mut error) }; + let (success, e): (bool, LarodError) = + unsafe { try_func!(larodMapGetInt, self.raw, key_cstr.as_ptr(), &mut v) }; if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapGetInt indicated success AND returned an error!" + ); Ok(v) } else { - Err(error.into()) + Err(e) } } fn get_int_arr2(&self, k: &str) -> Result<&[i64; 2]> { @@ -202,25 +289,23 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let maybe_int_arr = unsafe { - let ip = larodMapGetIntArr2(self.raw, key_cstr.as_ptr(), &mut error); - if ip.is_null() { - return Err(LarodError { - msg: String::from("Could not get integer array from LarodMap"), - code: LarodErrorCode::INVALID_TYPE, - }); - } else { - slice::from_raw_parts(ip, 2).try_into() + let (out_arr, e) = unsafe { try_func!(larodMapGetIntArr2, self.raw, key_cstr.as_ptr()) }; + if out_arr.is_null() { + Err(e) + } else { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapGetInt indicated success AND returned an error!" + ); + unsafe { + slice::from_raw_parts(out_arr, 2) + .try_into() + .or(Err(LarodError { + msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), + code: LarodErrorCode::INVALID_TYPE, + })) } - }; - let Ok(int_arr) = maybe_int_arr else { - return Err(LarodError { - msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), - code: LarodErrorCode::INVALID_TYPE, - }); - }; - Ok(int_arr) + } } fn get_int_arr4(&self, k: &str) -> Result<&[i64; 4]> { @@ -230,25 +315,23 @@ impl LarodMap { code: LarodErrorCode::ALLOC, }); }; - let mut error: *mut larodError = ptr::null_mut(); - let maybe_int_arr = unsafe { - let ip = larodMapGetIntArr4(self.raw, key_cstr.as_ptr(), &mut error); - if ip.is_null() { - return Err(LarodError { - msg: String::from("Could not get integer array from LarodMap"), - code: LarodErrorCode::INVALID_TYPE, - }); - } else { - slice::from_raw_parts(ip, 4).try_into() + let (out_arr, e) = unsafe { try_func!(larodMapGetIntArr4, self.raw, key_cstr.as_ptr()) }; + if out_arr.is_null() { + Err(e) + } else { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodMapGetIntArr4 indicated success AND returned an error!" + ); + unsafe { + slice::from_raw_parts(out_arr, 4) + .try_into() + .or(Err(LarodError { + msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), + code: LarodErrorCode::INVALID_TYPE, + })) } - }; - let Ok(int_arr) = maybe_int_arr else { - return Err(LarodError { - msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), - code: LarodErrorCode::INVALID_TYPE, - }); - }; - Ok(int_arr) + } } } From 3e004a0f8bb6e1e6369dccd65e38ebdd3799af16 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Wed, 30 Oct 2024 00:03:24 -0400 Subject: [PATCH 08/51] Added builder pattern for LarodClient. --- crates/larod/src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index c6ea5073..0372342e 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -12,6 +12,10 @@ //! to a larodError struct need to be dealocated. That is handled appropriately //! by copying the larodError data into a Rust LarodError struct and //! dealocating the larodError object if it is non-NULL. +//! +//! # 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; use larod_sys::*; @@ -343,6 +347,36 @@ impl std::ops::Drop for LarodMap { } } +pub struct LarodClientBuilder {} + +impl LarodClientBuilder { + pub fn build() -> Result { + let mut connection: *mut larodConnection = ptr::null_mut(); + let (success, e): (bool, LarodError) = unsafe { try_func!(larodConnect, &mut connection) }; + if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodConnect indicated success AND returned an error!" + ); + Ok(LarodClient { connection }) + } else { + Err(e) + } + } +} + +pub struct LarodClient { + connection: *mut larodConnection, +} + +impl std::ops::Drop for LarodClient { + fn drop(&mut self) { + unsafe { + try_func!(larodDisconnect, &mut self.connection); + } + } +} + #[cfg(test)] mod tests { use super::*; From 021ea65984488c81ccbcccccf3584a0f530b8c6b Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 31 Oct 2024 07:37:42 -0400 Subject: [PATCH 09/51] Add log. --- Cargo.lock | 1 + crates/larod/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d3848bc3..350e5f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1288,6 +1288,7 @@ name = "larod" version = "0.1.0" dependencies = [ "larod-sys", + "log", ] [[package]] diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index f00f6840..021942a9 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -6,3 +6,4 @@ edition.workspace = true [dependencies] larod-sys = { workspace = true } +log.workspace = true From f9e05066df35477f115a4636c44c1493baca5c57 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 31 Oct 2024 07:38:56 -0400 Subject: [PATCH 10/51] Start adding LarodDevice and Session - Stubbed out functions for Session (larodConnection) - Start implementing a few functions to list hand handle larodDevices --- crates/larod/src/lib.rs | 221 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 199 insertions(+), 22 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 0372342e..562b4170 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -17,11 +17,13 @@ //! - [ ] [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; +use core::{num, slice}; use larod_sys::*; use std::{ - ffi::{c_char, CStr, CString}, + collections::HashMap, + ffi::{c_char, c_int, CStr, CString}, ptr::{self, slice_from_raw_parts}, + slice::from_raw_parts, }; type Result = std::result::Result; @@ -43,15 +45,15 @@ macro_rules! try_func { // Now we will be able to write our own errors, defer to an underlying error // implementation, or do something in between. #[derive(Debug, Clone, Default)] -struct LarodError { - msg: String, - code: LarodErrorCode, +pub struct LarodError { + pub msg: String, + pub code: LarodErrorCode, } -/// Convert from liblarod larodError to LarodError -/// If larodError is not NULL, it must be dealocated by calling larodClearError -impl From<*mut larodError> for LarodError { - fn from(mut e: *mut larodError) -> Self { +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 { @@ -63,9 +65,9 @@ impl From<*mut larodError> for LarodError { .into() }; let code: LarodErrorCode = le.code.into(); - unsafe { - larodClearError(&mut e); - } + // unsafe { + // larodClearError(&mut e); + // } Self { msg, code } } } @@ -347,32 +349,207 @@ impl std::ops::Drop for LarodMap { } } -pub struct LarodClientBuilder {} +#[derive(Debug)] +pub struct LarodDevice { + ptr: *const larodDevice, + name: String, + id: u32, +} + +impl LarodDevice { + pub fn get_name(&self) -> &str { + &self.name + } -impl LarodClientBuilder { - pub fn build() -> Result { - let mut connection: *mut larodConnection = ptr::null_mut(); - let (success, e): (bool, LarodError) = unsafe { try_func!(larodConnect, &mut connection) }; + fn larod_get_name(pointer: *const larodDevice) -> Result { + unsafe { + let (c_char_ptr, error) = try_func!(larodGetDeviceName, pointer); + if c_char_ptr.is_null() { + Err(error) + } else { + debug_assert!( + matches!(error.code, LarodErrorCode::NONE), + "larodGetDeviceName returned an object pointer AND returned an error!" + ); + let c_name = CStr::from_ptr(c_char_ptr); + c_name + .to_str() + .map(|n| String::from(n)) + .map_err(|e| LarodError { + msg: String::from("Returned string is not valid UTF-8"), + code: LarodErrorCode::INVALID_TYPE, + }) + } + } + } + + fn larod_get_instance(pointer: *const larodDevice) -> Result { + unsafe { + let mut instance: u32 = 0; + let (success, error) = try_func!(larodGetDeviceInstance, pointer, &mut instance); + if success { + debug_assert!( + matches!(error.code, LarodErrorCode::NONE), + "larodGetDeviceInstance returned success AND returned an error!" + ); + Ok(instance) + } else { + Err(error) + } + } + } +} + +impl TryFrom<*const larodDevice> for LarodDevice { + type Error = LarodError; + fn try_from(value: *const larodDevice) -> Result { + let name = LarodDevice::larod_get_name(value)?; + let id = LarodDevice::larod_get_instance(value)?; + Ok(Self { + ptr: value, + name, + id, + }) + } +} + +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, e): (bool, LarodError) = unsafe { try_func!(larodConnect, &mut conn) }; if success { debug_assert!( matches!(e.code, LarodErrorCode::NONE), "larodConnect indicated success AND returned an error!" ); - Ok(LarodClient { connection }) + Ok(Session { + conn, + model_map: HashMap::new(), + devices: Vec::new(), + }) } else { Err(e) } } } -pub struct LarodClient { - connection: *mut larodConnection, +pub struct Session { + conn: *mut larodConnection, + model_map: HashMap, + devices: Vec, +} + +// 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, e): (bool, LarodError) = + unsafe { try_func!(larodDisconnect, &mut self.conn) }; + if success { + debug_assert!( + matches!(e.code, LarodErrorCode::NONE), + "larodDisconnect indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(e) + } + } + pub fn num_sessions() -> Result<()> { + Ok(()) + } + pub fn device() -> Result<()> { + Ok(()) + } + pub fn list_chips() -> Result<()> { + Ok(()) + } + pub fn list_devices(&mut self) -> Result> { + let mut num_devices: usize = 0; + let (d, e) = unsafe { + let (dev_ptr, e) = try_func!(larodListDevices, self.conn, &mut num_devices); + if dev_ptr.is_null() { + return Err(e); + } + let raw_devices = unsafe { slice::from_raw_parts(dev_ptr, num_devices) }; + let mut devices: Vec = Vec::with_capacity(num_devices); + for (idx, raw_device) in raw_devices.into_iter().enumerate() { + let device = LarodDevice::try_from(*raw_device); + match device { + Ok(d) => devices.push(d), + Err(conv_error) => + log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{}", idx, num_devices, conv_error.msg), + } + } + (devices, e) + }; + Ok(d) + } + + // Overloaded need to check that. + pub fn load_model(&mut self) -> Result<()> { + // let model_fd: c_int = 0; + // let (m, e) = unsafe { + // try_func!(larodLoadModel, &mut self.conn, model_fd, ) + // } + Ok(()) + } + pub fn get_model() -> Result<()> { + Ok(()) + } + pub fn get_models() -> Result<()> { + Ok(()) + } + pub fn delete_model() -> 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() -> Result<()> { + Ok(()) + } + 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 std::ops::Drop for LarodClient { +impl std::ops::Drop for Session { fn drop(&mut self) { unsafe { - try_func!(larodDisconnect, &mut self.connection); + try_func!(larodDisconnect, &mut self.conn); } } } From c05a0a1625c7669a5060ea5712dc5dbfe588083f Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 1 Nov 2024 20:39:46 -0400 Subject: [PATCH 11/51] Add remote testing - Add testing documentation to README.md - Add runner specification to .cargo/config.toml (new) - Set `aarch64-unknown-linux-gnu` to default build target. - Add remote-test.sh script to run tests on remote target. --- .cargo/config.toml | 5 +++++ README.md | 14 ++++++++++++++ remote-test.sh | 10 ++++++++++ 3 files changed, 29 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 remote-test.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..d5eccbc8 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "aarch64-unknown-linux-gnu" + +[target.aarch64-unknown-linux-gnu] +runner = ["/workspaces/acap-rs/remote-test.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 41022f42..e09dd43e 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,20 @@ Important workflows are documented in the [Makefile](./Makefile) and can now be Development environments outside containers are more difficult to reproduce and maintain. Should it nonetheless be of interest, one procedure is documented in [this workflow](.github/workflows/on-host-workflow.yml). +## Testing +Some items in this workspace rely on libraries or hardware on Axis cameras. This makes testing difficult since these tests cannot run on an arbitrary x86_64 host. Below are some steps to enable running unit test on device. + +1. Connect an Axis camera to your network and ensure it is accessible. +2. The user will likely need to be `root`, such that the Axis camera file system is writable. +3. Set the `CARGO_TEST_CAMERA` environment variable to the user and IP of the camera with the format `user@ip` +4. Set up an identity based SSH connection to the camera. + 1. Create an ID via `ssh-keygen` + 2. Copy the id to the device via `ssh-copy-id` + +Now, via the [remote-test.sh](remote-test.sh) script, and the `runner = ["/workspaces/acap-rs/remote-test.sh"]` line in the .cargo/config.toml, tests with the `aarch64-unknown-linux-gnu` target triplet will automatically be copied to the remote camera and executed there. Run these tests via `cargo test --target aarch64-unknown-linux-gnu`. + +If you want to run tests locally, just make sure you clear the `CARGO_TEST_CAMERA` environment variable via `unset CARGO_TEST_CAMERA`. + ## Example applications Below is the list of examples available in the repository. diff --git a/remote-test.sh b/remote-test.sh new file mode 100644 index 00000000..02a1c7f7 --- /dev/null +++ b/remote-test.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +if [[ -z "${CARGO_TEST_CAMERA}" ]]; then + f=`basename $1` + scp "$1" $CARGO_TEST_CAMERA:. + # echo $f + ssh $CARGO_TEST_CAMERA "chmod +x /root/$f" + ssh $CARGO_TEST_CAMERA "/root/$f" + # ^ note: may need to change this line, see https://stackoverflow.com/q/9379400 +fi From e0fa1403aa0dc095ed9cc6be152f19525cbe3708 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 1 Nov 2024 22:54:03 -0400 Subject: [PATCH 12/51] Hide on-device tests behind a device-tests feature --- crates/larod/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 021942a9..dad74c68 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -7,3 +7,6 @@ edition.workspace = true larod-sys = { workspace = true } log.workspace = true + +[features] +device-tests = [] \ No newline at end of file From 147e438d6789330c94067ebafcc25f65ee428279 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 1 Nov 2024 22:54:39 -0400 Subject: [PATCH 13/51] Inverted remote-test.sh check for CARGO_TEST_CAMERA definition --- remote-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote-test.sh b/remote-test.sh index 02a1c7f7..78b8fadf 100644 --- a/remote-test.sh +++ b/remote-test.sh @@ -1,6 +1,6 @@ #!/bin/sh set -e -if [[ -z "${CARGO_TEST_CAMERA}" ]]; then +if [ -n "${CARGO_TEST_CAMERA}" ]; then f=`basename $1` scp "$1" $CARGO_TEST_CAMERA:. # echo $f From 32db020e821b9695d1e071e081c6b6d51d69163f Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Mon, 4 Nov 2024 11:16:01 -0500 Subject: [PATCH 14/51] Update larod_sys/src/bindings.rs to match new enum style. --- crates/larod-sys/src/bindings.rs | 105 ++++++++++++++++++------------- 1 file changed, 60 insertions(+), 45 deletions(-) diff --git a/crates/larod-sys/src/bindings.rs b/crates/larod-sys/src/bindings.rs index 6ea38066..6e5497ab 100644 --- a/crates/larod-sys/src/bindings.rs +++ b/crates/larod-sys/src/bindings.rs @@ -5,51 +5,66 @@ pub struct larodDevice { _unused: [u8; 0], } -pub const larodAccess_LAROD_ACCESS_INVALID: larodAccess = 0; -pub const larodAccess_LAROD_ACCESS_PRIVATE: larodAccess = 1; -pub const larodAccess_LAROD_ACCESS_PUBLIC: larodAccess = 2; -pub type larodAccess = ::std::os::raw::c_uint; -pub const larodErrorCode_LAROD_ERROR_NONE: larodErrorCode = 0; -pub const larodErrorCode_LAROD_ERROR_JOB: larodErrorCode = -1; -pub const larodErrorCode_LAROD_ERROR_LOAD_MODEL: larodErrorCode = -2; -pub const larodErrorCode_LAROD_ERROR_FD: larodErrorCode = -3; -pub const larodErrorCode_LAROD_ERROR_MODEL_NOT_FOUND: larodErrorCode = -4; -pub const larodErrorCode_LAROD_ERROR_PERMISSION: larodErrorCode = -5; -pub const larodErrorCode_LAROD_ERROR_CONNECTION: larodErrorCode = -6; -pub const larodErrorCode_LAROD_ERROR_CREATE_SESSION: larodErrorCode = -7; -pub const larodErrorCode_LAROD_ERROR_KILL_SESSION: larodErrorCode = -8; -pub const larodErrorCode_LAROD_ERROR_INVALID_CHIP_ID: larodErrorCode = -9; -pub const larodErrorCode_LAROD_ERROR_INVALID_ACCESS: larodErrorCode = -10; -pub const larodErrorCode_LAROD_ERROR_DELETE_MODEL: larodErrorCode = -11; -pub const larodErrorCode_LAROD_ERROR_TENSOR_MISMATCH: larodErrorCode = -12; -pub const larodErrorCode_LAROD_ERROR_VERSION_MISMATCH: larodErrorCode = -13; -pub const larodErrorCode_LAROD_ERROR_ALLOC: larodErrorCode = -14; -pub const larodErrorCode_LAROD_ERROR_POWER_NOT_AVAILABLE: larodErrorCode = -15; -pub const larodErrorCode_LAROD_ERROR_MAX_ERRNO: larodErrorCode = 1024; -pub type larodErrorCode = ::std::os::raw::c_int; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INVALID: larodTensorDataType = 0; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UNSPECIFIED: larodTensorDataType = 1; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_BOOL: larodTensorDataType = 2; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT8: larodTensorDataType = 3; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT8: larodTensorDataType = 4; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT16: larodTensorDataType = 5; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT16: larodTensorDataType = 6; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT32: larodTensorDataType = 7; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT32: larodTensorDataType = 8; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_UINT64: larodTensorDataType = 9; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_INT64: larodTensorDataType = 10; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT16: larodTensorDataType = 11; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT32: larodTensorDataType = 12; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_FLOAT64: larodTensorDataType = 13; -pub const larodTensorDataType_LAROD_TENSOR_DATA_TYPE_MAX: larodTensorDataType = 14; -pub type larodTensorDataType = ::std::os::raw::c_uint; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_INVALID: larodTensorLayout = 0; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_UNSPECIFIED: larodTensorLayout = 1; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_NHWC: larodTensorLayout = 2; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_NCHW: larodTensorLayout = 3; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_420SP: larodTensorLayout = 4; -pub const larodTensorLayout_LAROD_TENSOR_LAYOUT_MAX: larodTensorLayout = 5; -pub type larodTensorLayout = ::std::os::raw::c_uint; +#[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 { From b3607bf4e32f6be87b9e08b4bb0ea65e7d5d2947 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 5 Nov 2024 20:57:35 -0500 Subject: [PATCH 15/51] Change to EnumVariation::Rust as default bindgen enum style. --- crates/larod-sys/build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/larod-sys/build.rs b/crates/larod-sys/build.rs index 4485f5ec..771449c1 100644 --- a/crates/larod-sys/build.rs +++ b/crates/larod-sys/build.rs @@ -7,6 +7,9 @@ fn populated_bindings(dst: &path::PathBuf) { .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 { From a5d62d58c1ea5f4a91bc014d68e79ad2e938dbd2 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 5 Nov 2024 21:34:58 -0500 Subject: [PATCH 16/51] Improve error handling and add documentation - Change error handling to better check for a non-NULL larodError pointer. - Add some documentation for LarodMap functions. - Set LarodMap functions to public. - Add some additional tests. - Hide on-device tests behind a device-tests feature. - Add a basic example. --- crates/larod/Cargo.toml | 6 +- crates/larod/examples/basic.rs | 25 ++ crates/larod/src/lib.rs | 553 ++++++++++++++++++++------------- 3 files changed, 373 insertions(+), 211 deletions(-) create mode 100644 crates/larod/examples/basic.rs diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index dad74c68..1cf2aa44 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -9,4 +9,8 @@ larod-sys = { workspace = true } log.workspace = true [features] -device-tests = [] \ No newline at end of file +device-tests = [] + +[[example]] +name = "basic" +crate-type = ["bin"] \ No newline at end of file diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs new file mode 100644 index 00000000..9febfc69 --- /dev/null +++ b/crates/larod/examples/basic.rs @@ -0,0 +1,25 @@ +use larod::{Error, Session}; + +fn main() -> Result<(), Error> { + let session = Session::new(); + let devices = match session.get_devices() { + Ok(d) => d, + Err(Error::LarodError(e)) => { + if let Ok(msg) = e.msg() { + eprintln!("Error while listing available devices! {}", msg); + } else { + eprintln!("Error while listing available devices. Error returned ") + } + return Err(Error::LarodError(e)); + } + Err(e) => { + eprintln!("Unknown error while listing devices: {:?}", e); + return Err(e); + } + }; + println!("Devices:"); + for d in devices { + println!("{} ({})", d.get_name(), d.get_instance()); + } + Ok(()) +} diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 562b4170..4519ce7d 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -2,16 +2,22 @@ //! //! # Gotchas //! Many of the C functions return either a bool or a pointer to some object. -//! Additionally, one of the out arguments is the pointer to the larodError -//! struct. If the normal return type is true, or not NULL in the case of a +//! 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 copying the larodError data into a Rust LarodError struct and -//! dealocating the larodError object if it is non-NULL. +//! by constructing the LarodError struct if the larodError pointer is non-NULL +//! and the impl Drop for LarodError will dealocate the object appropriately. +//! +//! Example +//! ```rust +//! let session = Session::new(); +//! let devices = session.devices(); +//! ``` //! //! # TODOs: //! - [ ] [larodDisconnect](https://axiscommunications.github.io/acap-documentation/docs/api/src/api/larod/html/larod_8h.html#ab8f97b4b4d15798384ca25f32ca77bba) @@ -26,139 +32,143 @@ use std::{ slice::from_raw_parts, }; -type Result = std::result::Result; +type Result = std::result::Result; macro_rules! try_func { ($func:ident $(,)?) => {{ let mut error: *mut larodError = ptr::null_mut(); let success = $func(&mut error); - (success, LarodError::from(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); - (success, LarodError::from(error)) + if error.is_null() { + (success, None) + } else { + (success, Some(Error::LarodError(LarodError{inner: error}))) + } + }} } -// Define our error types. These may be customized for our error handling cases. -// Now we will be able to write our own errors, defer to an underlying error -// implementation, or do something in between. -#[derive(Debug, Clone, Default)] +// 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 { - pub msg: String, - pub code: LarodErrorCode, + inner: *mut larodError, } 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() + pub fn msg(&self) -> Result { + if self.inner.is_null() { + Err(Error::NullLarodPointer) } 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 } + 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) + } + } } } -} -#[allow(non_camel_case_types)] -#[derive(Debug, Clone)] -enum LarodErrorCode { - NONE, - JOB, - LOAD_MODEL, - FD, - MODEL_NOT_FOUND, - PERMISSION, - CONNECTION, - CREATE_SESSION, - KILL_SESSION, - INVALID_CHIP_ID, - INVALID_ACCESS, - DELETE_MODEL, - TENSOR_MISMATCH, - VERSION_MISMATCH, - ALLOC, - POWER_NOT_AVAILABLE, - INVALID_TYPE, - MAX_ERRNO, -} - -impl Default for LarodErrorCode { - fn default() -> Self { - LarodErrorCode::NONE + pub fn code(&self) -> larodErrorCode { + unsafe { (*self.inner).code } } } -impl From for LarodErrorCode { - fn from(code: larodErrorCode) -> LarodErrorCode { - match code { - larodErrorCode_LAROD_ERROR_NONE => LarodErrorCode::NONE, - larodErrorCode_LAROD_ERROR_JOB => LarodErrorCode::JOB, - larodErrorCode_LAROD_ERROR_LOAD_MODEL => LarodErrorCode::LOAD_MODEL, - larodErrorCode_LAROD_ERROR_FD => LarodErrorCode::FD, - larodErrorCode_LAROD_ERROR_MODEL_NOT_FOUND => LarodErrorCode::MODEL_NOT_FOUND, - larodErrorCode_LAROD_ERROR_PERMISSION => LarodErrorCode::PERMISSION, - larodErrorCode_LAROD_ERROR_CONNECTION => LarodErrorCode::CONNECTION, - larodErrorCode_LAROD_ERROR_CREATE_SESSION => LarodErrorCode::CREATE_SESSION, - larodErrorCode_LAROD_ERROR_KILL_SESSION => LarodErrorCode::KILL_SESSION, - larodErrorCode_LAROD_ERROR_INVALID_CHIP_ID => LarodErrorCode::INVALID_CHIP_ID, - larodErrorCode_LAROD_ERROR_INVALID_ACCESS => LarodErrorCode::INVALID_ACCESS, - larodErrorCode_LAROD_ERROR_DELETE_MODEL => LarodErrorCode::DELETE_MODEL, - larodErrorCode_LAROD_ERROR_TENSOR_MISMATCH => LarodErrorCode::TENSOR_MISMATCH, - larodErrorCode_LAROD_ERROR_VERSION_MISMATCH => LarodErrorCode::VERSION_MISMATCH, - larodErrorCode_LAROD_ERROR_ALLOC => LarodErrorCode::ALLOC, - larodErrorCode_LAROD_ERROR_POWER_NOT_AVAILABLE => LarodErrorCode::POWER_NOT_AVAILABLE, - larodErrorCode_LAROD_ERROR_MAX_ERRNO => LarodErrorCode::MAX_ERRNO, - _ => unreachable!(), +impl Drop for LarodError { + fn drop(&mut self) { + if !self.inner.is_null() { + unsafe { larodClearError(&mut self.inner) } } } } +#[derive(Debug)] +pub enum Error { + LarodError(LarodError), + NullLarodPointer, + InvalidLarodMessage, + PointerToInvalidData, + CStringAllocation, + MissingLarodError, +} + +// 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 { - fn new() -> Result { - let (map, e): (*mut larodMap, LarodError) = unsafe { try_func!(larodCreateMap) }; - if map.is_null() { - Err(e) - } else { + /// 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!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodCreateMap allocated a map AND returned an error!" ); Ok(Self { raw: map }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn set_string(&mut self, k: &str, v: &str) -> Result<()> { + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; let Ok(value_cstr) = CString::new(v.as_bytes()) else { - return Err(LarodError { - msg: String::from("Could not allocate set_string value CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (success, e): (bool, LarodError) = unsafe { + let (success, maybe_error): (bool, Option) = unsafe { try_func!( larodMapSetStr, self.raw, @@ -168,61 +178,79 @@ impl LarodMap { }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapSetStr indicated success AND returned an error!" ); Ok(()) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn set_int(&mut self, k: &str, v: i64) -> Result<()> { + + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (success, e): (bool, LarodError) = + let (success, maybe_error): (bool, Option) = unsafe { try_func!(larodMapSetInt, self.raw, key_cstr.as_ptr(), v) }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapSetInt indicated success AND returned an error!" ); Ok(()) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn set_int_arr2(&mut self, k: &str, v: (i64, i64)) -> Result<()> { + + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (success, e): (bool, LarodError) = + let (success, maybe_error): (bool, Option) = unsafe { try_func!(larodMapSetIntArr2, self.raw, key_cstr.as_ptr(), v.0, v.1) }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapSetIntArr2 indicated success AND returned an error!" ); Ok(()) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn set_int_arr4(&mut self, k: &str, v: (i64, i64, i64, i64)) -> Result<()> { + + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (success, e): (bool, LarodError) = unsafe { + let (success, maybe_error): (bool, Option) = unsafe { try_func!( larodMapSetIntArr4, self.raw, @@ -236,107 +264,126 @@ impl LarodMap { if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapSetIntArr4 indicated success AND returned an error!" ); Ok(()) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn get_string(&self, k: &str) -> Result { + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (c_str_ptr, e): (*const c_char, LarodError) = + 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!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapGetStr returned a string AND returned an error!" ); Ok(String::from(rs)) } else { - return Err(LarodError { - msg: String::from("Returned string is not valid UTF-8"), - code: LarodErrorCode::INVALID_TYPE, - }); + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn get_int(&self, k: &str) -> Result { + + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; let mut v: i64 = 0; - let (success, e): (bool, LarodError) = + let (success, maybe_error): (bool, Option) = unsafe { try_func!(larodMapGetInt, self.raw, key_cstr.as_ptr(), &mut v) }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapGetInt indicated success AND returned an error!" ); Ok(v) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn get_int_arr2(&self, k: &str) -> Result<&[i64; 2]> { + + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (out_arr, e) = unsafe { try_func!(larodMapGetIntArr2, self.raw, key_cstr.as_ptr()) }; - if out_arr.is_null() { - Err(e) - } else { + let (out_arr, maybe_error) = + unsafe { try_func!(larodMapGetIntArr2, self.raw, key_cstr.as_ptr()) }; + if !out_arr.is_null() { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapGetInt indicated success AND returned an error!" ); unsafe { slice::from_raw_parts(out_arr, 2) .try_into() - .or(Err(LarodError { - msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), - code: LarodErrorCode::INVALID_TYPE, - })) + .or(Err(Error::PointerToInvalidData)) } + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } - fn get_int_arr4(&self, k: &str) -> Result<&[i64; 4]> { + /// 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(LarodError { - msg: String::from("Could not allocate set_string key CString"), - code: LarodErrorCode::ALLOC, - }); + return Err(Error::CStringAllocation); }; - let (out_arr, e) = unsafe { try_func!(larodMapGetIntArr4, self.raw, key_cstr.as_ptr()) }; - if out_arr.is_null() { - Err(e) - } else { + let (out_arr, maybe_error) = + unsafe { try_func!(larodMapGetIntArr4, self.raw, key_cstr.as_ptr()) }; + if !out_arr.is_null() { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodMapGetIntArr4 indicated success AND returned an error!" ); unsafe { slice::from_raw_parts(out_arr, 4) .try_into() - .or(Err(LarodError { - msg: String::from("&[i64; 2] data stored in LarodMap is invalid."), - code: LarodErrorCode::INVALID_TYPE, - })) + .or(Err(Error::PointerToInvalidData)) } + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } } @@ -349,36 +396,45 @@ impl std::ops::Drop for LarodMap { } } +/// A type representing a larodDevice. #[derive(Debug)] pub struct LarodDevice { + // 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, name: String, id: u32, } impl LarodDevice { + /// Get the name of a larodDevice. pub fn get_name(&self) -> &str { &self.name } + /// 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 get_instance(&self) -> u32 { + self.id + } + fn larod_get_name(pointer: *const larodDevice) -> Result { unsafe { - let (c_char_ptr, error) = try_func!(larodGetDeviceName, pointer); - if c_char_ptr.is_null() { - Err(error) - } else { + let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, pointer); + if !c_char_ptr.is_null() { debug_assert!( - matches!(error.code, LarodErrorCode::NONE), + 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(|n| String::from(n)) - .map_err(|e| LarodError { - msg: String::from("Returned string is not valid UTF-8"), - code: LarodErrorCode::INVALID_TYPE, - }) + .map_err(|e| Error::InvalidLarodMessage) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } } @@ -386,22 +442,22 @@ impl LarodDevice { fn larod_get_instance(pointer: *const larodDevice) -> Result { unsafe { let mut instance: u32 = 0; - let (success, error) = try_func!(larodGetDeviceInstance, pointer, &mut instance); + let (success, maybe_error) = try_func!(larodGetDeviceInstance, pointer, &mut instance); if success { debug_assert!( - matches!(error.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodGetDeviceInstance returned success AND returned an error!" ); Ok(instance) } else { - Err(error) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } } } impl TryFrom<*const larodDevice> for LarodDevice { - type Error = LarodError; + type Error = Error; fn try_from(value: *const larodDevice) -> Result { let name = LarodDevice::larod_get_name(value)?; let id = LarodDevice::larod_get_instance(value)?; @@ -421,27 +477,32 @@ impl SessionBuilder { } pub fn build(&self) -> Result { let mut conn: *mut larodConnection = ptr::null_mut(); - let (success, e): (bool, LarodError) = unsafe { try_func!(larodConnect, &mut conn) }; + let (success, maybe_error): (bool, Option) = + unsafe { try_func!(larodConnect, &mut conn) }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodConnect indicated success AND returned an error!" ); Ok(Session { conn, model_map: HashMap::new(), - devices: Vec::new(), }) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } } +impl Default for SessionBuilder { + fn default() -> Self { + SessionBuilder::new() + } +} + pub struct Session { conn: *mut larodConnection, model_map: HashMap, - devices: Vec, } // Using a session builder might not be necessary. @@ -460,47 +521,57 @@ impl Session { SessionBuilder::new() } pub fn disconnect(&mut self) -> Result<()> { - let (success, e): (bool, LarodError) = + let (success, maybe_error): (bool, Option) = unsafe { try_func!(larodDisconnect, &mut self.conn) }; if success { debug_assert!( - matches!(e.code, LarodErrorCode::NONE), + maybe_error.is_none(), "larodDisconnect indicated success AND returned an error!" ); Ok(()) } else { - Err(e) + Err(maybe_error.unwrap_or(Error::MissingLarodError)) } } pub fn num_sessions() -> Result<()> { Ok(()) } - pub fn device() -> Result<()> { - Ok(()) - } + + /// Returns a reference to an available device + // pub fn get_device(&self, name: &str) -> Option<&LarodDevice> { + // self.devices.get(name) + // } + pub fn list_chips() -> Result<()> { Ok(()) } - pub fn list_devices(&mut self) -> Result> { + + /// Get a reference to a HashMap of name LarodDevice pairs. + pub fn get_devices(&self) -> Result> { let mut num_devices: usize = 0; - let (d, e) = unsafe { - let (dev_ptr, e) = try_func!(larodListDevices, self.conn, &mut num_devices); - if dev_ptr.is_null() { - return Err(e); - } - let raw_devices = unsafe { slice::from_raw_parts(dev_ptr, num_devices) }; - let mut devices: Vec = Vec::with_capacity(num_devices); - for (idx, raw_device) in raw_devices.into_iter().enumerate() { - let device = LarodDevice::try_from(*raw_device); - match device { - Ok(d) => devices.push(d), - Err(conv_error) => - log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{}", idx, num_devices, conv_error.msg), - } + 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(dev_ptr, num_devices) }; + + let devices: Vec = raw_devices.iter().enumerate().filter_map(|(idx, raw_d)| { + match LarodDevice::try_from(*raw_d) { + Ok(d) => Some(d), + Err(Error::LarodError(e)) => { + let error_message = e.msg().unwrap_or(String::from("Error reading error message")); + log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{}", idx, num_devices, &error_message); + None + }, + Err(e) => { + log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{:?}", idx, num_devices, e); + None + }, } - (devices, e) - }; - Ok(d) + }).collect(); + + Ok(devices) } // Overloaded need to check that. @@ -546,6 +617,14 @@ impl Session { } } +impl Default for Session { + fn default() -> Self { + SessionBuilder::default() + .build() + .expect("Session::default()") + } +} + impl std::ops::Drop for Session { fn drop(&mut self) { unsafe { @@ -554,7 +633,7 @@ impl std::ops::Drop for Session { } } -#[cfg(test)] +#[cfg(all(test, target_arch = "aarch64", feature = "device-tests"))] mod tests { use super::*; use std::ptr; @@ -576,21 +655,75 @@ mod tests { 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 mut map = LarodMap::new().unwrap(); map.set_int("test_key", 10).unwrap(); } + #[test] + fn larod_map_can_get_int() { + 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 mut map = LarodMap::new().unwrap(); map.set_int_arr2("test_key", (1, 2)).unwrap(); } + #[test] + fn larod_map_can_get_2_tuple() { + 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 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 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 sess = Session::new(); + } + + #[test] + fn it_lists_devices() { + let sess = Session::new(); + let devices = sess.devices().unwrap(); + for device in devices { + println!( + "device: {}, id: {}, addr: {:?}", + device.get_name(), + device.id, + unsafe { std::ptr::addr_of!(*device.ptr) }, + ); + } + } } From 4309ed684fa1daa2305fdf2193c1e9f99a74098f Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 5 Nov 2024 21:36:20 -0500 Subject: [PATCH 17/51] Fix test failure due to renamed method. --- crates/larod/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 4519ce7d..1b436df7 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -716,7 +716,7 @@ mod tests { #[test] fn it_lists_devices() { let sess = Session::new(); - let devices = sess.devices().unwrap(); + let devices = sess.get_devices().unwrap(); for device in devices { println!( "device: {}, id: {}, addr: {:?}", From de2367be7a379c1e6ecb308c4eb454ab7a6c1f83 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 5 Nov 2024 21:51:25 -0500 Subject: [PATCH 18/51] Remove note that is no longer needed. --- remote-test.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/remote-test.sh b/remote-test.sh index 78b8fadf..be6a1f82 100644 --- a/remote-test.sh +++ b/remote-test.sh @@ -6,5 +6,4 @@ if [ -n "${CARGO_TEST_CAMERA}" ]; then # echo $f ssh $CARGO_TEST_CAMERA "chmod +x /root/$f" ssh $CARGO_TEST_CAMERA "/root/$f" - # ^ note: may need to change this line, see https://stackoverflow.com/q/9379400 fi From 1dc68960cbd242023df0cb2f5ea0a12325e01253 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 5 Nov 2024 21:55:26 -0500 Subject: [PATCH 19/51] Run executable is no camera. --- remote-test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/remote-test.sh b/remote-test.sh index be6a1f82..652a9984 100644 --- a/remote-test.sh +++ b/remote-test.sh @@ -6,4 +6,6 @@ if [ -n "${CARGO_TEST_CAMERA}" ]; then # echo $f ssh $CARGO_TEST_CAMERA "chmod +x /root/$f" ssh $CARGO_TEST_CAMERA "/root/$f" +else + $1 fi From 55a71586464a9394b99b6d4e1fcfd122c0c68f78 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 7 Nov 2024 11:09:34 -0500 Subject: [PATCH 20/51] Explicitly tie the lifetime of LarodDevice to the lifetime of the Session from which it was acquired. --- crates/larod/src/lib.rs | 55 ++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 1b436df7..c03a6254 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -28,6 +28,7 @@ use larod_sys::*; use std::{ collections::HashMap, ffi::{c_char, c_int, CStr, CString}, + marker::PhantomData, ptr::{self, slice_from_raw_parts}, slice::from_raw_parts, }; @@ -397,17 +398,31 @@ impl std::ops::Drop for LarodMap { } /// 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 +/// let sess = Session::new(); +/// let first_device = sess +/// .get_devices() +/// .expect("unable to get devices") +/// .pop() +/// .expect("empty devices list!"); +/// drop(sess); +/// println!("{:?}", first_device.get_name()); +/// ``` #[derive(Debug)] -pub struct LarodDevice { +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, name: String, id: u32, + phantom: PhantomData<&'a Session<'a>>, } -impl LarodDevice { +impl<'a> LarodDevice<'a> { /// Get the name of a larodDevice. pub fn get_name(&self) -> &str { &self.name @@ -456,7 +471,7 @@ impl LarodDevice { } } -impl TryFrom<*const larodDevice> for LarodDevice { +impl<'a> TryFrom<*const larodDevice> for LarodDevice<'a> { type Error = Error; fn try_from(value: *const larodDevice) -> Result { let name = LarodDevice::larod_get_name(value)?; @@ -465,6 +480,7 @@ impl TryFrom<*const larodDevice> for LarodDevice { ptr: value, name, id, + phantom: PhantomData, }) } } @@ -475,7 +491,7 @@ impl SessionBuilder { pub fn new() -> SessionBuilder { SessionBuilder {} } - pub fn build(&self) -> Result { + 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) }; @@ -487,6 +503,7 @@ impl SessionBuilder { Ok(Session { conn, model_map: HashMap::new(), + phantom: PhantomData, }) } else { Err(maybe_error.unwrap_or(Error::MissingLarodError)) @@ -500,21 +517,22 @@ impl Default for SessionBuilder { } } -pub struct Session { +pub struct Session<'a> { conn: *mut larodConnection, model_map: HashMap, + phantom: PhantomData<&'a larodConnection>, } // Using a session builder might not be necessary. // There's little to configure when starting a session. -impl Session { +impl<'a> Session<'a> { /// 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 { + pub fn new() -> Session<'a> { SessionBuilder::new().build().expect("Session::new()") } pub fn builder() -> SessionBuilder { @@ -538,9 +556,22 @@ impl Session { } /// Returns a reference to an available device - // pub fn get_device(&self, name: &str) -> Option<&LarodDevice> { - // self.devices.get(name) - // } + pub fn get_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::try_from(device_ptr)?) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } pub fn list_chips() -> Result<()> { Ok(()) @@ -617,7 +648,7 @@ impl Session { } } -impl Default for Session { +impl<'a> Default for Session<'a> { fn default() -> Self { SessionBuilder::default() .build() @@ -625,7 +656,7 @@ impl Default for Session { } } -impl std::ops::Drop for Session { +impl<'a> std::ops::Drop for Session<'a> { fn drop(&mut self) { unsafe { try_func!(larodDisconnect, &mut self.conn); From b294ca3938681e97db18b9bedc6e2970b81d0188 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 21:01:09 -0500 Subject: [PATCH 21/51] Don't copy larodDevice data from C to Rust, just provide access via functions. --- crates/larod/src/lib.rs | 73 ++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index c03a6254..25dff682 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -15,6 +15,7 @@ //! //! Example //! ```rust +//! use larod::Session; //! let session = Session::new(); //! let devices = session.devices(); //! ``` @@ -402,6 +403,7 @@ impl std::ops::Drop for LarodMap { /// [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 /// .get_devices() @@ -417,27 +419,14 @@ pub struct LarodDevice<'a> { // attempt to free it. The lifetime of the memory pointed to expires when // conn closes. ptr: *const larodDevice, - name: String, - id: u32, phantom: PhantomData<&'a Session<'a>>, } impl<'a> LarodDevice<'a> { /// Get the name of a larodDevice. - pub fn get_name(&self) -> &str { - &self.name - } - - /// 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 get_instance(&self) -> u32 { - self.id - } - - fn larod_get_name(pointer: *const larodDevice) -> Result { + fn get_name(&self) -> Result { unsafe { - let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, pointer); + let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, self.ptr); if !c_char_ptr.is_null() { debug_assert!( maybe_error.is_none(), @@ -454,10 +443,13 @@ impl<'a> LarodDevice<'a> { } } - fn larod_get_instance(pointer: *const larodDevice) -> Result { + /// 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.* + fn get_instance(&self) -> Result { unsafe { let mut instance: u32 = 0; - let (success, maybe_error) = try_func!(larodGetDeviceInstance, pointer, &mut instance); + let (success, maybe_error) = try_func!(larodGetDeviceInstance, self.ptr, &mut instance); if success { debug_assert!( maybe_error.is_none(), @@ -471,20 +463,6 @@ impl<'a> LarodDevice<'a> { } } -impl<'a> TryFrom<*const larodDevice> for LarodDevice<'a> { - type Error = Error; - fn try_from(value: *const larodDevice) -> Result { - let name = LarodDevice::larod_get_name(value)?; - let id = LarodDevice::larod_get_instance(value)?; - Ok(Self { - ptr: value, - name, - id, - phantom: PhantomData, - }) - } -} - pub struct SessionBuilder {} impl SessionBuilder { @@ -567,7 +545,10 @@ impl<'a> Session<'a> { maybe_error.is_none(), "larodGetDevice indicated success AND returned an error!" ); - Ok(LarodDevice::try_from(device_ptr)?) + Ok(LarodDevice { + ptr: device_ptr, + phantom: PhantomData, + }) } else { Err(maybe_error.unwrap_or(Error::MissingLarodError)) } @@ -585,22 +566,16 @@ impl<'a> Session<'a> { if dev_ptr.is_null() { return Err(maybe_error.unwrap_or(Error::MissingLarodError)); } - let raw_devices = unsafe { slice::from_raw_parts(dev_ptr, num_devices) }; - - let devices: Vec = raw_devices.iter().enumerate().filter_map(|(idx, raw_d)| { - match LarodDevice::try_from(*raw_d) { - Ok(d) => Some(d), - Err(Error::LarodError(e)) => { - let error_message = e.msg().unwrap_or(String::from("Error reading error message")); - log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{}", idx, num_devices, &error_message); - None - }, - Err(e) => { - log::error!("Could not identify larodDevice {} of {} returned from larodListDevices.\n{:?}", idx, num_devices, e); - None - }, - } - }).collect(); + let raw_devices = + unsafe { slice::from_raw_parts::<'a, *const larodDevice>(dev_ptr, num_devices) }; + + let devices: Vec = raw_devices + .iter() + .map(|ptr| LarodDevice { + ptr: *ptr, + phantom: PhantomData, + }) + .collect(); Ok(devices) } @@ -751,7 +726,7 @@ mod tests { for device in devices { println!( "device: {}, id: {}, addr: {:?}", - device.get_name(), + device.get_name().unwrap(), device.id, unsafe { std::ptr::addr_of!(*device.ptr) }, ); From bdeda8127c1499f07132bd3cb4c0496df3eb0445 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 21:01:44 -0500 Subject: [PATCH 22/51] Unwrap result for test listing devices. --- crates/larod/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 25dff682..2b49ad98 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -727,7 +727,7 @@ mod tests { println!( "device: {}, id: {}, addr: {:?}", device.get_name().unwrap(), - device.id, + device.get_instance().unwrap(), unsafe { std::ptr::addr_of!(*device.ptr) }, ); } From 1eb3252c84a3a7f694d0f020b86b8e19f6a3929a Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 21:04:04 -0500 Subject: [PATCH 23/51] Make LarodDevice.get_name() and LarodDevice.get_instance() public. Incorporate a few clippy lints. --- crates/larod/src/lib.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 2b49ad98..79c47e74 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -24,14 +24,13 @@ //! - [ ] [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::{num, slice}; +use core::slice; use larod_sys::*; use std::{ collections::HashMap, - ffi::{c_char, c_int, CStr, CString}, + ffi::{c_char, CStr, CString}, marker::PhantomData, - ptr::{self, slice_from_raw_parts}, - slice::from_raw_parts, + ptr::{self}, }; type Result = std::result::Result; @@ -424,7 +423,7 @@ pub struct LarodDevice<'a> { impl<'a> LarodDevice<'a> { /// Get the name of a larodDevice. - fn get_name(&self) -> Result { + pub fn get_name(&self) -> Result { unsafe { let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, self.ptr); if !c_char_ptr.is_null() { @@ -435,8 +434,8 @@ impl<'a> LarodDevice<'a> { let c_name = CStr::from_ptr(c_char_ptr); c_name .to_str() - .map(|n| String::from(n)) - .map_err(|e| Error::InvalidLarodMessage) + .map(String::from) + .map_err(|_e| Error::InvalidLarodMessage) } else { Err(maybe_error.unwrap_or(Error::MissingLarodError)) } @@ -446,7 +445,7 @@ impl<'a> LarodDevice<'a> { /// 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.* - fn get_instance(&self) -> Result { + pub fn get_instance(&self) -> Result { unsafe { let mut instance: u32 = 0; let (success, maybe_error) = try_func!(larodGetDeviceInstance, self.ptr, &mut instance); @@ -642,7 +641,6 @@ impl<'a> std::ops::Drop for Session<'a> { #[cfg(all(test, target_arch = "aarch64", feature = "device-tests"))] mod tests { use super::*; - use std::ptr; #[test] fn it_creates_larod_map() { From 7941c92a9079877a2d8c35017cb3b2bb8fb3df6e Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 21:58:48 -0500 Subject: [PATCH 24/51] Align getters/setters with RFC344. https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md#gettersetter-apis --- crates/larod/examples/basic.rs | 8 ++++++-- crates/larod/src/lib.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index 9febfc69..96316088 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -2,7 +2,7 @@ use larod::{Error, Session}; fn main() -> Result<(), Error> { let session = Session::new(); - let devices = match session.get_devices() { + let devices = match session.devices() { Ok(d) => d, Err(Error::LarodError(e)) => { if let Ok(msg) = e.msg() { @@ -19,7 +19,11 @@ fn main() -> Result<(), Error> { }; println!("Devices:"); for d in devices { - println!("{} ({})", d.get_name(), d.get_instance()); + println!( + "{} ({})", + d.name().expect("Couldn't get device name"), + d.instance().expect("Couldn't get device instance") + ); } Ok(()) } diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 79c47e74..8905fe09 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -405,12 +405,12 @@ impl std::ops::Drop for LarodMap { /// use larod::Session; /// let sess = Session::new(); /// let first_device = sess -/// .get_devices() +/// .devices() /// .expect("unable to get devices") /// .pop() /// .expect("empty devices list!"); /// drop(sess); -/// println!("{:?}", first_device.get_name()); +/// println!("{:?}", first_device.name()); /// ``` #[derive(Debug)] pub struct LarodDevice<'a> { @@ -423,7 +423,7 @@ pub struct LarodDevice<'a> { impl<'a> LarodDevice<'a> { /// Get the name of a larodDevice. - pub fn get_name(&self) -> Result { + pub fn name(&self) -> Result { unsafe { let (c_char_ptr, maybe_error) = try_func!(larodGetDeviceName, self.ptr); if !c_char_ptr.is_null() { @@ -445,7 +445,7 @@ impl<'a> LarodDevice<'a> { /// 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 get_instance(&self) -> Result { + pub fn instance(&self) -> Result { unsafe { let mut instance: u32 = 0; let (success, maybe_error) = try_func!(larodGetDeviceInstance, self.ptr, &mut instance); @@ -533,7 +533,7 @@ impl<'a> Session<'a> { } /// Returns a reference to an available device - pub fn get_device(&self, name: &str, instance: u32) -> Result { + pub fn device(&self, name: &str, instance: u32) -> Result { let Ok(name_cstr) = CString::new(name) else { return Err(Error::CStringAllocation); }; @@ -558,7 +558,7 @@ impl<'a> Session<'a> { } /// Get a reference to a HashMap of name LarodDevice pairs. - pub fn get_devices(&self) -> Result> { + 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) }; From 59b9ac539af6a54afdf769b9219fcb045ed38415 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 22:13:22 -0500 Subject: [PATCH 25/51] Fix getter names missed in previous commit. --- crates/larod/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 8905fe09..4cf2789b 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -720,12 +720,12 @@ mod tests { #[test] fn it_lists_devices() { let sess = Session::new(); - let devices = sess.get_devices().unwrap(); + let devices = sess.devices().unwrap(); for device in devices { println!( "device: {}, id: {}, addr: {:?}", - device.get_name().unwrap(), - device.get_instance().unwrap(), + device.name().unwrap(), + device.instance().unwrap(), unsafe { std::ptr::addr_of!(*device.ptr) }, ); } From 4373b188c8035421a9701618e78b269179242253 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Fri, 8 Nov 2024 22:13:41 -0500 Subject: [PATCH 26/51] Start stubbing LarodModel. --- crates/larod/src/lib.rs | 101 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 4cf2789b..bd4aed67 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -25,11 +25,15 @@ //! 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 std::{ collections::HashMap, ffi::{c_char, CStr, CString}, + fs::File, marker::PhantomData, + os::fd::AsRawFd, + path::Path, ptr::{self}, }; @@ -108,6 +112,7 @@ pub enum Error { PointerToInvalidData, CStringAllocation, MissingLarodError, + IOError(std::io::Error), } // impl LarodError { @@ -462,6 +467,49 @@ impl<'a> LarodDevice<'a> { } } +pub struct LarodModel { + ptr: *mut larodModel, +} + +impl LarodModel { + 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_inputs() -> Result<()> { + Ok(()) + } + pub fn create_model_outputs() -> Result<()> { + Ok(()) + } +} + +impl Drop for LarodModel { + fn drop(&mut self) { + unsafe { larodDestroyModel(&mut self.ptr) }; + } +} + pub struct SessionBuilder {} impl SessionBuilder { @@ -580,20 +628,53 @@ impl<'a> Session<'a> { } // Overloaded need to check that. - pub fn load_model(&mut self) -> Result<()> { - // let model_fd: c_int = 0; - // let (m, e) = unsafe { - // try_func!(larodLoadModel, &mut self.conn, model_fd, ) - // } - Ok(()) + pub fn load_model>( + &self, + path: T, + name: &str, + device: &LarodDevice, + access: &LarodAccess, + params: &LarodMap, + ) -> Result { + let file = File::open(path).map_err(Error::IOError)?; + let name_cstr = CString::new(name).map_err(|_e| Error::CStringAllocation)?; + let (model_ptr, maybe_error) = unsafe { + try_func!( + larodLoadModel, + self.conn, + file.as_raw_fd(), + device.ptr, + *access, + name_cstr.as_ptr(), + params.raw + ) + }; + if !model_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodLoadModel indicated success AND returned an error!" + ); + Ok(LarodModel { ptr: model_ptr }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } } - pub fn get_model() -> Result<()> { - Ok(()) + pub fn get_model(&self, model_id: u64) -> Result { + let (model_ptr, maybe_error) = unsafe { try_func!(larodGetModel, self.conn, model_id) }; + if !model_ptr.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodGetModel indicated success AND returned an error!" + ); + Ok(LarodModel { ptr: model_ptr }) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } } - pub fn get_models() -> Result<()> { + pub fn models() -> Result<()> { Ok(()) } - pub fn delete_model() -> Result<()> { + pub fn delete_model(&self) -> Result<()> { Ok(()) } pub fn alloc_model_inputs() -> Result<()> { From cdaf42ba42af188898021f25ac0071f96bec482f Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 14 Nov 2024 21:30:25 -0500 Subject: [PATCH 27/51] Remove `bin` crate type from [[example]] --- crates/larod/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 1cf2aa44..d6005897 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -12,5 +12,4 @@ log.workspace = true device-tests = [] [[example]] -name = "basic" -crate-type = ["bin"] \ No newline at end of file +name = "basic" \ No newline at end of file From ff868cbe5954123e461b2459c09af946571bc8ef Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 14 Nov 2024 21:31:32 -0500 Subject: [PATCH 28/51] Stub out Tensor struct. --- crates/larod/src/lib.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index bd4aed67..fff2ee33 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -402,6 +402,37 @@ impl std::ops::Drop for LarodMap { } } +#[derive(Eq, PartialEq, Hash)] +pub struct Tensor<'a> { + ptr: *mut larodTensor, + phantom: PhantomData<&'a Session<'a>>, +} + +/// A structure representing a larodTensor. +impl<'a> Tensor<'a> { + fn as_ptr(&self) -> *const larodTensor { + self.ptr.cast_const() + } + pub fn name() {} + pub fn byte_size() {} + pub fn dims() {} + pub fn set_dims() {} + pub fn pitches() {} + pub fn set_pitches() {} + pub fn data_type() {} + pub fn set_data_type() {} + pub fn layout() {} + pub fn set_layout() {} + pub fn fd() {} + pub fn set_fd() {} + 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() {} +} + /// 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 From 0382a94c5302a8ce5ba94560d49b1054ee524ca2 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 3 Dec 2024 08:52:20 -0500 Subject: [PATCH 29/51] Add LarodModel Trait Add preprocessor model and inference model Add TensorContainer struct and Tensor (WIP) Start using thiserror Expand basic example --- Cargo.lock | 1 + crates/larod/Cargo.toml | 1 + crates/larod/examples/basic.rs | 23 +- crates/larod/src/lib.rs | 668 ++++++++++++++++++++++++++++----- 4 files changed, 601 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 350e5f60..453a5e9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1289,6 +1289,7 @@ version = "0.1.0" dependencies = [ "larod-sys", "log", + "thiserror", ] [[package]] diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index d6005897..58f3d524 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true larod-sys = { workspace = true } log.workspace = true +thiserror.workspace = true [features] device-tests = [] diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index 96316088..036a56c0 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -1,4 +1,4 @@ -use larod::{Error, Session}; +use larod::{Error, ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; fn main() -> Result<(), Error> { let session = Session::new(); @@ -25,5 +25,26 @@ fn main() -> Result<(), Error> { d.instance().expect("Couldn't get device instance") ); } + 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)) => { + eprintln!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + eprintln!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + } + println!("Number of model inputs: {}", preprocessor.num_inputs()); Ok(()) } diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index fff2ee33..714c996b 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1,5 +1,13 @@ //! 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 @@ -13,12 +21,17 @@ //! by constructing the LarodError struct if the larodError pointer is non-NULL //! and the impl Drop for LarodError will dealocate the object appropriately. //! -//! Example -//! ```rust -//! use larod::Session; -//! let session = Session::new(); -//! let devices = session.devices(); -//! ``` +//! ## 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) @@ -28,13 +41,13 @@ use core::slice; pub use larod_sys::larodAccess as LarodAccess; use larod_sys::*; use std::{ - collections::HashMap, ffi::{c_char, CStr, CString}, + fmt::Display, fs::File, marker::PhantomData, os::fd::AsRawFd, path::Path, - ptr::{self}, + ptr::{self, slice_from_raw_parts}, }; type Result = std::result::Result; @@ -96,6 +109,18 @@ impl 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() { @@ -104,15 +129,26 @@ impl Drop for LarodError { } } -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum Error { - LarodError(LarodError), + #[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 { @@ -402,21 +438,73 @@ impl std::ops::Drop for LarodMap { } } -#[derive(Eq, PartialEq, Hash)] -pub struct Tensor<'a> { - ptr: *mut larodTensor, - phantom: PhantomData<&'a Session<'a>>, +// #[derive(Eq, PartialEq, Hash)] +// pub struct Tensor<'a> { +// ptr: *mut *mut larodTensor, +// phantom: PhantomData<&'a Session>, +// } + +struct LarodTensorContainer { + ptr: *mut *mut larodTensor, + num_tensors: usize, } +pub struct Tensor(*mut larodTensor); /// A structure representing a larodTensor. -impl<'a> Tensor<'a> { - fn as_ptr(&self) -> *const larodTensor { - self.ptr.cast_const() - } +impl Tensor { + // fn as_ptr(&self) -> *const larodTensor { + // self.ptr.cast_const() + // } + + // fn as_mut_ptr(&self) -> *mut larodTensor { + // self.ptr + // } + pub fn name() {} + pub fn byte_size() {} - pub fn dims() {} - pub fn set_dims() {} + + pub fn dims(&self) -> Result> { + let (dims, maybe_error) = unsafe { try_func!(larodGetTensorDims, self.0) }; + 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::>() + }; + Ok(d) + } 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.0, &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() {} pub fn set_pitches() {} pub fn data_type() {} @@ -431,6 +519,19 @@ impl<'a> Tensor<'a> { pub fn set_fd_offset() {} pub fn fd_props() {} pub fn set_fd_props() {} + // 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)) + // } + // } } /// A type representing a larodDevice. @@ -454,7 +555,7 @@ pub struct LarodDevice<'a> { // attempt to free it. The lifetime of the memory pointed to expires when // conn closes. ptr: *const larodDevice, - phantom: PhantomData<&'a Session<'a>>, + phantom: PhantomData<&'a Session>, } impl<'a> LarodDevice<'a> { @@ -498,11 +599,346 @@ impl<'a> LarodDevice<'a> { } } -pub struct LarodModel { +pub trait LarodModel { + fn create_model_inputs(&mut self) -> Result<()>; + fn num_inputs(&self) -> usize; + fn start(&self) -> Result<()>; + fn stop(&self); +} + +#[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, +} + +#[derive(Default)] +pub struct PreprocessorBuilder { + backend: PreProcBackend, + input_size: Option<(i64, i64)>, + crop: Option<(i64, i64, i64, i64)>, + output_size: Option<(i64, i64)>, + 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, size: (i64, i64)) -> Self { + self.output_size = Some(size); + self + } + + pub fn input_size(mut self, size: (i64, i64)) -> Self { + self.input_size = Some(size); + 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", s)?; + } + + 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", s)?; + } + + 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 for Preprocessor<'a> { + fn create_model_inputs(&mut self) -> Result<()> { + let (tensors, maybe_error) = + unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; + if !tensors.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelInputs indicated success AND returned an error!" + ); + self.input_tensors = Some(LarodTensorContainer { + ptr: tensors, + num_tensors: self.num_inputs, + }); + // let tensor_slice = + // unsafe { slice::from_raw_parts::<*mut larodTensor>(tensors, self.num_inputs) }; + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn num_inputs(&self) -> usize { + self.num_inputs + } + + fn start(&self) -> Result<()> { + if self.input_tensors.is_none() || self.output_tensors.is_none() { + return Err(Error::UnsatisfiedDependencies); + } + 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) + ); + } + Ok(()) + } + fn stop(&self) {} } -impl LarodModel { +impl<'a> Drop for Preprocessor<'a> { + fn drop(&mut self) { + if let Some(ref mut tensor_container) = self.input_tensors { + println!("Dropping Preprocessor input tensors!"); + unsafe { + try_func!( + larodDestroyTensors, + self.session.conn, + &mut tensor_container.ptr, + tensor_container.num_tensors + ) + }; + } + unsafe { larodDestroyModel(&mut self.ptr) }; + } +} + +// #[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, +} + +impl<'a> InferenceModel<'a> { pub fn id() -> Result<()> { Ok(()) } @@ -527,16 +963,53 @@ impl LarodModel { pub fn num_outputs() -> Result<()> { Ok(()) } - pub fn create_model_inputs() -> Result<()> { + + pub fn create_model_outputs() -> Result<()> { Ok(()) } - pub fn create_model_outputs() -> Result<()> { +} + +impl<'a> LarodModel for InferenceModel<'a> { + fn create_model_inputs(&mut self) -> Result<()> { + let (tensors, maybe_error) = + unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; + if !tensors.is_null() { + debug_assert!( + maybe_error.is_none(), + "larodCreateModelInputs indicated success AND returned an error!" + ); + self.input_tensors = Some(LarodTensorContainer { + ptr: tensors, + num_tensors: self.num_inputs, + }); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } + + fn num_inputs(&self) -> usize { + self.num_inputs + } + + fn start(&self) -> Result<()> { Ok(()) } + fn stop(&self) {} } -impl Drop for LarodModel { +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) }; } } @@ -547,7 +1020,7 @@ impl SessionBuilder { pub fn new() -> SessionBuilder { SessionBuilder {} } - pub fn build(&self) -> Result> { + 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) }; @@ -556,11 +1029,7 @@ impl SessionBuilder { maybe_error.is_none(), "larodConnect indicated success AND returned an error!" ); - Ok(Session { - conn, - model_map: HashMap::new(), - phantom: PhantomData, - }) + Ok(Session { conn }) } else { Err(maybe_error.unwrap_or(Error::MissingLarodError)) } @@ -573,22 +1042,20 @@ impl Default for SessionBuilder { } } -pub struct Session<'a> { +pub struct Session { conn: *mut larodConnection, - model_map: HashMap, - phantom: PhantomData<&'a larodConnection>, } // Using a session builder might not be necessary. // There's little to configure when starting a session. -impl<'a> Session<'a> { +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<'a> { + pub fn new() -> Session { SessionBuilder::new().build().expect("Session::new()") } pub fn builder() -> SessionBuilder { @@ -645,7 +1112,7 @@ impl<'a> Session<'a> { return Err(maybe_error.unwrap_or(Error::MissingLarodError)); } let raw_devices = - unsafe { slice::from_raw_parts::<'a, *const larodDevice>(dev_ptr, num_devices) }; + unsafe { slice::from_raw_parts::<*const larodDevice>(dev_ptr, num_devices) }; let devices: Vec = raw_devices .iter() @@ -658,50 +1125,6 @@ impl<'a> Session<'a> { Ok(devices) } - // Overloaded need to check that. - pub fn load_model>( - &self, - path: T, - name: &str, - device: &LarodDevice, - access: &LarodAccess, - params: &LarodMap, - ) -> Result { - let file = File::open(path).map_err(Error::IOError)?; - let name_cstr = CString::new(name).map_err(|_e| Error::CStringAllocation)?; - let (model_ptr, maybe_error) = unsafe { - try_func!( - larodLoadModel, - self.conn, - file.as_raw_fd(), - device.ptr, - *access, - name_cstr.as_ptr(), - params.raw - ) - }; - if !model_ptr.is_null() { - debug_assert!( - maybe_error.is_none(), - "larodLoadModel indicated success AND returned an error!" - ); - Ok(LarodModel { ptr: model_ptr }) - } else { - Err(maybe_error.unwrap_or(Error::MissingLarodError)) - } - } - pub fn get_model(&self, model_id: u64) -> Result { - let (model_ptr, maybe_error) = unsafe { try_func!(larodGetModel, self.conn, model_id) }; - if !model_ptr.is_null() { - debug_assert!( - maybe_error.is_none(), - "larodGetModel indicated success AND returned an error!" - ); - Ok(LarodModel { ptr: model_ptr }) - } else { - Err(maybe_error.unwrap_or(Error::MissingLarodError)) - } - } pub fn models() -> Result<()> { Ok(()) } @@ -717,9 +1140,19 @@ impl<'a> Session<'a> { pub fn destroy_tensors() -> Result<()> { Ok(()) } - pub fn track_tensor() -> 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(()) } @@ -734,7 +1167,7 @@ impl<'a> Session<'a> { } } -impl<'a> Default for Session<'a> { +impl Default for Session { fn default() -> Self { SessionBuilder::default() .build() @@ -742,11 +1175,12 @@ impl<'a> Default for Session<'a> { } } -impl<'a> std::ops::Drop for Session<'a> { +impl std::ops::Drop for Session { fn drop(&mut self) { - unsafe { - try_func!(larodDisconnect, &mut self.conn); - } + println!("Dropping Session!"); + // unsafe { + // try_func!(larodDisconnect, &mut self.conn); + // } } } @@ -826,7 +1260,7 @@ mod tests { #[test] fn it_establishes_session() { - let sess = Session::new(); + Session::new(); } #[test] @@ -842,4 +1276,56 @@ mod tests { ); } } + + #[test] + fn it_creates_and_destroys_model() { + 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)) => { + eprintln!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + eprintln!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + } + println!("Number of model inputs: {}", preprocessor.num_inputs); + } + + #[test] + fn model_errors_with_no_tensors() { + 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)) => { + eprintln!("Error building preprocessor: {:?}", e.msg()); + panic!() + } + Err(e) => { + eprintln!("Unexpected error while building preprocessor: {:?}", e); + panic!() + } + }; + if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { + eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + } + println!("Number of model inputs: {}", preprocessor.num_inputs); + } } From 300af7e21526f452b71752cf25b74593d45408e3 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 17 Dec 2024 15:51:42 -0500 Subject: [PATCH 30/51] Explicitly use height and width parameters for setting sizes. Create Resolution struct to help maintain clarity. --- crates/larod/src/lib.rs | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 714c996b..b6cb21c8 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -640,12 +640,17 @@ pub enum PreProcError { UnsupportedOperation, } +struct Resolution { + width: u32, + height: u32, +} + #[derive(Default)] pub struct PreprocessorBuilder { backend: PreProcBackend, - input_size: Option<(i64, i64)>, + input_size: Option, crop: Option<(i64, i64, i64, i64)>, - output_size: Option<(i64, i64)>, + output_size: Option, input_format: ImageFormat, output_format: ImageFormat, } @@ -670,13 +675,13 @@ impl PreprocessorBuilder { /// 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, size: (i64, i64)) -> Self { - self.output_size = Some(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, size: (i64, i64)) -> Self { - self.input_size = Some(size); + pub fn input_size(mut self, width: u32, height: u32) -> Self { + self.input_size = Some(Resolution { width, height }); self } @@ -745,7 +750,10 @@ impl PreprocessorBuilder { } } if let Some(s) = self.input_size { - map.set_int_arr2("image.input.size", s)?; + map.set_int_arr2( + "image.input.size", + (i64::from(s.width), i64::from(s.height)), + )?; } let mut crop_map: Option = None; @@ -758,7 +766,10 @@ impl PreprocessorBuilder { } if let Some(s) = self.output_size { - map.set_int_arr2("image.output.size", s)?; + map.set_int_arr2( + "image.output.size", + (i64::from(s.width), i64::from(s.height)), + )?; } let device_name = match self.backend { @@ -1282,8 +1293,8 @@ mod tests { let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) - .input_size((1920, 1080)) - .output_size((1920, 1080)) + .input_size(1920, 1080) + .output_size(1920, 1080) .backend(PreProcBackend::LibYUV) .load(&session) { @@ -1308,8 +1319,8 @@ mod tests { let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) - .input_size((1920, 1080)) - .output_size((1920, 1080)) + .input_size(1920, 1080) + .output_size(1920, 1080) .backend(PreProcBackend::LibYUV) .load(&session) { From 4b2f2cbc37ef72b6c461a9ba4ca70d8a5f8c83e1 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Wed, 18 Dec 2024 13:20:09 -0500 Subject: [PATCH 31/51] Implement Send and Sync for LarodError. --- crates/larod/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index b6cb21c8..e68273d9 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -109,6 +109,9 @@ impl LarodError { } } +unsafe impl Send for LarodError {} +unsafe impl Sync for LarodError {} + impl std::error::Error for LarodError {} impl Display for LarodError { From 486757aeb0e6576ddaa78f374865ce3bce82dbec Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Wed, 18 Dec 2024 15:15:45 -0500 Subject: [PATCH 32/51] Rename LarodModel.start() to LarodModel.start_job(). --- crates/larod/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index e68273d9..0d10b33f 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -605,7 +605,7 @@ impl<'a> LarodDevice<'a> { pub trait LarodModel { fn create_model_inputs(&mut self) -> Result<()>; fn num_inputs(&self) -> usize; - fn start(&self) -> Result<()>; + fn start_job(&self) -> Result<()>; fn stop(&self); } @@ -876,7 +876,7 @@ impl<'a> LarodModel for Preprocessor<'a> { self.num_inputs } - fn start(&self) -> Result<()> { + fn start_job(&self) -> Result<()> { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); } @@ -1006,7 +1006,7 @@ impl<'a> LarodModel for InferenceModel<'a> { self.num_inputs } - fn start(&self) -> Result<()> { + fn start_job(&self) -> Result<()> { Ok(()) } fn stop(&self) {} @@ -1292,7 +1292,7 @@ mod tests { } #[test] - fn it_creates_and_destroys_model() { + fn it_creates_and_destroys_preprocessor() { let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) From 714a8bd1f8c384c7d53520b38926d4054502515e Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Wed, 18 Dec 2024 15:56:45 -0500 Subject: [PATCH 33/51] Add env_logger as dev dependency for printing data during test. --- Cargo.lock | 1 + crates/larod/Cargo.toml | 3 +++ crates/larod/src/lib.rs | 35 ++++++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30dbc343..beab510e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1296,6 +1296,7 @@ dependencies = [ name = "larod" version = "0.1.0" dependencies = [ + "env_logger", "larod-sys", "log", "thiserror", diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 58f3d524..2011a682 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -9,6 +9,9 @@ larod-sys = { workspace = true } log.workspace = true thiserror.workspace = true +[dev-dependencies] +env_logger.workspace = true + [features] device-tests = [] diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 0d10b33f..56dc9343 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -901,7 +901,7 @@ impl<'a> LarodModel for Preprocessor<'a> { impl<'a> Drop for Preprocessor<'a> { fn drop(&mut self) { if let Some(ref mut tensor_container) = self.input_tensors { - println!("Dropping Preprocessor input tensors!"); + log::debug!("Dropping Preprocessor input tensors!"); unsafe { try_func!( larodDestroyTensors, @@ -1191,7 +1191,7 @@ impl Default for Session { impl std::ops::Drop for Session { fn drop(&mut self) { - println!("Dropping Session!"); + log::debug!("Dropping Session!"); // unsafe { // try_func!(larodDisconnect, &mut self.conn); // } @@ -1204,17 +1204,20 @@ mod tests { #[test] fn it_creates_larod_map() { + env_logger::builder().is_test(true).try_init(); assert!(LarodMap::new().is_ok()); } #[test] fn it_drops_map() { + env_logger::builder().is_test(true).try_init(); let map = LarodMap::new().unwrap(); std::mem::drop(map); } #[test] fn larod_map_can_set_str() { + env_logger::builder().is_test(true).try_init(); let mut map = LarodMap::new().unwrap(); map.set_string("test_key", "test_value").unwrap(); } @@ -1229,12 +1232,14 @@ mod tests { #[test] fn larod_map_can_set_int() { + 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() { + 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(); @@ -1243,11 +1248,13 @@ mod tests { #[test] fn larod_map_can_set_2_tuple() { + 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() { + 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(); @@ -1257,12 +1264,14 @@ mod tests { #[test] fn larod_map_can_set_4_tuple() { + 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() { + 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(); @@ -1274,15 +1283,17 @@ mod tests { #[test] fn it_establishes_session() { + env_logger::builder().is_test(true).try_init(); Session::new(); } #[test] fn it_lists_devices() { + env_logger::builder().is_test(true).try_init(); let sess = Session::new(); let devices = sess.devices().unwrap(); for device in devices { - println!( + log::info!( "device: {}, id: {}, addr: {:?}", device.name().unwrap(), device.instance().unwrap(), @@ -1293,6 +1304,7 @@ mod tests { #[test] fn it_creates_and_destroys_preprocessor() { + env_logger::builder().is_test(true).try_init(); let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) @@ -1303,22 +1315,23 @@ mod tests { { Ok(p) => p, Err(Error::LarodError(e)) => { - eprintln!("Error building preprocessor: {:?}", e.msg()); + log::error!("Error building preprocessor: {:?}", e.msg()); panic!() } Err(e) => { - eprintln!("Unexpected error while building preprocessor: {:?}", e); + log::error!("Unexpected error while building preprocessor: {:?}", e); panic!() } }; if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { - eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); } - println!("Number of model inputs: {}", preprocessor.num_inputs); + log::info!("Number of model inputs: {}", preprocessor.num_inputs); } #[test] fn model_errors_with_no_tensors() { + env_logger::builder().is_test(true).try_init(); let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) @@ -1329,17 +1342,17 @@ mod tests { { Ok(p) => p, Err(Error::LarodError(e)) => { - eprintln!("Error building preprocessor: {:?}", e.msg()); + log::error!("Error building preprocessor: {:?}", e.msg()); panic!() } Err(e) => { - eprintln!("Unexpected error while building preprocessor: {:?}", e); + log::error!("Unexpected error while building preprocessor: {:?}", e); panic!() } }; if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { - eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); } - println!("Number of model inputs: {}", preprocessor.num_inputs); + log::info!("Number of model inputs: {}", preprocessor.num_inputs); } } From ec86ed3a4b7019cea8c6864811554ce1e9986f97 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 19 Dec 2024 09:48:24 -0500 Subject: [PATCH 34/51] Delete remote-test.sh in root directory. --- remote-test.sh | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 remote-test.sh diff --git a/remote-test.sh b/remote-test.sh deleted file mode 100644 index 652a9984..00000000 --- a/remote-test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -e -if [ -n "${CARGO_TEST_CAMERA}" ]; then - f=`basename $1` - scp "$1" $CARGO_TEST_CAMERA:. - # echo $f - ssh $CARGO_TEST_CAMERA "chmod +x /root/$f" - ssh $CARGO_TEST_CAMERA "/root/$f" -else - $1 -fi From 2380e9c198d666f84bb21abc7e4851777eb825a9 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 19 Dec 2024 11:39:43 -0500 Subject: [PATCH 35/51] Remove default build target. --- .cargo/config.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 1ad62c53..fcfd81db 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,2 @@ -[build] -target = "aarch64-unknown-linux-gnu" - [target.'cfg(all(not(miri), not(target_arch = "x86_64")))'] runner = ["remote-test.sh"] From c3a87f4ad513c4f208679bbe19d4fe4b6e6d8366 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 19 Dec 2024 11:41:55 -0500 Subject: [PATCH 36/51] Implement iterating over `Tensors` pointed to by `LarodTensorContainer`. --- crates/larod/src/lib.rs | 112 +++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 56dc9343..4a182662 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -48,6 +48,7 @@ use std::{ os::fd::AsRawFd, path::Path, ptr::{self, slice_from_raw_parts}, + slice::Iter, }; type Result = std::result::Result; @@ -447,41 +448,61 @@ impl std::ops::Drop for LarodMap { // phantom: PhantomData<&'a Session>, // } -struct LarodTensorContainer { +pub struct LarodTensorContainer<'a> { ptr: *mut *mut larodTensor, + tensors: Vec>, num_tensors: usize, } -pub struct Tensor(*mut larodTensor); + +impl<'a> LarodTensorContainer<'a> { + pub fn as_slice(&self) -> &[Tensor] { + self.tensors.as_slice() + } + + pub fn iter(&self) -> Iter { + self.tensors.iter() + } + + pub fn len(&self) -> usize { + self.tensors.len() + } +} + +pub struct Tensor<'a> { + ptr: *mut larodTensor, + phantom: PhantomData<&'a Session>, +} /// A structure representing a larodTensor. -impl Tensor { - // fn as_ptr(&self) -> *const larodTensor { - // self.ptr.cast_const() - // } +impl<'a> Tensor<'a> { + fn as_ptr(&self) -> *const larodTensor { + self.ptr.cast_const() + } - // fn as_mut_ptr(&self) -> *mut larodTensor { - // self.ptr - // } + fn as_mut_ptr(&self) -> *mut larodTensor { + self.ptr + } pub fn name() {} pub fn byte_size() {} - pub fn dims(&self) -> Result> { - let (dims, maybe_error) = unsafe { try_func!(larodGetTensorDims, self.0) }; + 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::>() - }; - Ok(d) + // 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)) } @@ -496,7 +517,8 @@ impl Tensor { dims: dim_array, len: dims.len(), }; - let (success, maybe_error) = unsafe { try_func!(larodSetTensorDims, self.0, &dims_struct) }; + let (success, maybe_error) = + unsafe { try_func!(larodSetTensorDims, self.ptr, &dims_struct) }; if success { debug_assert!( maybe_error.is_none(), @@ -537,6 +559,15 @@ impl Tensor { // } } +impl<'a> From<*mut larodTensor> for Tensor<'a> { + fn from(value: *mut larodTensor) -> Self { + Self { + ptr: value, + 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 @@ -605,6 +636,7 @@ impl<'a> LarodDevice<'a> { pub trait LarodModel { fn create_model_inputs(&mut self) -> Result<()>; fn num_inputs(&self) -> usize; + fn input_tensors(&self) -> Option<&LarodTensorContainer>; fn start_job(&self) -> Result<()>; fn stop(&self); } @@ -838,9 +870,9 @@ impl PreprocessorBuilder { pub struct Preprocessor<'a> { session: &'a Session, ptr: *mut larodModel, - input_tensors: Option, + input_tensors: Option>, num_inputs: usize, - output_tensors: Option, + output_tensors: Option>, num_outputs: usize, crop: Option, } @@ -853,15 +885,22 @@ impl<'a> Preprocessor<'a> { impl<'a> LarodModel for Preprocessor<'a> { fn create_model_inputs(&mut self) -> Result<()> { - let (tensors, maybe_error) = + let (tensors_ptr, maybe_error) = unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; - if !tensors.is_null() { + 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_ptr, + tensors, num_tensors: self.num_inputs, }); // let tensor_slice = @@ -876,6 +915,10 @@ impl<'a> LarodModel for Preprocessor<'a> { self.num_inputs } + fn input_tensors(&self) -> Option<&LarodTensorContainer> { + self.input_tensors.as_ref() + } + fn start_job(&self) -> Result<()> { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); @@ -948,7 +991,7 @@ impl<'a> Drop for Preprocessor<'a> { pub struct InferenceModel<'a> { session: &'a Session, ptr: *mut larodModel, - input_tensors: Option, + input_tensors: Option>, num_inputs: usize, } @@ -985,15 +1028,22 @@ impl<'a> InferenceModel<'a> { impl<'a> LarodModel for InferenceModel<'a> { fn create_model_inputs(&mut self) -> Result<()> { - let (tensors, maybe_error) = + let (tensors_ptr, maybe_error) = unsafe { try_func!(larodCreateModelInputs, self.ptr, &mut self.num_inputs) }; - if !tensors.is_null() { + 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_ptr, + tensors, num_tensors: self.num_inputs, }); Ok(()) @@ -1006,6 +1056,10 @@ impl<'a> LarodModel for InferenceModel<'a> { self.num_inputs } + fn input_tensors(&self) -> Option<&LarodTensorContainer> { + self.input_tensors.as_ref() + } + fn start_job(&self) -> Result<()> { Ok(()) } From ae1384c32bbc914770cb309b2b12afbb6157d183 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 19 Dec 2024 11:42:34 -0500 Subject: [PATCH 37/51] Add compiler failure tests. --- Cargo.lock | 40 ++ crates/larod/Cargo.toml | 1 + crates/larod/README.md | 16 + .../lifetimes/tensor_lifetimes.rs | 33 ++ .../lifetimes/tensor_lifetimes.stderr | 12 + crates/larod/src/lib.rs | 367 +++++++++++------- 6 files changed, 324 insertions(+), 145 deletions(-) create mode 100644 crates/larod/README.md create mode 100644 crates/larod/compiler_tests/lifetimes/tensor_lifetimes.rs create mode 100644 crates/larod/compiler_tests/lifetimes/tensor_lifetimes.stderr diff --git a/Cargo.lock b/Cargo.lock index dc2b1ecc..8caee7b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1334,6 +1334,7 @@ dependencies = [ "larod-sys", "log", "thiserror", + "trybuild", ] [[package]] @@ -2367,6 +2368,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" @@ -2389,6 +2396,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" @@ -2705,6 +2721,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" @@ -2972,6 +3003,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/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 2011a682..137320a2 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -11,6 +11,7 @@ thiserror.workspace = true [dev-dependencies] env_logger.workspace = true +trybuild = "1.0.101" [features] device-tests = [] 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/src/lib.rs b/crates/larod/src/lib.rs index 4a182662..c49b4726 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -1252,161 +1252,238 @@ impl std::ops::Drop for Session { } } -#[cfg(all(test, target_arch = "aarch64", feature = "device-tests"))] +#[cfg(test)] mod tests { use super::*; - #[test] - fn it_creates_larod_map() { - env_logger::builder().is_test(true).try_init(); - assert!(LarodMap::new().is_ok()); - } - - #[test] - fn it_drops_map() { - env_logger::builder().is_test(true).try_init(); - let map = LarodMap::new().unwrap(); - std::mem::drop(map); - } - - #[test] - fn larod_map_can_set_str() { - 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() { - 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() { - 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() { - 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() { - 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() { - 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() { - 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() { - env_logger::builder().is_test(true).try_init(); - Session::new(); - } - - #[test] - fn it_lists_devices() { - 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) }, - ); + #[cfg(all(target_arch = "aarch64", feature = "device-tests"))] + mod device_test { + use super::*; + + #[test] + fn it_creates_larod_map() { + env_logger::builder().is_test(true).try_init(); + assert!(LarodMap::new().is_ok()); + } + + #[test] + fn it_drops_map() { + env_logger::builder().is_test(true).try_init(); + let map = LarodMap::new().unwrap(); + std::mem::drop(map); + } + + #[test] + fn larod_map_can_set_str() { + 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() { + 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() { + 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() { + 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() { + 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() { + 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() { + 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() { + env_logger::builder().is_test(true).try_init(); + Session::new(); } - } - #[test] - fn it_creates_and_destroys_preprocessor() { - 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!() + #[test] + fn it_lists_devices() { + 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) }, + ); } - Err(e) => { - log::error!("Unexpected error while building preprocessor: {:?}", e); - panic!() + } + + #[test] + fn it_creates_and_destroys_preprocessor() { + 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()); } - }; - 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); } - log::info!("Number of model inputs: {}", preprocessor.num_inputs); - } - - #[test] - fn model_errors_with_no_tensors() { - 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!() + + #[test] + fn model_errors_with_no_tensors() { + 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()); } - Err(e) => { - log::error!("Unexpected error while building preprocessor: {:?}", e); - panic!() + log::info!("Number of model inputs: {}", preprocessor.num_inputs); + } + + #[test] + fn preprocessor_is_safe_without_model_tensors() { + 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!() + } + }; + 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()); + } } - }; - 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_can_iterate_model_tensors() { + 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"); + } } } From 0eea902ad5f7419d9bc1bbda0e3ffb867c8556a3 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 19 Dec 2024 12:13:28 -0500 Subject: [PATCH 38/51] Update larod basic example. --- crates/larod/examples/basic.rs | 35 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index 036a56c0..f19d3dcc 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -1,19 +1,20 @@ use larod::{Error, ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; fn main() -> Result<(), Error> { + env_logger::init(); let session = Session::new(); let devices = match session.devices() { Ok(d) => d, Err(Error::LarodError(e)) => { if let Ok(msg) = e.msg() { - eprintln!("Error while listing available devices! {}", msg); + log::error!("Error while listing available devices! {}", msg); } else { - eprintln!("Error while listing available devices. Error returned ") + log::error!("Error while listing available devices. Error returned ") } return Err(Error::LarodError(e)); } Err(e) => { - eprintln!("Unknown error while listing devices: {:?}", e); + log::error!("Unknown error while listing devices: {:?}", e); return Err(e); } }; @@ -25,26 +26,20 @@ fn main() -> Result<(), Error> { d.instance().expect("Couldn't get device instance") ); } - let mut preprocessor = match Preprocessor::builder() + let mut preprocessor = Preprocessor::builder() .input_format(ImageFormat::NV12) - .input_size((1920, 1080)) - .output_size((1920, 1080)) + .input_size(1920, 1080) + .output_size(1920, 1080) .backend(PreProcBackend::LibYUV) - .load(&session) - { - Ok(p) => p, - Err(Error::LarodError(e)) => { - eprintln!("Error building preprocessor: {:?}", e.msg()); - panic!() - } - Err(e) => { - eprintln!("Unexpected error while building preprocessor: {:?}", e); - panic!() - } - }; + .load(&session)?; if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { - eprintln!("Error creating preprocessor inputs: {:?}", e.msg()); + log::error!("Error creating preprocessor inputs: {:?}", e.msg()); + } + 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()); + } } - println!("Number of model inputs: {}", preprocessor.num_inputs()); Ok(()) } From 63e4875625352cbdc17d7207e261aab3f206f6b6 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 31 Dec 2024 16:23:51 -0500 Subject: [PATCH 39/51] Clean up a few lints for unused results and imports. --- crates/larod/src/lib.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index c49b4726..cbb5b2c2 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -45,10 +45,8 @@ use std::{ fmt::Display, fs::File, marker::PhantomData, - os::fd::AsRawFd, - path::Path, - ptr::{self, slice_from_raw_parts}, - slice::Iter, + os::fd::{AsFd, AsRawFd}, + ptr::{self}, }; type Result = std::result::Result; @@ -1262,20 +1260,20 @@ mod tests { #[test] fn it_creates_larod_map() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); assert!(LarodMap::new().is_ok()); } #[test] fn it_drops_map() { - env_logger::builder().is_test(true).try_init(); + 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() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let mut map = LarodMap::new().unwrap(); map.set_string("test_key", "test_value").unwrap(); } @@ -1290,14 +1288,14 @@ mod tests { #[test] fn larod_map_can_set_int() { - env_logger::builder().is_test(true).try_init(); + 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() { - env_logger::builder().is_test(true).try_init(); + 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(); @@ -1306,13 +1304,13 @@ mod tests { #[test] fn larod_map_can_set_2_tuple() { - env_logger::builder().is_test(true).try_init(); + 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() { - env_logger::builder().is_test(true).try_init(); + 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(); @@ -1322,14 +1320,14 @@ mod tests { #[test] fn larod_map_can_set_4_tuple() { - env_logger::builder().is_test(true).try_init(); + 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() { - env_logger::builder().is_test(true).try_init(); + 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(); @@ -1341,13 +1339,13 @@ mod tests { #[test] fn it_establishes_session() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); Session::new(); } #[test] fn it_lists_devices() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let sess = Session::new(); let devices = sess.devices().unwrap(); for device in devices { @@ -1362,7 +1360,7 @@ mod tests { #[test] fn it_creates_and_destroys_preprocessor() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) @@ -1389,7 +1387,7 @@ mod tests { #[test] fn model_errors_with_no_tensors() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) @@ -1416,9 +1414,9 @@ mod tests { #[test] fn preprocessor_is_safe_without_model_tensors() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let session = Session::new(); - let mut preprocessor = match Preprocessor::builder() + let preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) .input_size(1920, 1080) .output_size(1920, 1080) @@ -1446,7 +1444,7 @@ mod tests { #[test] fn preprocessor_can_iterate_model_tensors() { - env_logger::builder().is_test(true).try_init(); + let _ = env_logger::builder().is_test(true).try_init(); let session = Session::new(); let mut preprocessor = match Preprocessor::builder() .input_format(ImageFormat::NV12) From 9be7b764729d0c1667115a9183f17ee5693b8c23 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 31 Dec 2024 16:25:56 -0500 Subject: [PATCH 40/51] Implement iterating over Tensors. - Set tensor buffers - Get tensor name - Get tensor pitches - Get tensor layout --- crates/larod/src/lib.rs | 108 +++++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 12 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index cbb5b2c2..9aff2cdf 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -457,10 +457,14 @@ impl<'a> LarodTensorContainer<'a> { self.tensors.as_slice() } - pub fn iter(&self) -> Iter { + 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() } @@ -468,6 +472,7 @@ impl<'a> LarodTensorContainer<'a> { pub struct Tensor<'a> { ptr: *mut larodTensor, + buffer: Option, phantom: PhantomData<&'a Session>, } @@ -481,7 +486,23 @@ impl<'a> Tensor<'a> { self.ptr } - pub fn name() {} + 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() {} @@ -528,14 +549,67 @@ impl<'a> Tensor<'a> { } } - pub fn pitches() {} + 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() {} + 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() {} - pub fn set_fd() {} + 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<()> { + self.buffer = Some(file); + let (success, maybe_error) = unsafe { + try_func!( + larodSetTensorFd, + self.ptr, + self.buffer.as_mut().unwrap().as_raw_fd() + ) + }; + if success { + debug_assert!( + maybe_error.is_none(), + "larodSetTensorFd indicated success AND returned an error!" + ); + Ok(()) + } else { + Err(maybe_error.unwrap_or(Error::MissingLarodError)) + } + } pub fn fd_size() {} pub fn set_fd_size() {} pub fn fd_offset() {} @@ -561,6 +635,7 @@ impl<'a> From<*mut larodTensor> for Tensor<'a> { fn from(value: *mut larodTensor) -> Self { Self { ptr: value, + buffer: None, phantom: PhantomData, } } @@ -631,10 +706,11 @@ impl<'a> LarodDevice<'a> { } } -pub trait LarodModel { +pub trait LarodModel<'a> { fn create_model_inputs(&mut self) -> Result<()>; fn num_inputs(&self) -> usize; - fn input_tensors(&self) -> Option<&LarodTensorContainer>; + fn input_tensors(&self) -> Option<&LarodTensorContainer<'a>>; + fn input_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>>; fn start_job(&self) -> Result<()>; fn stop(&self); } @@ -881,7 +957,7 @@ impl<'a> Preprocessor<'a> { } } -impl<'a> LarodModel for Preprocessor<'a> { +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) }; @@ -913,10 +989,14 @@ impl<'a> LarodModel for Preprocessor<'a> { self.num_inputs } - fn input_tensors(&self) -> Option<&LarodTensorContainer> { + 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 start_job(&self) -> Result<()> { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); @@ -1024,7 +1104,7 @@ impl<'a> InferenceModel<'a> { } } -impl<'a> LarodModel for InferenceModel<'a> { +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) }; @@ -1054,10 +1134,14 @@ impl<'a> LarodModel for InferenceModel<'a> { self.num_inputs } - fn input_tensors(&self) -> Option<&LarodTensorContainer> { + 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 start_job(&self) -> Result<()> { Ok(()) } From 65de92046938f327570aabab5bf2d64c5e5ad8bc Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 31 Dec 2024 16:26:27 -0500 Subject: [PATCH 41/51] Update basic example for iterating over tensors. --- crates/larod/examples/basic.rs | 53 ++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index f19d3dcc..c8f7aecc 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -1,23 +1,25 @@ -use larod::{Error, ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; +use anyhow::Context; +use larod::{ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; +use std::{fs, path::Path}; -fn main() -> Result<(), Error> { +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 = match session.devices() { - Ok(d) => d, - Err(Error::LarodError(e)) => { - if let Ok(msg) = e.msg() { - log::error!("Error while listing available devices! {}", msg); - } else { - log::error!("Error while listing available devices. Error returned ") - } - return Err(Error::LarodError(e)); - } - Err(e) => { - log::error!("Unknown error while listing devices: {:?}", e); - return Err(e); - } - }; + let devices = session + .devices() + .context("could not list available devices")?; println!("Devices:"); for d in devices { println!( @@ -32,14 +34,21 @@ fn main() -> Result<(), Error> { .output_size(1920, 1080) .backend(PreProcBackend::LibYUV) .load(&session)?; - if let Err(Error::LarodError(e)) = preprocessor.create_model_inputs() { - log::error!("Error creating preprocessor inputs: {:?}", e.msg()); - } - if let Some(tensors) = preprocessor.input_tensors() { + 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() { + 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)?; } } + Ok(()) } From 682cb21d4428d69d25c418f68f9d4763a7651745 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 31 Dec 2024 16:26:46 -0500 Subject: [PATCH 42/51] Add anyhow development dependency for basic example. --- crates/larod/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 137320a2..24ea29a9 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -11,6 +11,7 @@ thiserror.workspace = true [dev-dependencies] env_logger.workspace = true +anyhow.workspace = true trybuild = "1.0.101" [features] From 670fb23274654d4b0d7672b19af58bca1ee3d7a1 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 31 Dec 2024 16:27:03 -0500 Subject: [PATCH 43/51] Update cargo lock file for anyhow dependency. --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 6e34a5ee..8615144f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1330,6 +1330,7 @@ dependencies = [ name = "larod" version = "0.1.0" dependencies = [ + "anyhow", "env_logger", "larod-sys", "log", From 845fadb70ec9593a5f4bf625ac08b6bd90d78bdf Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 08:01:55 -0500 Subject: [PATCH 44/51] Implement ops::Deref for LarodTensorContainer<'a> for Target = [Tensor<'a>] --- crates/larod/examples/basic.rs | 5 ++++- crates/larod/src/lib.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index c8f7aecc..7d254211 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -1,6 +1,6 @@ use anyhow::Context; use larod::{ImageFormat, LarodModel, PreProcBackend, Preprocessor, Session}; -use std::{fs, path::Path}; +use std::{fs, iter::Iterator, path::Path}; fn get_file(name: &str) -> anyhow::Result { let path = Path::new(name); @@ -49,6 +49,9 @@ fn main() -> anyhow::Result<()> { t.set_buffer(file)?; } } + let Some(input_tensor) = preprocessor.input_tensors().and_then(|tc| tc.first()) 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 index 9aff2cdf..5c86c6a3 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -45,6 +45,7 @@ use std::{ fmt::Display, fs::File, marker::PhantomData, + ops, os::fd::{AsFd, AsRawFd}, ptr::{self}, }; @@ -470,6 +471,15 @@ impl<'a> LarodTensorContainer<'a> { } } +impl<'a> ops::Deref for LarodTensorContainer<'a> { + type Target = [Tensor<'a>]; + + #[inline] + fn deref(&self) -> &[Tensor<'a>] { + self.tensors.as_slice() + } +} + pub struct Tensor<'a> { ptr: *mut larodTensor, buffer: Option, From 44e00d785bbb11535552d028a9c3e9556bf840f5 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 08:07:51 -0500 Subject: [PATCH 45/51] Implement ops::DerefMut for LarodTensorContainer<'a> for Target = [Tensor<'a>] --- crates/larod/examples/basic.rs | 5 ++++- crates/larod/src/lib.rs | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/larod/examples/basic.rs b/crates/larod/examples/basic.rs index 7d254211..287da3c6 100644 --- a/crates/larod/examples/basic.rs +++ b/crates/larod/examples/basic.rs @@ -49,7 +49,10 @@ fn main() -> anyhow::Result<()> { t.set_buffer(file)?; } } - let Some(input_tensor) = preprocessor.input_tensors().and_then(|tc| tc.first()) else { + let Some(input_tensor) = preprocessor + .input_tensors_mut() + .and_then(|tc| tc.first_mut()) + else { return Err(anyhow::anyhow!("preprocessor has no input tensors")); }; diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 5c86c6a3..a0383934 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -480,6 +480,13 @@ impl<'a> ops::Deref for LarodTensorContainer<'a> { } } +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, From 620808db3671e80a62cd368f7876078c4d58a828 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 12:02:05 -0500 Subject: [PATCH 46/51] Add memory mapped buffer for Tensor. - Add memmap2 dependency - Create and store memory map object when Tensor.set_buffer() is called. - Add Tensor.copy_from_slice() to copy data to memory mapped resource. --- Cargo.lock | 10 ++++++++++ crates/larod/Cargo.toml | 2 +- crates/larod/src/lib.rs | 20 +++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8615144f..042c416a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1334,6 +1334,7 @@ dependencies = [ "env_logger", "larod-sys", "log", + "memmap2", "thiserror", "trybuild", ] @@ -1479,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" diff --git a/crates/larod/Cargo.toml b/crates/larod/Cargo.toml index 24ea29a9..8dda0351 100644 --- a/crates/larod/Cargo.toml +++ b/crates/larod/Cargo.toml @@ -4,10 +4,10 @@ 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 diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index a0383934..de606665 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -40,6 +40,7 @@ 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, @@ -490,6 +491,7 @@ impl<'a> ops::DerefMut for LarodTensorContainer<'a> { pub struct Tensor<'a> { ptr: *mut larodTensor, buffer: Option, + mmap: Option, phantom: PhantomData<&'a Session>, } @@ -609,7 +611,6 @@ impl<'a> Tensor<'a> { /// 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<()> { - self.buffer = Some(file); let (success, maybe_error) = unsafe { try_func!( larodSetTensorFd, @@ -622,6 +623,17 @@ impl<'a> Tensor<'a> { 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)) @@ -633,6 +645,11 @@ impl<'a> Tensor<'a> { pub fn set_fd_offset() {} pub fn fd_props() {} pub fn set_fd_props() {} + 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) }; @@ -653,6 +670,7 @@ impl<'a> From<*mut larodTensor> for Tensor<'a> { Self { ptr: value, buffer: None, + mmap: None, phantom: PhantomData, } } From fff440fb227faf55e8ccac7897aed92203189db9 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 13:44:02 -0500 Subject: [PATCH 47/51] Use file, not self.buffer when setting buffer on Tensor. --- crates/larod/src/lib.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index de606665..c72e3190 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -611,13 +611,8 @@ impl<'a> Tensor<'a> { /// 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, - self.buffer.as_mut().unwrap().as_raw_fd() - ) - }; + let (success, maybe_error) = + unsafe { try_func!(larodSetTensorFd, self.ptr, file.as_raw_fd()) }; if success { debug_assert!( maybe_error.is_none(), From 9c96b53fac271994d1437ef3ddc69df1ca27377c Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 14:22:20 -0500 Subject: [PATCH 48/51] Create model outputs. --- crates/larod/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index c72e3190..49f5d399 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -741,6 +741,10 @@ pub trait LarodModel<'a> { 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 start_job(&self) -> Result<()>; fn stop(&self); } @@ -1007,8 +1011,31 @@ impl<'a> LarodModel<'a> for Preprocessor<'a> { tensors, num_tensors: self.num_inputs, }); - // let tensor_slice = - // unsafe { slice::from_raw_parts::<*mut larodTensor>(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)) @@ -1027,6 +1054,18 @@ impl<'a> LarodModel<'a> for Preprocessor<'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 start_job(&self) -> Result<()> { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); @@ -1101,6 +1140,8 @@ pub struct InferenceModel<'a> { ptr: *mut larodModel, input_tensors: Option>, num_inputs: usize, + output_tensors: Option>, + num_outputs: usize, } impl<'a> InferenceModel<'a> { @@ -1160,6 +1201,31 @@ impl<'a> LarodModel<'a> for InferenceModel<'a> { } } + 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 } @@ -1172,6 +1238,18 @@ impl<'a> LarodModel<'a> for InferenceModel<'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 start_job(&self) -> Result<()> { Ok(()) } From 295dfc8cee0530ff97eb3d9893d19768efbaf2a2 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 2 Jan 2025 14:49:14 -0500 Subject: [PATCH 49/51] Add JobRequest struct from larodCreateJobRequest. --- crates/larod/src/lib.rs | 57 ++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 49f5d399..15b10ec3 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -745,8 +745,7 @@ pub trait LarodModel<'a> { fn num_outputs(&self) -> usize; fn output_tensors(&self) -> Option<&LarodTensorContainer<'a>>; fn output_tensors_mut(&mut self) -> Option<&mut LarodTensorContainer<'a>>; - fn start_job(&self) -> Result<()>; - fn stop(&self); + fn create_job(&self) -> Result; } #[derive(Default)] @@ -1066,11 +1065,11 @@ impl<'a> LarodModel<'a> for Preprocessor<'a> { self.output_tensors.as_mut() } - fn start_job(&self) -> Result<()> { + fn create_job(&self) -> Result { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); } - unsafe { + let (job_ptr, maybe_error) = unsafe { try_func!( larodCreateJobRequest, self.ptr, @@ -1081,11 +1080,21 @@ impl<'a> LarodModel<'a> for Preprocessor<'a> { 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)) } - Ok(()) } - fn stop(&self) {} } impl<'a> Drop for Preprocessor<'a> { @@ -1105,6 +1114,34 @@ impl<'a> Drop for Preprocessor<'a> { } } +pub struct JobRequest<'a> { + raw: *mut larodJobRequest, + session: &'a Session, +} + +impl<'a> JobRequest<'a> { + pub fn run_job(&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, @@ -1250,10 +1287,12 @@ impl<'a> LarodModel<'a> for InferenceModel<'a> { self.output_tensors.as_mut() } - fn start_job(&self) -> Result<()> { - Ok(()) + fn create_job(&self) -> Result { + Ok(JobRequest { + raw: ptr::null_mut(), + session: self.session, + }) } - fn stop(&self) {} } impl<'a> Drop for InferenceModel<'a> { From 90430ccdad5039b26e27d547c5ebca48a574dad4 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Tue, 7 Jan 2025 12:21:49 -0500 Subject: [PATCH 50/51] Add missing lifetime parameters. --- crates/larod/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 15b10ec3..1d6b3e5a 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -745,7 +745,7 @@ pub trait LarodModel<'a> { 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; + fn create_job(&self) -> Result>; } #[derive(Default)] @@ -837,7 +837,7 @@ impl PreprocessorBuilder { self } - pub fn load(self, session: &Session) -> Result { + 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")?, @@ -1065,7 +1065,7 @@ impl<'a> LarodModel<'a> for Preprocessor<'a> { self.output_tensors.as_mut() } - fn create_job(&self) -> Result { + fn create_job(&self) -> Result> { if self.input_tensors.is_none() || self.output_tensors.is_none() { return Err(Error::UnsatisfiedDependencies); } @@ -1287,7 +1287,7 @@ impl<'a> LarodModel<'a> for InferenceModel<'a> { self.output_tensors.as_mut() } - fn create_job(&self) -> Result { + fn create_job(&self) -> Result> { Ok(JobRequest { raw: ptr::null_mut(), session: self.session, From 6aa9c1e545eff03f803a62aa5bfd587606b46122 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 16 Jan 2025 08:34:04 -0500 Subject: [PATCH 51/51] Add InferenceModel job creation. - Get Tensor as slice. - Rename JobRequest.run_job to JobRequest.run --- crates/larod/src/lib.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/crates/larod/src/lib.rs b/crates/larod/src/lib.rs index 1d6b3e5a..412d794c 100644 --- a/crates/larod/src/lib.rs +++ b/crates/larod/src/lib.rs @@ -640,6 +640,9 @@ impl<'a> Tensor<'a> { 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); @@ -1120,7 +1123,7 @@ pub struct JobRequest<'a> { } impl<'a> JobRequest<'a> { - pub fn run_job(&self) -> Result<()> { + pub fn run(&self) -> Result<()> { let (success, maybe_error) = unsafe { try_func!(larodRunJob, self.session.conn, self.raw) }; if success { debug_assert!( @@ -1288,10 +1291,34 @@ impl<'a> LarodModel<'a> for InferenceModel<'a> { } fn create_job(&self) -> Result> { - Ok(JobRequest { - raw: ptr::null_mut(), - session: self.session, - }) + 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)) + } } }