Skip to content

[WIP] Write Rust Bindings #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -18,3 +18,6 @@ result/
sftp-config.json
# Clangd cache
*.cache/
libcachesim-rs/target/
zstd-1.5.0/
zstd-1.5.0.tar.gz
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -57,6 +57,10 @@ elseif(${CMAKE_BUILD_TYPE} MATCHES "Debug")
if(LOG_LEVEL STREQUAL "NONE")
set(LOG_LEVEL DEBUG)
endif()
# elseif($(CMAKE_BUILD_TYPE) MATCHES "Rust")
# if(LOG_LEVEL STREQUAL "NONE")
# set(LOG_LEVEL INFO)
# endif()
else()
set(LOG_LEVEL INFO)
endif()
@@ -180,6 +184,13 @@ if(NOT ${CMAKE_BUILD_TYPE} MATCHES "Debug")
endif()
endif()

# # Check if the build type is Rust, if so, we turn on -fkeep-inline-functions
# if (${CMAKE_BUILD_TYPE} MATCHES "Rust")
# message(STATUS "Turn on -fkeep-inline-functions for bindgen to handle inline functions")
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fkeep-inline-functions")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fkeep-inline-functions")
# endif()

find_package(Threads)

# find_package(Boost REQUIRED)
Empty file added data/trace.csv
Empty file.
327 changes: 327 additions & 0 deletions libcachesim-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions libcachesim-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

[package]
name = "libcachesim-rs"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2" # For C types
bindgen = { version = "0.71.1", features = ["experimental"] }

[lib]
name = "libcachesim_rs"
path = "src/lib.rs"

[build]
rustflags = []

[build-dependencies]
cc = "1.0" # For compiling the C library
bindgen = { version = "0.71.1", features = ["experimental"] }
pkg-config = "0.3"

