diff --git a/Cargo.toml b/Cargo.toml index 9ed446cc..b4584ce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ categories = ["compression", "api-bindings"] [workspace] [dependencies] -libc = "0.2" bzip2-sys = { version = "0.1.11", path = "bzip2-sys" } tokio-io = { version = "0.1", optional = true } futures = { version = "0.1", optional = true } diff --git a/README.md b/README.md index 52177323..0a886392 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,21 @@ A streaming compression/decompression library for rust with bindings to libbz2. bzip2 = "0.4" ``` +## WASM +bzip2-rs can be compiled to WASM. Make sure you added `wasm32-unknown-unknown` target +```bash +rustup target add wasm32-unknown-unknown +``` +To build and run WASM example make sure that you working directory in terminal is `bzip2-sys` +### Build WASM target +```bash +cargo build --target wasm32-unknown-unknown --no-default-features --example it_work +``` + +### Run WASM target using wasmtime +```bash +wasmtime ..\target\wasm32-unknown-unknown\debug\examples\it_work.wasm --invoke test_decompress +``` # License diff --git a/bzip2-sys/Cargo.toml b/bzip2-sys/Cargo.toml index bc2318f6..5eab5d11 100644 --- a/bzip2-sys/Cargo.toml +++ b/bzip2-sys/Cargo.toml @@ -18,9 +18,6 @@ categories = ["external-ffi-bindings"] name = "bzip2_sys" path = "lib.rs" -[dependencies] -libc = "0.2" - [build-dependencies] pkg-config = "0.3.9" cc = "1.0" diff --git a/bzip2-sys/build.rs b/bzip2-sys/build.rs index feb004ca..e8fa89da 100644 --- a/bzip2-sys/build.rs +++ b/bzip2-sys/build.rs @@ -2,7 +2,7 @@ extern crate cc; extern crate pkg_config; use std::path::PathBuf; -use std::{env, fs}; +use std::{env, fmt, fs}; fn main() { let mut cfg = cc::Build::new(); @@ -23,6 +23,27 @@ fn main() { } } + // List out the WASM targets that need wasm-shim. + // Note that Emscripten already provides its own C standard library so + // wasm32-unknown-emscripten should not be included here. + // See: https://github.com/gyscos/zstd-rs/pull/209 + let need_wasm_shim = + env::var("TARGET").map_or(false, |target| target == "wasm32-unknown-unknown"); + + if need_wasm_shim { + cargo_print(&"rerun-if-changed=wasm_shim/stdlib.h"); + cargo_print(&"rerun-if-changed=wasm_shim/string.h"); + + cfg.include("wasm_shim/"); + cfg.opt_level(3); + } + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + + if target_arch == "wasm32" || target_os == "hermit" { + cargo_print(&"rustc-cfg=feature=\"std\""); + } + let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap()); cfg.include("bzip2-1.0.8") @@ -42,6 +63,18 @@ fn main() { let include = dst.join("include"); fs::create_dir_all(&include).unwrap(); fs::copy(src.join("bzlib.h"), dst.join("include/bzlib.h")).unwrap(); - println!("cargo:root={}", dst.display()); - println!("cargo:include={}", dst.join("include").display()); + cargo_print(&format_args!("cargo:root={}", dst.display())); + cargo_print(&format_args!( + "cargo:include={}", + dst.join("include").display() + )); +} + +/// Print a line for cargo. +/// +/// If non-cargo is set, do not print anything. +fn cargo_print(content: &dyn fmt::Display) { + if cfg!(not(feature = "non-cargo")) { + println!("cargo:{}", content); + } } diff --git a/bzip2-sys/examples/it_work.rs b/bzip2-sys/examples/it_work.rs new file mode 100644 index 00000000..86ac6992 --- /dev/null +++ b/bzip2-sys/examples/it_work.rs @@ -0,0 +1,31 @@ +use std::os::raw::{c_char, c_int, c_uint}; + +#[no_mangle] +pub extern "C" fn test_decompress() -> bool { + let uncompressed_bytes = include_bytes!("../bzip2-1.0.8/sample1.ref"); + let compressed_bytes = include_bytes!("../bzip2-1.0.8/sample1.bz2"); + let mut raw: Box = unsafe { Box::new(std::mem::zeroed()) }; + let mut buf: [u8; 100352] = [0; 98 * 1024]; + unsafe { + assert_eq!(bzip2_sys::BZ2_bzDecompressInit(&mut *raw, 0, 0 as c_int), 0); + raw.next_in = compressed_bytes.as_ptr() as *mut c_char; + raw.avail_in = compressed_bytes.len().min(c_uint::MAX as usize) as c_uint; + + raw.next_out = buf.as_mut_ptr() as *mut c_char; + raw.avail_out = buf.len() as c_uint; + assert_eq!( + bzip2_sys::BZ2_bzDecompress(&mut *raw), + bzip2_sys::BZ_STREAM_END + ); + bzip2_sys::BZ2_bzDecompressEnd(&mut *raw); + }; + let total_out = ((raw.total_out_lo32 as u64) | ((raw.total_out_hi32 as u64) << 32)) as usize; + assert_eq!(total_out, uncompressed_bytes.len()); + + let slice: &[u8] = buf[0..total_out].as_ref(); + assert_eq!(uncompressed_bytes, slice); + + return true; +} + +fn main() {} diff --git a/bzip2-sys/lib.rs b/bzip2-sys/lib.rs index 54d08053..93749c29 100644 --- a/bzip2-sys/lib.rs +++ b/bzip2-sys/lib.rs @@ -1,8 +1,13 @@ #![doc(html_root_url = "https://docs.rs/bzip2-sys/0.1")] -extern crate libc; +#[cfg(all( + target_arch = "wasm32", + target_vendor = "unknown", + target_os = "unknown" +))] +mod wasm_shim; -use libc::{c_char, c_int, c_uint, c_void}; +use std::os::raw::{c_char, c_int, c_uint, c_void}; pub const BZ_RUN: c_int = 0; pub const BZ_FLUSH: c_int = 1; diff --git a/bzip2-sys/wasm_shim/README.md b/bzip2-sys/wasm_shim/README.md new file mode 100644 index 00000000..45bad3b8 --- /dev/null +++ b/bzip2-sys/wasm_shim/README.md @@ -0,0 +1,13 @@ +# WASM shims + +This directory contains some WASM shims for C-functions used by bzip2 that are not available otherwise for the `wasm32-unknown-unknow` target. +Specifically, these are: + +- `malloc` +- `calloc` +- `free` +- `memset` +- `memcpy` +- `memmove` + +The shims are implemented in Rust and exposed as C functions that the bzip2-sys crate can then use / link against. diff --git a/bzip2-sys/wasm_shim/mod.rs b/bzip2-sys/wasm_shim/mod.rs new file mode 100644 index 00000000..c3c0fddd --- /dev/null +++ b/bzip2-sys/wasm_shim/mod.rs @@ -0,0 +1,85 @@ +use std::alloc::{alloc, alloc_zeroed, dealloc, Layout}; +use std::mem; +use std::ptr; + +// Define a struct to hold the size before the allocated memory +#[repr(C)] +struct AllocationHeader { + size: usize, +} + +const HEADER_SIZE: usize = mem::size_of::(); +const ALIGNMENT: usize = 2 * mem::size_of::(); + +// Helper function to create a layout that includes the header +fn layout_with_header(size: usize) -> Layout { + let adjusted_size = HEADER_SIZE + size; + Layout::from_size_align(adjusted_size, ALIGNMENT).expect("Layout creation failed") +} + +#[no_mangle] +pub extern "C" fn rust_bzip2_wasm_shim_malloc(size: usize) -> *mut u8 { + unsafe { + let layout = layout_with_header(size); + let ptr = alloc(layout) as *mut AllocationHeader; + if !ptr.is_null() { + // Store the original size in the header + (*ptr).size = size; + // Return a pointer to the memory after the header + ptr.add(1) as *mut u8 + } else { + ptr::null_mut() + } + } +} + +#[no_mangle] +pub extern "C" fn rust_bzip2_wasm_shim_calloc(nmemb: usize, size: usize) -> *mut u8 { + let total_size = nmemb * size; + unsafe { + let layout = layout_with_header(total_size); + let ptr = alloc_zeroed(layout) as *mut AllocationHeader; + if !ptr.is_null() { + (*ptr).size = total_size; + ptr.add(1) as *mut u8 + } else { + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_bzip2_wasm_shim_free(ptr: *mut u8) { + if !ptr.is_null() { + let ptr = (ptr as *mut AllocationHeader).sub(1); // Move back to the header + let size = (*ptr).size; + let layout = layout_with_header(size); + dealloc(ptr as *mut u8, layout); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_bzip2_wasm_shim_memcpy( + dest: *mut u8, + src: *const u8, + n: usize, +) -> *mut u8 { + std::ptr::copy_nonoverlapping(src, dest, n); + dest +} + +#[no_mangle] +pub unsafe extern "C" fn rust_bzip2_wasm_shim_memmove( + dest: *mut u8, + src: *const u8, + n: usize, +) -> *mut u8 { + std::ptr::copy(src, dest, n); + dest +} + +#[no_mangle] +pub unsafe extern "C" fn rust_bzip2_wasm_shim_memset(dest: *mut u8, c: i32, n: usize) -> *mut u8 { + std::ptr::write_bytes(dest, c as u8, n); + dest +} diff --git a/bzip2-sys/wasm_shim/stdlib.h b/bzip2-sys/wasm_shim/stdlib.h new file mode 100644 index 00000000..1b0c1116 --- /dev/null +++ b/bzip2-sys/wasm_shim/stdlib.h @@ -0,0 +1,22 @@ +#include + +#ifndef _STDLIB_H +#define _STDLIB_H 1 + +void *rust_bzip2_wasm_shim_malloc(size_t size); +void *rust_bzip2_wasm_shim_calloc(size_t nmemb, size_t size); +void rust_bzip2_wasm_shim_free(void *ptr); + +inline void *malloc(size_t size) { + return rust_bzip2_wasm_shim_malloc(size); +} + +inline void *calloc(size_t nmemb, size_t size) { + return rust_bzip2_wasm_shim_calloc(nmemb, size); +} + +inline void free(void *ptr) { + rust_bzip2_wasm_shim_free(ptr); +} + +#endif // _STDLIB_H diff --git a/bzip2-sys/wasm_shim/string.h b/bzip2-sys/wasm_shim/string.h new file mode 100644 index 00000000..6042c872 --- /dev/null +++ b/bzip2-sys/wasm_shim/string.h @@ -0,0 +1,22 @@ +#include + +#ifndef _STRING_H +#define _STRING_H 1 + +void *rust_bzip2_wasm_shim_memcpy(void *restrict dest, const void *restrict src, size_t n); +void *rust_bzip2_wasm_shim_memmove(void *dest, const void *src, size_t n); +void *rust_bzip2_wasm_shim_memset(void *dest, int c, size_t n); + +inline void *memcpy(void *restrict dest, const void *restrict src, size_t n) { + return rust_bzip2_wasm_shim_memcpy(dest, src, n); +} + +inline void *memmove(void *dest, const void *src, size_t n) { + return rust_bzip2_wasm_shim_memmove(dest, src, n); +} + +inline void *memset(void *dest, int c, size_t n) { + return rust_bzip2_wasm_shim_memset(dest, c, n); +} + +#endif // _STRING_H diff --git a/src/lib.rs b/src/lib.rs index 4eb97fc7..519a27f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,6 @@ #![doc(html_root_url = "https://docs.rs/bzip2/")] extern crate bzip2_sys as ffi; -extern crate libc; #[cfg(test)] extern crate partial_io; #[cfg(test)] diff --git a/src/mem.rs b/src/mem.rs index ff7d9a5a..c9410528 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -6,7 +6,7 @@ use std::marker; use std::mem; use std::slice; -use libc::{c_int, c_uint}; +use std::os::raw::{c_int, c_uint}; use {ffi, Compression};