From e0f53e6c9f4f9773d6e73fe1eb6047bf223d8fcf Mon Sep 17 00:00:00 2001 From: Jake Hughes Date: Fri, 20 Jun 2025 10:37:50 +0100 Subject: [PATCH 1/3] Default to using libgc alloc instead of system alloc Previously users had to ensure they set the #[global_allocator] in order to use Alloy. This was never ideal, but it was necessary because it was too difficult to implement when we statically linked libgc. We've changed this to use dynamic linkage now, and I also know my way around the bootstrap process, so I've fixed this long standing annoyance. In addition, this is also needed for more precise metric tracking in our benchmarks, where we use different allocators depending whether we're constructing an `Rc` / `Arc` etc. Now, these will always correctly resolve to the libgc allocator. --- library/alloc/src/bdwgc.rs | 8 +-- library/std/Cargo.toml | 1 + library/std/src/gc.rs | 4 ++ library/std/src/sys/alloc/unix.rs | 93 ++++++++++++++++--------- library/sysroot/Cargo.toml | 1 + src/bootstrap/src/core/config/config.rs | 5 ++ src/bootstrap/src/lib.rs | 3 + tests/ui/runtime/gc/disable.rs | 5 +- 8 files changed, 83 insertions(+), 37 deletions(-) diff --git a/library/alloc/src/bdwgc.rs b/library/alloc/src/bdwgc.rs index c0253731f4bff..ca9a02d9efe39 100644 --- a/library/alloc/src/bdwgc.rs +++ b/library/alloc/src/bdwgc.rs @@ -19,8 +19,8 @@ use crate::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; pub fn init(finalizer_thread: extern "C" fn()) { unsafe { api::GC_set_finalize_on_demand(1); + api::GC_set_warn_proc(Some(api::GC_ignore_warn_proc)); api::GC_set_finalizer_notifier(Some(finalizer_thread)); - #[cfg(feature = "gc-disable")] api::GC_disable(); metrics::init(); // The final initialization must come last. @@ -53,7 +53,7 @@ unsafe impl GlobalAlloc for GcAllocator { } #[inline] -unsafe fn gc_malloc(layout: Layout) -> *mut u8 { +pub unsafe fn gc_malloc(layout: Layout) -> *mut u8 { if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { unsafe { api::GC_malloc(layout.size()) as *mut u8 } } else { @@ -69,7 +69,7 @@ unsafe fn gc_malloc(layout: Layout) -> *mut u8 { } #[inline] -unsafe fn gc_realloc(ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 { +pub unsafe fn gc_realloc(ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 { if old_layout.align() <= MIN_ALIGN && old_layout.align() <= new_size { unsafe { api::GC_realloc(ptr as *mut c_void, new_size) as *mut u8 } } else { @@ -88,7 +88,7 @@ unsafe fn gc_realloc(ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut } #[inline] -unsafe fn gc_free(ptr: *mut u8, _: Layout) { +pub unsafe fn gc_free(ptr: *mut u8, _: Layout) { unsafe { api::GC_free(ptr as *mut c_void); } diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index c4f8e1f8a2a41..c42f033d0d757 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -108,6 +108,7 @@ premature-finalizer-prevention = [] premature-finalizer-prevention-optimize = [] finalizer-elision = [] gc-disable = ["alloc/gc-disable"] +gc-default-allocator = [] # Make panics and failed asserts immediately abort without formatting any message panic_immediate_abort = [ diff --git a/library/std/src/gc.rs b/library/std/src/gc.rs index f8b6c7b29fabf..a61997051e777 100644 --- a/library/std/src/gc.rs +++ b/library/std/src/gc.rs @@ -427,6 +427,10 @@ impl Gc { #[inline(always)] #[cfg(not(no_global_oom_handling))] unsafe fn new_internal(value: T) -> Self { + if !is_enabled() { + enable(); + } + #[cfg(not(bootstrap))] { #[cfg(feature = "finalizer-elision")] diff --git a/library/std/src/sys/alloc/unix.rs b/library/std/src/sys/alloc/unix.rs index 1af9d76629014..156b6895c61c1 100644 --- a/library/std/src/sys/alloc/unix.rs +++ b/library/std/src/sys/alloc/unix.rs @@ -1,63 +1,92 @@ -use super::{MIN_ALIGN, realloc_fallback}; use crate::alloc::{GlobalAlloc, Layout, System}; +#[cfg(not(feature = "gc-default-allocator"))] use crate::ptr; #[stable(feature = "alloc_system_type", since = "1.28.0")] unsafe impl GlobalAlloc for System { #[inline] unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - // jemalloc provides alignment less than MIN_ALIGN for small allocations. - // So only rely on MIN_ALIGN if size >= align. - // Also see and - // . - if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { - unsafe { libc::malloc(layout.size()) as *mut u8 } - } else { - // `posix_memalign` returns a non-aligned value if supplied a very - // large alignment on older versions of Apple's platforms (unknown - // exactly which version range, but the issue is definitely - // present in macOS 10.14 and iOS 13.3). - // - // - #[cfg(target_vendor = "apple")] - { - if layout.align() > (1 << 31) { - return ptr::null_mut(); + #[cfg(feature = "gc-default-allocator")] + unsafe { + alloc::bdwgc::gc_malloc(layout) + } + #[cfg(not(feature = "gc-default-allocator"))] + { + // jemalloc provides alignment less than MIN_ALIGN for small allocations. + // So only rely on MIN_ALIGN if size >= align. + // Also see and + // . + if layout.align() <= super::MIN_ALIGN && layout.align() <= layout.size() { + unsafe { libc::malloc(layout.size()) as *mut u8 } + } else { + // `posix_memalign` returns a non-aligned value if supplied a very + // large alignment on older versions of Apple's platforms (unknown + // exactly which version range, but the issue is definitely + // present in macOS 10.14 and iOS 13.3). + // + // + #[cfg(target_vendor = "apple")] + { + if layout.align() > (1 << 31) { + return ptr::null_mut(); + } } + unsafe { aligned_malloc(&layout) } } - unsafe { aligned_malloc(&layout) } } } #[inline] unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - // See the comment above in `alloc` for why this check looks the way it does. - if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { - unsafe { libc::calloc(layout.size(), 1) as *mut u8 } - } else { - let ptr = unsafe { self.alloc(layout) }; - if !ptr.is_null() { - unsafe { ptr::write_bytes(ptr, 0, layout.size()) }; + #[cfg(feature = "gc-default-allocator")] + unsafe { + alloc::bdwgc::gc_malloc(layout) + } + #[cfg(not(feature = "gc-default-allocator"))] + { + // See the comment above in `alloc` for why this check looks the way it does. + if layout.align() <= super::MIN_ALIGN && layout.align() <= layout.size() { + unsafe { libc::calloc(layout.size(), 1) as *mut u8 } + } else { + let ptr = unsafe { self.alloc(layout) }; + if !ptr.is_null() { + unsafe { ptr::write_bytes(ptr, 0, layout.size()) }; + } + ptr } - ptr } } #[inline] unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - unsafe { libc::free(ptr as *mut libc::c_void) } + #[cfg(feature = "gc-default-allocator")] + unsafe { + alloc::bdwgc::gc_free(ptr, _layout) + } + #[cfg(not(feature = "gc-default-allocator"))] + unsafe { + libc::free(ptr as *mut libc::c_void) + } } #[inline] unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - if layout.align() <= MIN_ALIGN && layout.align() <= new_size { - unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 } - } else { - unsafe { realloc_fallback(self, ptr, layout, new_size) } + #[cfg(feature = "gc-default-allocator")] + unsafe { + alloc::bdwgc::gc_realloc(ptr, layout, new_size) + } + #[cfg(not(feature = "gc-default-allocator"))] + { + if layout.align() <= super::MIN_ALIGN && layout.align() <= new_size { + unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 } + } else { + unsafe { super::realloc_fallback(self, ptr, layout, new_size) } + } } } } +#[cfg(not(feature = "gc-default-allocator"))] cfg_if::cfg_if! { // We use posix_memalign wherever possible, but some targets have very incomplete POSIX coverage // so we need a fallback for those. diff --git a/library/sysroot/Cargo.toml b/library/sysroot/Cargo.toml index eede7e1bbd4a2..8be6dcb42c7a9 100644 --- a/library/sysroot/Cargo.toml +++ b/library/sysroot/Cargo.toml @@ -39,3 +39,4 @@ premature-finalizer-prevention = ["std/premature-finalizer-prevention"] premature-finalizer-prevention-optimize = ["std/premature-finalizer-prevention-optimize"] gc-disable = ["std/gc-disable"] gc-metrics = ["std/gc-metrics"] +gc-default-allocator = ["std/gc-default-allocator"] diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 26ace66239fe0..f18e49f897c88 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -354,6 +354,7 @@ pub struct Config { pub gc_assertions: bool, pub gc_debug: bool, pub gc_disable: bool, + pub gc_default_allocator: bool, // misc pub low_priority: bool, @@ -1240,6 +1241,7 @@ define_config! { gc_assertions: Option = "gc-assertions", gc_debug: Option = "gc-debug", gc_disable: Option = "gc-disable", + gc_default_allocator: Option = "gc-default-allocator", } } @@ -1331,6 +1333,7 @@ impl Config { gc_assertions: false, gc_debug: false, gc_disable: false, + gc_default_allocator: true, ..Default::default() } @@ -2067,6 +2070,7 @@ impl Config { gc_assertions, gc_debug, gc_disable, + gc_default_allocator, } = alloy; set(&mut config.gc_metrics, gc_metrics); @@ -2080,6 +2084,7 @@ impl Config { set(&mut config.gc_assertions, gc_assertions); set(&mut config.gc_debug, gc_debug); set(&mut config.gc_disable, gc_disable); + set(&mut config.gc_default_allocator, gc_default_allocator); } if let Some(llvm) = toml.llvm { diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index d33999ec0aec3..f3cbf6a295f82 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -695,6 +695,9 @@ impl Build { if self.config.gc_disable { features.insert("gc-disable"); } + if self.config.gc_default_allocator { + features.insert("gc-default-allocator"); + } features.into_iter().collect::>().join(" ") } diff --git a/tests/ui/runtime/gc/disable.rs b/tests/ui/runtime/gc/disable.rs index 5bd33e6b56afd..dccb69411ba0d 100644 --- a/tests/ui/runtime/gc/disable.rs +++ b/tests/ui/runtime/gc/disable.rs @@ -4,9 +4,12 @@ #![allow(unused_variables)] #![allow(unused_imports)] -use std::gc::{disable, enable, is_enabled, try_enable}; +use std::gc::{Gc, disable, enable, is_enabled, try_enable}; fn main() { + assert!(!is_enabled()); + // First allocation needed to enable the GC + let gc = Gc::new(123); assert!(is_enabled()); disable(); disable(); From a2847b506e81b3beb252c06c6fb1de89dd71983f Mon Sep 17 00:00:00 2001 From: Jake Hughes Date: Mon, 23 Jun 2025 14:47:06 +0100 Subject: [PATCH 2/3] Restructure metrics to use nested modules correctly --- library/alloc/src/bdwgc.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/library/alloc/src/bdwgc.rs b/library/alloc/src/bdwgc.rs index ca9a02d9efe39..24584a8e86c0d 100644 --- a/library/alloc/src/bdwgc.rs +++ b/library/alloc/src/bdwgc.rs @@ -130,12 +130,15 @@ pub mod metrics { } #[cfg(feature = "gc-metrics")] - mod active { + pub mod active { + #![allow(dead_code)] use core::sync::atomic::{AtomicU64, Ordering}; use super::{Metric, MetricsImpl}; + use crate::bdwgc::api; - pub(super) struct Metrics { + #[derive(Debug)] + pub struct Metrics { finalizers_registered: AtomicU64, finalizers_elidable: AtomicU64, finalizers_completed: AtomicU64, @@ -161,8 +164,9 @@ pub mod metrics { } } - pub extern "C" fn record_post_collection(event: crate::GC_EventType) { - if event == crate::GC_EventType_GC_EVENT_END { + #[no_mangle] + pub extern "C" fn record_post_collection(event: api::GC_EventType) { + if event == api::GC_EventType_GC_EVENT_END { super::METRICS.capture(false); } } @@ -170,8 +174,8 @@ pub mod metrics { impl MetricsImpl for Metrics { fn init(&self) { unsafe { - crate::GC_enable_benchmark_stats(); - crate::GC_set_on_collection_event(Some(record_post_collection)); + api::GC_enable_benchmark_stats(); + api::GC_set_on_collection_event(Some(record_post_collection)); } } @@ -207,7 +211,7 @@ pub mod metrics { // Must preserve this ordering as it's hardcoded inside BDWGC. // See src/bdwgc/misc.c:2812 unsafe { - crate::GC_log_metrics( + api::GC_log_metrics( self.finalizers_completed.load(Ordering::Relaxed), self.finalizers_registered.load(Ordering::Relaxed), self.allocated_gc.load(Ordering::Relaxed), From 9738e1c086ee1631659d625bf56fde9439424110 Mon Sep 17 00:00:00 2001 From: Jake Hughes Date: Mon, 23 Jun 2025 14:49:20 +0100 Subject: [PATCH 3/3] Allow ./x.py install to symlink libgc For platform compatibilty with Windows, Rust disables support for distributing custom toolchains which contain symlinks. However, our custom `libgc`, which is installed in sysroot, might include symlinks to the versioned `libgc.so` SONAME on some platforms. We should allow this because we don't support Windows anyway. --- src/bootstrap/src/core/build_steps/dist.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index e0ad6856dc72c..08d55684002ad 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -378,7 +378,9 @@ impl Step for Rustc { let compiler = self.compiler; let host = self.compiler.host; - let tarball = Tarball::new(builder, "rustc", &host.triple); + let mut tarball = Tarball::new(builder, "rustc", &host.triple); + + tarball.permit_symlinks(true); // Prepare the rustc "image", what will actually end up getting installed prepare_image(builder, compiler, tarball.image_dir()); @@ -678,6 +680,7 @@ impl Step for Std { let mut tarball = Tarball::new(builder, "rust-std", &target.triple); tarball.include_target_in_component_name(true); + tarball.permit_symlinks(true); let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target); let stamp = build_stamp::libstd_stamp(builder, compiler_to_use, target);