[[bin]]
name = "main"
path = "src/main.rs"
92 changes: 92 additions & 0 deletions libcachesim-rs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Rust Bindings
We generate Rust bindings using `bindgen` (https://rust-lang.github.io/rust-bindgen/).
## Generate Bindings
In the `libcachesim-rs` directory, if we run cargo build, the `build.rs` script will generate the bindings directory and all the files inside this directory. Then we can include the `bindings.rs` to our `lib.rs` and write safe Rust wrappers in wrapper.rs. For now, I have `pub struct Cache`, `pub struct Reader`, and `pub struct Request` with all corresponding methods. With these, I write a Rust equivalent `test.c` file in `main.rs`.


## How to handle inline functions (In Command Line)? (Tentative Incomplete) Use `build.rs` intead
```bash
bindgen --experimental --wrap-static-fns \
--wrap-static-fns-path bindings/wrap_static_fns \
../libCacheSim/include/libCacheSim.h \
-o bindings/bindings.rs \
--blocklist-item "_Tp" \
--blocklist-item "std::.*" \
--blocklist-file ".*/glib-2.0/glib/.*" \
--blocklist-file ".*zstdReader.*" \
-- \
-I../libCacheSim/include \
$(pkg-config --cflags glib-2.0)
```
This creates a `wrap_static_fns.c`. For now, we manually change the `#include "../libCacheSim/include/libCacheSim.h"` to `#include "libCacheSim.h"`. I have yet to figure out a way to make `bindgen` write relative path of the header file to the `wrap_static_fns.c` when `wrap_static_fns` is used.

Now, we compile `wrap_static_fns.c` into an object file, then bundle it into a static library — while ensuring it includes the right header file.

Step 1: Compile `wrap_static_fns.c`
Run this command to compile `wrap_static_fns.c` into an object file (`.o`):
```Bash
clang -O -c \
-o bindings/wrap_static_fns.o \
bindings/wrap_static_fns.c \
-include ../libCacheSim/include/libCacheSim.h \
-I/../libCacheSim/include \
$(pkg-config --cflags glib-2.0)
```
where
- `-O`: Optimizes the build.
- `-c`: Compiles without linking (produces .o file).
- `-o ...wrap_static_fns.o`: Output object file path.
- `-include ...libCacheSim.h`: Forces inclusion of the header.
- `-I...`: Adds include path for libCacheSim.
- `$(pkg-config --cflags glib-2.0)`: Ensures GLib headers are accessible.

Step 2: Create a static library (`.a`)
Now bundle the object file into a static library:
```Bash
ar rcs bindings/libwrap_static_fns.a \
bindings/wrap_static_fns.o
```
where
- `ar`: Archive utility to create static libraries.
- `r`: Replaces existing object files in the archive.
- `c`: Creates the archive if it doesn’t exist.
- `s`: Adds an index (helps the linker find symbols faster).





# Testing `test.c`
If we see `no LC_RPATH's found`, we first doule check the library is installed
```Bash
brew install zstd
```
And verify the library exists:
```Bash
ls /opt/homebrew/lib/libzstd*.dylib
```

Next, we can set RPATH during compilation.
```Bash
gcc test.c $(pkg-config --cflags --libs libCacheSim glib-2.0) -lzstd -o test.out -Wl,-rpath,/opt/homebrew/lib
```
If we see error `... was built for newer 'macOS' version (15.1) than being linked (15.0)`, we can add `MACOSX_DEPLOYMENT_TARGET=15.1` to the Bash command.

Now, we verify RPATH is set correctly, where we check the final binary with:
```Bash
otool -l test.out | grep -A2 RPATH
```
You should see:
```
cmd LC_RPATH
path /opt/homebrew/lib (offset 12)
```









11,399 changes: 11,399 additions & 0 deletions libcachesim-rs/bindings/bindings.rs

Large diffs are not rendered by default.

Binary file added libcachesim-rs/bindings/libwrap_static_fns.a
Binary file not shown.
35 changes: 35 additions & 0 deletions libcachesim-rs/bindings/wrap_static_fns.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "libCacheSim.h"

// Static wrappers

void log_header__extern(int level, const char *file, int line) { log_header(level, file, line); }
__uint16_t _OSSwapInt16__extern(__uint16_t _data) { return _OSSwapInt16(_data); }
__uint32_t _OSSwapInt32__extern(__uint32_t _data) { return _OSSwapInt32(_data); }
__uint64_t _OSSwapInt64__extern(__uint64_t _data) { return _OSSwapInt64(_data); }
request_t * new_request__extern(void) { return new_request(); }
void copy_request__extern(request_t *req_dest, const request_t *req_src) { copy_request(req_dest, req_src); }
request_t * clone_request__extern(const request_t *req) { return clone_request(req); }
void free_request__extern(request_t *req) { free_request(req); }
void print_request__extern(const request_t *req) { print_request(req); }
admissioner_t * create_admissioner__extern(const char *admission_algo, const char *admission_params) { return create_admissioner(admission_algo, admission_params); }
cache_obj_t * prev_obj_in_slist__extern(cache_obj_t *head, cache_obj_t *cache_obj) { return prev_obj_in_slist(head, cache_obj); }
void free_cache_obj__extern(cache_obj_t *cache_obj) { free_cache_obj(cache_obj); }
common_cache_params_t default_common_cache_params__extern(void) { return default_common_cache_params(); }
int64_t cache_get_occupied_byte_default__extern(const cache_t *cache) { return cache_get_occupied_byte_default(cache); }
int64_t cache_get_n_obj_default__extern(const cache_t *cache) { return cache_get_n_obj_default(cache); }
int64_t cache_get_reference_time__extern(const cache_t *cache) { return cache_get_reference_time(cache); }
int64_t cache_get_logical_time__extern(const cache_t *cache) { return cache_get_logical_time(cache); }
int64_t cache_get_virtual_time__extern(const cache_t *cache) { return cache_get_virtual_time(cache); }
void print_cache_stat__extern(const cache_t *cache) { print_cache_stat(cache); }
void record_log2_eviction_age__extern(cache_t *cache, const unsigned long long age) { record_log2_eviction_age(cache, age); }
void record_eviction_age__extern(cache_t *cache, cache_obj_t *obj, const int64_t age) { record_eviction_age(cache, obj, age); }
void generate_cache_name__extern(cache_t *cache, char *str_dest) { generate_cache_name(cache, str_dest); }
void print_sampler__extern(sampler_t *sampler) { print_sampler(sampler); }
void set_default_reader_init_params__extern(reader_init_param_t *params) { set_default_reader_init_params(params); }
reader_init_param_t default_reader_init_params__extern(void) { return default_reader_init_params(); }
reader_t * open_trace__extern(const char *path, const trace_type_e type, const reader_init_param_t *reader_init_param) { return open_trace(path, type, reader_init_param); }
trace_type_e get_trace_type__extern(const const reader_t *const reader) { return get_trace_type(reader); }
bool obj_id_is_num__extern(const const reader_t *const reader) { return obj_id_is_num(reader); }
int read_trace__extern(const reader_t *const reader, const request_t *const req) { return read_trace(reader, req); }
int close_trace__extern(const reader_t *const reader) { return close_trace(reader); }
void print_reader__extern(reader_t *reader) { print_reader(reader); }
Binary file added libcachesim-rs/bindings/wrap_static_fns.o
Binary file not shown.
248 changes: 248 additions & 0 deletions libcachesim-rs/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
// Specify the header file
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let header = PathBuf::from(&manifest_dir)
.join("..")
.join("libCacheSim/include/libCacheSim.h");
let lib_path = PathBuf::from(&manifest_dir)
.join("..")
.join("libCacheSim/_build/liblibCacheSim.a");
// This is the path to the object file generated by bindgen for inline functions.
let binding_dir = PathBuf::from(&manifest_dir).join("bindings");
let rust_src_dir = PathBuf::from(&manifest_dir)
.join("src");
let obj_path = binding_dir.join("wrap_static_fns.o");
let source_path = binding_dir.join("wrap_static_fns.c");
let lib_obj_path = binding_dir.join("libwrap_static_fns.a");

// Get GLib include paths using pkg-config
let glib_cflags = Command::new("pkg-config")
.args(&["--cflags", "glib-2.0"])
.output()
.expect("Failed to run pkg-config")
.stdout;

let glib_cflags = String::from_utf8_lossy(&glib_cflags);
let include_paths: Vec<&str> = glib_cflags
.split_whitespace()
.filter(|s| s.starts_with("-I"))
.map(|s| s.trim_start_matches("-I"))
.take(2)
.collect();
println!("GLib include paths: {:?}", include_paths);

// Generate bindings
// let bindings = bindgen::Builder::default()
// .header(header.to_str().unwrap())
// .clang_arg("-xc++") // Treat the header as C++
// .clang_arg("-std=c++14") // Specify the C++ standard
// // Add GLib include paths
// .clang_arg(format!("-I{}", include_paths[0]))// Add GLib include paths
// .clang_arg(format!("-I{}", include_paths[1]))
// // Block problematic templates
// .blocklist_item("_Tp") // Skip unknown template types
// .blocklist_item("std::.*") // Skip all std:: types (optional)
// // Bindgen output options
// .wrap_static_fns(true)
// // .generate_inline_functions(true) // supports inline C++ functions too
// .disable_name_namespacing() // keeps names simple
// .layout_tests(false) // faster compilation, skips layout tests
// .generate()
// .expect("Unable to generate bindings");

// // // Generate bindings
// let bindings = bindgen::Builder::default()
// .header(header.to_str().unwrap())
// .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// .clang_arg("-xc++")
// .clang_arg("-std=c++14") // Specify the C++ standard
// .clang_arg(format!("-I{}", include_paths[0]))// Add GLib include paths
// .clang_arg(format!("-I{}", include_paths[1]))
// // .clang_arg(format!("-I{}", "/opt/homebrew/Cellar/zstd/1.5.7"))
// .blocklist_item("_Tp") // Skip unknown template types
// .blocklist_item("std::.*") // Skip all std:: types (optional)
// // .blocklist_file(".*/glib-2.0/.*")
// // .blocklist_file(".*/CommandLineTools/.*")
// // .blocklist_file(".*zstdReader.*")
// // .wrap_static_fns(true)
// // .wrap_static_fns_suffix("__extern")
// // .wrap_static_fns_path(rust_src_dir.join("wrap_static_fns.c"))// Bindgen output options
// .disable_name_namespacing()
// .layout_tests(false)
// .use_core() // Use core types
// .ctypes_prefix("libc") // Explicitly link to libc types like FILE
// // .clang_args(&["-x", "c++", "-std=c++14", &format!("-I{}", include_paths[0]), &format!("-I{}", include_paths[1]), &format!("-I{}", "/opt/homebrew/Cellar/zstd/1.5.7")])
// .generate()
// .expect("Unable to generate bindings");


// wrap_static_fns does not generate wrap_static_fns.c when xc++ flag is on, see issue
// on bindgen https://github.com/rust-lang/rust-bindgen/issues/3157
// Now, what we do is that:
// 1) we comment out .clang_arg(&["-x", "c++", "-std=c++14"]) and .blocklist_file(".*/glib-2.0/((?!include/glibconfig\\.h).)*$")

// Generate bindings
let bindings = bindgen::Builder::default()
.header(header.to_str().unwrap())
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.blocklist_file(".*/glib-2.0/glib/.*")
// .blocklist_file(".*/CommandLineTools/.*")
.blocklist_file(".*zstdReader.*")
.blocklist_item("_Tp") // Skip unknown template types
.blocklist_item("std::.*") // Skip all std:: types (optional)
.wrap_static_fns(true)
.wrap_static_fns_suffix("__extern")
.wrap_static_fns_path(binding_dir.join("wrap_static_fns.c"))// Bindgen output options
.clang_args(&[
// "-x",
// "c++",
// "-std=c++14",
&format!("-I{}", include_paths[0]),
&format!("-I{}", include_paths[1]),
&format!("-I{}", "/opt/homebrew/Cellar/zstd/1.5.7")
])
.generate()
.expect("Unable to generate bindings");


// Compile the generated wrappers into an object file.
let clang_output = std::process::Command::new("clang")
.arg("-O")
.arg("-c")
.arg("-o")
.arg(&obj_path)
.arg(source_path)
.arg("-include")
.arg(header)
.arg(format!("-I{}", include_paths[0]))
.arg(format!("-I{}", include_paths[1]))
.arg(format!("-I{}", "/opt/homebrew/Cellar/zstd/1.5.7"))
.output()
.unwrap();

if !clang_output.status.success() {
panic!(
"Could not compile object file:\n{}",
String::from_utf8_lossy(&clang_output.stderr)
);
}

// Turn the object file into a static library
let lib_output = Command::new("ar")
.arg("rcs")
.arg(lib_obj_path)
.arg(obj_path)
.output()
.unwrap();
if !lib_output.status.success() {
panic!(
"Could not emit library file:\n{}",
String::from_utf8_lossy(&lib_output.stderr)
);
}

// Tell cargo to statically link against the `libwrap_static_fns` static library.
println!("cargo:rustc-link-search={}", binding_dir.to_str().unwrap());
println!("cargo:rustc-link-lib=static=wrap_static_fns");

println!("cargo:rustc-link-search=../_build");
println!("cargo:rustc-link-lib=static=libCacheSim");

// Link ZSTD and GLib dynamically
println!("cargo:rustc-link-lib=zstd");
// Add GLib and Gettext library paths
println!("cargo:rustc-link-search=native=/opt/homebrew/Cellar/glib/2.84.0/lib");
println!("cargo:rustc-link-search=native=/opt/homebrew/opt/gettext/lib");
// Link GLib and intl from Gettext
println!("cargo:rustc-link-lib=glib-2.0");
println!("cargo:rustc-link-lib=intl");

println!("cargo:rustc-link-arg=-mmacosx-version-min=15.1");

// Write the bindings to the $libcachesim-rs/src/bindings.rs file
bindings
.write_to_file(binding_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
}


// use std::env;
// use std::path::PathBuf;

// fn main() {
// // Link to the C++ standard library
// // println!("cargo:rustc-link-search=/opt/homebrew/opt/gcc/lib/gcc/current");
// println!("cargo:rustc-link-arg=-mmacosx-version-min=15.1");
// println!("cargo:rustc-link-lib=zstd");
// println!("cargo:rustc-link-lib=glib-2.0");
// println!("cargo:rustc-link-lib=c++");

// println!("cargo:rustc-link-search=../_build");
// println!("cargo:rustc-link-lib=static=libCacheSim");

// println!("cargo:rustc-link-search=/Users/yangliu/Desktop/Research/libCacheSim/libcachesim-rs/src");
// println!("cargo:rustc-link-lib=static=wrap_static_fns");


// // Specify the header file
// let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
// let header = PathBuf::from(&manifest_dir)
// .join("..")
// .join("libCacheSim/include/libCacheSim.h");

// // Get GLib include paths
// let glib_include_path = "/opt/homebrew/Cellar/glib/2.84.0/include/glib-2.0";
// let glib_config_include = "/opt/homebrew/Cellar/glib/2.84.0/lib/glib-2.0/include";


// // Generate bindings
// let bindings = bindgen::Builder::default()
// .header(header.to_str().unwrap())
// .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// // Bindgen output options
// .wrap_static_fns_path("/Users/yangliu/Desktop/Research/libCacheSim/libcachesim-rs/src/wrap_static_fns")
// .wrap_static_fns(true)
// .clang_arg("-xc++") // Treat the header as C++
// .clang_arg("-std=c++14") // Specify the C++ standard
// // Add GLib include paths
// .clang_arg(format!("-I{}", glib_include_path))
// .clang_arg(format!("-I{}", glib_config_include))
// // Block problematic templates
// .blocklist_item("_Tp") // Skip unknown template types
// .blocklist_item("std::.*") // Skip all std:: types (optional)
// .blocklist_item(".*zstdReader.*")
// // .generate_inline_functions(true) // supports inline C++ functions too
// // .disable_name_namespacing() // keeps names simple
// .generate()
// .expect("Unable to generate bindings");

// // This is the path to the object file.
// let obj_path = "/Users/yangliu/Desktop/Research/libCacheSim/libcachesim-rs/src/libwrap_static_fns.o";
// // This is the path to the static library file.
// let lib_path = "/Users/yangliu/Desktop/Research/libCacheSim/_build/liblibCacheSim.a";

// // Compile the generated wrappers into an object file.
// let clang_output = std::process::Command::new("clang")
// .arg("-O")
// .arg("-c")
// .arg("-o")
// .arg(&obj_path)
// .arg(std::env::temp_dir().unwrap().join("bindgen").join("/Users/yangliu/Desktop/Research/libCacheSim/libcachesim-rs/src/libwrap_static_fns.c"))
// .arg("-include")
// .arg(input)
// .output()
// .unwrap();

// // Write the bindings to the $libcachesim-rs/src/bindings.rs file
// let bindings_path = PathBuf::from(&manifest_dir)
// .join("src")
// .join("bindings.rs");
// bindings
// .write_to_file(bindings_path)
// .expect("Couldn't write bindings!");
// }

9 changes: 9 additions & 0 deletions libcachesim-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]


