Skip to content

Commit

Permalink
feat: add ffi binding
Browse files Browse the repository at this point in the history
  • Loading branch information
liuq19 committed Nov 25, 2024
1 parent 7a7ad92 commit fea1a23
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ categories = ["encoding", "parser-implementations"]
description = "Sonic-rs is a fast Rust JSON library based on SIMD"
documentation = "https://docs.rs/sonic-rs"
edition = "2021"
exclude = ["benchmarks", "assets"]
exclude = ["benchmarks", "assets", "bindings"]
keywords = ["json", "simd", "serde", "serialization"]
license = "Apache-2.0"
name = "sonic-rs"
Expand Down
5 changes: 5 additions & 0 deletions bindings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# A collections of bindings for sonic-rs

1. ffi: the low-level APIs of sonic-rs, should not used directly
2. cpp: the C++ bindings of sonic-rs
...
13 changes: 13 additions & 0 deletions bindings/ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
edition = "2021"
name = "sonic_rs_ffi"
version = "0.1.0"

[dependencies]
sonic-rs = { path = "../../" }

[build]
lib = ["staticlib"]

[build-dependencies]
cbindgen = "0.27"
17 changes: 17 additions & 0 deletions bindings/ffi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::env;

use cbindgen::Language::C;

fn main() {
setup_cbindgen();
}

fn setup_cbindgen() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(C)
.generate()
.expect("Unable to generate bindings")
.write_to_file("include/sonic_ffi.h");
}
136 changes: 136 additions & 0 deletions bindings/ffi/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# This is a template cbindgen.toml file with all of the default values.
# Some values are commented out because their absence is the real default.
#
# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml
# for detailed documentation of every option here.


language = "C"


############## Options for Wrapping the Contents of the Header #################

# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
# trailer = "/* Text to put at the end of the generated file */"
# include_guard = "my_bindings_h"
# pragma_once = true
# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
include_version = false
# namespace = "my_namespace"
includes = []
namespaces = ["sonic_rs_ffi"]
no_includes = false
sys_includes = []
using_namespaces = []
# cpp_compat = true
after_includes = ""


############################ Code Style Options ################################

braces = "SameLine"
documentation = true
documentation_length = "full"
documentation_style = "auto"
line_endings = "LF" # also "CR", "CRLF", "Native"
line_length = 100
tab_width = 2


############################# Codegen Options ##################################

sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
style = "both"
usize_is_size_t = true


[defines]
# "target_os = freebsd" = "DEFINE_FREEBSD"
# "feature = serde" = "DEFINE_SERDE"


[export]
exclude = []
include = []
# prefix = "CAPI_"
item_types = []
renaming_overrides_prefixing = false


[export.rename]


[export.body]


[export.mangle]


[fn]
rename_args = "None"
# must_use = "MUST_USE_FUNC"
# deprecated = "DEPRECATED_FUNC"
# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE"
# no_return = "NO_RETURN"
# prefix = "START_FUNC"
# postfix = "END_FUNC"
args = "auto"
sort_by = "Name"


[struct]
rename_fields = "None"
# must_use = "MUST_USE_STRUCT"
# deprecated = "DEPRECATED_STRUCT"
# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE"
derive_constructor = false
derive_eq = false
derive_gt = false
derive_gte = false
derive_lt = false
derive_lte = false
derive_neq = false


[enum]
rename_variants = "None"
# must_use = "MUST_USE_ENUM"
# deprecated = "DEPRECATED_ENUM"
# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE"
add_sentinel = false
derive_const_casts = false
derive_helper_methods = false
derive_mut_casts = false
prefix_with_name = false
# cast_assert_name = "ASSERT"
derive_tagged_enum_copy_constructor = false
derive_tagged_enum_destructor = false
enum_class = true
private_default_tagged_enum_constructor = false


[const]
allow_constexpr = false
allow_static_const = true
sort_by = "Name"


[macro_expansion]
bitflags = false


############## Options for How Your Rust library Should Be Parsed ##############

[parse]
parse_deps = false
# include = []
clean = false
exclude = []
extra_bindings = []


[parse.expand]
all_features = false
crates = []
default_features = true
features = []
53 changes: 53 additions & 0 deletions bindings/ffi/include/sonic_ffi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#define SONIC_RS_DESERIALIZE_USE_RAW 1

#define SONIC_RS_DESERIALIZE_USE_RAWNUMBER 2

#define SONIC_RS_DESERIALIZE_UTF8_LOSSY 4

#define SONIC_RS_SERIALIZE_PRETTY 1

typedef struct SonicCString {
const void *buf;
uintptr_t len;
} SonicCString;

typedef struct SonicDeserializeRet {
const void *value;
struct SonicCString err;
} SonicDeserializeRet;

