chore(tfhe)!: remove dependency on the dynamic buffer lib

- this was required in a semver trick setting and is not needed anymore

BREAKING CHANGE:
the way to build the C API has changed and no longer requires the dynamic
buffer lib
This commit is contained in:
Arthur Meyre
2024-07-23 11:24:41 +02:00
parent 8d7c45bf17
commit a1b92a6db8
5 changed files with 220 additions and 13 deletions

View File

@@ -58,9 +58,6 @@ concrete-csprng = { version = "0.4.1", path = "../concrete-csprng", features = [
"generator_fallback",
"parallel",
] }
tfhe-c-api-dynamic-buffer = { version = "0.1.0", optional = true, features = [
"c_api",
] }
lazy_static = { version = "1.4.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
rayon = { version = "1.5.0" }
@@ -107,7 +104,7 @@ experimental = []
experimental-force_fft_algo_dif4 = []
# End experimental section
__c_api = ["dep:cbindgen", "dep:tfhe-c-api-dynamic-buffer"]
__c_api = ["dep:cbindgen"]
# For the semver trick to skip the build.rs
__force_skip_cbindgen = []
boolean-c-api = ["boolean", "__c_api"]

View File

@@ -14,8 +14,6 @@ include_directories(${TFHE_C_API_RELEASE})
include_directories(${TFHE_C_API_RELEASE}/deps)
add_library(Tfhe STATIC IMPORTED)
set_target_properties(Tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API_RELEASE}/libtfhe.a)
add_library(TfheDynamicBuffer STATIC IMPORTED)
set_target_properties(TfheDynamicBuffer PROPERTIES IMPORTED_LOCATION ${TFHE_C_API_RELEASE}/deps/libtfhe_c_api_dynamic_buffer.a)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
@@ -40,7 +38,7 @@ foreach (testsourcefile ${TEST_CASES})
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Testing
)
target_include_directories(${testname} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${testname} LINK_PUBLIC Tfhe TfheDynamicBuffer m pthread dl)
target_link_libraries(${testname} LINK_PUBLIC Tfhe m pthread dl)
if (WITH_FEATURE_GPU)
target_link_libraries(${testname} LINK_PUBLIC CUDA::cudart -lstdc++ OpenMP::OpenMP_CXX)

View File

@@ -20,7 +20,7 @@ include_version = false
namespaces = []
using_namespaces = []
sys_includes = []
includes = ["tfhe-c-api-dynamic-buffer.h"]
includes = []
no_includes = false
cpp_compat = true
after_includes = ""

View File

@@ -40,8 +40,6 @@ include_directories(${TFHE_C_API})
include_directories(${TFHE_C_API}/deps)
add_library(tfhe STATIC IMPORTED)
set_target_properties(tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/libtfhe.a)
add_library(tfheDynamicBuffer STATIC IMPORTED)
set_target_properties(tfheDynamicBuffer PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/deps/libtfhe_c_api_dynamic_buffer.a)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
@@ -53,7 +51,7 @@ endif()
set(EXECUTABLE_NAME my-executable)
add_executable(${EXECUTABLE_NAME} main.c)
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe tfheDynamicBuffer m pthread dl)
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe m pthread dl)
if(APPLE)
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC ${SECURITY_FRAMEWORK})
endif()

View File