pub mod bindings {
include!("../bindings/bindings.rs"); // These bindings are generated by bindgen
}
pub mod wrapper;
65 changes: 65 additions & 0 deletions libcachesim-rs/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use libcachesim_rs::bindings::*;
use libcachesim_rs::wrapper::*;

fn main() {
// Open the trace file
println!("main test file");

let mut init_params_csv = Reader::default_reader_init_params();
init_params_csv.delimiter = ',' as i8; // b',' as std::os::raw::c_char,
init_params_csv.time_field = 2;
init_params_csv.obj_id_field = 6;
init_params_csv.obj_size_field = 4;
init_params_csv.has_header = false;

let mut reader = match Reader::open_trace("../data/trace.csv", trace_type_e_CSV_TRACE, &init_params_csv) {
Some(r) => r,
None => {
eprintln!("Failed to open trace.");
return;
}
};

// Create a request container
let req = match Request::new_request() {
Some(r) => r,
None => {
eprintln!("Failed to create request.");
return;
}
};

// Define cache parameters and create the cache
let cache_params = common_cache_params_t {
cache_size: 1024 * 1024,
..Default::default()
};
let cache = match Cache::LRU_init(cache_params, "") {
Some(c) => c,
None => {
eprintln!("Failed to initialize cache.");
return;
}
};

// Counters
let mut n_req = 0;
let mut n_miss = 0;

// Loop through the trace
while reader.read_one_req(&req) == 0 {
if !cache.get(&req) {
n_miss += 1;
}
n_req += 1;
}

// Print the miss ratio
if n_req > 0 {
println!("miss ratio: {:.4}", n_miss as f64 / n_req as f64);
} else {
println!("No requests were processed.");
}

// Clean up happens automatically due to Drop implementations
}
345 changes: 345 additions & 0 deletions libcachesim-rs/src/wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
use crate::bindings::*;
use std::ffi::CString;
use std::ptr;


