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..4b3973e6 100644 --- a/bzip2-sys/Cargo.toml +++ b/bzip2-sys/Cargo.toml @@ -28,3 +28,5 @@ cc = "1.0" [features] # Enable this feature if you want to have a statically linked bzip2 static = [] +std = [] # Use std types instead of libc in bindgen + diff --git a/bzip2-sys/build.rs b/bzip2-sys/build.rs index feb004ca..55ac5b39 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,29 @@ 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" || target == "wasm32-wasi" + }); + + 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.define("XXH_STATIC_ASSERT", Some("0")); + 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 +65,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/bzlib.h b/bzip2-sys/bzlib.h new file mode 100644 index 00000000..f91ae837 --- /dev/null +++ b/bzip2-sys/bzlib.h @@ -0,0 +1,15 @@ +#ifdef PKG_CONFIG + +/* Just use installed headers */ +#include + +#else // #ifdef PKG_CONFIG + +#include "bzip2-1.0.8/bzlib.h" + +#endif // #ifdef PKG_CONFIG + + +/* This file is used to generate bindings for both headers. + * Check update_bindings.sh to see how to use it. + * Or use the `bindgen` feature, which will create the bindings automatically. */ diff --git a/bzip2-sys/examples/it_work.rs b/bzip2-sys/examples/it_work.rs new file mode 100644 index 00000000..af92f004 --- /dev/null +++ b/bzip2-sys/examples/it_work.rs @@ -0,0 +1,35 @@ +#[cfg(feature = "std")] +use std::os::raw::{c_char, c_int, c_uint}; + +#[cfg(not(feature = "std"))] +use libc::{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..84f7b7b9 100644 --- a/bzip2-sys/lib.rs +++ b/bzip2-sys/lib.rs @@ -2,6 +2,13 @@ extern crate libc; +#[cfg(target_arch = "wasm32")] +mod wasm_shim; + +#[cfg(feature = "std")] +use std::os::raw::{c_char, c_int, c_uint, c_void}; + +#[cfg(not(feature = "std"))] use libc::{c_char, c_int, c_uint, c_void}; pub const BZ_RUN: c_int = 0; diff --git a/bzip2-sys/wasm-shim/stdlib.h b/bzip2-sys/wasm-shim/stdlib.h new file mode 100644 index 00000000..be102cc2 --- /dev/null +++ b/bzip2-sys/wasm-shim/stdlib.h @@ -0,0 +1,22 @@ +#include + +#ifndef _STDLIB_H +#define _STDLIB_H 1 + +void *rust_zstd_wasm_shim_malloc(size_t size); +void *rust_zstd_wasm_shim_calloc(size_t nmemb, size_t size); +void rust_zstd_wasm_shim_free(void *ptr); + +inline void *malloc(size_t size) { + return rust_zstd_wasm_shim_malloc(size); +} + +inline void *calloc(size_t nmemb, size_t size) { + return rust_zstd_wasm_shim_calloc(nmemb, size); +} + +inline void free(void *ptr) { + rust_zstd_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..b0abf79c --- /dev/null +++ b/bzip2-sys/wasm-shim/string.h @@ -0,0 +1,22 @@ +#include + +#ifndef _STRING_H +#define _STRING_H 1 + +void *rust_zstd_wasm_shim_memcpy(void *restrict dest, const void *restrict src, size_t n); +void *rust_zstd_wasm_shim_memmove(void *dest, const void *src, size_t n); +void *rust_zstd_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_zstd_wasm_shim_memcpy(dest, src, n); +} + +inline void *memmove(void *dest, const void *src, size_t n) { + return rust_zstd_wasm_shim_memmove(dest, src, n); +} + +inline void *memset(void *dest, int c, size_t n) { + return rust_zstd_wasm_shim_memset(dest, c, n); +} + +#endif // _STRING_H diff --git a/bzip2-sys/wasm_shim.rs b/bzip2-sys/wasm_shim.rs new file mode 100644 index 00000000..8bfd7baf --- /dev/null +++ b/bzip2-sys/wasm_shim.rs @@ -0,0 +1,55 @@ +use std::alloc::{alloc, dealloc, Layout}; +use std::os::raw::{c_int, c_void}; + +#[no_mangle] +pub extern "C" fn rust_zstd_wasm_shim_malloc(size: usize) -> *mut c_void { + unsafe { + let layout = Layout::from_size_align_unchecked(size, 1); + alloc(layout).cast() + } +} + +#[no_mangle] +pub extern "C" fn rust_zstd_wasm_shim_calloc(nmemb: usize, size: usize) -> *mut c_void { + unsafe { + let layout = Layout::from_size_align_unchecked(size * nmemb, 1); + alloc(layout).cast() + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_zstd_wasm_shim_free(ptr: *mut c_void) { + // layout is not actually used + let layout = Layout::from_size_align_unchecked(1, 1); + dealloc(ptr.cast(), layout); +} + +#[no_mangle] +pub unsafe extern "C" fn rust_zstd_wasm_shim_memcpy( + dest: *mut c_void, + src: *const c_void, + n: usize, +) -> *mut c_void { + std::ptr::copy_nonoverlapping(src as *const u8, dest as *mut u8, n); + dest +} + +#[no_mangle] +pub unsafe extern "C" fn rust_zstd_wasm_shim_memmove( + dest: *mut c_void, + src: *const c_void, + n: usize, +) -> *mut c_void { + std::ptr::copy(src as *const u8, dest as *mut u8, n); + dest +} + +#[no_mangle] +pub unsafe extern "C" fn rust_zstd_wasm_shim_memset( + dest: *mut c_void, + c: c_int, + n: usize, +) -> *mut c_void { + std::ptr::write_bytes(dest as *mut u8, c as u8, n); + dest +}