blob: b785423413e2f019067533c1d3c8e8e04f03de26 [file] [edit]
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
use std::env;
use std::path::PathBuf;
// Locate the mimalloc sources: `MIMALLOC_SRC_DIR`, a vendored `mimalloc/` directory, or this repository.
fn mimalloc_dir() -> PathBuf {
println!("cargo:rerun-if-env-changed=MIMALLOC_SRC_DIR");
let manifest = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let dir = match env::var_os("MIMALLOC_SRC_DIR") {
Some(dir) => PathBuf::from(dir),
None if manifest.join("mimalloc/src/static.c").exists() => manifest.join("mimalloc"),
None => manifest.join("../.."),
};
if !dir.join("src/static.c").exists() {
panic!(
"cannot find the mimalloc sources in {} (set MIMALLOC_SRC_DIR)",
dir.display()
);
}
dir
}
fn feature(name: &str) -> bool {
env::var_os(format!("CARGO_FEATURE_{}", name.to_uppercase())).is_some()
}
// Extra defines from `MIMALLOC_DEFINES`, e.g. `MI_DEBUG=3 MI_SKIP_COLLECT_ON_EXIT=1`
fn user_defines() -> Vec<(String, Option<String>)> {
println!("cargo:rerun-if-env-changed=MIMALLOC_DEFINES");
let defines = env::var("MIMALLOC_DEFINES").unwrap_or_default();
defines
.split(|c: char| c.is_whitespace() || c == ',')
.filter(|d| !d.is_empty())
.map(|d| match d.split_once('=') {
Some((name, value)) => (name.to_string(), Some(value.to_string())),
None => (d.to_string(), None),
})
.collect()
}
// The `Allocator` trait is stable since Rust 1.100 (but not on all 1.100 nightlies)
fn allocator_api_is_stable() -> bool {
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
let Ok(out) = std::process::Command::new(rustc).arg("--version").output() else {
return false;
};
let version = String::from_utf8_lossy(&out.stdout);
let minor = version
.split_whitespace()
.nth(1)
.and_then(|v| v.split('.').nth(1))
.and_then(|m| m.parse::<u32>().ok());
minor.is_some_and(|m| m > 100 || (m == 100 && !version.contains("nightly")))
}
fn main() {
println!("cargo:rustc-check-cfg=cfg(allocator_api_stable)");
if allocator_api_is_stable() {
println!("cargo:rustc-cfg=allocator_api_stable");
}
let dir = mimalloc_dir();
let include = dir.join("include");
println!("cargo:rerun-if-changed={}", include.display());
println!("cargo:rerun-if-changed={}", dir.join("src").display());
println!("cargo:include={}", include.display());
let var = |name: &str| env::var(name).unwrap_or_default();
let target_os = var("CARGO_CFG_TARGET_OS");
let target_env = var("CARGO_CFG_TARGET_ENV");
let target_arch = var("CARGO_CFG_TARGET_ARCH");
let target_vendor = var("CARGO_CFG_TARGET_VENDOR");
let target_features = var("CARGO_CFG_TARGET_FEATURE");
let has_features = |names: &[&str]| {
names
.iter()
.all(|f| target_features.split(',').any(|t| t == *f))
};
let is_apple = target_vendor == "apple";
let is_windows = target_os == "windows";
let is_musl = target_env == "musl";
let mut build = cc::Build::new();
build.include(&include).file(dir.join("src/static.c"));
let compiler = build.get_compiler();
let is_msvc = compiler.is_like_msvc();
let is_gnu_like = (compiler.is_like_gnu() || compiler.is_like_clang()) && !is_msvc;
// `MIMALLOC_DEFINES` take precedence over the defines set by features
let user = user_defines();
let is_user = |name: &str| user.iter().any(|(n, _)| n == name);
let mut define = |name: &str, value: &str| {
if !is_user(name) {
build.define(name, value);
}
};
// build options
let debug = feature("debug")
|| (feature("debug_in_debug") && env::var_os("CARGO_CFG_DEBUG_ASSERTIONS").is_some());
if debug {
define("MI_DEBUG", "2");
} else if !is_user("MI_DEBUG") {
define("MI_DEBUG", "0");
define("NDEBUG", "1");
}
if feature("secure") {
define("MI_SECURE", "4");
}
let is_override = feature("override") && !is_windows;
if is_override {
define("MI_MALLOC_OVERRIDE", "1");
if is_apple {
define("MI_OSX_ZONE", "1");
define("MI_OSX_INTERPOSE", "1");
}
}
if feature("no_thp") && (target_os == "linux" || target_os == "android") {
define("MI_DEFAULT_ALLOW_THP", "0");
}
if is_musl {
define("MI_LIBC_MUSL", "1");
}
if feature("win_direct_tls") && is_windows {
define("MI_WIN_DIRECT_TLS", "1");
}
for (name, value) in &user {
build.define(name, value.as_deref());
}
if is_override && is_gnu_like {
build.flag("-fno-builtin-malloc");
}
// compiler flags (as in CMakeLists.txt)
if is_msvc {
// use C++ atomics with msvc and clang-cl
build
.cpp(true)
.flag("/TP")
.flag("/Zc:__cplusplus")
.std("c++17");
} else if is_gnu_like {
build
.flag("-fvisibility=hidden")
.flag("-Wno-unknown-pragmas");
if compiler.is_like_clang() {
build
.flag("-Wno-static-in-inline")
.flag_if_supported("-mno-outline");
if is_apple && target_arch == "aarch64" && is_apple_clang(&compiler) {
build.flag("-Xclang").flag("-fno-split-cold-code");
}
}
if target_os != "haiku" {
let tls = if feature("local_dynamic_tls") || is_musl {
"local-dynamic"
} else {
"initial-exec"
};
build.flag(format!("-ftls-model={tls}"));
}
}
// architecture specific optimizations: enabled with `opt_arch`, or if the Rust target already implies them
let opt_arch = feature("opt_arch");
match target_arch.as_str() {
"x86_64" if opt_arch || has_features(&["avx2", "bmi1", "bmi2", "lzcnt"]) => {
if is_msvc {
build.flag("/arch:AVX2");
} else if is_gnu_like {
build.flag("-march=haswell").flag("-mavx2");
}
}
"aarch64" if is_apple && (opt_arch || has_features(&["lse", "rcpc"])) => {
if is_gnu_like {
build.flag("-march=armv8.3-a");
}
}
"aarch64" if opt_arch || has_features(&["lse"]) => {
if is_msvc {
build.flag("/arch:armv8.1");
} else if is_gnu_like {
build.flag("-march=armv8.1-a");
}
}
_ => {}
}
build.compile("mimalloc");
// system libraries
if is_windows {
for lib in ["psapi", "shell32", "user32", "advapi32", "bcrypt"] {
println!("cargo:rustc-link-lib={lib}");
}
} else if target_os == "linux" && (target_arch == "arm" || target_arch.starts_with("riscv32")) {
println!("cargo:rustc-link-lib=atomic");
}
}
fn is_apple_clang(compiler: &cc::Tool) -> bool {
let out = compiler.to_command().arg("--version").output();
out.is_ok_and(|out| String::from_utf8_lossy(&out.stdout).contains("Apple clang"))
}