pub struct Cache {
cache_ptr: *mut cache_t,
}

impl Cache {
/// Initializes a new LRU cache.
pub fn LRU_init(params: common_cache_params_t, specific_params: &str) -> Option<Self> {
let c_params = CString::new(specific_params).expect("CString conversion failed");
let cache_ptr = unsafe { LRU_init(params, c_params.as_ptr()) };

if cache_ptr.is_null() {
None
} else {
Some(Self { cache_ptr })
}
}

/// Frees the cache.
pub fn free(&mut self) {
if !self.cache_ptr.is_null() {
unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.cache_free.is_none() {
(cache_ref.cache_free.unwrap())(self.cache_ptr);
self.cache_ptr = std::ptr::null_mut(); // Invalidate the pointer after freeing
} else {
eprintln!("cache_free function pointer is null!");
}
};
}
}

/// Checks if a request hits the cache.
pub fn get(&self, req: &Request) -> bool {
if self.cache_ptr.is_null() {
return false;
}
unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.get.is_none() {
(cache_ref.get.unwrap())(self.cache_ptr, req.as_raw())
} else {
eprintln!("get function pointer is null!");
false
}
}
}

/// Finds an object in the cache, optionally updating it.
pub fn find(&self, req: &Request, update_cache: bool) -> Option<*mut cache_obj_t> {
if self.cache_ptr.is_null() {
return None;
}
let obj_ptr = unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.find.is_none() {
(cache_ref.find.unwrap())(self.cache_ptr, req.as_raw(), update_cache)
} else {
eprintln!("find function pointer is null!");
ptr::null_mut()
}
};
if obj_ptr.is_null() {
None
} else {
Some(obj_ptr)
}
}

