From e4d3ba4a5eb51c8ad1b8fb30a4e591d63309420b Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Fri, 7 Feb 2025 22:48:21 +0800 Subject: [PATCH 1/2] upstream snmalloc-rs as part of snmalloc --- .github/workflows/rust.yml | 49 +++ .gitignore | 4 + Cargo.toml | 6 + rust/snmalloc-rs/CHANGELOG.md | 168 ++++++++++ rust/snmalloc-rs/Cargo.toml | 32 ++ rust/snmalloc-rs/LICENSE | 21 ++ rust/snmalloc-rs/README.md | 114 +++++++ rust/snmalloc-rs/src/lib.rs | 215 +++++++++++++ rust/snmalloc-sys/Cargo.toml | 34 +++ rust/snmalloc-sys/build.rs | 561 ++++++++++++++++++++++++++++++++++ rust/snmalloc-sys/src/lib.rs | 128 ++++++++ 11 files changed, 1332 insertions(+) create mode 100644 .github/workflows/rust.yml create mode 100644 Cargo.toml create mode 100644 rust/snmalloc-rs/CHANGELOG.md create mode 100644 rust/snmalloc-rs/Cargo.toml create mode 100644 rust/snmalloc-rs/LICENSE create mode 100644 rust/snmalloc-rs/README.md create mode 100644 rust/snmalloc-rs/src/lib.rs create mode 100644 rust/snmalloc-sys/Cargo.toml create mode 100644 rust/snmalloc-sys/build.rs create mode 100644 rust/snmalloc-sys/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 000000000..ea10f7456 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,49 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, macos-14, macos-15, ubuntu-latest] + rust: [stable, nightly] + steps: + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + - name: update dependency + run: | + if bash -c 'uname -s | grep 'Linux' >/dev/null'; then + sudo apt-get update -y && sudo apt-get --reinstall install -y libc6-dev + fi + shell: bash + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --all + - name: Run tests debug + run: cargo test --all --features debug + - name: Run tests check + run: cargo test --all --features check + - name: Run tests build_cc + run: cargo test --all --features "build_cc usecxx17" + - name: Run tests native-cpu + run: cargo test --all --features native-cpu + - name: Run tests local_dynamic_tls + run: cargo test --all --features local_dynamic_tls + - name: Run tests lto + run: cargo test --all --features lto diff --git a/.gitignore b/.gitignore index eb06e179b..3c11b84ff 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ # cache dirs .cache + +# rust related +/target/ +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..6b453bf10 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +resolver = "2" +members = [ + "rust/snmalloc-sys", + "rust/snmalloc-rs" +] diff --git a/rust/snmalloc-rs/CHANGELOG.md b/rust/snmalloc-rs/CHANGELOG.md new file mode 100644 index 000000000..f843daf6e --- /dev/null +++ b/rust/snmalloc-rs/CHANGELOG.md @@ -0,0 +1,168 @@ +## Changelog + +### 0.3.8 + +- Tracking upstream to match version 0.7.1 +- Recommended to upgrade from 0.3.7 to get an important bug fix. + +### 0.3.7 + +- Tracking upstream to match version 0.7 + +### 0.3.4 +- Tracking upstream to match version 0.6.2 + +### 0.3.3 +- Tracking upstream to fix Linux PAL typo. + +### 0.3.2 +- Tracking upstream to enable old Linux variants. + +### 0.3.1 +- Fixes `build_cc` feature (broken in 0.3.0 release). +- Fixes `native-cpu` feature (broken in 0.3.0 release). + +### 0.3.0 +- Release to follow upstream 0.6.0 +- **upstream** Major redesign of the code to improve performance and + enable a mode that provides strong checks against corruption. + +### 0.3.0-beta.1 + +- Beta release to support snmalloc 2 + +### 0.2.28 +- Deprecation of `cache-friendly` +- Use exposed `alloc_zeroed` from `snmalloc` +- **upstream** changes of remote communication, corruption detection and compilation flag detection. + +### 0.2.27 + +- Reduction of libc dependency +- **upstream** Windows 7 and windows 8 compatibility added +- **upstream** Option to use C++20 standards if available +- **upstream** Preparations of cherification (heavy refactors of the structure) +- **upstream** Cold routine annotations + +### 0.2.26 + +- **upstream** Building adjustment +- option of cc crate as build feature, only c compiler needed, no cmake required +- Addition of dynamic local TLS option + +### 0.2.25 + +- **upstream** Apple M1 support +- **upstream** Building adjust +- non-allocation tracking functions + +### 0.2.24 + +- **upstream** update to use a more efficient power of 2 check +- fix msvc support w/ crt-static + +### 0.2.23 + +- **upstream** fix external pagemap usage + +### 0.2.22 + +- **upstream** avoid amplification when routing +- **upstream** remotely store sizeclass +- **upstream** limit flat pagemap size +- **upstream** limit medium slab header +- **upstream** solaris support fix + +### 0.2.21 + +- **upstream** bug fix for using failing to initialise meta-data + +### 0.2.20 + +- **upstream** pass through Haiku build fix. +- **upstream** fix typo in macro definition for 16MiB shared library shim. +- **upstream** DragonFly support (userland). +- **upstream** natural alignment for USE_MALLOC +- **upstream** fix bug in pagemap when index has many level +- **upstream** add constexpr annotation to align_up/down. + +### 0.2.19 + +- **upstream** stats +- **upstream** PAL updates and concepts +- **upstream** ddd constexpr annotation to align_up/down +- change macOS CI to follow xcode 12 + +### 0.2.18 + +- add msvc flag /EHsc to fix warning C4530 + +### 0.2.17 + +- **upstream** add backoff for large reservation +- **upstream** default chunk configuration to 1mib +- add new feature flags + +### 0.2.16 + +- **upstream** New implementation of address space reservation leading to + - better integration with transparent huge pages; and + - lower address space requirements and fragmentation. +- Notice MinGW broken state + +### 0.2.15 + +- **upstream** fix VS2019 build +- **upstream** fix wrong realloc behavior and performance issue + +### 0.2.14 + +- **upstream** refactor ptr representation. +- **upstream** improve for more targets and architectures. +- seperate native CPU feature + +### 0.2.13 + +- **upstream** large realloc fix and minor updates + +### 0.2.12 + +- improve mingw support + +### 0.2.11 + +- add android support +- **upstream** support x86 +- **upstream** support android +- **upstream** fix callback + +### 0.2.10 + +- follow upstream 0.4.0 +- **upstream** defense TLS teardown +- **upstream** adjust GCC warning +- **upstream** other release optimizations + +### 0.2.9 + +- **upstream** fix OpenEnclave +- **upstream** adjust remote batch size (performance improved dramatically, see [benchmark](https://github.com/microsoft/snmalloc/pull/158#issuecomment-605816017) +- **upstream** improve slow path performance for allocation + +### 0.2.8 + +- More CI (**ARM64 on QEMU**) +- **upstream** ARM(32/64) support +- **upstream** x86-SGX support + +### 0.2.7 + +- partially fixed `mingw` +- **upstream** remote dealloc refactor (higher performance) +- **upstream** remove extra assertions + +### 0.2.6 + +- fix `macos`/`freebsd ` support +- add more ci tests +- mark the `mingw` problem diff --git a/rust/snmalloc-rs/Cargo.toml b/rust/snmalloc-rs/Cargo.toml new file mode 100644 index 000000000..4461b5599 --- /dev/null +++ b/rust/snmalloc-rs/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "snmalloc-rs" +version = "0.3.8" +authors = ["schrodingerzhu "] +edition = "2021" +license = "MIT" +description = "rust bindings of snmalloc." +keywords = ["snmalloc", "allocator"] +categories = ["memory-management", "api-bindings"] +homepage = "https://github.com/microsoft/snmalloc" +repository = "https://github.com/SchrodingerZhu/snmalloc-rs" +readme = "README.md" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +snmalloc-sys = { version = "0.3.8", path = "../snmalloc-sys", default-features = false } + +[features] +default = ["snmalloc-sys/build_cmake", "snmalloc-sys/usewait-on-address"] +build_cc = ["snmalloc-sys/build_cc"] +qemu = ["snmalloc-sys/qemu"] +debug = ["snmalloc-sys/debug"] +android-lld = ["snmalloc-sys/android-lld"] +native-cpu = ["snmalloc-sys/native-cpu"] +local_dynamic_tls = ["snmalloc-sys/local_dynamic_tls"] +win8compat = ["snmalloc-sys/win8compat"] +usecxx17 = ["snmalloc-sys/usecxx17"] +check = ["snmalloc-sys/check"] +lto = ["snmalloc-sys/lto"] +notls = ["snmalloc-sys/notls"] +stats = ["snmalloc-sys/stats"] +usewait-on-address = ["snmalloc-sys/usewait-on-address"] diff --git a/rust/snmalloc-rs/LICENSE b/rust/snmalloc-rs/LICENSE new file mode 100644 index 000000000..7f6c08fe6 --- /dev/null +++ b/rust/snmalloc-rs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 SchrodingerZhu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rust/snmalloc-rs/README.md b/rust/snmalloc-rs/README.md new file mode 100644 index 000000000..869da4bc0 --- /dev/null +++ b/rust/snmalloc-rs/README.md @@ -0,0 +1,114 @@ +# snmalloc-rs + +**Notice: MinGW Build is broken and may not be fixed in a near future. +See [this PR](https://github.com/microsoft/snmalloc/pull/217) in the upstream.** + +MSVC/MinGW/Linux/MacOS: [![Actions Status](https://github.com/schrodingerzhu/snmalloc-rs/workflows/Rust/badge.svg)](https://github.com/schrodingerzhu/snmalloc-rs/actions) + +FreeBSD: [![Build Status](https://api.cirrus-ci.com/github/SchrodingerZhu/snmalloc-rs.svg)](https://cirrus-ci.com/github/SchrodingerZhu/snmalloc-rs) + +`snmalloc-rs` provides a wrapper for [`microsoft/snmalloc`](https://github.com/microsoft/snmalloc) to make it usable as +a global allocator for rust. snmalloc is a research allocator. Its key design features are: + +- Memory that is freed by the same thread that allocated it does not require any synchronising operations. +- Freeing memory in a different thread to initially allocated it, does not take any locks and instead uses a novel + message passing scheme to return the memory to the original allocator, where it is recycled. +- The allocator uses large ranges of pages to reduce the amount of meta-data required. + +Some old benchmark results are available in +the [`snmalloc` paper](https://github.com/microsoft/snmalloc/blob/master/snmalloc.pdf). Some recent benchmark results +are listed at +[bench_suite](https://github.com/SchrodingerZhu/bench_suite). There are three features defined in this crate: + +- `debug`: Enable the `Debug` mode in `snmalloc`. +- ~~`1mib`: Use the `1mib` chunk configuration. From `0.2.17`, this is set as a default feature~~ (removed since 0.3.0) +- ~~`16mib`: Use the `16mib` chunk configuration.~~ (removed since 0.3.0) +- ~~`cache-friendly`: Make the allocator more cache friendly (setting `CACHE_FRIENDLY_OFFSET` to `64` in building the + library).~~ (removed since 0.3.0) +- `native-cpu`: Optimize `snmalloc` for the native CPU of the host machine. (this is not a default behavior + since `0.2.14`) +- `qemu`: Workaround `madvise` problem of QEMU environment +- ~~`stats`: Enable statistics~~ (removed since 0.3.0) +- `local_dynamic_tls`: Workaround cannot allocate memory in static tls block +- `build_cc`: Use of cc crate instead of cmake (cmake still default) as builder (more platform agnostic) +- ~~`usecxx20`: Enable C++20 standard if available~~ (removed since 0.3.0) +- `usecxx17`: Use C++17 standard +- `check`: Enable extra checks to improve security, see upstream [security docs](https://github.com/microsoft/snmalloc/tree/main/docs/security). + Note that the `memcpy` protection is not enabled in Rust. +- `win8compat`: Improve compatibility for old Windows platforms (removing usages of `VirtualAlloc2` and other new APIs) +- `lto`: Links with InterProceduralOptimization/LinkTimeOptimization +- `notls`: Enables to be loaded dynamically, thus disable tls. +- `stats`: Enables allocation statistics. + +**To get the crates compiled, you need to choose either `1mib` or `16mib` to determine the chunk configuration** + +To use `snmalloc-rs` add it as a dependency: + +```toml +# Cargo.toml +[dependencies] +snmalloc-rs = "0.3.8" +``` + +To set `SnMalloc` as the global allocator add this to your project: + +```rust +#[global_allocator] +static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; +``` + +## For MinGW Users + +`mingw` version is only tested on nightly branch with MSYS environment. We are using dynamic linking method. Hence, +please make sure the following libs are in your `PATH`: + +- `winpthread` +- `atomic` +- `stdc++` +- `gcc_s` + +**Notice:** since version `0.2.12`, we no longer require you to provide additional environment variables for `mingw` +target. + +## For Android Cross-Compilation + +- `ANDROID_NDK` must be provided as an environment variable +- `ANDROID_PLATFORM` can be passed as an optional environment variable +- `ANDROID_ABI` used by CMake is detected automatically +- feature `android-lld` can be used to set the linker of `snmalloc` to `lld` +- ~~feature `android-shared-std` can be used to set the STL library of `snmalloc` to `c++_shared` (it uses `c++_static` by + default)~~ (`libstdc++` is no longer a dependency) + +## Changelog + +### 0.3.8 + +- Tracking upstream to match version 0.7.1 +- Recommended to upgrade from 0.3.7 to get an important bug fix. + +### 0.3.7 + +- Tracking upstream to match version 0.7 + +### 0.3.4 +- Tracking upstream to version 0.6.2. + +### 0.3.3 +- Tracking upstream to fix Linux PAL typo. + +### 0.3.2 + +- Tracking upstream to enable old Linux variants. + +### 0.3.1 + +- Fixes `build_cc` feature (broken in 0.3.0 release). +- Fixes `native-cpu` feature (broken in 0.3.0 release). + +### 0.3.0 + +- Release to support snmalloc 0.6.0. + +### 0.3.0-beta.1 + +- Beta release to support snmalloc ~~2~~ 0.6.0 diff --git a/rust/snmalloc-rs/src/lib.rs b/rust/snmalloc-rs/src/lib.rs new file mode 100644 index 000000000..c4ab77411 --- /dev/null +++ b/rust/snmalloc-rs/src/lib.rs @@ -0,0 +1,215 @@ +#![no_std] +//! `snmalloc-rs` provides a wrapper for [`microsoft/snmalloc`](https://github.com/microsoft/snmalloc) to make it usable as a global allocator for rust. +//! snmalloc is a research allocator. Its key design features are: +//! - Memory that is freed by the same thread that allocated it does not require any synchronising operations. +//! - Freeing memory in a different thread to initially allocated it, does not take any locks and instead uses a novel message passing scheme to return the memory to the original allocator, where it is recycled. +//! - The allocator uses large ranges of pages to reduce the amount of meta-data required. +//! +//! The benchmark is available at the [paper](https://github.com/microsoft/snmalloc/blob/master/snmalloc.pdf) of `snmalloc` +//! There are three features defined in this crate: +//! - `debug`: Enable the `Debug` mode in `snmalloc`. +//! - `1mib`: Use the `1mib` chunk configuration. +//! - `cache-friendly`: Make the allocator more cache friendly (setting `CACHE_FRIENDLY_OFFSET` to `64` in building the library). +//! +//! The whole library supports `no_std`. +//! +//! To use `snmalloc-rs` add it as a dependency: +//! ```toml +//! # Cargo.toml +//! [dependencies] +//! snmalloc-rs = "0.1.0" +//! ``` +//! +//! To set `SnMalloc` as the global allocator add this to your project: +//! ```rust +//! #[global_allocator] +//! static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; +//! ``` +extern crate snmalloc_sys as ffi; + +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::NonNull, +}; + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct SnMalloc; + +unsafe impl Send for SnMalloc {} +unsafe impl Sync for SnMalloc {} + +impl SnMalloc { + #[inline(always)] + pub const fn new() -> Self { + Self + } + + /// Returns the available bytes in a memory block. + #[inline(always)] + pub fn usable_size(&self, ptr: *const u8) -> Option { + match ptr.is_null() { + true => None, + false => Some(unsafe { ffi::sn_rust_usable_size(ptr.cast()) }) + } + } + + /// Allocates memory with the given layout, returning a non-null pointer on success + #[inline(always)] + pub fn alloc_aligned(&self, layout: Layout) -> Option> { + match layout.size() { + 0 => NonNull::new(layout.align() as *mut u8), + size => NonNull::new(unsafe { ffi::sn_rust_alloc(layout.align(), size) }.cast()) + } + } +} + +unsafe impl GlobalAlloc for SnMalloc { + /// Allocate the memory with the given alignment and size. + /// On success, it returns a pointer pointing to the required memory address. + /// On failure, it returns a null pointer. + /// The client must assure the following things: + /// - `alignment` is greater than zero + /// - Other constrains are the same as the rust standard library. + /// + /// The program may be forced to abort if the constrains are not full-filled. + #[inline(always)] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + match layout.size() { + 0 => layout.align() as *mut u8, + size => ffi::sn_rust_alloc(layout.align(), size).cast() + } + } + + /// De-allocate the memory at the given address with the given alignment and size. + /// The client must assure the following things: + /// - the memory is acquired using the same allocator and the pointer points to the start position. + /// - Other constrains are the same as the rust standard library. + /// + /// The program may be forced to abort if the constrains are not full-filled. + #[inline(always)] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + if layout.size() != 0 { + ffi::sn_rust_dealloc(ptr as _, layout.align(), layout.size()); + } + } + + /// Behaves like alloc, but also ensures that the contents are set to zero before being returned. + #[inline(always)] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + match layout.size() { + 0 => layout.align() as *mut u8, + size => ffi::sn_rust_alloc_zeroed(layout.align(), size).cast() + } + } + + /// Re-allocate the memory at the given address with the given alignment and size. + /// On success, it returns a pointer pointing to the required memory address. + /// The memory content within the `new_size` will remains the same as previous. + /// On failure, it returns a null pointer. In this situation, the previous memory is not returned to the allocator. + /// The client must assure the following things: + /// - the memory is acquired using the same allocator and the pointer points to the start position + /// - `alignment` fulfills all the requirements as `rust_alloc` + /// - Other constrains are the same as the rust standard library. + /// + /// The program may be forced to abort if the constrains are not full-filled. + #[inline(always)] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + match new_size { + 0 => { + self.dealloc(ptr, layout); + layout.align() as *mut u8 + } + new_size if layout.size() == 0 => { + self.alloc(Layout::from_size_align_unchecked(new_size, layout.align())) + } + _ => ffi::sn_rust_realloc(ptr.cast(), layout.align(), layout.size(), new_size).cast() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn allocation_lifecycle() { + let alloc = SnMalloc::new(); + unsafe { + let layout = Layout::from_size_align(8, 8).unwrap(); + + // Test regular allocation + let ptr = alloc.alloc(layout); + alloc.dealloc(ptr, layout); + + // Test zeroed allocation + let ptr = alloc.alloc_zeroed(layout); + alloc.dealloc(ptr, layout); + + // Test reallocation + let ptr = alloc.alloc(layout); + let ptr = alloc.realloc(ptr, layout, 16); + alloc.dealloc(ptr, layout); + + // Test large allocation + let large_layout = Layout::from_size_align(1 << 20, 32).unwrap(); + let ptr = alloc.alloc(large_layout); + alloc.dealloc(ptr, large_layout); + } + } + #[test] + fn it_frees_allocated_memory() { + unsafe { + let layout = Layout::from_size_align(8, 8).unwrap(); + let alloc = SnMalloc; + + let ptr = alloc.alloc(layout); + alloc.dealloc(ptr, layout); + } + } + + #[test] + fn it_frees_zero_allocated_memory() { + unsafe { + let layout = Layout::from_size_align(8, 8).unwrap(); + let alloc = SnMalloc; + + let ptr = alloc.alloc_zeroed(layout); + alloc.dealloc(ptr, layout); + } + } + + #[test] + fn it_frees_reallocated_memory() { + unsafe { + let layout = Layout::from_size_align(8, 8).unwrap(); + let alloc = SnMalloc; + + let ptr = alloc.alloc(layout); + let ptr = alloc.realloc(ptr, layout, 16); + alloc.dealloc(ptr, layout); + } + } + + #[test] + fn it_frees_large_alloc() { + unsafe { + let layout = Layout::from_size_align(1 << 20, 32).unwrap(); + let alloc = SnMalloc; + + let ptr = alloc.alloc(layout); + alloc.dealloc(ptr, layout); + } + } + + #[test] + fn test_usable_size() { + let alloc = SnMalloc::new(); + unsafe { + let layout = Layout::from_size_align(8, 8).unwrap(); + let ptr = alloc.alloc(layout); + let usz = alloc.usable_size(ptr).expect("usable_size returned None"); + alloc.dealloc(ptr, layout); + assert!(usz >= 8); + } + } +} diff --git a/rust/snmalloc-sys/Cargo.toml b/rust/snmalloc-sys/Cargo.toml new file mode 100644 index 000000000..56f612e06 --- /dev/null +++ b/rust/snmalloc-sys/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snmalloc-sys" +version = "0.3.8" +authors = ["schrodingerzhu "] +edition = "2021" +license = "MIT" +description = "rust raw bindings of snmalloc." +keywords = ["snmalloc", "allocator"] +categories = ["memory-management", "api-bindings"] +homepage = "https://github.com/microsoft/snmalloc" +repository = "https://github.com/SchrodingerZhu/snmalloc-rs" +build = "build.rs" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +cc = { version = "1.0", optional = true } +cmake = { version = "0.1", optional = true } + +[features] +default = ["build_cmake"] +build_cc = ["cc"] +build_cmake = ["cmake"] +qemu = [] +debug = [] +android-lld = [] +native-cpu = [] +local_dynamic_tls = [] +win8compat = [] +usecxx17 = [] +check = [] +lto = [] +notls = [] +stats = [] +usewait-on-address = [] diff --git a/rust/snmalloc-sys/build.rs b/rust/snmalloc-sys/build.rs new file mode 100644 index 000000000..9c80942a1 --- /dev/null +++ b/rust/snmalloc-sys/build.rs @@ -0,0 +1,561 @@ +#![allow(dead_code)] + +use std::env; + +#[derive(Debug, PartialEq)] +enum Compiler { + Clang, + Gcc, + Msvc, + Unknown, +} + +struct BuildConfig { + debug: bool, + optim_level: String, + target_os: String, + target_env: String, + target_family: String, + target: String, + out_dir: String, + build_type: String, + msystem: Option, + cmake_cxx_standard: String, + target_lib: String, + features: BuildFeatures, + #[cfg(feature = "build_cc")] + builder: cc::Build, + #[cfg(not(feature = "build_cc"))] + builder: cmake::Config, + compiler: Compiler, +} + +impl std::fmt::Debug for BuildConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BuildConfig") + .field("debug", &self.debug) + .field("optim_level", &self.optim_level) + .field("target_os", &self.target_os) + .field("target_env", &self.target_env) + .field("target_family", &self.target_family) + .field("target", &self.target) + .field("out_dir", &self.out_dir) + .field("build_type", &self.build_type) + .field("msystem", &self.msystem) + .field("cmake_cxx_standard", &self.cmake_cxx_standard) + .field("target_lib", &self.target_lib) + .field("features", &self.features) + .finish() + } +} + +#[derive(Debug, Clone)] +struct BuildFeatures { + native_cpu: bool, + qemu: bool, + wait_on_address: bool, + lto: bool, + notls: bool, + win8compat: bool, + stats: bool, + android_lld: bool, + local_dynamic_tls: bool, +} + +impl BuildConfig { + fn new() -> Self { + let debug = cfg!(feature = "debug"); + #[cfg(feature = "build_cc")] + let builder = cc::Build::new(); + + #[cfg(not(feature = "build_cc"))] + let builder = Config::new("../.."); + + let mut config = Self { + debug, + optim_level: (if debug { "-O0" } else { "-O3" }).to_string(), + target_os: env::var("CARGO_CFG_TARGET_OS").expect("target_os not defined!"), + target_env: env::var("CARGO_CFG_TARGET_ENV").expect("target_env not defined!"), + target_family: env::var("CARGO_CFG_TARGET_FAMILY").expect("target family not set"), + target: env::var("TARGET").expect("TARGET not set"), + out_dir: env::var("OUT_DIR").unwrap(), + build_type: (if debug { "Debug" } else { "Release" }).to_string(), + msystem: env::var("MSYSTEM").ok(), + cmake_cxx_standard: (if cfg!(feature = "usecxx17") { + "17" + } else { + "20" + }) + .to_string(), + target_lib: (if cfg!(feature = "check") { + "snmallocshim-checks-rust" + } else { + "snmallocshim-rust" + }) + .to_string(), + features: BuildFeatures::new(), + builder, + compiler: Compiler::Unknown, + }; + config.compiler = config.detect_compiler(); + config.embed_build_info(); + config + } + + fn detect_compiler(&self) -> Compiler { + // Check MSYSTEM for MSYS2 environments + if let Some(msystem) = &self.msystem { + match msystem.as_str() { + "CLANG64" | "CLANGARM64" => return Compiler::Clang, + "MINGW64" | "UCRT64" => return Compiler::Gcc, + _ => {} + } + } + + // Check target environment + if let Ok(env) = env::var("CARGO_CFG_TARGET_ENV") { + match env.as_str() { + "msvc" => return Compiler::Msvc, + "gnu" => return Compiler::Gcc, + _ => {} + } + } + + // Check CC environment variable + if let Ok(cc) = env::var("CC") { + let cc = cc.to_lowercase(); + if cc.contains("clang") { + return Compiler::Clang; + } else if cc.contains("gcc") { + return Compiler::Gcc; + } + } + + // Default based on platform and target + if self.target.contains("msvc") { + Compiler::Msvc + } else if cfg!(windows) { + Compiler::Gcc // Assume GCC for non-MSVC Windows environments + } else if cfg!(unix) { + Compiler::Clang // Default to Clang for Unix-like systems + } else { + Compiler::Unknown + } + } + + fn embed_build_info(&self) { + let build_info = [ + ("BUILD_TARGET_OS", &self.target_os), + ("BUILD_TARGET_ENV", &self.target_env), + ("BUILD_TARGET_FAMILY", &self.target_family), + ("BUILD_TARGET", &self.target), + ("BUILD_CC", &format!("{:#?}", self.compiler)), + ("BUILD_TYPE", &self.build_type), + ("BUILD_DEBUG", &self.debug.to_string()), + ("BUILD_OPTIM_LEVEL", &self.optim_level), + ("BUILD_CXX_STANDARD", &self.cmake_cxx_standard), + ]; + + for (key, value) in build_info { + println!("cargo:rustc-env={}={}", key, value); + } + + if let Some(ms) = &self.msystem { + println!("cargo:rustc-env=BUILD_MSYSTEM={}", ms); + } + } + + fn get_cpp_flags(&self) -> [&'static str; 2] { + if cfg!(feature = "usecxx17") { + ["-std=c++17", "/std:c++17"] + } else { + ["-std=c++20", "/std:c++20"] + } + } + + fn is_msvc(&self) -> bool { + self.target_env == "msvc" + } + + fn is_gnu(&self) -> bool { + self.target_env == "gnu" + } + + fn is_windows(&self) -> bool { + self.target_os == "windows" + } + + fn is_linux(&self) -> bool { + self.target_os == "linux" + } + + fn is_unix(&self) -> bool { + self.target_family == "unix" + } + + fn is_clang_msys(&self) -> bool { + self.msystem + .as_deref() + .map_or(false, |s| s.contains("CLANG")) + } + + fn is_ucrt64(&self) -> bool { + self.msystem.as_deref() == Some("UCRT64") + } +} + +trait BuilderDefine { + fn define(&mut self, key: &str, value: &str) -> &mut Self; + fn flag_if_supported(&mut self, flag: &str) -> &mut Self; + fn build_lib(&mut self, target_lib: &str) -> std::path::PathBuf; + fn configure_output_dir(&mut self, out_dir: &str) -> &mut Self; + fn configure_cpp(&mut self, debug: bool) -> &mut Self; +} + +#[cfg(feature = "build_cc")] +impl BuilderDefine for cc::Build { + fn define(&mut self, key: &str, value: &str) -> &mut Self { + self.define(key, Some(value)) + } + + fn flag_if_supported(&mut self, flag: &str) -> &mut Self { + self.flag_if_supported(flag) + } + + fn build_lib(&mut self, target_lib: &str) -> std::path::PathBuf { + self.compile(target_lib); + std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()) + } + + fn configure_output_dir(&mut self, out_dir: &str) -> &mut Self { + self.out_dir(out_dir) + } + + fn configure_cpp(&mut self, debug: bool) -> &mut Self { + self.include("../../src") + .file("../../src/snmalloc/override/rust.cc") + .cpp(true) + .debug(debug) + .static_crt(true) + } +} + +#[cfg(not(feature = "build_cc"))] +impl BuilderDefine for cmake::Config { + fn define(&mut self, key: &str, value: &str) -> &mut Self { + self.define(key, value) + } + + fn flag_if_supported(&mut self, _flag: &str) -> &mut Self { + self + } + + fn build_lib(&mut self, target_lib: &str) -> std::path::PathBuf { + self.build_target(target_lib).build() + } + + fn configure_output_dir(&mut self, out_dir: &str) -> &mut Self { + self.out_dir(out_dir) + } + + fn configure_cpp(&mut self, _debug: bool) -> &mut Self { + self.define("SNMALLOC_RUST_SUPPORT", "ON") + .very_verbose(true) + .define("CMAKE_SH", "CMAKE_SH-NOTFOUND") + .always_configure(true) + .static_crt(true) + } +} + +fn apply_defines(builder: &mut T, defines: &[(&str, &str)]) { + for (key, value) in defines { + builder.define(key, value); + } +} +impl BuildFeatures { + fn new() -> Self { + Self { + native_cpu: cfg!(feature = "native-cpu"), + qemu: cfg!(feature = "qemu"), + wait_on_address: cfg!(feature = "usewait-on-address"), + lto: cfg!(feature = "lto"), + notls: cfg!(feature = "notls"), + win8compat: cfg!(feature = "win8compat"), + stats: cfg!(feature = "stats"), + android_lld: cfg!(feature = "android-lld"), + local_dynamic_tls: cfg!(feature = "local_dynamic_tls"), + } + } +} + +fn configure_platform(config: &mut BuildConfig) { + // Basic optimization and compiler flags + config + .builder + .flag_if_supported(&config.optim_level) + .flag_if_supported("-fomit-frame-pointer"); + + // C++ standard flags + for std in config.get_cpp_flags() { + config.builder.flag_if_supported(std); + } + + // Common feature configurations + if config.features.native_cpu { + config + .builder + .define("SNMALLOC_OPTIMISE_FOR_CURRENT_MACHINE", "ON"); + #[cfg(feature = "build_cc")] + config.builder.flag_if_supported("-march=native"); + } + + // Platform-specific configurations + match () { + _ if config.is_windows() => { + let common_flags = vec!["-mcx16", "-fno-exceptions", "-fno-rtti", "-pthread"]; + for flag in common_flags { + config.builder.flag_if_supported(flag); + } + + if let Some(msystem) = &config.msystem { + match msystem.as_str() { + "CLANG64" | "CLANGARM64" => { + let defines = vec![ + ("CMAKE_CXX_COMPILER", "clang++"), + ("CMAKE_C_COMPILER", "clang"), + ("CMAKE_CXX_FLAGS", "-fuse-ld=lld -stdlib=libc++ -mcx16 -Wno-error=unknown-pragmas -Qunused-arguments"), + ("CMAKE_C_FLAGS", "-fuse-ld=lld -Wno-error=unknown-pragmas -Qunused-arguments"), + ("CMAKE_EXE_LINKER_FLAGS", "-fuse-ld=lld -stdlib=libc++"), + ("CMAKE_SHARED_LINKER_FLAGS", "-fuse-ld=lld -stdlib=libc++") + ]; + apply_defines(&mut config.builder, &defines); + if config.features.lto { + config.builder.flag_if_supported("-flto=thin"); + } + } + "UCRT64" | "MINGW64" => { + let defines = vec![ + ("CMAKE_CXX_FLAGS", "-fuse-ld=lld -Wno-error=unknown-pragmas"), + ("CMAKE_SYSTEM_NAME", "Windows"), + ("CMAKE_C_FLAGS", "-fuse-ld=lld -Wno-error=unknown-pragmas"), + ("CMAKE_EXE_LINKER_FLAGS", "-fuse-ld=lld"), + ("CMAKE_SHARED_LINKER_FLAGS", "-fuse-ld=lld"), + ]; + apply_defines(&mut config.builder, &defines); + } + _ => {} + } + } + } + _ if config.is_msvc() => { + let msvc_flags = vec![ + "/nologo", + "/W4", + "/WX", + "/wd4127", + "/wd4324", + "/wd4201", + "/Ob2", + "/DNDEBUG", + "/EHsc", + "/Gd", + "/TP", + "/Gm-", + "/GS", + "/fp:precise", + "/Zc:wchar_t", + "/Zc:forScope", + "/Zc:inline", + ]; + for flag in msvc_flags { + config.builder.flag_if_supported(flag); + } + + if config.features.lto { + config + .builder + .flag_if_supported("/GL") + .define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", "TRUE") + .define("SNMALLOC_IPO", "ON"); + println!("cargo:rustc-link-arg=/LTCG"); + } + + config + .builder + .define("CMAKE_CXX_FLAGS_RELEASE", "/O2 /Ob2 /DNDEBUG /EHsc") + .define("CMAKE_C_FLAGS_RELEASE", "/O2 /Ob2 /DNDEBUG /EHsc"); + } + _ if config.is_unix() => { + let unix_flags = vec![ + "-fPIC", + "-pthread", + "-fno-exceptions", + "-fno-rtti", + "-mcx16", + "-Wno-unused-parameter", + ]; + for flag in unix_flags { + config.builder.flag_if_supported(flag); + } + + if config.target_os != "haiku" { + let tls_model = if config.features.local_dynamic_tls { + "-ftls-model=local-dynamic" + } else { + "-ftls-model=initial-exec" + }; + config.builder.flag_if_supported(tls_model); + } + } + _ => {} + } + + // Feature configurations + config + .builder + .define( + "SNMALLOC_QEMU_WORKAROUND", + if config.features.qemu { "ON" } else { "OFF" }, + ) + .define( + "SNMALLOC_ENABLE_DYNAMIC_LOADING", + if config.features.notls { "ON" } else { "OFF" }, + ) + .define( + "SNMALLOC_USE_WAIT_ON_ADDRESS", + if config.features.wait_on_address { + "1" + } else { + "0" + }, + ) + .define( + "USE_SNMALLOC_STATS", + if config.features.stats { "ON" } else { "OFF" }, + ); + + // Android configuration + if config.target.contains("android") { + let ndk = env::var("ANDROID_NDK").expect("ANDROID_NDK environment variable not set"); + config + .builder + .define( + "CMAKE_TOOLCHAIN_FILE", + &*format!("{}/build/cmake/android.toolchain.cmake", ndk), + ) + .define( + "ANDROID_PLATFORM", + &*env::var("ANDROID_PLATFORM").unwrap_or_default(), + ); + + if cfg!(feature = "android-lld") { + config.builder.define("ANDROID_LD", "lld"); + } + + let (abi, arm_mode) = match config.target.as_str() { + t if t.contains("aarch64") => ("arm64-v8a", None), + t if t.contains("armv7") => ("armeabi-v7a", Some("arm")), + t if t.contains("x86_64") => ("x86_64", None), + t if t.contains("i686") => ("x86", None), + t if t.contains("neon") => ("armeabi-v7a with NEON", None), + t if t.contains("arm") => ("armeabi-v7a", None), + _ => panic!("Unsupported Android architecture: {}", config.target), + }; + config.builder.define("ANDROID_ABI", abi); + if let Some(mode) = arm_mode { + config.builder.define("ANDROID_ARM_MODE", mode); + } + } +} + +fn configure_linking(config: &BuildConfig) { + match () { + _ if config.is_msvc() => { + // Windows MSVC specific libraries + if !config.features.win8compat { + println!("cargo:rustc-link-lib=mincore"); + } + // Essential Windows libraries + println!("cargo:rustc-link-lib=kernel32"); + println!("cargo:rustc-link-lib=user32"); + println!("cargo:rustc-link-lib=advapi32"); + println!("cargo:rustc-link-lib=ws2_32"); + println!("cargo:rustc-link-lib=userenv"); + println!("cargo:rustc-link-lib=bcrypt"); + println!("cargo:rustc-link-lib=msvcrt"); + } + _ if config.is_windows() && config.is_gnu() => { + println!("cargo:rustc-link-lib=kernel32"); + println!("cargo:rustc-link-lib=bcrypt"); + println!("cargo:rustc-link-lib=winpthread"); + + if config.is_clang_msys() { + println!("cargo:rustc-link-lib=c++"); + } else if config.is_ucrt64() { + println!("cargo:rustc-link-lib=stdc++"); + } else { + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:rustc-link-lib=atomic"); + } + } + _ if cfg!(target_os = "freebsd") => { + println!("cargo:rustc-link-lib=c++"); + } + _ if config.is_linux() => { + println!("cargo:rustc-link-lib=atomic"); + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:rustc-link-lib=pthread"); + println!("cargo:rustc-link-lib=c"); + println!("cargo:rustc-link-lib=gcc_s"); + println!("cargo:rustc-link-lib=util"); + println!("cargo:rustc-link-lib=rt"); + println!("cargo:rustc-link-lib=dl"); + println!("cargo:rustc-link-lib=m"); + + if cfg!(feature = "usecxx17") && !config.is_clang_msys() { + println!("cargo:rustc-link-lib=gcc"); + } + } + _ if config.is_unix() && !cfg!(any(target_os = "macos", target_os = "freebsd")) => { + if config.is_gnu() { + println!("cargo:rustc-link-lib=c_nonshared"); + } + } + _ if !config.is_windows() => { + let cxxlib = if cfg!(any(target_os = "macos", target_os = "openbsd")) { + "c++" + } else { + "stdc++" + }; + println!("cargo:rustc-link-lib={}", cxxlib); + } + _ => {} + } +} + +#[cfg(feature = "build_cc")] +use cc; +#[cfg(not(feature = "build_cc"))] +use cmake::Config; + +fn main() { + let mut config = BuildConfig::new(); + + config + .builder + .configure_cpp(config.debug) + .configure_output_dir(&config.out_dir); + + // Apply all configurations + configure_platform(&mut config); + + // Build and configure output + println!("cargo:rustc-link-search=/usr/local/lib"); + println!("cargo:rustc-link-search={}", config.out_dir); + println!("cargo:rustc-link-search={}/build", config.out_dir); + println!("cargo:rustc-link-search={}/build/Debug", config.out_dir); + println!("cargo:rustc-link-search={}/build/Release", config.out_dir); + let mut _dst = config.builder.build_lib(&config.target_lib); + println!("cargo:rustc-link-lib={}", config.target_lib); + configure_linking(&config); +} diff --git a/rust/snmalloc-sys/src/lib.rs b/rust/snmalloc-sys/src/lib.rs new file mode 100644 index 000000000..499dc410b --- /dev/null +++ b/rust/snmalloc-sys/src/lib.rs @@ -0,0 +1,128 @@ +#![no_std] +#![allow(non_camel_case_types)] + +use core::ffi::c_void; + +extern "C" { + /// Allocate the memory with the given alignment and size. + /// On success, it returns a pointer pointing to the required memory address. + /// On failure, it returns a null pointer. + /// The client must assure the following things: + /// - `alignment` is greater than zero + /// - `alignment` is a power of 2 + /// The program may be forced to abort if the constrains are not full-filled. + pub fn sn_rust_alloc(alignment: usize, size: usize) -> *mut c_void; + + /// De-allocate the memory at the given address with the given alignment and size. + /// The client must assure the following things: + /// - the memory is acquired using the same allocator and the pointer points to the start position. + /// - `alignment` and `size` is the same as allocation + /// The program may be forced to abort if the constrains are not full-filled. + pub fn sn_rust_dealloc(ptr: *mut c_void, alignment: usize, size: usize) -> c_void; + + /// Behaves like rust_alloc, but also ensures that the contents are set to zero before being returned. + pub fn sn_rust_alloc_zeroed(alignment: usize, size: usize) -> *mut c_void; + + /// Re-allocate the memory at the given address with the given alignment and size. + /// On success, it returns a pointer pointing to the required memory address. + /// The memory content within the `new_size` will remains the same as previous. + /// On failure, it returns a null pointer. In this situation, the previous memory is not returned to the allocator. + /// The client must assure the following things: + /// - the memory is acquired using the same allocator and the pointer points to the start position + /// - `alignment` and `old_size` is the same as allocation + /// - `alignment` fulfills all the requirements as `rust_alloc` + /// The program may be forced to abort if the constrains are not full-filled. + pub fn sn_rust_realloc( + ptr: *mut c_void, + alignment: usize, + old_size: usize, + new_size: usize, + ) -> *mut c_void; + + /// Allocate `count` items of `size` length each. + /// Returns `null` if `count * size` overflows or on out-of-memory. + /// All items are initialized to zero. + pub fn calloc(count: usize, size: usize) -> *mut c_void; + + /// Allocate `size` bytes. + /// Returns pointer to the allocated memory or null if out of memory. + /// Returns a unique pointer if called with `size` 0. + pub fn malloc(size: usize) -> *mut c_void; + + /// Re-allocate memory to `newsize` bytes. + /// Return pointer to the allocated memory or null if out of memory. If null + /// is returned, the pointer `p` is not freed. Otherwise the original + /// pointer is either freed or returned as the reallocated result (in case + /// it fits in-place with the new size). + /// If `p` is null, it behaves as [`sn_malloc`]. If `newsize` is larger than + /// the original `size` allocated for `p`, the bytes after `size` are + /// uninitialized. + pub fn realloc(p: *mut c_void, newsize: usize) -> *mut c_void; + + /// Free previously allocated memory. + /// The pointer `p` must have been allocated before (or be null). + pub fn free(p: *mut c_void); + + /// Return the available bytes in a memory block. + pub fn sn_rust_usable_size(p: *const c_void) -> usize; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_zero_allocs_correctly() { + let ptr = unsafe { sn_rust_alloc_zeroed(8, 1024) } as *mut u8 as *mut [u8; 1024]; + unsafe { + assert!((*ptr).iter().all(|x| *x == 0)); + }; + unsafe { sn_rust_dealloc(ptr as *mut c_void, 8, 1024) }; + } + + #[test] + fn it_frees_memory_malloc() { + let ptr = unsafe { sn_rust_alloc(8, 8) } as *mut u8; + unsafe { + *ptr = 127; + assert_eq!(*ptr, 127) + }; + unsafe { sn_rust_dealloc(ptr as *mut c_void, 8, 8) }; + } + + #[test] + fn it_frees_memory_sn_malloc() { + let ptr = unsafe { malloc(8) } as *mut u8; + unsafe { free(ptr as *mut c_void) }; + } + + #[test] + fn it_frees_memory_sn_realloc() { + let ptr = unsafe { malloc(8) } as *mut u8; + let ptr = unsafe { realloc(ptr as *mut c_void, 8) } as *mut u8; + unsafe { free(ptr as *mut c_void) }; + } + + #[test] + fn it_reallocs_correctly() { + let mut ptr = unsafe { sn_rust_alloc(8, 8) } as *mut u8; + unsafe { + *ptr = 127; + assert_eq!(*ptr, 127) + }; + ptr = unsafe { sn_rust_realloc(ptr as *mut c_void, 8, 8, 16) } as *mut u8; + unsafe { assert_eq!(*ptr, 127) }; + unsafe { sn_rust_dealloc(ptr as *mut c_void, 8, 16) }; + } + + #[test] + fn it_calculates_usable_size() { + let ptr = unsafe { sn_rust_alloc(32, 8) } as *mut u8; + let usable_size = unsafe { sn_rust_usable_size(ptr as *mut c_void) }; + assert!( + usable_size >= 32, + "usable_size should at least equal to the allocated size" + ); + unsafe { sn_rust_dealloc(ptr as *mut c_void, 32, 8) }; + } +} From e650d5a0e458b5975483882ac44fd6ca2309570d Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Sun, 9 Feb 2025 09:13:22 +0000 Subject: [PATCH 2/2] Update rust.yml --- .github/workflows/rust.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ea10f7456..fcf9ca5f5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,10 @@ name: Rust on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] + workflow_dispatch: env: CARGO_TERM_COLOR: always