@@ -1,3 +1,217 @@
//! Module providing some common `C` FFI utilities for key serialization and deserialization.
//! This module provides a [`DynamicBuffer`] struct that allows to easily share a pointer to u8 with
//! C APIs and free that pointer properly by carrying a `destructor_callback`. In that regard it is
//! carrying a very barebone vtable so that freeing the memory pointed to by the [`DynamicBuffer`]
//! is easy either on the C or Rust side.
//!
//! A `From` implementation is provided to convert a `Vec` of `u8` into a [`DynamicBuffer`] easily,
//! the destructor being populated automatically.
//!
//! A [`DynamicBufferView`] is also provided to indicate that the struct does not own the data and
//! is merely used to share data in a read-only way.
use super::utils::get_mut_checked;
use std::ffi::c_int;
pub(super) use tfhe_c_api_dynamic_buffer::{DynamicBuffer, DynamicBufferView};
#[repr(C)]
pub struct DynamicBufferView {
pub pointer: *const u8,
pub length: usize,
}
impl DynamicBufferView {
/// Returns a view to the memory borrowed by the [`DynamicBufferView`].
///
/// # Safety
///
/// This is safe to call as long as the pointer is valid and the length corresponds to the
/// length of the underlying buffer.
pub unsafe fn as_slice(&self) -> &[u8] {
std::slice::from_raw_parts(self.pointer, self.length)
}
}
impl From<&[u8]> for DynamicBufferView {
fn from(a: &[u8]) -> Self {
Self {
pointer: a.as_ptr(),
length: a.len(),
}
}
}
#[repr(C)]
pub struct DynamicBuffer {
pub pointer: *mut u8,
pub length: usize,
pub destructor: Option<unsafe extern "C" fn(*mut u8, usize) -> c_int>,
}
impl DynamicBuffer {
/// Destroy the [`DynamicBuffer`] freeing the underlying memory using the provided
/// `destructor_callback` and clearing or zeroing all the members.
///
/// If the `pointer` stored in [`DynamicBuffer`] is NULL, then `length` is zeroed out and the
/// `destructor_callback` is set to `None`. It is similar to how free ignores NULL in C, we just
/// do some additional housekeeping to signal the [`DynamicBuffer`] is an empty shell.
///
/// # Safety
///
/// Destroy is safe to call only if the `destructor_callback` is the method that needs to be
/// called to free the stored pointer. For example in C++, memory allocated with `new` must be
/// freed with `delete`, memory allocated with `new[]` must be freed with `delete[]`.
///
/// Length must indicate how many `u8` are present in the allocation and can be used by the
/// `destructor_callback` to free memory. For example in the case of a `Vec` being turned into a
/// [`DynamicBuffer`] the length is obtained by first calling the `len` function on the `Vec`.
pub unsafe fn destroy(&mut self) -> Result<(), &str> {
if self.pointer.is_null() {
// Finish emptying stuff
self.length = 0;
self.destructor = None;
return Ok(());
}
match self.destructor {
Some(destructor_callback) => {
let res = destructor_callback(self.pointer, self.length);
if res == 0 {
// If the deallocation is successful then we empty the buffer
self.pointer = std::ptr::null_mut();
self.length = 0;
self.destructor = None;
return Ok(());
}
Err("destructor returned a non 0 error code")
}
// We could not free because of a missing destructor, return an error
None => Err("destructor is NULL, could not destroy DynamicBuffer"),
}
}
}
unsafe extern "C" fn free_u8_ptr_built_from_vec_u8(pointer: *mut u8, length: usize) -> c_int {
if pointer.is_null() {
return 0;
}
let slice = std::slice::from_raw_parts_mut(pointer, length);
drop(Box::from_raw(slice));
0
}
impl From<Vec<u8>> for DynamicBuffer {
fn from(value: Vec<u8>) -> Self {
// into_boxed_slice shrinks the allocation to fit the content of the vec
let boxed_slice = value.into_boxed_slice();
let length = boxed_slice.len();
let pointer = Box::leak(boxed_slice);
Self {
pointer: pointer.as_mut_ptr(),
length,
destructor: Some(free_u8_ptr_built_from_vec_u8),
}
}
}
/// C API to destroy a [`DynamicBuffer`].
///
/// # Safety
///
/// This function is safe to call if `dynamic_buffer` is not aliased to avoid double frees.
#[no_mangle]
pub unsafe extern "C" fn destroy_dynamic_buffer(dynamic_buffer: *mut DynamicBuffer) -> c_int {
// Mimicks C for calls of free on NULL, nothing occurs
if dynamic_buffer.is_null() {
return 0;
}
let Ok(dynamic_buffer) = get_mut_checked(dynamic_buffer) else {
return 1;
};
match dynamic_buffer.destroy() {
Ok(_) => 0,
Err(_) => 1,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_dynamic_buffer_vec_u8_custom_destructor() {
let vec = vec![99u8; 1000];
let len = vec.len();
let ptr = vec.leak();
unsafe extern "C" fn custom_destroy_vec_u8_buffer(
pointer: *mut u8,
length: usize,
) -> c_int {
if pointer.is_null() {
return 0;
}
let slice = std::slice::from_raw_parts_mut(pointer, length);
drop(Box::from_raw(slice));
0
}
let mut dynamic_buffer = DynamicBuffer {
pointer: ptr.as_mut_ptr(),
length: len,
destructor: Some(custom_destroy_vec_u8_buffer),
};
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
assert!(dynamic_buffer.pointer.is_null());
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
// Same as free in C, destroy on a NULL pointer does nothing
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
let mut some_u8 = 0u8;
dynamic_buffer.pointer = &mut some_u8 as *mut u8;
assert!(dynamic_buffer.destructor.is_none());
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 1);
}
#[test]
fn test_dynamic_buffer_vec_u8_default_destructor() {
let vec = vec![99u8; 1000];
let mut dynamic_buffer: DynamicBuffer = vec.clone().into();
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
let mut dynamic_buffer: DynamicBuffer = vec.into();
let res = unsafe { dynamic_buffer.destroy() };
assert!(res.is_ok());
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
}
}