typedef struct SonicSerializeRet {
struct SonicCString json;
struct SonicCString err;
} SonicSerializeRet;

/**
* # Safety
*
* The caller should drop the returned `value` or `err`.
*/
struct SonicDeserializeRet sonic_rs_deserialize_value(const char *json,
uintptr_t len,
uint64_t cfg);

/**
* # Safety
*
* The caller should drop the returned `json` or `err`.
*/
struct SonicSerializeRet sonic_rs_serialize_value(const void *value, uint64_t cfg);

/**
* # Safety
*/
void sonic_rs_drop_value(void *value);

/**
* # Safety
*/
void sonic_rs_drop_string(void *buf, uint64_t len);
139 changes: 139 additions & 0 deletions bindings/ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use std::{ffi::c_char, mem::ManuallyDrop, os::raw::c_void};

use sonic_rs::Value;

/// A string allocated in Rust, ending with `\0`. Used for serialize output and error message.
#[derive(Debug)]
#[repr(C)]
pub struct SonicCString {
buf: *const c_void,
len: usize,
}

impl Default for SonicCString {
fn default() -> Self {
SonicCString {
buf: std::ptr::null(),
len: 0,
}
}
}

#[derive(Debug)]
#[repr(C)]
pub struct SonicDeserializeRet {
value: *const c_void,
err: SonicCString,
}

pub const SONIC_RS_DESERIALIZE_USE_RAW: u64 = 1;
pub const SONIC_RS_DESERIALIZE_USE_RAWNUMBER: u64 = 2;
pub const SONIC_RS_DESERIALIZE_UTF8_LOSSY: u64 = 4;

/// # Safety
///
/// The caller should drop the returned `value` or `err`.
#[no_mangle]
pub unsafe extern "C" fn sonic_rs_deserialize_value(
json: *const c_char,
len: usize,
cfg: u64,
) -> SonicDeserializeRet {
let json = std::slice::from_raw_parts(json as *const u8, len);
let mut de = sonic_rs::serde::Deserializer::from_slice(json);

if cfg & SONIC_RS_DESERIALIZE_USE_RAWNUMBER != 0 {
de = de.use_rawnumber();
}

if cfg & SONIC_RS_DESERIALIZE_USE_RAW != 0 {
de = de.use_raw();
}

if cfg & SONIC_RS_DESERIALIZE_UTF8_LOSSY != 0 {
de = de.utf8_lossy();
}

match de.deserialize::<Value>() {
Ok(value) => SonicDeserializeRet {
value: Box::into_raw(Box::new(value)) as *const _,
err: SonicCString::default(),
},
Err(e) => {
// messega always end with '\0'
let msg = ManuallyDrop::new(format!("{}\0", e));
let err = SonicCString {
buf: msg.as_ptr() as *const c_void,
len: msg.len(),
};
SonicDeserializeRet {
value: std::ptr::null_mut(),
err,
}
}
}
}

#[derive(Debug)]
#[repr(C)]
pub struct SonicSerializeRet {
json: SonicCString,
err: SonicCString,
}

pub const SONIC_RS_SERIALIZE_PRETTY: u64 = 1;

/// # Safety
///
/// The caller should drop the returned `json` or `err`.
#[no_mangle]
pub unsafe extern "C" fn sonic_rs_serialize_value(
value: *const c_void,
cfg: u64,
) -> SonicSerializeRet {
let value = unsafe { &*(value as *const Value) };
let ret = if cfg & SONIC_RS_SERIALIZE_PRETTY != 0 {
sonic_rs::to_string_pretty(value)
} else {
sonic_rs::to_string(value)
};

match ret {
Ok(json) => {
let json = ManuallyDrop::new(json);
let json = SonicCString {
buf: json.as_ptr() as *const c_void,
len: json.len(),
};
SonicSerializeRet {
json,
err: SonicCString::default(),
}
}
Err(e) => {
// NOTE: should be dropped manually in the foreign caller
let msg = ManuallyDrop::new(format!("{}\0", e));
let err = SonicCString {
buf: msg.as_ptr() as *const c_void,
len: msg.len(),
};
SonicSerializeRet {
json: SonicCString::default(),
err,
}
}
}
}

/// # Safety
#[no_mangle]
pub unsafe extern "C" fn sonic_rs_drop_value(value: *mut c_void) {
std::mem::drop(Box::from_raw(value as *mut Value));
}

/// # Safety
#[no_mangle]
pub unsafe extern "C" fn sonic_rs_drop_string(buf: *mut c_void, len: u64) {
let s = Vec<u8>::from_raw_parts(buf as *mut u8, len, len);
std::mem::drop(s);
}

0 comments on commit fea1a23

Please sign in to comment.