/// Inserts a new object into the cache.
pub fn insert(&mut self, req: &Request) -> Option<*mut cache_obj_t> {
if self.cache_ptr.is_null() {
return None;
}
let obj_ptr = unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.insert.is_none() {
(cache_ref.insert.unwrap())(self.cache_ptr, req.as_raw())
} else {
eprintln!("insert function pointer is null!");
ptr::null_mut()
}
};
if obj_ptr.is_null() {
None
} else {
Some(obj_ptr)
}
}

/// Determines which object to evict.
pub fn to_evict(&self, req: &Request) -> Option<*mut cache_obj_t> {
if self.cache_ptr.is_null() {
return None;
}
let obj_ptr = unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.to_evict.is_none() {
(cache_ref.to_evict.unwrap())(self.cache_ptr, req.as_raw())
} else {
eprintln!("to_evict function pointer is null!");
ptr::null_mut()
}
};
if obj_ptr.is_null() {
None
} else {
Some(obj_ptr)
}
}

/// Evicts an object from the cache.
pub fn evict(&mut self, req: &Request) {
if !self.cache_ptr.is_null() {
unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.evict.is_none() {
(cache_ref.evict.unwrap())(self.cache_ptr, req.as_raw())
} else {
eprintln!("evict function pointer is null!");
}
};
}
}

/// Removes an object from the cache by its ID.
pub fn remove(&mut self, obj_id: obj_id_t) -> bool {
if self.cache_ptr.is_null() {
return false;
}
unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.remove.is_none() {
(cache_ref.remove.unwrap())(self.cache_ptr, obj_id)
} else {
eprintln!("remove function pointer is null!");
false
}
}
}

pub fn print_cache(&self) {
if !self.cache_ptr.is_null() {
unsafe {
let cache_ref = *self.cache_ptr;
// Ensure the function pointer is valid, then call it
if !cache_ref.print_cache.is_none() {
(cache_ref.print_cache.unwrap())(self.cache_ptr);
} else {
eprintln!("print_cache function pointer is null!");
}
};
}
}
}

// Ensure the cache gets freed properly when dropped.
impl Drop for Cache {
fn drop(&mut self) {
self.free();
}
}


pub struct Reader {
reader_ptr: *mut reader_t,
}


impl Reader {
fn from_raw(ptr: *mut reader_t) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self { reader_ptr: ptr })
}
}

pub fn set_default_reader_init_params(params: &mut reader_init_param_t) {
unsafe { set_default_reader_init_params(params) };
}

pub fn default_reader_init_params() -> reader_init_param_t {
unsafe { default_reader_init_params() }
}

pub fn setup_reader(trace_path: &str, trace_type: trace_type_e, init_params: &reader_init_param_t) -> Option<Self> {
let path = CString::new(trace_path).unwrap();
let ptr = unsafe { setup_reader(path.as_ptr(), trace_type, init_params) };
Self::from_raw(ptr)
}

pub fn open_trace(trace_path: &str, trace_type: trace_type_e, init_params: &reader_init_param_t) -> Option<Self> {
let path = CString::new(trace_path).unwrap();
let ptr = unsafe { open_trace(path.as_ptr(), trace_type, init_params) };
Self::from_raw(ptr)
}

pub fn get_num_of_req(&self) -> u64 {
if self.reader_ptr.is_null() {
return 0;
}
unsafe { get_num_of_req(self.reader_ptr) }
}

pub fn get_trace_type(&self) -> trace_type_e {
unsafe { get_trace_type(self.reader_ptr) }
}

pub fn obj_id_is_num(&self) -> bool {
unsafe { obj_id_is_num(self.reader_ptr) }
}

pub fn read_one_req(&mut self, req: &Request) -> i32 {
unsafe { read_one_req(self.reader_ptr, req.as_raw()) }
}

pub fn read_trace(&mut self, req: &Request) -> i32 {
unsafe { read_trace(self.reader_ptr, req.as_raw())}
}

pub fn reset_reader(&mut self) {
unsafe { reset_reader(self.reader_ptr) };
}

pub fn close_reader(&mut self) -> bool {
let result = unsafe { close_reader(self.reader_ptr) };
self.reader_ptr = ptr::null_mut();
result == 0
}

pub fn close_trace(&mut self) -> i32 {
unsafe { close_trace(self.reader_ptr) }
}

pub fn clone_reader(&self) -> Option<Self> {
let ptr = unsafe { clone_reader(self.reader_ptr) };
Self::from_raw(ptr)
}

pub fn read_first_req(&mut self, req: &Request) {
unsafe { read_first_req(self.reader_ptr, req.as_raw()) };
}

pub fn read_last_req(&mut self, req: &Request) {
unsafe { read_last_req(self.reader_ptr, req.as_raw()) };
}

pub fn skip_n_req(&mut self, n: i32) -> i32 {
unsafe { skip_n_req(self.reader_ptr, n) }
}

pub fn read_one_req_above(&mut self, req: &Request) -> i32 {
unsafe { read_one_req_above(self.reader_ptr, req.as_raw()) }
}

pub fn go_back_one_req(&mut self) -> i32 {
unsafe { go_back_one_req(self.reader_ptr) }
}

pub fn reader_set_read_pos(&mut self, pos: f64) {
unsafe { reader_set_read_pos(self.reader_ptr, pos) };
}

pub fn print_reader(&self) {
unsafe { print_reader(self.reader_ptr) };
}
}

impl Drop for Reader {
fn drop(&mut self) {
self.close_reader();
}
}

pub struct Request(*mut request_t);

impl Request {
/// Creates a new request
pub fn new_request() -> Option<Self> {
let req = unsafe { new_request() };
if req.is_null() {
None
} else {
Some(Self(req))
}
}

pub fn as_raw(&self) -> *mut request_t {
self.0
}

/// Copies data from another request
pub fn copy_request(&mut self, source: &Request) {
unsafe { copy_request(self.0, source.0) };
}

/// Clones the current request
pub fn clone_request(&self) -> Option<Self> {
let req_clone = unsafe { clone_request(self.0) };
if req_clone.is_null() {
None
} else {
Some(Self(req_clone))
}
}

/// Prints the request
pub fn print_request(&self) {
unsafe { print_request(self.0) };
}
}

impl Drop for Request {
/// Frees memory when Request is dropped
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { free_request(self.0) };
}
}
}

impl Default for common_cache_params_t {
fn default() -> Self {
Self {
cache_size: 0,
default_ttl: 0,
hashpower: 0,
consider_obj_metadata: false,
}
}
}

4 changes: 3 additions & 1 deletion test.c
Original file line number Diff line number Diff line change
@@ -6,7 +6,9 @@

int main(int argc, char *argv[]) {
/* open trace, see quickstart.md for opening csv and binary trace */
reader_t *reader = open_trace("../data/trace.vscsi", VSCSI_TRACE, NULL);
reader_init_param_t init_params_csv =
{.delimiter=',', .time_field=2, .obj_id_field=6, .obj_size_field=4, .has_header=FALSE};
reader_t *reader = open_trace("../data/trace.csv", CSV_TRACE, &init_params_csv);

/* create a container for reading from trace */
request_t *req = new_request();